diff --git a/Documentation/developer-guide.md b/Documentation/developer-guide.md index f68aebd14..ed6dc0a09 100644 --- a/Documentation/developer-guide.md +++ b/Documentation/developer-guide.md @@ -36,6 +36,22 @@ In order to build .NET Command Line Interface, you need the following installed 2. `dotnet restore` 3. `dotnet run` +## Running tests + +All the CLI tests are located under `test`. In order to run them, after doing a restore on the CLI repo just do the following: + +1. Navigate to a test project, for instance: `cd test\dotnet-test.UnitTests` +2. `dotnet test` + +For unit test projects (they have UnitTests at the name), that's all that you need to do, as they take a dependency on the product code directly, which gets rebuilt by dotnet when you run the tests. + +For E2E and functional tests, they all depend on the binaries located under `artifacts\rid\stage2\bin`. So, after changing the code, you will need to re-build the product code and copy the new bits to the folder above. For instance, imagine you changed something in dotnet itself, you would have to do the following: + +1. `cd src\dotnet\` +2. `dotnet build` +3. `cp bin\debug\netstandardapp1.5\dotnet.dll artifacts\rid\stage2\bin` +4. `cd ..\..\test\dotnet-build.Tests` +5. `dotnet test` ##Adding a Command diff --git a/Documentation/known-issues.md b/Documentation/known-issues.md index a5f8bb038..735164987 100644 --- a/Documentation/known-issues.md +++ b/Documentation/known-issues.md @@ -1,23 +1,6 @@ Known issues & workarounds ========================== -## El Capitan support -If you try to use the `dotnet` commands on El Capitan (OS X 10.11), you will encounter errors as it is not currently -fully supported for all scenarios. - -**Issues tracking this:** - -* [#498](https://github.com/dotnet/cli/issues/498) -* [#291](https://github.com/dotnet/cli/issues/291) - -**Affects:** most of the commands, but more than likely you will not be able to -use `dotnet compile` and `dotnet-run` on El Capitan. For others, there is a -workaround. - -**Workaround:** use the --runtime switch with the value of `osx.10.11-x64` in -`dotnet restore` and `dotnet publish` and you will be able to run your app from -the published directory. - ## Resolving the Standard library packages The StdLib package is on a MyGet feed. In order to restore it, a MyGet feed needs to be added to the NuGet feeds, either locally per application or in a central location. diff --git a/Documentation/specs/runtime-configuration-file.md b/Documentation/specs/runtime-configuration-file.md index f35de1ec7..ff2df4c33 100644 --- a/Documentation/specs/runtime-configuration-file.md +++ b/Documentation/specs/runtime-configuration-file.md @@ -29,7 +29,8 @@ The files are both JSON files stored in UTF-8 encoding. Below are sample files. "gcConcurrent": false, "framework": { "name": "Microsoft.DotNetCore", - "version": "1.0.1" + "version": "1.0.1", + "rollForward": false, } } } @@ -118,7 +119,11 @@ This section is copied verbatim from an identical section in the input `project. * `gcServer` - Boolean indicating if the server GC should be used (Default: _TBD_). Note: This is designed to mirror the existing [app.config](https://msdn.microsoft.com/en-us/library/ms229357.aspx) setting) * `gcConcurrent` - Boolean indicating if background garbage collection should be used (Default: _TBD_). Note: This is designed to mirror the existing [app.config](https://msdn.microsoft.com/en-us/library/yhwwzef8.aspx) setting). -* `framework` - Indicates the name and version of the shared framework to use when activating the application. The presence of this section indicates that the application is a portable app designed to use a shared redistributable framework. +* `framework` - Indicates the `name`, `version`, and other properties of the shared framework to use when activating the application. The presence of this section indicates that the application is a portable app designed to use a shared redistributable framework. + * `rollForward` - When `false`, the framework version is strictly obeyed by the host. When `rollForward` is unspecified or specified as `true`, the framework from either the same or a higher version that differs only in the `SemVer` patch field will be used. + * For example, if `version=1.0.1` and `rollForward` is `true`, the host would load the shared framework from `1.0.{n}`, where `n >= 1`, but will not load from `1.1.0`, even if present. When `rollForward` is `false`, the shared framework will be loaded from `1.0.1` strictly. + * **Note:** This does not apply to `SemVer`'s `prerelease` versions, but only for `production` releases. + * **Note:** This section will not be present for standalone applications that do not rely upon a shared framework. * Others _TBD_ These settings are read by `corehost` to determine how to initialize the runtime. All versions of `corehost` **must ignore** settings in this section that they do not understand (thus allowing new settings to be added in later versions). diff --git a/Microsoft.DotNet.Cli.sln b/Microsoft.DotNet.Cli.sln index faa3eb2c8..044847001 100644 --- a/Microsoft.DotNet.Cli.sln +++ b/Microsoft.DotNet.Cli.sln @@ -1,6 +1,7 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.25020.0 +VisualStudioVersion = 14.0.25029.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{ED2FE3E2-F7E7-4389-8231-B65123F2076F}" EndProject @@ -76,6 +77,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.TestFramew EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "dotnet-test.UnitTests", "test\dotnet-test.UnitTests\dotnet-test.UnitTests.xproj", "{857274AC-E741-4266-A7FD-14DEE0C1CC96}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "dotnet-test.Tests", "test\dotnet-test.Tests\dotnet-test.Tests.xproj", "{60C33D0A-A5D8-4AB0-9956-1F804654DF05}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "update-dependencies", "scripts\update-dependencies\update-dependencies.xproj", "{A28BD8AC-DF15-4F58-8299-98A9AE2B8726}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -472,22 +477,6 @@ Global {0745410A-6629-47EB-AAB5-08D6288CAD72}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU {0745410A-6629-47EB-AAB5-08D6288CAD72}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU {0745410A-6629-47EB-AAB5-08D6288CAD72}.RelWithDebInfo|x64.Build.0 = Release|Any CPU - {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.Debug|x64.ActiveCfg = Debug|Any CPU - {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.Debug|x64.Build.0 = Debug|Any CPU - {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU - {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU - {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU - {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.MinSizeRel|x64.Build.0 = Debug|Any CPU - {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.Release|Any CPU.Build.0 = Release|Any CPU - {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.Release|x64.ActiveCfg = Release|Any CPU - {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.Release|x64.Build.0 = Release|Any CPU - {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU - {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU - {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU - {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.RelWithDebInfo|x64.Build.0 = Release|Any CPU {0B31C336-149D-471A-B7B1-27B0F1E80F83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0B31C336-149D-471A-B7B1-27B0F1E80F83}.Debug|Any CPU.Build.0 = Debug|Any CPU {0B31C336-149D-471A-B7B1-27B0F1E80F83}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -504,22 +493,22 @@ Global {0B31C336-149D-471A-B7B1-27B0F1E80F83}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU {0B31C336-149D-471A-B7B1-27B0F1E80F83}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU {0B31C336-149D-471A-B7B1-27B0F1E80F83}.RelWithDebInfo|x64.Build.0 = Release|Any CPU - {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Debug|Any CPU.Build.0 = Debug|Any CPU - {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Debug|x64.ActiveCfg = Debug|Any CPU - {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Debug|x64.Build.0 = Debug|Any CPU - {857274AC-E741-4266-A7FD-14DEE0C1CC96}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU - {857274AC-E741-4266-A7FD-14DEE0C1CC96}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU - {857274AC-E741-4266-A7FD-14DEE0C1CC96}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU - {857274AC-E741-4266-A7FD-14DEE0C1CC96}.MinSizeRel|x64.Build.0 = Debug|Any CPU - {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Release|Any CPU.ActiveCfg = Release|Any CPU - {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Release|Any CPU.Build.0 = Release|Any CPU - {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Release|x64.ActiveCfg = Release|Any CPU - {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Release|x64.Build.0 = Release|Any CPU - {857274AC-E741-4266-A7FD-14DEE0C1CC96}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU - {857274AC-E741-4266-A7FD-14DEE0C1CC96}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU - {857274AC-E741-4266-A7FD-14DEE0C1CC96}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU - {857274AC-E741-4266-A7FD-14DEE0C1CC96}.RelWithDebInfo|x64.Build.0 = Release|Any CPU + {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.Debug|x64.ActiveCfg = Debug|Any CPU + {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.Debug|x64.Build.0 = Debug|Any CPU + {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.Release|Any CPU.Build.0 = Release|Any CPU + {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.Release|x64.ActiveCfg = Release|Any CPU + {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.Release|x64.Build.0 = Release|Any CPU + {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU + {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU + {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU + {4A4711D8-4312-49FC-87B5-4F183F4C6A51}.RelWithDebInfo|x64.Build.0 = Release|Any CPU {0724ED7C-56E3-4604-9970-25E600611383}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0724ED7C-56E3-4604-9970-25E600611383}.Debug|Any CPU.Build.0 = Debug|Any CPU {0724ED7C-56E3-4604-9970-25E600611383}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -536,6 +525,54 @@ Global {0724ED7C-56E3-4604-9970-25E600611383}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU {0724ED7C-56E3-4604-9970-25E600611383}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU {0724ED7C-56E3-4604-9970-25E600611383}.RelWithDebInfo|x64.Build.0 = Release|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Debug|Any CPU.Build.0 = Debug|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Debug|x64.ActiveCfg = Debug|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Debug|x64.Build.0 = Debug|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Release|Any CPU.ActiveCfg = Release|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Release|Any CPU.Build.0 = Release|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Release|x64.ActiveCfg = Release|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.Release|x64.Build.0 = Release|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU + {857274AC-E741-4266-A7FD-14DEE0C1CC96}.RelWithDebInfo|x64.Build.0 = Release|Any CPU + {60C33D0A-A5D8-4AB0-9956-1F804654DF05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60C33D0A-A5D8-4AB0-9956-1F804654DF05}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60C33D0A-A5D8-4AB0-9956-1F804654DF05}.Debug|x64.ActiveCfg = Debug|Any CPU + {60C33D0A-A5D8-4AB0-9956-1F804654DF05}.Debug|x64.Build.0 = Debug|Any CPU + {60C33D0A-A5D8-4AB0-9956-1F804654DF05}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {60C33D0A-A5D8-4AB0-9956-1F804654DF05}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {60C33D0A-A5D8-4AB0-9956-1F804654DF05}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {60C33D0A-A5D8-4AB0-9956-1F804654DF05}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {60C33D0A-A5D8-4AB0-9956-1F804654DF05}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60C33D0A-A5D8-4AB0-9956-1F804654DF05}.Release|Any CPU.Build.0 = Release|Any CPU + {60C33D0A-A5D8-4AB0-9956-1F804654DF05}.Release|x64.ActiveCfg = Release|Any CPU + {60C33D0A-A5D8-4AB0-9956-1F804654DF05}.Release|x64.Build.0 = Release|Any CPU + {60C33D0A-A5D8-4AB0-9956-1F804654DF05}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU + {60C33D0A-A5D8-4AB0-9956-1F804654DF05}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU + {60C33D0A-A5D8-4AB0-9956-1F804654DF05}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU + {60C33D0A-A5D8-4AB0-9956-1F804654DF05}.RelWithDebInfo|x64.Build.0 = Release|Any CPU + {A28BD8AC-DF15-4F58-8299-98A9AE2B8726}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A28BD8AC-DF15-4F58-8299-98A9AE2B8726}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A28BD8AC-DF15-4F58-8299-98A9AE2B8726}.Debug|x64.ActiveCfg = Debug|Any CPU + {A28BD8AC-DF15-4F58-8299-98A9AE2B8726}.Debug|x64.Build.0 = Debug|Any CPU + {A28BD8AC-DF15-4F58-8299-98A9AE2B8726}.MinSizeRel|Any CPU.ActiveCfg = Debug|Any CPU + {A28BD8AC-DF15-4F58-8299-98A9AE2B8726}.MinSizeRel|Any CPU.Build.0 = Debug|Any CPU + {A28BD8AC-DF15-4F58-8299-98A9AE2B8726}.MinSizeRel|x64.ActiveCfg = Debug|Any CPU + {A28BD8AC-DF15-4F58-8299-98A9AE2B8726}.MinSizeRel|x64.Build.0 = Debug|Any CPU + {A28BD8AC-DF15-4F58-8299-98A9AE2B8726}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A28BD8AC-DF15-4F58-8299-98A9AE2B8726}.Release|Any CPU.Build.0 = Release|Any CPU + {A28BD8AC-DF15-4F58-8299-98A9AE2B8726}.Release|x64.ActiveCfg = Release|Any CPU + {A28BD8AC-DF15-4F58-8299-98A9AE2B8726}.Release|x64.Build.0 = Release|Any CPU + {A28BD8AC-DF15-4F58-8299-98A9AE2B8726}.RelWithDebInfo|Any CPU.ActiveCfg = Release|Any CPU + {A28BD8AC-DF15-4F58-8299-98A9AE2B8726}.RelWithDebInfo|Any CPU.Build.0 = Release|Any CPU + {A28BD8AC-DF15-4F58-8299-98A9AE2B8726}.RelWithDebInfo|x64.ActiveCfg = Release|Any CPU + {A28BD8AC-DF15-4F58-8299-98A9AE2B8726}.RelWithDebInfo|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -565,11 +602,13 @@ Global {DA8E0E9E-A6D6-4583-864C-8F40465E3A48} = {713CBFBB-5392-438D-B766-A9A585EF1BB8} {0138CB8F-4AA9-4029-A21E-C07C30F425BA} = {713CBFBB-5392-438D-B766-A9A585EF1BB8} {BD4F0750-4E81-4AD2-90B5-E470881792C3} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F} - {4A4711D8-4312-49FC-87B5-4F183F4C6A51} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} {0745410A-6629-47EB-AAB5-08D6288CAD72} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} {0E3300A4-DF54-40BF-87D8-E7658330C288} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} {0B31C336-149D-471A-B7B1-27B0F1E80F83} = {0E3300A4-DF54-40BF-87D8-E7658330C288} - {857274AC-E741-4266-A7FD-14DEE0C1CC96} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} + {4A4711D8-4312-49FC-87B5-4F183F4C6A51} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} {0724ED7C-56E3-4604-9970-25E600611383} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F} + {857274AC-E741-4266-A7FD-14DEE0C1CC96} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} + {60C33D0A-A5D8-4AB0-9956-1F804654DF05} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7} + {A28BD8AC-DF15-4F58-8299-98A9AE2B8726} = {88278B81-7649-45DC-8A6A-D3A645C5AFC3} EndGlobalSection EndGlobal diff --git a/README.md b/README.md index 6bc696ff8..5f0ff2628 100644 --- a/README.md +++ b/README.md @@ -18,18 +18,18 @@ If you don't find your issue, please file one! However, given that this is a ver Build Status ------------ -|Ubuntu 14.04 |Windows |Mac OS X |CentOS 7.1 | -|:------:|:------:|:------:|:------:| -|![](https://devdiv.visualstudio.com/DefaultCollection/_apis/public/build/definitions/0bdbc590-a062-4c3f-b0f6-9383f67865ee/601/badge)|![](https://devdiv.visualstudio.com/DefaultCollection/_apis/public/build/definitions/0bdbc590-a062-4c3f-b0f6-9383f67865ee/602/badge)|![](https://devdiv.visualstudio.com/DefaultCollection/_apis/public/build/definitions/0bdbc590-a062-4c3f-b0f6-9383f67865ee/600/badge) |![](https://devdiv.visualstudio.com/DefaultCollection/_apis/public/build/definitions/0bdbc590-a062-4c3f-b0f6-9383f67865ee/597/badge) | +|Ubuntu 14.04 |Windows x64 |Windows x86 |Mac OS X |CentOS 7.1 |RHEL 7.2 | +|:------:|:------:|:------:|:------:|:------:|:------:| +|![](https://devdiv.visualstudio.com/DefaultCollection/_apis/public/build/definitions/0bdbc590-a062-4c3f-b0f6-9383f67865ee/601/badge)|![](https://mseng.visualstudio.com/DefaultCollection/_apis/public/build/definitions/d09b7a4d-0a51-4c0e-a15a-07921d5b558f/3022/badge)|![](https://mseng.visualstudio.com/DefaultCollection/_apis/public/build/definitions/d09b7a4d-0a51-4c0e-a15a-07921d5b558f/3071/badge)|![](https://devdiv.visualstudio.com/DefaultCollection/_apis/public/build/definitions/0bdbc590-a062-4c3f-b0f6-9383f67865ee/600/badge) |![](https://devdiv.visualstudio.com/DefaultCollection/_apis/public/build/definitions/0bdbc590-a062-4c3f-b0f6-9383f67865ee/597/badge) |![](https://devdiv.visualstudio.com/DefaultCollection/_apis/public/build/definitions/0bdbc590-a062-4c3f-b0f6-9383f67865ee/897/badge) | Installers ---------- -| |Ubuntu 14.04 |Windows |Mac OS X |CentOS 7.1 | -|---------|:------:|:------:|:------:|:------:| -|**Version**|![](https://dotnetcli.blob.core.windows.net/dotnet/beta/Binaries/Latest/Ubuntu_x64_Release_version_badge.svg?nocache)|![](https://dotnetcli.blob.core.windows.net/dotnet/beta/Binaries/Latest/Windows_x64_Release_version_badge.svg?nocache)|![](https://dotnetcli.blob.core.windows.net/dotnet/beta/Binaries/Latest/OSX_x64_Release_version_badge.svg?nocache)|![](https://dotnetcli.blob.core.windows.net/dotnet/beta/Binaries/Latest/CentOS_x64_Release_version_badge.svg?nocache)| -|**Installers**|[Download Debian Package](https://dotnetcli.blob.core.windows.net/dotnet/beta/Installers/Latest/dotnet-ubuntu-x64.latest.deb)|[Download Msi](https://dotnetcli.blob.core.windows.net/dotnet/beta/Installers/Latest/dotnet-win-x64.latest.exe)|[Download Pkg](https://dotnetcli.blob.core.windows.net/dotnet/beta/Installers/Latest/dotnet-osx-x64.latest.pkg) |N/A | -|**Binaries**|[Download tar file](https://dotnetcli.blob.core.windows.net/dotnet/beta/Binaries/Latest/dotnet-ubuntu-x64.latest.tar.gz)|[Download zip file](https://dotnetcli.blob.core.windows.net/dotnet/beta/Binaries/Latest/dotnet-win-x64.latest.zip)|[Download tar file](https://dotnetcli.blob.core.windows.net/dotnet/beta/Binaries/Latest/dotnet-osx-x64.latest.tar.gz) |[Download tar file](https://dotnetcli.blob.core.windows.net/dotnet/beta/Binaries/Latest/dotnet-centos-x64.latest.tar.gz) | +| |Ubuntu 14.04 |Windows x64 |Windows x86 |Mac OS X |CentOS 7.1 |RHEL 7.2 | +|---------|:------:|:------:|:------:|:------:|:------:|:------:| +|**Version**|![](https://dotnetcli.blob.core.windows.net/dotnet/beta/Binaries/Latest/Ubuntu_x64_Release_version_badge.svg)|![](https://dotnetcli.blob.core.windows.net/dotnet/beta/Binaries/Latest/Windows_x64_Release_version_badge.svg)|![](https://dotnetcli.blob.core.windows.net/dotnet/beta/Binaries/Latest/Windows_x86_Release_version_badge.svg)|![](https://dotnetcli.blob.core.windows.net/dotnet/beta/Binaries/Latest/OSX_x64_Release_version_badge.svg)|![](https://dotnetcli.blob.core.windows.net/dotnet/beta/Binaries/Latest/CentOS_x64_Release_version_badge.svg)|![](https://dotnetcli.blob.core.windows.net/dotnet/beta/Binaries/Latest/RHEL_x64_Release_version_badge.svg)| +|**Installers**|[Download Debian Package](https://dotnetcli.blob.core.windows.net/dotnet/beta/Installers/Latest/dotnet-ubuntu-x64.latest.deb)|[Download Installer](https://dotnetcli.blob.core.windows.net/dotnet/beta/Installers/Latest/dotnet-win-x64.latest.exe)|[Download Installer](https://dotnetcli.blob.core.windows.net/dotnet/beta/Installers/Latest/dotnet-win-x86.latest.exe)|[Download Pkg](https://dotnetcli.blob.core.windows.net/dotnet/beta/Installers/Latest/dotnet-osx-x64.latest.pkg) |N/A |N/A | +|**Binaries**|[Download tar file](https://dotnetcli.blob.core.windows.net/dotnet/beta/Binaries/Latest/dotnet-combined-framework-sdk-host-ubuntu-x64.latest.tar.gz)|[Download zip file](https://dotnetcli.blob.core.windows.net/dotnet/beta/Binaries/Latest/dotnet-combined-framework-sdk-host-win-x64.latest.zip)|[Download zip file](https://dotnetcli.blob.core.windows.net/dotnet/beta/Binaries/Latest/dotnet-combined-framework-sdk-host-win-x86.latest.zip)|[Download tar file](https://dotnetcli.blob.core.windows.net/dotnet/beta/Binaries/Latest/dotnet-combined-framework-sdk-host-osx-x64.latest.tar.gz) |[Download tar file](https://dotnetcli.blob.core.windows.net/dotnet/beta/Binaries/Latest/dotnet-combined-framework-sdk-host-centos-x64.latest.tar.gz)|[Download tar file](https://dotnetcli.blob.core.windows.net/dotnet/beta/Binaries/Latest/dotnet-combined-framework-sdk-host-rhel-x64.latest.tar.gz) | Interested in .NET Core + ASP.NET Core 1.0 RC1 bits? ---------------------------------------------------- diff --git a/TestAssets/FSharpTestProjects/CompileFail/project.json b/TestAssets/FSharpTestProjects/CompileFail/project.json index 40621376b..84d87f6df 100644 --- a/TestAssets/FSharpTestProjects/CompileFail/project.json +++ b/TestAssets/FSharpTestProjects/CompileFail/project.json @@ -1,20 +1,19 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true - }, - "compilerName": "fsc", - "compileFiles": [ - "Program.fs" - ], - "dependencies": { - "Microsoft.FSharp.Core.netcore": "1.0.0-alpha-151221", - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "compilerName": "fsc", + "compileFiles": [ + "Program.fs" + ], + "dependencies": { + "Microsoft.FSharp.Core.netcore": "1.0.0-alpha-151221", + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/FSharpTestProjects/TestApp/project.json b/TestAssets/FSharpTestProjects/TestApp/project.json index 141270ca8..90fcb92b9 100644 --- a/TestAssets/FSharpTestProjects/TestApp/project.json +++ b/TestAssets/FSharpTestProjects/TestApp/project.json @@ -1,21 +1,20 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true - }, - "compilerName": "fsc", - "compileFiles": [ - "Program.fs" - ], - "dependencies": { - "TestLibrary": "1.0.0-*", - "NETStandard.Library": "1.0.0-rc2-23901", - "Microsoft.FSharp.Core.netcore": "1.0.0-alpha-151221" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "compilerName": "fsc", + "compileFiles": [ + "Program.fs" + ], + "dependencies": { + "TestLibrary": "1.0.0-*", + "NETStandard.Library": "1.5.0-rc2-23911", + "Microsoft.FSharp.Core.netcore": "1.0.0-alpha-151221" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/FSharpTestProjects/TestAppWithArgs/project.json b/TestAssets/FSharpTestProjects/TestAppWithArgs/project.json index 40621376b..84d87f6df 100644 --- a/TestAssets/FSharpTestProjects/TestAppWithArgs/project.json +++ b/TestAssets/FSharpTestProjects/TestAppWithArgs/project.json @@ -1,20 +1,19 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true - }, - "compilerName": "fsc", - "compileFiles": [ - "Program.fs" - ], - "dependencies": { - "Microsoft.FSharp.Core.netcore": "1.0.0-alpha-151221", - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "compilerName": "fsc", + "compileFiles": [ + "Program.fs" + ], + "dependencies": { + "Microsoft.FSharp.Core.netcore": "1.0.0-alpha-151221", + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/FSharpTestProjects/TestLibrary/project.json b/TestAssets/FSharpTestProjects/TestLibrary/project.json index 61961870b..0b29b1792 100644 --- a/TestAssets/FSharpTestProjects/TestLibrary/project.json +++ b/TestAssets/FSharpTestProjects/TestLibrary/project.json @@ -1,17 +1,17 @@ { - "version": "1.0.0-*", - "dependencies": { - "Microsoft.FSharp.Core.netcore": "1.0.0-alpha-151221", - "NETStandard.Library": "1.0.0-rc2-23901" - }, - "compilerName": "fsc", - "compileFiles": [ - "Helper2.fs", - "Helper.fs" - ], - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "dependencies": { + "Microsoft.FSharp.Core.netcore": "1.0.0-alpha-151221", + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "compilerName": "fsc", + "compileFiles": [ + "Helper2.fs", + "Helper.fs" + ], + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/ProjectModelServer/DthTestProjects/src/BrokenProjectPathSample/project.json b/TestAssets/ProjectModelServer/DthTestProjects/src/BrokenProjectPathSample/project.json index d183d244f..7ce8666ac 100644 --- a/TestAssets/ProjectModelServer/DthTestProjects/src/BrokenProjectPathSample/project.json +++ b/TestAssets/ProjectModelServer/DthTestProjects/src/BrokenProjectPathSample/project.json @@ -1,11 +1,11 @@ { - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "EmptyLibrary": "1.0.0-*" - }, - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "EmptyLibrary": "1.0.0-*" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/ProjectModelServer/DthTestProjects/src/EmptyConsoleApp/project.json b/TestAssets/ProjectModelServer/DthTestProjects/src/EmptyConsoleApp/project.json index 38a3ba296..a81519a70 100644 --- a/TestAssets/ProjectModelServer/DthTestProjects/src/EmptyConsoleApp/project.json +++ b/TestAssets/ProjectModelServer/DthTestProjects/src/EmptyConsoleApp/project.json @@ -1,12 +1,12 @@ { - "dependencies": { }, + "dependencies": {}, "frameworks": { "netstandardapp1.5": { "imports": "dnxcore50", "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" + "NETStandard.Library": "1.5.0-rc2-23911" } }, - "dnx451": { } + "dnx451": {} } } diff --git a/TestAssets/ProjectModelServer/DthTestProjects/src/EmptyLibrary/project.json b/TestAssets/ProjectModelServer/DthTestProjects/src/EmptyLibrary/project.json index 667685804..17c4e9e46 100644 --- a/TestAssets/ProjectModelServer/DthTestProjects/src/EmptyLibrary/project.json +++ b/TestAssets/ProjectModelServer/DthTestProjects/src/EmptyLibrary/project.json @@ -1,11 +1,11 @@ { "version": "1.0.0-*", - "dependencies": { }, + "dependencies": {}, "frameworks": { "netstandard1.3": { "imports": "dnxcore50", "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" + "NETStandard.Library": "1.5.0-rc2-23911" } } } diff --git a/TestAssets/ProjectModelServer/DthTestProjects/src/FailReleaseProject/project.json b/TestAssets/ProjectModelServer/DthTestProjects/src/FailReleaseProject/project.json index 505047ad2..83b5d4335 100644 --- a/TestAssets/ProjectModelServer/DthTestProjects/src/FailReleaseProject/project.json +++ b/TestAssets/ProjectModelServer/DthTestProjects/src/FailReleaseProject/project.json @@ -3,9 +3,9 @@ "netstandardapp1.5": { "imports": "dnxcore50", "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" + "NETStandard.Library": "1.5.0-rc2-23911" } } }, - "dependencies": { } + "dependencies": {} } diff --git a/TestAssets/ProjectModelServer/DthTestProjects/src/IncompatiblePackageSample/project.json b/TestAssets/ProjectModelServer/DthTestProjects/src/IncompatiblePackageSample/project.json index 58af503dc..37a82e632 100644 --- a/TestAssets/ProjectModelServer/DthTestProjects/src/IncompatiblePackageSample/project.json +++ b/TestAssets/ProjectModelServer/DthTestProjects/src/IncompatiblePackageSample/project.json @@ -1,11 +1,11 @@ { - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "Microsoft.Web.Administration": "7.0.0" - }, - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "Microsoft.Web.Administration": "7.0.0" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/ProjectModelServer/IncorrectGlobalJson/src/Project1/project.json b/TestAssets/ProjectModelServer/IncorrectGlobalJson/src/Project1/project.json index 8c87d36c9..bb3a9cebf 100755 --- a/TestAssets/ProjectModelServer/IncorrectGlobalJson/src/Project1/project.json +++ b/TestAssets/ProjectModelServer/IncorrectGlobalJson/src/Project1/project.json @@ -1,14 +1,12 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true - }, - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23811" - }, - - "frameworks": { - "dnxcore50": { } - } + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "dnxcore50": {} + } } diff --git a/TestAssets/TestPackages/dotnet-hello/v1/dotnet-hello/project.json b/TestAssets/TestPackages/dotnet-hello/v1/dotnet-hello/project.json index ea118a24d..cafaf976b 100644 --- a/TestAssets/TestPackages/dotnet-hello/v1/dotnet-hello/project.json +++ b/TestAssets/TestPackages/dotnet-hello/v1/dotnet-hello/project.json @@ -1,16 +1,14 @@ { - "version": "1.0.0", - "compilationOptions": { - "emitEntryPoint": true - }, - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestPackages/dotnet-hello/v2/dotnet-hello/project.json b/TestAssets/TestPackages/dotnet-hello/v2/dotnet-hello/project.json index 464f94681..c1bd9c205 100644 --- a/TestAssets/TestPackages/dotnet-hello/v2/dotnet-hello/project.json +++ b/TestAssets/TestPackages/dotnet-hello/v2/dotnet-hello/project.json @@ -1,16 +1,14 @@ { - "version": "2.0.0", - "compilationOptions": { - "emitEntryPoint": true - }, - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "2.0.0", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestPackages/dotnet-portable/Program.cs b/TestAssets/TestPackages/dotnet-portable/Program.cs new file mode 100644 index 000000000..58d61b1bc --- /dev/null +++ b/TestAssets/TestPackages/dotnet-portable/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace ConsoleApplication +{ + public class Program + { + public static void Main(string[] args) + { + Console.WriteLine("Hello Portable World!"); + } + } +} diff --git a/TestAssets/TestPackages/dotnet-portable/project.json b/TestAssets/TestPackages/dotnet-portable/project.json new file mode 100644 index 000000000..0f9d20ba9 --- /dev/null +++ b/TestAssets/TestPackages/dotnet-portable/project.json @@ -0,0 +1,18 @@ +{ + "version": "1.0.0", + "compilationOptions": { + "emitEntryPoint": true + }, + + "frameworks": { + "netstandard1.5": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ], + "dependencies": { + "Microsoft.NETCore.App": "1.0.0-rc2-23911" + } + } + } +} diff --git a/TestAssets/TestProjects/AppWithDirectAndToolDependency/project.json b/TestAssets/TestProjects/AppWithDirectAndToolDependency/project.json index 679acc364..c888180eb 100644 --- a/TestAssets/TestProjects/AppWithDirectAndToolDependency/project.json +++ b/TestAssets/TestProjects/AppWithDirectAndToolDependency/project.json @@ -1,23 +1,25 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true - }, - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "dotnet-hello": { "version": "1.0.0", "target": "package" } - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } - }, - - "testRunner": "must-be-specified-to-generate-deps", - - "tools": { - "dotnet-hello": { "version": "2.0.0", "target": "package" } + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "dotnet-hello": { + "version": "1.0.0", + "target": "package" } + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" + } + }, + "testRunner": "must-be-specified-to-generate-deps", + "tools": { + "dotnet-portable": { + "version": "1.0.0", + "target": "package" + } + } } diff --git a/TestAssets/TestProjects/AppWithDirectDependency/project.json b/TestAssets/TestProjects/AppWithDirectDependency/project.json index 2ce5cf482..14cc1022a 100644 --- a/TestAssets/TestProjects/AppWithDirectDependency/project.json +++ b/TestAssets/TestProjects/AppWithDirectDependency/project.json @@ -1,19 +1,19 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true - }, - - "testRunner": "must-be-specified-to-generate-deps", - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "dotnet-hello": {"version": "1.0.0", "target": "package"} - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "testRunner": "must-be-specified-to-generate-deps", + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "dotnet-hello": { + "version": "1.0.0", + "target": "package" } + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" + } + } } diff --git a/TestAssets/TestProjects/AppWithToolDependency/project.json b/TestAssets/TestProjects/AppWithToolDependency/project.json index 9537feb8f..b25c0b2a3 100644 --- a/TestAssets/TestProjects/AppWithToolDependency/project.json +++ b/TestAssets/TestProjects/AppWithToolDependency/project.json @@ -1,20 +1,20 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true - }, - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } - }, - - "tools": { - "dotnet-hello": { "version": "2.0.0", "target": "package" } + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + }, + "tools": { + "dotnet-portable": { + "version": "1.0.0", + "target": "package" + } + } } diff --git a/TestAssets/TestProjects/BuildTestPortableProject/Lib.cs b/TestAssets/TestProjects/BuildTestPortableProject/Lib.cs deleted file mode 100644 index 2ac94ca9d..000000000 --- a/TestAssets/TestProjects/BuildTestPortableProject/Lib.cs +++ /dev/null @@ -1,3 +0,0 @@ -public static class Thingy -{ -} diff --git a/TestAssets/TestProjects/BuildTestPortableProject/project.json b/TestAssets/TestProjects/BuildTestPortableProject/project.json deleted file mode 100644 index 841c77d4d..000000000 --- a/TestAssets/TestProjects/BuildTestPortableProject/project.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "dependencies": { - }, - "frameworks": { - "netstandardapp1.5": { - "imports": [ - "dnxcore50", - "portable-net45+win8" - ], - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - } - } - } -} diff --git a/TestAssets/TestProjects/BuildTestStandaloneProject/Lib.cs b/TestAssets/TestProjects/BuildTestStandaloneProject/Lib.cs deleted file mode 100644 index 2ac94ca9d..000000000 --- a/TestAssets/TestProjects/BuildTestStandaloneProject/Lib.cs +++ /dev/null @@ -1,3 +0,0 @@ -public static class Thingy -{ -} diff --git a/TestAssets/TestProjects/CompileFail/project.json b/TestAssets/TestProjects/CompileFail/project.json index c02d0dffe..2e5ebbe30 100644 --- a/TestAssets/TestProjects/CompileFail/project.json +++ b/TestAssets/TestProjects/CompileFail/project.json @@ -1,16 +1,14 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true - }, - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestProjects/DependencyContextValidator/DependencyContextValidator/project.json b/TestAssets/TestProjects/DependencyContextValidator/DependencyContextValidator/project.json index 90a5334e8..00cd67349 100644 --- a/TestAssets/TestProjects/DependencyContextValidator/DependencyContextValidator/project.json +++ b/TestAssets/TestProjects/DependencyContextValidator/DependencyContextValidator/project.json @@ -1,16 +1,15 @@ { - "version": "1.0.0-*", - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "Microsoft.Extensions.DependencyModel": { - "target": "project", - "version": "1.0.0-*" - } - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "Microsoft.Extensions.DependencyModel": { + "target": "project", + "version": "1.0.0-*" } + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" + } + } } diff --git a/TestAssets/TestProjects/DependencyContextValidator/TestApp/project.json b/TestAssets/TestProjects/DependencyContextValidator/TestApp/project.json index 4c2908690..965a3f06e 100644 --- a/TestAssets/TestProjects/DependencyContextValidator/TestApp/project.json +++ b/TestAssets/TestProjects/DependencyContextValidator/TestApp/project.json @@ -1,18 +1,16 @@ -{ - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true, - "preserveCompilationContext": true - }, - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "DependencyContextValidator": "1.0.0-*" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } +{ + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true, + "preserveCompilationContext": true + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "DependencyContextValidator": "1.0.0-*" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestProjects/DependencyContextValidator/TestAppDeps/project.json b/TestAssets/TestProjects/DependencyContextValidator/TestAppDeps/project.json index 9f1f402f6..f41c7127f 100644 --- a/TestAssets/TestProjects/DependencyContextValidator/TestAppDeps/project.json +++ b/TestAssets/TestProjects/DependencyContextValidator/TestAppDeps/project.json @@ -1,17 +1,15 @@ -{ - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true - }, - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "DependencyContextValidator": "1.0.0-*" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } +{ + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "DependencyContextValidator": "1.0.0-*" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestProjects/OutputStandardOutputAndError/project.json b/TestAssets/TestProjects/OutputStandardOutputAndError/project.json index c02d0dffe..2e5ebbe30 100644 --- a/TestAssets/TestProjects/OutputStandardOutputAndError/project.json +++ b/TestAssets/TestProjects/OutputStandardOutputAndError/project.json @@ -1,16 +1,14 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true - }, - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestProjects/PortableTests/PortableApp/.noautobuild b/TestAssets/TestProjects/PortableTests/PortableApp/.noautobuild new file mode 100644 index 000000000..e69de29bb diff --git a/TestAssets/TestProjects/PortableTests/PortableApp/Program.cs b/TestAssets/TestProjects/PortableTests/PortableApp/Program.cs new file mode 100644 index 000000000..fbe8e9b0e --- /dev/null +++ b/TestAssets/TestProjects/PortableTests/PortableApp/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace PortableApp +{ + public static class Program + { + public static void Main(string[] args) + { + Console.WriteLine("Hello, World!"); + } + } +} diff --git a/TestAssets/TestProjects/PortableTests/PortableApp/project.json b/TestAssets/TestProjects/PortableTests/PortableApp/project.json new file mode 100644 index 000000000..3111191ad --- /dev/null +++ b/TestAssets/TestProjects/PortableTests/PortableApp/project.json @@ -0,0 +1,17 @@ +{ + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": {}, + "frameworks": { + "netstandard1.5": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ], + "dependencies": { + "Microsoft.NETCore.App": "1.0.0-rc2-23911" + } + } + } +} diff --git a/TestAssets/TestProjects/PortableTests/PortableAppWithNative/.noautobuild b/TestAssets/TestProjects/PortableTests/PortableAppWithNative/.noautobuild new file mode 100644 index 000000000..e69de29bb diff --git a/TestAssets/TestProjects/PortableTests/PortableAppWithNative/Program.cs b/TestAssets/TestProjects/PortableTests/PortableAppWithNative/Program.cs new file mode 100644 index 000000000..01b2c06d5 --- /dev/null +++ b/TestAssets/TestProjects/PortableTests/PortableAppWithNative/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace PortableAppWithNative +{ + public static class Program + { + public static void Main(string[] args) + { + Console.WriteLine("Hello, World!"); + } + } +} diff --git a/TestAssets/TestProjects/PortableTests/PortableAppWithNative/project.json b/TestAssets/TestProjects/PortableTests/PortableAppWithNative/project.json new file mode 100644 index 000000000..b315d44e7 --- /dev/null +++ b/TestAssets/TestProjects/PortableTests/PortableAppWithNative/project.json @@ -0,0 +1,14 @@ +{ + "compilationOptions": { + "emitEntryPoint": true + }, + "frameworks": { + "netstandard1.5": { + "imports": [ "dnxcore50", "portable-net45+win8" ], + "dependencies": { + "Microsoft.NETCore.App": "1.0.0-rc2-23911", + "Microsoft.AspNetCore.Server.Kestrel": "1.0.0-*" + } + } + } +} diff --git a/TestAssets/TestProjects/PortableTests/StandaloneApp/.noautobuild b/TestAssets/TestProjects/PortableTests/StandaloneApp/.noautobuild new file mode 100644 index 000000000..e69de29bb diff --git a/TestAssets/TestProjects/PortableTests/StandaloneApp/Program.cs b/TestAssets/TestProjects/PortableTests/StandaloneApp/Program.cs new file mode 100644 index 000000000..529893b0e --- /dev/null +++ b/TestAssets/TestProjects/PortableTests/StandaloneApp/Program.cs @@ -0,0 +1,12 @@ +using System; + +namespace StandaloneApp +{ + public static class Program + { + public static void Main(string[] args) + { + Console.WriteLine("Hello, World!"); + } + } +} diff --git a/TestAssets/TestProjects/PortableTests/StandaloneApp/project.json b/TestAssets/TestProjects/PortableTests/StandaloneApp/project.json new file mode 100644 index 000000000..2cf04776c --- /dev/null +++ b/TestAssets/TestProjects/PortableTests/StandaloneApp/project.json @@ -0,0 +1,26 @@ +{ + "compilationOptions": { + "emitEntryPoint": true + }, + "frameworks": { + "netstandardapp1.5": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ], + "dependencies": { + "Microsoft.NETCore.App": "1.0.0-rc2-23911" + } + } + }, + "runtimes": { + "win7-x64": { }, + "win7-x86": { }, + "osx.10.10-x64": { }, + "osx.10.11-x64": { }, + "ubuntu.14.04-x64": { }, + "centos.7-x64": { }, + "rhel.7.2-x64": { }, + "debian.8.2-x64": { } + } +} diff --git a/TestAssets/TestProjects/ProjectWithTests/GivenThatIWantSomeFakeTests.cs b/TestAssets/TestProjects/ProjectWithTests/GivenThatIWantSomeFakeTests.cs new file mode 100644 index 000000000..4eca454af --- /dev/null +++ b/TestAssets/TestProjects/ProjectWithTests/GivenThatIWantSomeFakeTests.cs @@ -0,0 +1,22 @@ +// 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 Xunit; + +namespace FakeTests +{ + public class GivenThatIWantSomeFakeTests + { + [Fact] + public void It_succeeds() + { + Assert.True(true); + } + + [Fact] + public void It_fails() + { + Assert.True(false); + } + } +} \ No newline at end of file diff --git a/TestAssets/TestProjects/ProjectWithTests/project.json b/TestAssets/TestProjects/ProjectWithTests/project.json new file mode 100644 index 000000000..db7fc17fa --- /dev/null +++ b/TestAssets/TestProjects/ProjectWithTests/project.json @@ -0,0 +1,21 @@ +{ + "version": "1.0.0-*", + + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + + "xunit": "2.1.0", + "dotnet-test-xunit": "1.0.0-dev-91790-12" + }, + + "frameworks": { + "netstandardapp1.5": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ] + } + }, + + "testRunner": "xunit" +} diff --git a/TestAssets/TestProjects/RunTestsApps/TestAppMultiTarget/project.json b/TestAssets/TestProjects/RunTestsApps/TestAppMultiTarget/project.json index 5ed30dfa4..a081d6a7d 100644 --- a/TestAssets/TestProjects/RunTestsApps/TestAppMultiTarget/project.json +++ b/TestAssets/TestProjects/RunTestsApps/TestAppMultiTarget/project.json @@ -1,16 +1,15 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50", + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + } }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50", - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - } - }, - "net451": { } - } + "net451": {} + } } diff --git a/TestAssets/TestProjects/TestAppCompilationContext/TestApp/project.json b/TestAssets/TestProjects/TestAppCompilationContext/TestApp/project.json index ded1b2439..6df0b0c6d 100644 --- a/TestAssets/TestProjects/TestAppCompilationContext/TestApp/project.json +++ b/TestAssets/TestProjects/TestAppCompilationContext/TestApp/project.json @@ -1,19 +1,16 @@ -{ - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true, - "preserveCompilationContext": true - }, - - "dependencies": { - "TestLibrary": "1.0.0-*", - - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } +{ + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true, + "preserveCompilationContext": true + }, + "dependencies": { + "TestLibrary": "1.0.0-*", + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestProjects/TestAppCompilationContext/TestLibrary/project.json b/TestAssets/TestProjects/TestAppCompilationContext/TestLibrary/project.json index dce6a0dad..f0b60ee88 100644 --- a/TestAssets/TestProjects/TestAppCompilationContext/TestLibrary/project.json +++ b/TestAssets/TestProjects/TestAppCompilationContext/TestLibrary/project.json @@ -1,17 +1,20 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "nowarn": [ "CS1591" ], - "xmlDoc": true, - "additionalArguments": [ "-highentropyva+" ] - }, - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "compilationOptions": { + "nowarn": [ + "CS1591" + ], + "xmlDoc": true, + "additionalArguments": [ + "-highentropyva+" + ] + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestProjects/TestAppWithArgs/project.json b/TestAssets/TestProjects/TestAppWithArgs/project.json index c02d0dffe..2e5ebbe30 100644 --- a/TestAssets/TestProjects/TestAppWithArgs/project.json +++ b/TestAssets/TestProjects/TestAppWithArgs/project.json @@ -1,16 +1,14 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true - }, - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestProjects/TestAppWithContentPackage/project.json b/TestAssets/TestProjects/TestAppWithContentPackage/project.json index 43575128d..6e9d2a179 100644 --- a/TestAssets/TestProjects/TestAppWithContentPackage/project.json +++ b/TestAssets/TestProjects/TestAppWithContentPackage/project.json @@ -1,11 +1,10 @@ -{ +{ "version": "1.0.0-*", "compilationOptions": { "emitEntryPoint": true }, - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", + "NETStandard.Library": "1.5.0-rc2-23911", "SharedContentA": "1.0.0-*" }, "frameworks": { @@ -13,4 +12,4 @@ "imports": "dnxcore50" } } -} \ No newline at end of file +} diff --git a/TestAssets/TestProjects/TestAppWithContents/project.json b/TestAssets/TestProjects/TestAppWithContents/project.json index c149fddc2..e5f81bb6f 100644 --- a/TestAssets/TestProjects/TestAppWithContents/project.json +++ b/TestAssets/TestProjects/TestAppWithContents/project.json @@ -1,18 +1,15 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true - }, - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "content": "testcontentfile.txt", - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "content": "testcontentfile.txt", + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestProjects/TestAppWithLibrary/TestApp/project.json b/TestAssets/TestProjects/TestAppWithLibrary/TestApp/project.json index f9f77f358..88dd7cbee 100644 --- a/TestAssets/TestProjects/TestAppWithLibrary/TestApp/project.json +++ b/TestAssets/TestProjects/TestAppWithLibrary/TestApp/project.json @@ -1,19 +1,19 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true, - "preserveCompilationContext": true + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true, + "preserveCompilationContext": true + }, + "dependencies": { + "TestLibrary": { + "target": "project", + "version": "1.0.0-*" }, - - "dependencies": { - "TestLibrary": { "target":"project", "version":"1.0.0-*" }, - - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestProjects/TestAppWithLibrary/TestLibrary/project.json b/TestAssets/TestProjects/TestAppWithLibrary/TestLibrary/project.json index 8e8b984b1..f0b60ee88 100644 --- a/TestAssets/TestProjects/TestAppWithLibrary/TestLibrary/project.json +++ b/TestAssets/TestProjects/TestAppWithLibrary/TestLibrary/project.json @@ -1,17 +1,20 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "nowarn": [ "CS1591" ], - "xmlDoc": true, - "additionalArguments": [ "-highentropyva+" ] - }, - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23811" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "compilationOptions": { + "nowarn": [ + "CS1591" + ], + "xmlDoc": true, + "additionalArguments": [ + "-highentropyva+" + ] + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestProjects/TestAppWithScripts/project.json b/TestAssets/TestProjects/TestAppWithScripts/project.json index 8de4803d7..11692199c 100644 --- a/TestAssets/TestProjects/TestAppWithScripts/project.json +++ b/TestAssets/TestProjects/TestAppWithScripts/project.json @@ -1,24 +1,21 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true - }, - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } - }, - - "scripts": { - "prepublish" : ["echoscript prepublish_output ?%publish:ProjectPath%? ?%publish:Configuration%? ?%publish:OutputPath%? ?%publish:TargetFramework%? ?%publish:Runtime%?"], - "postpublish" : ["echoscript postpublish_output ?%publish:ProjectPath%? ?%publish:Configuration%? ?%publish:OutputPath%? ?%publish:TargetFramework%? ?%publish:Runtime%?"], - - "precompile" : ["echoscript precompile_output ?%compile:ProjectPath%? ?%compile:Configuration%? ?%compile:OutputPath%? ?%compile:TargetFramework%? ?%compile:Runtime%?"], - "postcompile" : ["echoscript postcompile_output ?%compile:ProjectPath%? ?%compile:Configuration%? ?%compile:OutputPath%? ?%compile:TargetFramework%? ?%compile:Runtime%?"] + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + }, + "scripts": { + "prepublish": [ "echoscript prepublish_output ?%publish:ProjectPath%? ?%publish:Configuration%? ?%publish:OutputPath%? ?%publish:TargetFramework%? ?%publish:Runtime%?" ], + "postpublish": [ "echoscript postpublish_output ?%publish:ProjectPath%? ?%publish:Configuration%? ?%publish:OutputPath%? ?%publish:TargetFramework%? ?%publish:Runtime%?" ], + + "precompile": [ "echoscript precompile_output ?%compile:ProjectPath%? ?%compile:Configuration%? ?%compile:OutputPath%? ?%compile:TargetFramework%? ?%compile:Runtime%?" ], + "postcompile": [ "echoscript postcompile_output ?%compile:ProjectPath%? ?%compile:Configuration%? ?%compile:OutputPath%? ?%compile:TargetFramework%? ?%compile:Runtime%?" ] + } } diff --git a/TestAssets/TestProjects/TestAppWithTransitiveAppDependency/TestApp/project.json b/TestAssets/TestProjects/TestAppWithTransitiveAppDependency/TestApp/project.json index a1cb82f95..efafdef41 100644 --- a/TestAssets/TestProjects/TestAppWithTransitiveAppDependency/TestApp/project.json +++ b/TestAssets/TestProjects/TestAppWithTransitiveAppDependency/TestApp/project.json @@ -1,24 +1,27 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true, - "preserveCompilationContext": true + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true, + "preserveCompilationContext": true + }, + "dependencies": { + "TestLibrary": { + "target": "project", + "version": "1.0.0-*" }, - - "dependencies": { - "TestLibrary": { "target":"project", "version":"1.0.0-*" }, - - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } - }, - - "scripts": { - "prepublish" : ["echo prepublish_output ?%publish:ProjectPath%? ?%publish:Configuration%? ?%publish:OutputPath%? ?%publish:Framework%? ?%publish:Runtime%?"], - "postpublish" : ["echo postpublish_output ?%publish:ProjectPath%? ?%publish:Configuration%? ?%publish:OutputPath%? ?%publish:Framework%? ?%publish:Runtime%?"] + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + }, + "scripts": { + "prepublish": [ + "echo prepublish_output ?%publish:ProjectPath%? ?%publish:Configuration%? ?%publish:OutputPath%? ?%publish:Framework%? ?%publish:Runtime%?" + ], + "postpublish": [ + "echo postpublish_output ?%publish:ProjectPath%? ?%publish:Configuration%? ?%publish:OutputPath%? ?%publish:Framework%? ?%publish:Runtime%?" + ] + } } diff --git a/TestAssets/TestProjects/TestAppWithTransitiveAppDependency/TestLibrary/project.json b/TestAssets/TestProjects/TestAppWithTransitiveAppDependency/TestLibrary/project.json index dce6a0dad..f0b60ee88 100644 --- a/TestAssets/TestProjects/TestAppWithTransitiveAppDependency/TestLibrary/project.json +++ b/TestAssets/TestProjects/TestAppWithTransitiveAppDependency/TestLibrary/project.json @@ -1,17 +1,20 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "nowarn": [ "CS1591" ], - "xmlDoc": true, - "additionalArguments": [ "-highentropyva+" ] - }, - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "compilationOptions": { + "nowarn": [ + "CS1591" + ], + "xmlDoc": true, + "additionalArguments": [ + "-highentropyva+" + ] + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestProjects/TestAppWithTransitiveAppDependency/TestLibrary2/project.json b/TestAssets/TestProjects/TestAppWithTransitiveAppDependency/TestLibrary2/project.json index 06a4d2157..bc94bb833 100644 --- a/TestAssets/TestProjects/TestAppWithTransitiveAppDependency/TestLibrary2/project.json +++ b/TestAssets/TestProjects/TestAppWithTransitiveAppDependency/TestLibrary2/project.json @@ -1,18 +1,18 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "TestLibraryWithAppDependency": { + "target": "project", + "version": "1.0.0-*" }, - - "dependencies": { - "TestLibraryWithAppDependency": { "target":"project", "version":"1.0.0-*" }, - - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestProjects/TestAppWithTransitiveAppDependency/TestLibraryWithAppDependency/project.json b/TestAssets/TestProjects/TestAppWithTransitiveAppDependency/TestLibraryWithAppDependency/project.json index 42c1b5f55..9b7ae73b7 100644 --- a/TestAssets/TestProjects/TestAppWithTransitiveAppDependency/TestLibraryWithAppDependency/project.json +++ b/TestAssets/TestProjects/TestAppWithTransitiveAppDependency/TestLibraryWithAppDependency/project.json @@ -1,14 +1,15 @@ { - "version": "1.0.0-*", - "dependencies": { - "TestApp": { "target":"project", "version":"1.0.0-*" }, - - "NETStandard.Library": "1.0.0-rc2-23901" + "version": "1.0.0-*", + "dependencies": { + "TestApp": { + "target": "project", + "version": "1.0.0-*" }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestProjects/TestAppWithWrapperProjectDependency/TestApp/project.json b/TestAssets/TestProjects/TestAppWithWrapperProjectDependency/TestApp/project.json index f9f77f358..88dd7cbee 100644 --- a/TestAssets/TestProjects/TestAppWithWrapperProjectDependency/TestApp/project.json +++ b/TestAssets/TestProjects/TestAppWithWrapperProjectDependency/TestApp/project.json @@ -1,19 +1,19 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true, - "preserveCompilationContext": true + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true, + "preserveCompilationContext": true + }, + "dependencies": { + "TestLibrary": { + "target": "project", + "version": "1.0.0-*" }, - - "dependencies": { - "TestLibrary": { "target":"project", "version":"1.0.0-*" }, - - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestProjects/TestAppWithWrapperProjectDependency/TestLibrary/README.md b/TestAssets/TestProjects/TestAppWithWrapperProjectDependency/TestLibrary/README.md new file mode 100644 index 000000000..add0a6729 --- /dev/null +++ b/TestAssets/TestProjects/TestAppWithWrapperProjectDependency/TestLibrary/README.md @@ -0,0 +1,2 @@ +# What is this? +This is a test wrapped project where we've checked in the binaries. To protect it from the build scripts cleaning the `bin` folder, we've renamed that folder to `bin.keep`. Please don't rename it! \ No newline at end of file diff --git a/TestAssets/TestProjects/TestAppWithWrapperProjectDependency/TestLibrary/bin/Debug/dnxcore50/TestLibrary.dll b/TestAssets/TestProjects/TestAppWithWrapperProjectDependency/TestLibrary/bin.keep/Debug/dnxcore50/TestLibrary.dll similarity index 100% rename from TestAssets/TestProjects/TestAppWithWrapperProjectDependency/TestLibrary/bin/Debug/dnxcore50/TestLibrary.dll rename to TestAssets/TestProjects/TestAppWithWrapperProjectDependency/TestLibrary/bin.keep/Debug/dnxcore50/TestLibrary.dll diff --git a/TestAssets/TestProjects/TestAppWithWrapperProjectDependency/TestLibrary/bin/Debug/dnxcore50/TestLibrary.pdb b/TestAssets/TestProjects/TestAppWithWrapperProjectDependency/TestLibrary/bin.keep/Debug/dnxcore50/TestLibrary.pdb similarity index 100% rename from TestAssets/TestProjects/TestAppWithWrapperProjectDependency/TestLibrary/bin/Debug/dnxcore50/TestLibrary.pdb rename to TestAssets/TestProjects/TestAppWithWrapperProjectDependency/TestLibrary/bin.keep/Debug/dnxcore50/TestLibrary.pdb diff --git a/TestAssets/TestProjects/TestAppWithWrapperProjectDependency/TestLibrary/bin/Debug/dnxcore50/TestLibrary.xml b/TestAssets/TestProjects/TestAppWithWrapperProjectDependency/TestLibrary/bin.keep/Debug/dnxcore50/TestLibrary.xml similarity index 100% rename from TestAssets/TestProjects/TestAppWithWrapperProjectDependency/TestLibrary/bin/Debug/dnxcore50/TestLibrary.xml rename to TestAssets/TestProjects/TestAppWithWrapperProjectDependency/TestLibrary/bin.keep/Debug/dnxcore50/TestLibrary.xml diff --git a/TestAssets/TestProjects/TestAppWithWrapperProjectDependency/TestLibrary/project.json b/TestAssets/TestProjects/TestAppWithWrapperProjectDependency/TestLibrary/project.json index 5fb2b0c73..44ae2133b 100644 --- a/TestAssets/TestProjects/TestAppWithWrapperProjectDependency/TestLibrary/project.json +++ b/TestAssets/TestProjects/TestAppWithWrapperProjectDependency/TestLibrary/project.json @@ -3,8 +3,8 @@ "netstandardapp1.5": { "imports": "dnxcore50", "bin": { - "assembly": "bin\\{configuration}\\dnxcore50\\TestLibrary.dll", - "pdb": "bin\\{configuration}\\dnxcore50\\TestLibrary.pdb" + "assembly": "bin.keep\\{configuration}\\dnxcore50\\TestLibrary.dll", + "pdb": "bin.keep\\{configuration}\\dnxcore50\\TestLibrary.pdb" } } } diff --git a/TestAssets/TestProjects/TestBindingRedirectGeneration/TestLibraryGreater/project.json b/TestAssets/TestProjects/TestBindingRedirectGeneration/TestLibraryGreater/project.json index 4b957d224..6254293c5 100644 --- a/TestAssets/TestProjects/TestBindingRedirectGeneration/TestLibraryGreater/project.json +++ b/TestAssets/TestProjects/TestBindingRedirectGeneration/TestLibraryGreater/project.json @@ -1,20 +1,19 @@ -{ - "version": "1.0.0-*", - "testRunner": "xunit", - "dependencies": { - "Newtonsoft.Json": "7.0.1" - }, - - "frameworks": { - "net451": { }, - "netstandardapp1.5": { - "imports": [ - "dnxcore50", - "portable-net45+wp80+win8" - ], - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - } - } +{ + "version": "1.0.0-*", + "testRunner": "xunit", + "dependencies": { + "Newtonsoft.Json": "7.0.1" + }, + "frameworks": { + "net451": {}, + "netstandardapp1.5": { + "imports": [ + "dnxcore50", + "portable-net45+wp80+win8" + ], + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + } } + } } diff --git a/TestAssets/TestProjects/TestBindingRedirectGeneration/TestLibraryLesser/project.json b/TestAssets/TestProjects/TestBindingRedirectGeneration/TestLibraryLesser/project.json index 0cfc9606d..f1bb8120d 100644 --- a/TestAssets/TestProjects/TestBindingRedirectGeneration/TestLibraryLesser/project.json +++ b/TestAssets/TestProjects/TestBindingRedirectGeneration/TestLibraryLesser/project.json @@ -1,21 +1,22 @@ { - "version": "1.0.0-*", - "testRunner": "xunit", - "dependencies": { - "Newtonsoft.Json": "6.0.0", - "TestLibraryGreater": {"target":"project"} - }, - - "frameworks": { - "net451": { }, - "netstandardapp1.5": { - "imports": [ - "dnxcore50", - "portable-net45+wp80+win8" - ], - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - } - } + "version": "1.0.0-*", + "testRunner": "xunit", + "dependencies": { + "Newtonsoft.Json": "6.0.0", + "TestLibraryGreater": { + "target": "project" } + }, + "frameworks": { + "net451": {}, + "netstandardapp1.5": { + "imports": [ + "dnxcore50", + "portable-net45+wp80+win8" + ], + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + } + } + } } diff --git a/TestAssets/TestProjects/TestLibraryWithAnalyzer/project.json b/TestAssets/TestProjects/TestLibraryWithAnalyzer/project.json index 126757ed1..930858382 100644 --- a/TestAssets/TestProjects/TestLibraryWithAnalyzer/project.json +++ b/TestAssets/TestProjects/TestLibraryWithAnalyzer/project.json @@ -1,17 +1,18 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true - }, - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "System.Runtime.Analyzers": { "version": "1.1.0", "type": "build" } - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "System.Runtime.Analyzers": { + "version": "1.1.0", + "type": "build" } + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" + } + } } diff --git a/TestAssets/TestProjects/TestLibraryWithConfiguration/project.json b/TestAssets/TestProjects/TestLibraryWithConfiguration/project.json index 731ac1d10..a4c57dce6 100644 --- a/TestAssets/TestProjects/TestLibraryWithConfiguration/project.json +++ b/TestAssets/TestProjects/TestLibraryWithConfiguration/project.json @@ -1,21 +1,23 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "nowarn": [ "CS1591" ], - "xmlDoc": true, - "additionalArguments": [ "-highentropyva+" ] - }, - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - }, - "configurations": { - "Test": { - - } - }, - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "compilationOptions": { + "nowarn": [ + "CS1591" + ], + "xmlDoc": true, + "additionalArguments": [ + "-highentropyva+" + ] + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "configurations": { + "Test": {} + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestProjects/TestLibraryWithMultipleFrameworks/project.json b/TestAssets/TestProjects/TestLibraryWithMultipleFrameworks/project.json index 391a5fc11..f12c8dafa 100644 --- a/TestAssets/TestProjects/TestLibraryWithMultipleFrameworks/project.json +++ b/TestAssets/TestProjects/TestLibraryWithMultipleFrameworks/project.json @@ -1,21 +1,19 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": false - }, - - "dependencies": { }, - - "frameworks": { - "net20": { }, - "net35": { }, - "net40": { }, - "net461": { }, - "netstandardapp1.5": { - "imports": "dnxcore50", - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - } - } + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": false + }, + "dependencies": {}, + "frameworks": { + "net20": {}, + "net35": {}, + "net40": {}, + "net461": {}, + "netstandardapp1.5": { + "imports": "dnxcore50", + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + } } + } } diff --git a/TestAssets/TestProjects/TestMicrosoftCSharpReference/project.json b/TestAssets/TestProjects/TestMicrosoftCSharpReference/project.json index 8f97305eb..8d78b8ecc 100644 --- a/TestAssets/TestProjects/TestMicrosoftCSharpReference/project.json +++ b/TestAssets/TestProjects/TestMicrosoftCSharpReference/project.json @@ -1,17 +1,17 @@ { - "version": "1.0.0", - "dependencies": { }, - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50", - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - } - }, - "dnx451": { - "dependencies": { - "Microsoft.CSharp": "4.0.1-*" - } - } + "version": "1.0.0", + "dependencies": {}, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50", + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + } + }, + "dnx451": { + "dependencies": { + "Microsoft.CSharp": "4.0.1-rc2-23911" + } } -} \ No newline at end of file + } +} diff --git a/TestAssets/TestProjects/TestProjectToProjectDependencies/src/L0/project.json b/TestAssets/TestProjects/TestProjectToProjectDependencies/src/L0/project.json index ef971a748..06f18fa20 100644 --- a/TestAssets/TestProjects/TestProjectToProjectDependencies/src/L0/project.json +++ b/TestAssets/TestProjects/TestProjectToProjectDependencies/src/L0/project.json @@ -1,19 +1,16 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true - }, - - "dependencies": { - "L11": "1.0.0-*", - "L12": "1.0.0-*", - - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "L11": "1.0.0-*", + "L12": "1.0.0-*", + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestProjects/TestProjectToProjectDependencies/src/L11/project.json b/TestAssets/TestProjects/TestProjectToProjectDependencies/src/L11/project.json index ed55b003b..ed65c4c04 100644 --- a/TestAssets/TestProjects/TestProjectToProjectDependencies/src/L11/project.json +++ b/TestAssets/TestProjects/TestProjectToProjectDependencies/src/L11/project.json @@ -1,16 +1,13 @@ { - "version": "1.0.0-*", - - "dependencies": { - "L12": "1.0.0-*", - "L21": "1.0.0-*", - - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "dependencies": { + "L12": "1.0.0-*", + "L21": "1.0.0-*", + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestProjects/TestProjectToProjectDependencies/src/L12/project.json b/TestAssets/TestProjects/TestProjectToProjectDependencies/src/L12/project.json index ac03a9cf7..e9486914f 100644 --- a/TestAssets/TestProjects/TestProjectToProjectDependencies/src/L12/project.json +++ b/TestAssets/TestProjects/TestProjectToProjectDependencies/src/L12/project.json @@ -1,15 +1,12 @@ { - "version": "1.0.0-*", - - "dependencies": { - "L22": "1.0.0-*", - - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "dependencies": { + "L22": "1.0.0-*", + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestProjects/TestProjectToProjectDependencies/src/L21/project.json b/TestAssets/TestProjects/TestProjectToProjectDependencies/src/L21/project.json index 8af014e51..155cb2f02 100644 --- a/TestAssets/TestProjects/TestProjectToProjectDependencies/src/L21/project.json +++ b/TestAssets/TestProjects/TestProjectToProjectDependencies/src/L21/project.json @@ -1,13 +1,11 @@ { - "version": "1.0.0-*", - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestProjects/TestProjectToProjectDependencies/src/L22/project.json b/TestAssets/TestProjects/TestProjectToProjectDependencies/src/L22/project.json index 8af014e51..155cb2f02 100644 --- a/TestAssets/TestProjects/TestProjectToProjectDependencies/src/L22/project.json +++ b/TestAssets/TestProjects/TestProjectToProjectDependencies/src/L22/project.json @@ -1,13 +1,11 @@ { - "version": "1.0.0-*", - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestProjects/TestProjectWithCultureSpecificResource/project.json b/TestAssets/TestProjects/TestProjectWithCultureSpecificResource/project.json index c02d0dffe..2e5ebbe30 100644 --- a/TestAssets/TestProjects/TestProjectWithCultureSpecificResource/project.json +++ b/TestAssets/TestProjects/TestProjectWithCultureSpecificResource/project.json @@ -1,16 +1,14 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true - }, - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestProjects/TestProjectWithResource/project.json b/TestAssets/TestProjects/TestProjectWithResource/project.json index c02d0dffe..2e5ebbe30 100644 --- a/TestAssets/TestProjects/TestProjectWithResource/project.json +++ b/TestAssets/TestProjects/TestProjectWithResource/project.json @@ -1,16 +1,14 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true - }, - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/TestAssets/TestProjects/TestSimpleIncrementalApp/project.json b/TestAssets/TestProjects/TestSimpleIncrementalApp/project.json index 3605210ec..7c888a5ad 100644 --- a/TestAssets/TestProjects/TestSimpleIncrementalApp/project.json +++ b/TestAssets/TestProjects/TestSimpleIncrementalApp/project.json @@ -1,17 +1,15 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true, - "xmlDoc": true - }, - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true, + "xmlDoc": true + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/netci.groovy b/netci.groovy index 0ff8b01e2..695a2c264 100644 --- a/netci.groovy +++ b/netci.groovy @@ -7,61 +7,56 @@ import jobs.generation.Utilities; def project = GithubProject def branch = GithubBranchName +def isPR = true -def osList = ['Ubuntu', 'OSX', 'Windows_NT', 'Windows_2016', 'CentOS7.1'] +def platformList = ['Ubuntu:x64:Debug', 'Ubuntu:x64:Release', 'OSX:x64:Release', 'Windows_NT:x64:Release', 'Windows_2016:x64:Release', 'Windows_2016:x86:Debug', 'RHEL7.2:x64:Release', 'CentOS7.1:x64:Debug'] -def static getBuildJobName(def configuration, def os) { - return configuration.toLowerCase() + '_' + os.toLowerCase() +def static getBuildJobName(def configuration, def os, def architecture) { + return configuration.toLowerCase() + '_' + os.toLowerCase() + '_' + architecture.toLowerCase() } -[true, false].each { isPR -> - ['Debug', 'Release'].each { configuration -> - osList.each { os -> - // Calculate names - def lowerConfiguration = configuration.toLowerCase() - // Calculate job name - def jobName = getBuildJobName(configuration, os) - def buildCommand = ''; +platformList.each { platform -> + // Calculate names + def (os, architecture, configuration) = platform.tokenize(':') - // Calculate the build command - if (os == 'Windows_NT') { - buildCommand = ".\\build.cmd -Configuration ${lowerConfiguration} -Targets Default" - } - else if (os == 'Windows_2016') { - buildCommand = ".\\build.cmd -Configuration ${lowerConfiguration} -RunInstallerTestsInDocker -Targets Default" - } - else if (os == 'Ubuntu') { - buildCommand = "./build.sh --skip-prereqs --configuration ${lowerConfiguration} --docker ubuntu --targets Default" + // Calculate job name + def jobName = getBuildJobName(configuration, os, architecture) + def buildCommand = ''; + + // Calculate the build command + if (os == 'Windows_NT') { + buildCommand = ".\\build.cmd -Configuration ${configuration} -Architecture ${architecture} -Targets Default" + } + else if (os == 'Windows_2016') { + buildCommand = ".\\build.cmd -Configuration ${configuration} -Architecture ${architecture} -RunInstallerTestsInDocker -Targets Default" + } + else if (os == 'Ubuntu') { + buildCommand = "./build.sh --skip-prereqs --configuration ${configuration} --docker ubuntu --targets Default" + } + else { + // Jenkins non-Ubuntu CI machines don't have docker + buildCommand = "./build.sh --skip-prereqs --configuration ${configuration} --targets Default" + } + + def newJob = job(Utilities.getFullJobName(project, jobName, isPR)) { + // Set the label. + steps { + if (os == 'Windows_NT' || os == 'Windows_2016') { + // Batch + batchFile(buildCommand) } else { - // Jenkins non-Ubuntu CI machines don't have docker - buildCommand = "./build.sh --skip-prereqs --configuration ${lowerConfiguration} --targets Default" - } - - def newJob = job(Utilities.getFullJobName(project, jobName, isPR)) { - // Set the label. - steps { - if (os == 'Windows_NT' || os == 'Windows_2016') { - // Batch - batchFile(buildCommand) - } - else { - // Shell - shell(buildCommand) - } - } - } - - Utilities.setMachineAffinity(newJob, os, 'latest-or-auto') - Utilities.standardJobSetup(newJob, project, isPR, "*/${branch}") - Utilities.addXUnitDotNETResults(newJob, '**/*-testResults.xml') - if (isPR) { - Utilities.addGithubPRTriggerForBranch(newJob, branch, "${os} ${configuration} Build") - } - else { - Utilities.addGithubPushTrigger(newJob) + // Shell + shell(buildCommand) } } } + + Utilities.setMachineAffinity(newJob, os, 'latest-or-auto') + Utilities.standardJobSetup(newJob, project, isPR, "*/${branch}") + Utilities.addXUnitDotNETResults(newJob, '**/*-testResults.xml') + Utilities.addGithubPRTriggerForBranch(newJob, branch, "${os} ${architecture} ${configuration} Build") } + + diff --git a/packaging/host/debian/dotnet-sharedhost-debian_config.json b/packaging/host/debian/dotnet-sharedhost-debian_config.json new file mode 100644 index 000000000..840e5945f --- /dev/null +++ b/packaging/host/debian/dotnet-sharedhost-debian_config.json @@ -0,0 +1,35 @@ +{ + "maintainer_name":"Microsoft", + "maintainer_email": "dotnetcore@microsoft.com", + + "package_name": "dotnet-host", + "install_root": "/usr/share/dotnet", + + "short_description": ".NET Core Shared Host", + "long_description": ".NET Core is a cross-platform implementation of .NET Framework, a modern, modular platform\n for building diverse kinds of applications, from command-line applications to microservices and \n modern websites.\n This package contains the host that launches a .NET Core application.", + "homepage": "https://dotnet.github.io/core", + + "release":{ + "package_version":"1.0.0.0", + "package_revision":"1", + "urgency" : "low", + "changelog_message" : "Inital shared host." + }, + + "control": { + "priority":"standard", + "section":"libs", + "architecture":"amd64" + }, + + "copyright": "2015 Microsoft", + "license": { + "type": "MIT", + "full_text": "Copyright (c) 2015 Microsoft\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE." + }, + + "debian_dependencies":{ + "libssl-dev" : {}, + "libcurl3" : {} + } +} diff --git a/packaging/host/windows/generatemsi.ps1 b/packaging/host/windows/generatemsi.ps1 new file mode 100644 index 000000000..124a987e8 --- /dev/null +++ b/packaging/host/windows/generatemsi.ps1 @@ -0,0 +1,109 @@ +# 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. + +param( + [Parameter(Mandatory=$true)][string]$SharedHostPublishRoot, + [Parameter(Mandatory=$true)][string]$DotnetHostMSIOutput, + [Parameter(Mandatory=$true)][string]$WixRoot, + [Parameter(Mandatory=$true)][string]$DotnetMSIVersion, + [Parameter(Mandatory=$true)][string]$DotnetCLIVersion, + [Parameter(Mandatory=$true)][string]$Architecture, + [Parameter(Mandatory=$true)][string]$WixObjRoot +) + +. "$PSScriptRoot\..\..\..\scripts\common\_common.ps1" +$RepoRoot = Convert-Path "$PSScriptRoot\..\..\.." + +function RunCandle +{ + $result = $true + pushd "$WixRoot" + + Write-Host Running candle.. + $AuthWsxRoot = Join-Path $RepoRoot "packaging\host\windows" + + .\candle.exe -nologo ` + -out "$WixObjRoot\" ` + -ext WixDependencyExtension.dll ` + -dHostSrc="$SharedHostPublishRoot" ` + -dMicrosoftEula="$RepoRoot\packaging\osx\resources\en.lproj\eula.rtf" ` + -dBuildVersion="$DotnetMSIVersion" ` + -dDisplayVersion="$DotnetCLIVersion" ` + -arch $Architecture ` + "$AuthWsxRoot\host.wxs" ` + "$AuthWsxRoot\provider.wxs" ` + "$AuthWsxRoot\registrykeys.wxs" | Out-Host + + if($LastExitCode -ne 0) + { + $result = $false + Write-Host "Candle failed with exit code $LastExitCode." + } + + popd + return $result +} + +function RunLight +{ + $result = $true + pushd "$WixRoot" + + Write-Host Running light.. + + .\light.exe -nologo ` + -ext WixUIExtension.dll ` + -ext WixDependencyExtension.dll ` + -ext WixUtilExtension.dll ` + -cultures:en-us ` + "$WixObjRoot\host.wixobj" ` + "$WixObjRoot\provider.wixobj" ` + "$WixObjRoot\registrykeys.wixobj" ` + -out $DotnetHostMSIOutput | Out-Host + + if($LastExitCode -ne 0) + { + $result = $false + Write-Host "Light failed with exit code $LastExitCode." + } + + popd + return $result +} + +if(!(Test-Path $SharedHostPublishRoot)) +{ + throw "$SharedHostPublishRoot not found" +} + +if(!(Test-Path $WixObjRoot)) +{ + throw "$WixObjRoot not found" +} + +Write-Host "Creating shared host MSI at $DotnetHostMSIOutput" + +if([string]::IsNullOrEmpty($WixRoot)) +{ + Exit -1 +} + +if(-Not (RunCandle)) +{ + Exit -1 +} + +if(-Not (RunLight)) +{ + Exit -1 +} + +if(!(Test-Path $DotnetHostMSIOutput)) +{ + throw "Unable to create the shared host msi." + Exit -1 +} + +Write-Host -ForegroundColor Green "Successfully created shared host MSI - $DotnetHostMSIOutput" + +exit $LastExitCode \ No newline at end of file diff --git a/packaging/host/windows/host.wxs b/packaging/host/windows/host.wxs new file mode 100644 index 000000000..a2bc03f95 --- /dev/null +++ b/packaging/host/windows/host.wxs @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packaging/host/windows/provider.wxs b/packaging/host/windows/provider.wxs new file mode 100644 index 000000000..be191eb7e --- /dev/null +++ b/packaging/host/windows/provider.wxs @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/packaging/host/windows/registrykeys.wxs b/packaging/host/windows/registrykeys.wxs new file mode 100644 index 000000000..3d971599b --- /dev/null +++ b/packaging/host/windows/registrykeys.wxs @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packaging/host/windows/variables.wxi b/packaging/host/windows/variables.wxi new file mode 100644 index 000000000..71ea4a1d8 --- /dev/null +++ b/packaging/host/windows/variables.wxi @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packaging/osx/Distribution-Template b/packaging/osx/Distribution-Template deleted file mode 100644 index c220fa5ac..000000000 --- a/packaging/osx/Distribution-Template +++ /dev/null @@ -1,22 +0,0 @@ - - - .NET CLI {VERSION} - - - - - - - - - - - - - - - - - - dotnet-osx-x64.{VERSION}.pkg - diff --git a/packaging/osx/clisdk/Distribution-Template b/packaging/osx/clisdk/Distribution-Template new file mode 100644 index 000000000..7e3f3b7ad --- /dev/null +++ b/packaging/osx/clisdk/Distribution-Template @@ -0,0 +1,33 @@ + + + .NET CLI {VERSION} + + + + + + + + + + + + + + + + + + + + + + + + + + + com.microsoft.dotnet.sharedframework.{SharedFrameworkNugetName}.{SharedFrameworkNugetVersion}.component.osx.x64.pkg + com.microsoft.dotnet.sharedhost.osx.x64.pkg + com.microsoft.dotnet.sdk.osx.x64.pkg + diff --git a/packaging/osx/scripts/postinstall b/packaging/osx/clisdk/scripts/postinstall similarity index 86% rename from packaging/osx/scripts/postinstall rename to packaging/osx/clisdk/scripts/postinstall index 0a649953a..428b79064 100755 --- a/packaging/osx/scripts/postinstall +++ b/packaging/osx/clisdk/scripts/postinstall @@ -12,7 +12,7 @@ INSTALL_DESTINATION=$2 chmod -R 755 $INSTALL_DESTINATION # Add the installation bin directory to the system-wide paths -echo $INSTALL_DESTINATION/bin | tee -a /etc/paths.d/dotnet +echo $INSTALL_DESTINATION | tee -a /etc/paths.d/dotnet exit 0 diff --git a/packaging/osx/package-osx.sh b/packaging/osx/package-osx.sh deleted file mode 100755 index 819a7e1f6..000000000 --- a/packaging/osx/package-osx.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/bin/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. -# - -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 )" - -help(){ - echo "Usage: $0 [--version ] [--input ] [--output ] [--help]" - echo "" - echo "Options:" - echo " --version Specify a version for the package. Version format is 4 '.' separated numbers - ..." - echo " --input Package the entire contents of the directory tree." - echo " --output The path to where the package will be written." - exit 1 -} - -while [[ $# > 0 ]]; do - lowerI="$(echo $1 | awk '{print tolower($0)}')" - case $lowerI in - -v|--version) - DOTNET_CLI_VERSION=$2 - shift - ;; - -o|--output) - OUTPUT_PKG=$2 - shift - ;; - -i|--input) - INPUT_DIR=$2 - shift - ;; - --help) - help - ;; - *) - break - ;; - esac - shift -done - -if [ -z "$DOTNET_CLI_VERSION" ]; then - echo "Provide a version number. Missing option '--version'" && help -fi - -if [ -z "$OUTPUT_PKG" ]; then - echo "Provide an output pkg. Missing option '--output'" && help -fi - -if [ -z "$INPUT_DIR" ]; then - echo "Provide an input directory. Missing option '--input'" && help -fi - -if [ ! -d "$INPUT_DIR" ]; then - echo "'$INPUT_DIR' - is either missing or not a directory" 1>&2 - exit 1 -fi - -PACKAGE_DIR=$(dirname "${OUTPUT_PKG}") -[ -d "$PACKAGE_DIR" ] || mkdir -p $PACKAGE_DIR - -PACKAGE_ID=$(basename "${OUTPUT_PKG}") - -#chmod -R 755 $INPUT_DIR -pkgbuild --root $INPUT_DIR \ - --version $DOTNET_CLI_VERSION \ - --scripts $DIR/scripts \ - --identifier com.microsoft.dotnet.cli.pkg.dotnet-osx-x64 \ - --install-location /usr/local/share/dotnet \ - $DIR/$PACKAGE_ID - -cat $DIR/Distribution-Template | sed "/{VERSION}/s//$DOTNET_CLI_VERSION/g" > $DIR/Dist - -productbuild --version $DOTNET_CLI_VERSION --identifier com.microsoft.dotnet.cli --package-path $DIR --resources $DIR/resources --distribution $DIR/Dist $OUTPUT_PKG - -#Clean temp files -rm $DIR/$PACKAGE_ID -rm $DIR/Dist diff --git a/packaging/osx/sharedframework/scripts/postinstall b/packaging/osx/sharedframework/scripts/postinstall new file mode 100755 index 000000000..50fd8f1b7 --- /dev/null +++ b/packaging/osx/sharedframework/scripts/postinstall @@ -0,0 +1,13 @@ +#!/bin/sh +# +# 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. +# + +PACKAGE=$1 +INSTALL_DESTINATION=$2 + +# A temporary fix for the permissions issue(s) +chmod -R 755 $INSTALL_DESTINATION/shared + +exit 0 diff --git a/packaging/osx/sharedframework/shared-framework-distribution-template.xml b/packaging/osx/sharedframework/shared-framework-distribution-template.xml new file mode 100644 index 000000000..15ff9e92a --- /dev/null +++ b/packaging/osx/sharedframework/shared-framework-distribution-template.xml @@ -0,0 +1,24 @@ + + + .NET Core Shared Framework ({SharedFrameworkNugetName} {SharedFrameworkNugetVersion}) + + + + + + + + + + + + + + + + + + + com.microsoft.dotnet.sharedframework.{SharedFrameworkNugetName}.{SharedFrameworkNugetVersion}.component.osx.x64.pkg + com.microsoft.dotnet.sharedhost.osx.x64.pkg + diff --git a/packaging/osx/sharedhost/scripts/postinstall b/packaging/osx/sharedhost/scripts/postinstall new file mode 100755 index 000000000..bac3303f2 --- /dev/null +++ b/packaging/osx/sharedhost/scripts/postinstall @@ -0,0 +1,13 @@ +#!/bin/sh +# +# 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. +# + +PACKAGE=$1 +INSTALL_DESTINATION=$2 + +# A temporary fix for the permissions issue(s) +chmod 755 $INSTALL_DESTINATION/dotnet + +exit 0 diff --git a/packaging/sharedframework/debian/dotnet-sharedframework-debian_config.json b/packaging/sharedframework/debian/dotnet-sharedframework-debian_config.json new file mode 100644 index 000000000..93d32d198 --- /dev/null +++ b/packaging/sharedframework/debian/dotnet-sharedframework-debian_config.json @@ -0,0 +1,35 @@ +{ + "maintainer_name":"Microsoft", + "maintainer_email": "dotnetcore@microsoft.com", + + "package_name": "%SHARED_FRAMEWORK_DEBIAN_PACKAGE_NAME%", + "install_root": "/usr/share/dotnet", + + "short_description": ".NET Core Shared Framework %SHARED_FRAMEWORK_NUGET_NAME% %SHARED_FRAMEWORK_NUGET_VERSION%", + "long_description": ".NET Core is a cross-platform implementation of .NET Framework, a modern, modular platform\n for building diverse kinds of applications, from command-line applications to microservices and \n modern websites.\n This package contains a runtime and framework which can be used by .NET Core applications.", + "homepage": "https://dotnet.github.io/core", + + "release":{ + "package_version":"1.0.0.0", + "package_revision":"1", + "urgency" : "low", + "changelog_message" : "Inital shared framework." + }, + + "control": { + "priority":"standard", + "section":"libs", + "architecture":"amd64" + }, + + "copyright": "2015 Microsoft", + "license": { + "type": "MIT", + "full_text": "Copyright (c) 2015 Microsoft\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE." + }, + + "debian_dependencies":{ + "libssl1.0.0" : {}, + "libcurl3" : {} + } +} diff --git a/packaging/sharedframework/windows/generatemsi.ps1 b/packaging/sharedframework/windows/generatemsi.ps1 new file mode 100644 index 000000000..ed78f847d --- /dev/null +++ b/packaging/sharedframework/windows/generatemsi.ps1 @@ -0,0 +1,151 @@ +# 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. + +param( + [Parameter(Mandatory=$true)][string]$SharedFrameworkPublishRoot, + [Parameter(Mandatory=$true)][string]$SharedFrameworkMSIOutput, + [Parameter(Mandatory=$true)][string]$WixRoot, + [Parameter(Mandatory=$true)][string]$DotnetMSIVersion, + [Parameter(Mandatory=$true)][string]$SharedFrameworkNugetName, + [Parameter(Mandatory=$true)][string]$SharedFrameworkNugetVersion, + [Parameter(Mandatory=$true)][string]$SharedFrameworkUpgradeCode, + [Parameter(Mandatory=$true)][string]$Architecture, + [Parameter(Mandatory=$true)][string]$WixObjRoot +) + +. "$PSScriptRoot\..\..\..\scripts\common\_common.ps1" +$RepoRoot = Convert-Path "$PSScriptRoot\..\..\.." + +$InstallFileswsx = "$WixObjRoot\install-files.wxs" +$InstallFilesWixobj = "$WixObjRoot\install-files.wixobj" + + +function RunHeat +{ + $result = $true + pushd "$WixRoot" + + Write-Host Running heat.. + + .\heat.exe dir `"$SharedFrameworkPublishRoot`" ` + -nologo ` + -template fragment ` + -sreg -gg ` + -var var.SharedFrameworkSource ` + -cg InstallFiles ` + -srd ` + -dr DOTNETHOME ` + -out $InstallFileswsx | Out-Host + + if($LastExitCode -ne 0) + { + $result = $false + Write-Host "Heat failed with exit code $LastExitCode." + } + + popd + return $result +} + +function RunCandle +{ + $result = $true + pushd "$WixRoot" + + Write-Host Running candle.. + $AuthWsxRoot = Join-Path $RepoRoot "packaging\sharedframework\windows" + $SharedFrameworkComponentVersion = $SharedFrameworkNugetVersion.Replace('-', '_'); + + .\candle.exe -nologo ` + -out "$WixObjRoot\" ` + -dSharedFrameworkSource="$SharedFrameworkPublishRoot" ` + -dMicrosoftEula="$RepoRoot\packaging\osx\resources\en.lproj\eula.rtf" ` + -dFrameworkName="$SharedFrameworkNugetName" ` + -dFrameworkDisplayVersion="$SharedFrameworkNugetVersion" ` + -dFrameworkComponentVersion="$SharedFrameworkComponentVersion" ` + -dFrameworkUpgradeCode="$SharedFrameworkUpgradeCode" ` + -dBuildVersion="$DotnetMSIVersion" ` + -arch $Architecture ` + -ext WixDependencyExtension.dll ` + "$AuthWsxRoot\sharedframework.wxs" ` + "$AuthWsxRoot\provider.wxs" ` + "$AuthWsxRoot\registrykeys.wxs" ` + $InstallFileswsx | Out-Host + + if($LastExitCode -ne 0) + { + $result = $false + Write-Host "Candle failed with exit code $LastExitCode." + } + + popd + return $result +} + +function RunLight +{ + $result = $true + pushd "$WixRoot" + + Write-Host Running light.. + $CabCache = Join-Path $WixRoot "cabcache" + + .\light.exe -nologo -ext WixUIExtension -ext WixDependencyExtension -ext WixUtilExtension ` + -cultures:en-us ` + "$WixObjRoot\sharedframework.wixobj" ` + "$WixObjRoot\provider.wixobj" ` + "$WixObjRoot\registrykeys.wixobj" ` + "$InstallFilesWixobj" ` + -out $SharedFrameworkMSIOutput | Out-Host + + if($LastExitCode -ne 0) + { + $result = $false + Write-Host "Light failed with exit code $LastExitCode." + } + + popd + return $result +} + +if(!(Test-Path $SharedFrameworkPublishRoot)) +{ + throw "$SharedHostPublishRoot not found" +} + +if(!(Test-Path $WixObjRoot)) +{ + throw "$WixObjRoot not found" +} + +Write-Host "Creating dotnet shared framework MSI at $SharedFrameworkMSIOutput" + +if([string]::IsNullOrEmpty($WixRoot)) +{ + Exit -1 +} + +if(-Not (RunHeat)) +{ + Exit -1 +} + +if(-Not (RunCandle)) +{ + Exit -1 +} + +if(-Not (RunLight)) +{ + Exit -1 +} + +if(!(Test-Path $SharedFrameworkMSIOutput)) +{ + throw "Unable to create the dotnet shared framework msi." + Exit -1 +} + +Write-Host -ForegroundColor Green "Successfully created shared framework MSI - $SharedFrameworkMSIOutput" + +exit $LastExitCode diff --git a/packaging/sharedframework/windows/provider.wxs b/packaging/sharedframework/windows/provider.wxs new file mode 100644 index 000000000..665d58c4a --- /dev/null +++ b/packaging/sharedframework/windows/provider.wxs @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packaging/sharedframework/windows/registrykeys.wxs b/packaging/sharedframework/windows/registrykeys.wxs new file mode 100644 index 000000000..51d322da1 --- /dev/null +++ b/packaging/sharedframework/windows/registrykeys.wxs @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packaging/sharedframework/windows/sharedframework.wxs b/packaging/sharedframework/windows/sharedframework.wxs new file mode 100644 index 000000000..88680f5f2 --- /dev/null +++ b/packaging/sharedframework/windows/sharedframework.wxs @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/sharedframework/windows/variables.wxi b/packaging/sharedframework/windows/variables.wxi new file mode 100644 index 000000000..ad175258f --- /dev/null +++ b/packaging/sharedframework/windows/variables.wxi @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packaging/windows/bundle.wxs b/packaging/windows/bundle.wxs index 6999e8a3c..664087a99 100644 --- a/packaging/windows/bundle.wxs +++ b/packaging/windows/bundle.wxs @@ -32,7 +32,13 @@ - + + + + + + + diff --git a/packaging/windows/generatebundle.ps1 b/packaging/windows/generatebundle.ps1 index e50300363..dd73daaaa 100644 --- a/packaging/windows/generatebundle.ps1 +++ b/packaging/windows/generatebundle.ps1 @@ -2,7 +2,9 @@ # Licensed under the MIT license. See LICENSE file in the project root for full license information. param( - [Parameter(Mandatory=$true)][string]$DotnetMSIFile, + [Parameter(Mandatory=$true)][string]$CLISDKMSIFile, + [Parameter(Mandatory=$true)][string]$SharedFxMSIFile, + [Parameter(Mandatory=$true)][string]$SharedHostMSIFile, [Parameter(Mandatory=$true)][string]$DotnetBundleOutput, [Parameter(Mandatory=$true)][string]$WixRoot, [Parameter(Mandatory=$true)][string]$DotnetMSIVersion, @@ -28,7 +30,9 @@ function RunCandleForBundle -dBuildVersion="$DotnetMSIVersion" ` -dDisplayVersion="$DotnetCLIVersion" ` -dReleaseSuffix="$ReleaseSuffix" ` - -dMsiSourcePath="$DotnetMSIFile" ` + -dCLISDKMsiSourcePath="$CLISDKMSIFile" ` + -dSharedFXMsiSourcePath="$SharedFxMSIFile" ` + -dSharedHostMsiSourcePath="$SharedHostMSIFile" ` -arch "$Architecture" ` -ext WixBalExtension.dll ` -ext WixUtilExtension.dll ` @@ -73,9 +77,9 @@ function RunLightForBundle } -if(!(Test-Path $DotnetMSIFile)) +if(!(Test-Path $CLISDKMSIFile)) { - throw "$DotnetMSIFile not found" + throw "$CLISDKMSIFile not found" } Write-Host "Creating dotnet Bundle at $DotnetBundleOutput" @@ -103,6 +107,6 @@ if(!(Test-Path $DotnetBundleOutput)) Write-Host -ForegroundColor Green "Successfully created dotnet bundle - $DotnetBundleOutput" -_ $RepoRoot\test\Installer\testmsi.ps1 @("$DotnetMSIFile") +_ $RepoRoot\test\Installer\testmsi.ps1 @("$CLISDKMSIFile") exit $LastExitCode diff --git a/packaging/windows/registrykeys.wxs b/packaging/windows/registrykeys.wxs index 9bd84eb25..efb5c2b39 100644 --- a/packaging/windows/registrykeys.wxs +++ b/packaging/windows/registrykeys.wxs @@ -23,8 +23,6 @@ - - diff --git a/scripts/Microsoft.DotNet.Cli.Build.Framework/AnsiConsole.cs b/scripts/Microsoft.DotNet.Cli.Build.Framework/AnsiConsole.cs index 7f3e26852..c66c8e5d5 100644 --- a/scripts/Microsoft.DotNet.Cli.Build.Framework/AnsiConsole.cs +++ b/scripts/Microsoft.DotNet.Cli.Build.Framework/AnsiConsole.cs @@ -38,8 +38,8 @@ namespace Microsoft.DotNet.Cli.Build.Framework Console.ForegroundColor = c < 0 ? color : // unknown, just use it - _boldRecursion > 0 ? (ConsoleColor)(c & ~Light) : // ensure color is dark - (ConsoleColor)(c | Light); // ensure color is light + _boldRecursion > 0 ? (ConsoleColor)(c | Light) : // ensure color is light + (ConsoleColor)(c & ~Light); // ensure color is dark } private void SetBold(bool bold) diff --git a/scripts/Microsoft.DotNet.Cli.Build.Framework/BuildContext.cs b/scripts/Microsoft.DotNet.Cli.Build.Framework/BuildContext.cs index e4d8846f4..acd500f51 100644 --- a/scripts/Microsoft.DotNet.Cli.Build.Framework/BuildContext.cs +++ b/scripts/Microsoft.DotNet.Cli.Build.Framework/BuildContext.cs @@ -22,7 +22,17 @@ namespace Microsoft.DotNet.Cli.Build.Framework public object this[string name] { - get { return Properties.ContainsKey(name) ? Properties[name] : null; } + get + { + if (Properties.ContainsKey(name)) + { + return Properties[name]; + } + else + { + throw new KeyNotFoundException("No property with key " + name + " was found."); + } + } set { Properties[name] = value; } } diff --git a/scripts/Microsoft.DotNet.Cli.Build.Framework/CommandResult.cs b/scripts/Microsoft.DotNet.Cli.Build.Framework/CommandResult.cs index 6630a857b..e93320453 100644 --- a/scripts/Microsoft.DotNet.Cli.Build.Framework/CommandResult.cs +++ b/scripts/Microsoft.DotNet.Cli.Build.Framework/CommandResult.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Text; using System.Diagnostics; namespace Microsoft.DotNet.Cli.Build.Framework @@ -23,11 +24,26 @@ namespace Microsoft.DotNet.Cli.Build.Framework StdErr = stdErr; } - public void EnsureSuccessful() + public void EnsureSuccessful(bool suppressOutput = false) { if(ExitCode != 0) { - throw new BuildFailureException($"Command failed with exit code {ExitCode}: {StartInfo.FileName} {StartInfo.Arguments}"); + StringBuilder message = new StringBuilder($"Command failed with exit code {ExitCode}: {StartInfo.FileName} {StartInfo.Arguments}"); + + if (!suppressOutput) + { + if (!string.IsNullOrEmpty(StdOut)) + { + message.AppendLine($"{Environment.NewLine}Standard Output:{Environment.NewLine}{StdOut}"); + } + + if (!string.IsNullOrEmpty(StdErr)) + { + message.AppendLine($"{Environment.NewLine}Standard Error:{Environment.NewLine}{StdErr}"); + } + } + + throw new BuildFailureException(message.ToString()); } } } diff --git a/scripts/Microsoft.DotNet.Cli.Build.Framework/TargetConditions/EnvironmentAttribute.cs b/scripts/Microsoft.DotNet.Cli.Build.Framework/TargetConditions/EnvironmentAttribute.cs index 2a9aea581..0a8455e5c 100644 --- a/scripts/Microsoft.DotNet.Cli.Build.Framework/TargetConditions/EnvironmentAttribute.cs +++ b/scripts/Microsoft.DotNet.Cli.Build.Framework/TargetConditions/EnvironmentAttribute.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Linq; namespace Microsoft.DotNet.Cli.Build.Framework { @@ -13,7 +13,11 @@ namespace Microsoft.DotNet.Cli.Build.Framework { if (string.IsNullOrEmpty(envVar)) { - throw new ArgumentNullException("envVar"); + throw new ArgumentNullException(nameof(envVar)); + } + if (expectedVals == null) + { + throw new ArgumentNullException(nameof(expectedVals)); } _envVar = envVar; @@ -24,15 +28,14 @@ namespace Microsoft.DotNet.Cli.Build.Framework { var actualVal = Environment.GetEnvironmentVariable(_envVar); - foreach (var expectedVal in _expectedVals) + if (_expectedVals.Any()) { - if (string.Equals(actualVal, expectedVal, StringComparison.Ordinal)) - { - return true; - } + return _expectedVals.Any(ev => string.Equals(actualVal, ev, StringComparison.Ordinal)); + } + else + { + return !string.IsNullOrEmpty(actualVal); } - - return false; } } } diff --git a/scripts/Microsoft.DotNet.Cli.Build.Framework/project.json b/scripts/Microsoft.DotNet.Cli.Build.Framework/project.json index 85a4e9c85..cb16f68b2 100644 --- a/scripts/Microsoft.DotNet.Cli.Build.Framework/project.json +++ b/scripts/Microsoft.DotNet.Cli.Build.Framework/project.json @@ -1,15 +1,13 @@ { - "version": "1.0.0-*", - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "System.Diagnostics.Process": "4.1.0-rc2-23901", - "Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-16537" - }, - - "frameworks": { - "netstandard1.3": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "System.Diagnostics.Process": "4.1.0-rc2-23911", + "Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-16537" + }, + "frameworks": { + "netstandard1.3": { + "imports": "dnxcore50" } + } } diff --git a/scripts/dotnet-cli-build/CompileTargets.cs b/scripts/dotnet-cli-build/CompileTargets.cs index 1fc300ce0..13f0a53c0 100644 --- a/scripts/dotnet-cli-build/CompileTargets.cs +++ b/scripts/dotnet-cli-build/CompileTargets.cs @@ -7,12 +7,14 @@ using Microsoft.Extensions.PlatformAbstractions; using static Microsoft.DotNet.Cli.Build.FS; using static Microsoft.DotNet.Cli.Build.Framework.BuildHelpers; +using System.Text.RegularExpressions; +using System.Reflection.PortableExecutable; namespace Microsoft.DotNet.Cli.Build { public class CompileTargets { - public static readonly string CoreCLRVersion = "1.0.2-rc2-23901"; + public static readonly string CoreCLRVersion = "1.0.2-rc2-23911"; public static readonly string AppDepSdkVersion = "1.0.6-prerelease-00003"; public static readonly bool IsWinx86 = CurrentPlatform.IsWindows && CurrentArchitecture.Isx86; @@ -45,6 +47,12 @@ namespace Microsoft.DotNet.Cli.Build "Microsoft.Extensions.Testing.Abstractions" }; + public const string SharedFrameworkName = "Microsoft.NETCore.App"; + + private static string CoreHostBaseName => $"corehost{Constants.ExeSuffix}"; + private static string DotnetHostFxrBaseName => $"{Constants.DynamicLibPrefix}hostfxr{Constants.DynamicLibSuffix}"; + private static string HostPolicyBaseName => $"{Constants.DynamicLibPrefix}hostpolicy{Constants.DynamicLibSuffix}"; + // Updates the stage 2 with recent changes. [Target(nameof(PrepareTargets.Init), nameof(CompileStage2))] public static BuildTargetResult UpdateBuild(BuildTargetContext c) @@ -70,15 +78,20 @@ namespace Microsoft.DotNet.Cli.Build var configuration = c.BuildContext.Get("Configuration"); // Run the build + string version = DotNetCli.Stage0.Exec("", "--version").CaptureStdOut().Execute().StdOut; + string rid = Array.Find(version.Split(Environment.NewLine.ToCharArray()), (e) => e.Contains("Runtime Id:")).Replace("Runtime Id:", "").Trim(); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { // Why does Windows directly call cmake but Linux/Mac calls "build.sh" in the corehost dir? // See the comment in "src/corehost/build.sh" for details. It doesn't work for some reason. var visualStudio = IsWinx86 ? "Visual Studio 14 2015" : "Visual Studio 14 2015 Win64"; var archMacro = IsWinx86 ? "-DCLI_CMAKE_PLATFORM_ARCH_I386=1" : "-DCLI_CMAKE_PLATFORM_ARCH_AMD64=1"; + var ridMacro = $"-DCLI_CMAKE_RUNTIME_ID:STRING={rid}"; + ExecIn(cmakeOut, "cmake", Path.Combine(c.BuildContext.BuildDirectory, "src", "corehost"), archMacro, + ridMacro, "-G", visualStudio); @@ -101,14 +114,21 @@ namespace Microsoft.DotNet.Cli.Build File.Copy(Path.Combine(cmakeOut, "cli", configuration, "corehost.pdb"), Path.Combine(Dirs.Corehost, "corehost.pdb"), overwrite: true); File.Copy(Path.Combine(cmakeOut, "cli", "dll", configuration, "hostpolicy.dll"), Path.Combine(Dirs.Corehost, "hostpolicy.dll"), overwrite: true); File.Copy(Path.Combine(cmakeOut, "cli", "dll", configuration, "hostpolicy.pdb"), Path.Combine(Dirs.Corehost, "hostpolicy.pdb"), overwrite: true); + File.Copy(Path.Combine(cmakeOut, "cli", "fxr", configuration, "hostfxr.dll"), Path.Combine(Dirs.Corehost, "hostfxr.dll"), overwrite: true); + File.Copy(Path.Combine(cmakeOut, "cli", "fxr", configuration, "hostfxr.pdb"), Path.Combine(Dirs.Corehost, "hostfxr.pdb"), overwrite: true); } else { - ExecIn(cmakeOut, Path.Combine(c.BuildContext.BuildDirectory, "src", "corehost", "build.sh")); + ExecIn(cmakeOut, Path.Combine(c.BuildContext.BuildDirectory, "src", "corehost", "build.sh"), + "--arch", + "amd64", + "--rid", + rid); // Copy the output out - File.Copy(Path.Combine(cmakeOut, "cli", "corehost"), Path.Combine(Dirs.Corehost, "corehost"), overwrite: true); - File.Copy(Path.Combine(cmakeOut, "cli", "dll", $"{Constants.DynamicLibPrefix}hostpolicy{Constants.DynamicLibSuffix}"), Path.Combine(Dirs.Corehost, $"{Constants.DynamicLibPrefix}hostpolicy{Constants.DynamicLibSuffix}"), overwrite: true); + File.Copy(Path.Combine(cmakeOut, "cli", "corehost"), Path.Combine(Dirs.Corehost, CoreHostBaseName), overwrite: true); + File.Copy(Path.Combine(cmakeOut, "cli", "dll", HostPolicyBaseName), Path.Combine(Dirs.Corehost, HostPolicyBaseName), overwrite: true); + File.Copy(Path.Combine(cmakeOut, "cli", "fxr", DotnetHostFxrBaseName), Path.Combine(Dirs.Corehost, DotnetHostFxrBaseName), overwrite: true); } return c.Success(); @@ -119,7 +139,16 @@ namespace Microsoft.DotNet.Cli.Build { CleanBinObj(c, Path.Combine(c.BuildContext.BuildDirectory, "src")); CleanBinObj(c, Path.Combine(c.BuildContext.BuildDirectory, "test")); - return CompileStage(c, + + if (Directory.Exists(Dirs.Stage1)) + { + Utils.DeleteDirectory(Dirs.Stage1); + } + Directory.CreateDirectory(Dirs.Stage1); + + CopySharedHost(Dirs.Stage1); + PublishSharedFramework(c, Dirs.Stage1, DotNetCli.Stage0); + return CompileCliSdk(c, dotnet: DotNetCli.Stage0, outputDir: Dirs.Stage1); } @@ -131,7 +160,16 @@ namespace Microsoft.DotNet.Cli.Build CleanBinObj(c, Path.Combine(c.BuildContext.BuildDirectory, "src")); CleanBinObj(c, Path.Combine(c.BuildContext.BuildDirectory, "test")); - var result = CompileStage(c, + + if (Directory.Exists(Dirs.Stage2)) + { + Utils.DeleteDirectory(Dirs.Stage2); + } + Directory.CreateDirectory(Dirs.Stage2); + + PublishSharedFramework(c, Dirs.Stage2, DotNetCli.Stage1); + CopySharedHost(Dirs.Stage2); + var result = CompileCliSdk(c, dotnet: DotNetCli.Stage1, outputDir: Dirs.Stage2); @@ -162,15 +200,117 @@ namespace Microsoft.DotNet.Cli.Build return c.Success(); } - private static BuildTargetResult CompileStage(BuildTargetContext c, DotNetCli dotnet, string outputDir) + private static void CopySharedHost(string outputDir) { - Rmdir(outputDir); + // corehost will be renamed to dotnet at some point and then this can be removed. + File.Copy( + Path.Combine(Dirs.Corehost, CoreHostBaseName), + Path.Combine(outputDir, $"dotnet{Constants.ExeSuffix}"), true); + File.Copy( + Path.Combine(Dirs.Corehost, DotnetHostFxrBaseName), + Path.Combine(outputDir, DotnetHostFxrBaseName), true); + } + public static void PublishSharedFramework(BuildTargetContext c, string outputDir, DotNetCli dotnetCli) + { + string SharedFrameworkSourceRoot = Path.Combine(Dirs.RepoRoot, "src", "sharedframework", "framework"); + string SharedFrameworkNugetVersion = c.BuildContext.Get("SharedFrameworkNugetVersion"); + + // We publish to a sub folder of the PublishRoot so tools like heat and zip can generate folder structures easier. + string SharedFrameworkNameAndVersionRoot = Path.Combine(outputDir, "shared", SharedFrameworkName, SharedFrameworkNugetVersion); + + if (Directory.Exists(SharedFrameworkNameAndVersionRoot)) + { + Utils.DeleteDirectory(SharedFrameworkNameAndVersionRoot); + } + + string publishFramework = "dnxcore50"; // Temporary, use "netcoreapp" when we update nuget. + string publishRuntime; + if (PlatformServices.Default.Runtime.OperatingSystemPlatform == Platform.Windows) + { + publishRuntime = $"win7-{PlatformServices.Default.Runtime.RuntimeArchitecture}"; + } + else + { + publishRuntime = PlatformServices.Default.Runtime.GetRuntimeIdentifier(); + } + + dotnetCli.Publish( + "--output", SharedFrameworkNameAndVersionRoot, + "-r", publishRuntime, + "-f", publishFramework, + SharedFrameworkSourceRoot).Execute().EnsureSuccessful(); + + // Clean up artifacts that dotnet-publish generates which we don't need + File.Delete(Path.Combine(SharedFrameworkNameAndVersionRoot, $"framework{Constants.ExeSuffix}")); + File.Delete(Path.Combine(SharedFrameworkNameAndVersionRoot, "framework.dll")); + File.Delete(Path.Combine(SharedFrameworkNameAndVersionRoot, "framework.pdb")); + File.Delete(Path.Combine(SharedFrameworkNameAndVersionRoot, "framework.runtimeconfig.json")); + + // Rename the .deps file + var destinationDeps = Path.Combine(SharedFrameworkNameAndVersionRoot, $"{SharedFrameworkName}.deps.json"); + File.Move(Path.Combine(SharedFrameworkNameAndVersionRoot, "framework.deps"), Path.Combine(SharedFrameworkNameAndVersionRoot, $"{SharedFrameworkName}.deps")); + File.Move(Path.Combine(SharedFrameworkNameAndVersionRoot, "framework.deps.json"), destinationDeps); + + // Generate RID fallback graph + string runtimeGraphGeneratorRuntime = null; + switch (PlatformServices.Default.Runtime.OperatingSystemPlatform) + { + case Platform.Windows: + runtimeGraphGeneratorRuntime = "win"; + break; + case Platform.Linux: + runtimeGraphGeneratorRuntime = "linux"; + break; + case Platform.Darwin: + runtimeGraphGeneratorRuntime = "osx"; + break; + } + if (!string.IsNullOrEmpty(runtimeGraphGeneratorRuntime)) + { + var runtimeGraphGeneratorName = "RuntimeGraphGenerator"; + var runtimeGraphGeneratorProject = Path.Combine(Dirs.RepoRoot, "tools", runtimeGraphGeneratorName); + var runtimeGraphGeneratorOutput = Path.Combine(Dirs.Output, "tools", runtimeGraphGeneratorName); + + dotnetCli.Publish( + "--output", runtimeGraphGeneratorOutput, + runtimeGraphGeneratorProject).Execute().EnsureSuccessful(); + var runtimeGraphGeneratorExe = Path.Combine(runtimeGraphGeneratorOutput, $"{runtimeGraphGeneratorName}{Constants.ExeSuffix}"); + + Cmd(runtimeGraphGeneratorExe, "--project", SharedFrameworkSourceRoot, "--deps", destinationDeps, runtimeGraphGeneratorRuntime) + .Execute(); + } + else + { + c.Error($"Could not determine rid graph generation runtime for platform {PlatformServices.Default.Runtime.OperatingSystemPlatform}"); + } + + // corehost will be renamed to dotnet at some point and then we will not need to rename it here. + File.Copy( + Path.Combine(Dirs.Corehost, CoreHostBaseName), + Path.Combine(SharedFrameworkNameAndVersionRoot, $"dotnet{Constants.ExeSuffix}")); + File.Copy( + Path.Combine(Dirs.Corehost, HostPolicyBaseName), + Path.Combine(SharedFrameworkNameAndVersionRoot, HostPolicyBaseName), true); + + if (File.Exists(Path.Combine(SharedFrameworkNameAndVersionRoot, "mscorlib.ni.dll"))) + { + // Publish already places the crossgen'd version of mscorlib into the output, so we can + // remove the IL version + File.Delete(Path.Combine(SharedFrameworkNameAndVersionRoot, "mscorlib.dll")); + } + + CrossgenSharedFx(c, SharedFrameworkNameAndVersionRoot); + } + + private static BuildTargetResult CompileCliSdk(BuildTargetContext c, DotNetCli dotnet, string outputDir) + { var configuration = c.BuildContext.Get("Configuration"); - var binDir = Path.Combine(outputDir, "bin"); - var buildVesion = c.BuildContext.Get("BuildVersion"); + var buildVersion = c.BuildContext.Get("BuildVersion"); + outputDir = Path.Combine(outputDir, "sdk", buildVersion.NuGetVersion); - Mkdirp(binDir); + Rmdir(outputDir); + Mkdirp(outputDir); foreach (var project in ProjectsToPublish) { @@ -179,11 +319,11 @@ namespace Microsoft.DotNet.Cli.Build dotnet.Publish( "--native-subdirectory", "--output", - binDir, + outputDir, "--configuration", configuration, Path.Combine(c.BuildContext.BuildDirectory, "src", project)) - .Environment("DOTNET_BUILD_VERSION", buildVesion.VersionSuffix) + .Environment("DOTNET_BUILD_VERSION", buildVersion.VersionSuffix) .Execute() .EnsureSuccessful(); } @@ -191,8 +331,9 @@ namespace Microsoft.DotNet.Cli.Build FixModeFlags(outputDir); // Copy corehost - File.Copy(Path.Combine(Dirs.Corehost, $"corehost{Constants.ExeSuffix}"), Path.Combine(binDir, $"corehost{Constants.ExeSuffix}"), overwrite: true); - File.Copy(Path.Combine(Dirs.Corehost, $"{Constants.DynamicLibPrefix}hostpolicy{Constants.DynamicLibSuffix}"), Path.Combine(binDir, $"{Constants.DynamicLibPrefix}hostpolicy{Constants.DynamicLibSuffix}"), overwrite: true); + File.Copy(Path.Combine(Dirs.Corehost, $"corehost{Constants.ExeSuffix}"), Path.Combine(outputDir, $"corehost{Constants.ExeSuffix}"), overwrite: true); + File.Copy(Path.Combine(Dirs.Corehost, $"{Constants.DynamicLibPrefix}hostpolicy{Constants.DynamicLibSuffix}"), Path.Combine(outputDir, $"{Constants.DynamicLibPrefix}hostpolicy{Constants.DynamicLibSuffix}"), overwrite: true); + File.Copy(Path.Combine(Dirs.Corehost, $"{Constants.DynamicLibPrefix}hostfxr{Constants.DynamicLibSuffix}"), Path.Combine(outputDir, $"{Constants.DynamicLibPrefix}hostfxr{Constants.DynamicLibSuffix}"), overwrite: true); // Corehostify binaries foreach (var binaryToCorehostify in BinariesForCoreHost) @@ -200,9 +341,9 @@ namespace Microsoft.DotNet.Cli.Build try { // Yes, it is .exe even on Linux. This is the managed exe we're working with - File.Copy(Path.Combine(binDir, $"{binaryToCorehostify}.exe"), Path.Combine(binDir, $"{binaryToCorehostify}.dll")); - File.Delete(Path.Combine(binDir, $"{binaryToCorehostify}.exe")); - File.Copy(Path.Combine(binDir, $"corehost{Constants.ExeSuffix}"), Path.Combine(binDir, binaryToCorehostify + Constants.ExeSuffix)); + File.Copy(Path.Combine(outputDir, $"{binaryToCorehostify}.exe"), Path.Combine(outputDir, $"{binaryToCorehostify}.dll")); + File.Delete(Path.Combine(outputDir, $"{binaryToCorehostify}.exe")); + File.Copy(Path.Combine(outputDir, $"corehost{Constants.ExeSuffix}"), Path.Combine(outputDir, binaryToCorehostify + Constants.ExeSuffix)); } catch (Exception ex) { @@ -211,25 +352,25 @@ namespace Microsoft.DotNet.Cli.Build } // dotnet.exe is from stage0. But we must be using the newly built corehost in stage1 - File.Delete(Path.Combine(binDir, $"dotnet{Constants.ExeSuffix}")); - File.Copy(Path.Combine(binDir, $"corehost{Constants.ExeSuffix}"), Path.Combine(binDir, $"dotnet{Constants.ExeSuffix}")); + File.Delete(Path.Combine(outputDir, $"dotnet{Constants.ExeSuffix}")); + File.Copy(Path.Combine(outputDir, $"corehost{Constants.ExeSuffix}"), Path.Combine(outputDir, $"dotnet{Constants.ExeSuffix}")); // Crossgen Roslyn - var result = Crossgen(c, binDir); + var result = CrossgenCliSdk(c, outputDir); if (!result.Success) { return result; } // Copy AppDeps - result = CopyAppDeps(c, binDir); + result = CopyAppDeps(c, outputDir); if (!result.Success) { return result; } // Generate .version file - var version = buildVesion.SimpleVersion; + var version = buildVersion.SimpleVersion; var content = $@"{c.BuildContext["CommitHash"]}{Environment.NewLine}{version}{Environment.NewLine}"; File.WriteAllText(Path.Combine(outputDir, ".version"), content); @@ -283,51 +424,23 @@ namespace Microsoft.DotNet.Cli.Build return c.Success(); } - private static BuildTargetResult Crossgen(BuildTargetContext c, string outputDir) + private static BuildTargetResult CrossgenCliSdk(BuildTargetContext c, string outputDir) { // Check if we need to skip crossgen if (string.Equals(Environment.GetEnvironmentVariable("DOTNET_BUILD_SKIP_CROSSGEN"), "1")) { - c.Warn("Skipping crossgen because DOTNET_BUILD_SKIP_CROSSGEN is set"); + c.Warn("Skipping crossgen for Cli Sdk because DOTNET_BUILD_SKIP_CROSSGEN is set"); return c.Success(); } // Find crossgen - string arch = PlatformServices.Default.Runtime.RuntimeArchitecture; - string packageId; - if (CurrentPlatform.IsWindows) - { - packageId = $"runtime.win7-{arch}.Microsoft.NETCore.Runtime.CoreCLR"; - } - else if (CurrentPlatform.IsUbuntu) - { - packageId = "runtime.ubuntu.14.04-x64.Microsoft.NETCore.Runtime.CoreCLR"; - } - else if (CurrentPlatform.IsCentOS || CurrentPlatform.IsRHEL) - { - // CentOS runtime is in the runtime.rhel.7-x64... package. - packageId = "runtime.rhel.7-x64.Microsoft.NETCore.Runtime.CoreCLR"; - } - else if (CurrentPlatform.IsDebian) - { - packageId = "runtime.debian.8.2-x64.Microsoft.NETCore.Runtime.CoreCLR"; - } - else if (CurrentPlatform.IsOSX) - { - packageId = "runtime.osx.10.10-x64.Microsoft.NETCore.Runtime.CoreCLR"; - } - else + var crossGenExePath = Microsoft.DotNet.Cli.Build.Crossgen.GetCrossgenPathForVersion(CoreCLRVersion); + + if (string.IsNullOrEmpty(crossGenExePath)) { return c.Failed("Unsupported OS Platform"); } - var crossGenExePath = Path.Combine( - Dirs.NuGetPackages, - packageId, - CoreCLRVersion, - "tools", - $"crossgen{Constants.ExeSuffix}"); - // We have to copy crossgen next to mscorlib var crossgen = Path.Combine(outputDir, $"crossgen{Constants.ExeSuffix}"); File.Copy(crossGenExePath, crossgen, overwrite: true); @@ -342,7 +455,7 @@ namespace Microsoft.DotNet.Cli.Build foreach (var assemblyToCrossgen in AssembliesToCrossGen) { c.Info($"Crossgenning {assemblyToCrossgen}"); - ExecInSilent(outputDir, crossgen, "-nologo", "-platform_assemblies_paths", outputDir, assemblyToCrossgen); + ExecInSilent(outputDir, crossgen, "-FragileNonVersionable", "-nologo", "-platform_assemblies_paths", outputDir, assemblyToCrossgen); } c.Info("Crossgen complete"); @@ -361,9 +474,61 @@ namespace Microsoft.DotNet.Cli.Build return c.Success(); } + public static BuildTargetResult CrossgenSharedFx(BuildTargetContext c, string pathToAssemblies) + { + // Check if we need to skip crossgen + if (!string.Equals(Environment.GetEnvironmentVariable("CROSSGEN_SHAREDFRAMEWORK"), "1")) + { + c.Warn("Skipping crossgen for SharedFx because CROSSGEN_SHAREDFRAMEWORK is not set to 1"); + return c.Success(); + } + + foreach (var file in Directory.GetFiles(pathToAssemblies)) + { + string fileName = Path.GetFileName(file); + + if (fileName == "mscorlib.dll" || fileName == "mscorlib.ni.dll" || !HasMetadata(file)) + { + continue; + } + + string tempPathName = Path.ChangeExtension(file, "readytorun"); + + // This is not always correct. The version of crossgen we need to pick up is whatever one was restored as part + // of the Microsoft.NETCore.Runtime.CoreCLR package that is part of the shared library. For now, the version hardcoded + // in CompileTargets and the one in the shared library project.json match and are updated in lock step, but long term + // we need to be able to look at the project.lock.json file and figure out what version of Microsoft.NETCore.Runtime.CoreCLR + // was used, and then select that version. + ExecSilent(Crossgen.GetCrossgenPathForVersion(CompileTargets.CoreCLRVersion), + "-readytorun", "-in", file, "-out", tempPathName, "-platform_assemblies_paths", pathToAssemblies); + + File.Delete(file); + File.Move(tempPathName, file); + } + + return c.Success(); + } + + private static bool HasMetadata(string pathToFile) + { + try + { + using (var inStream = File.OpenRead(pathToFile)) + { + using (var peReader = new PEReader(inStream)) + { + return peReader.HasMetadata; + } + } + } + catch (BadImageFormatException) { } + + return false; + } + private static List GetAssembliesToCrossGen() { - var list = new List + return new List { "System.Collections.Immutable.dll", "System.Reflection.Metadata.dll", @@ -373,15 +538,6 @@ namespace Microsoft.DotNet.Cli.Build "csc.dll", "vbc.dll" }; - - // mscorlib is already crossgenned on Windows - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // mscorlib has to be crossgenned first - list.Insert(0, "mscorlib.dll"); - } - - return list; } } } diff --git a/scripts/dotnet-cli-build/InstallerTargets.cs b/scripts/dotnet-cli-build/InstallerTargets.cs index be1a4836a..feef6e319 100644 --- a/scripts/dotnet-cli-build/InstallerTargets.cs +++ b/scripts/dotnet-cli-build/InstallerTargets.cs @@ -14,23 +14,45 @@ namespace Microsoft.DotNet.Cli.Build { [Target(nameof(MsiTargets.GenerateMsis), nameof(MsiTargets.GenerateBundle), - nameof(InstallerTargets.GeneratePkg), - nameof(InstallerTargets.GenerateDeb))] + nameof(PkgTargets.GeneratePkgs), + nameof(InstallerTargets.GenerateDebs))] public static BuildTargetResult GenerateInstaller(BuildTargetContext c) { return c.Success(); } - + [Target(nameof(InstallerTargets.GenerateSdkDeb), + nameof(InstallerTargets.GenerateSharedFrameworkDeb), + nameof(InstallerTargets.GenerateSharedHostDeb))] + [BuildPlatforms(BuildPlatform.Ubuntu)] + public static BuildTargetResult GenerateDebs(BuildTargetContext c) + { + return c.Success(); + } [Target] - [BuildPlatforms(BuildPlatform.OSX)] - public static BuildTargetResult GeneratePkg(BuildTargetContext c) + [BuildPlatforms(BuildPlatform.Ubuntu)] + public static BuildTargetResult GenerateSdkDeb(BuildTargetContext c) { + var channel = c.BuildContext.Get("Channel").ToLower(); + var packageName = Monikers.GetDebianPackageName(c); var version = c.BuildContext.Get("BuildVersion").SimpleVersion; - var pkg = c.BuildContext.Get("InstallerFile"); - Cmd(Path.Combine(Dirs.RepoRoot, "packaging", "osx", "package-osx.sh"), - "-v", version, "-i", Dirs.Stage2, "-o", pkg) + var debFile = c.BuildContext.Get("CombinedFrameworkSDKHostInstallerFile"); + var input = c.BuildContext.Get("CLISDKRoot"); + var manPagesDir = Path.Combine(Dirs.RepoRoot, "Documentation", "manpages"); + var previousVersionURL = $"https://dotnetcli.blob.core.windows.net/dotnet/{channel}/Installers/Latest/dotnet-ubuntu-x64.latest.deb"; + + var objRoot = Path.Combine(Dirs.Output, "obj", "debian", "sdk"); + + if (Directory.Exists(objRoot)) + { + Directory.Delete(objRoot, true); + } + + Directory.CreateDirectory(objRoot); + + Cmd(Path.Combine(Dirs.RepoRoot, "scripts", "package", "package-debian.sh"), + "-v", version, "-i", input, "-o", debFile, "-p", packageName, "-m", manPagesDir, "--previous-version-url", previousVersionURL, "--obj-root", objRoot) .Execute() .EnsureSuccessful(); return c.Success(); @@ -38,17 +60,49 @@ namespace Microsoft.DotNet.Cli.Build [Target] [BuildPlatforms(BuildPlatform.Ubuntu)] - public static BuildTargetResult GenerateDeb(BuildTargetContext c) + public static BuildTargetResult GenerateSharedHostDeb(BuildTargetContext c) { - var channel = c.BuildContext.Get("Channel").ToLower(); - var packageName = Monikers.GetDebianPackageName(c); var version = c.BuildContext.Get("BuildVersion").SimpleVersion; - var debFile = c.BuildContext.Get("InstallerFile"); - var manPagesDir = Path.Combine(Dirs.RepoRoot, "Documentation", "manpages"); - var previousVersionURL = $"https://dotnetcli.blob.core.windows.net/dotnet/{channel}/Installers/Latest/dotnet-ubuntu-x64.latest.deb"; + var inputRoot = c.BuildContext.Get("SharedHostPublishRoot"); + var debFile = c.BuildContext.Get("SharedHostInstallerFile"); + var objRoot = Path.Combine(Dirs.Output, "obj", "debian", "sharedhost"); - Cmd(Path.Combine(Dirs.RepoRoot, "scripts", "package", "package-debian.sh"), - "-v", version, "-i", Dirs.Stage2, "-o", debFile, "-p", packageName, "-m", manPagesDir, "--previous-version-url", previousVersionURL) + if (Directory.Exists(objRoot)) + { + Directory.Delete(objRoot, true); + } + + Directory.CreateDirectory(objRoot); + + Cmd(Path.Combine(Dirs.RepoRoot, "scripts", "package", "package-sharedhost-debian.sh"), + "--input", inputRoot, "--output", debFile, "--obj-root", objRoot, "--version", version) + .Execute() + .EnsureSuccessful(); + return c.Success(); + } + + [Target] + [BuildPlatforms(BuildPlatform.Ubuntu)] + public static BuildTargetResult GenerateSharedFrameworkDeb(BuildTargetContext c) + { + var packageName = Monikers.GetDebianSharedFrameworkPackageName(c); + var version = c.BuildContext.Get("BuildVersion").SimpleVersion; + var inputRoot = c.BuildContext.Get("SharedFrameworkPublishRoot"); + var debFile = c.BuildContext.Get("SharedFrameworkInstallerFile"); + var objRoot = Path.Combine(Dirs.Output, "obj", "debian", "sharedframework"); + + if (Directory.Exists(objRoot)) + { + Directory.Delete(objRoot, true); + } + + Directory.CreateDirectory(objRoot); + + Cmd(Path.Combine(Dirs.RepoRoot, "scripts", "package", "package-sharedframework-debian.sh"), + "--input", inputRoot, "--output", debFile, "--package-name", packageName, + "--framework-nuget-name", Monikers.SharedFrameworkName, + "--framework-nuget-version", c.BuildContext.Get("SharedFrameworkNugetVersion"), + "--obj-root", objRoot, "--version", version) .Execute() .EnsureSuccessful(); return c.Success(); diff --git a/scripts/dotnet-cli-build/MsiTargets.cs b/scripts/dotnet-cli-build/MsiTargets.cs index 6d83a8794..5d2adf871 100644 --- a/scripts/dotnet-cli-build/MsiTargets.cs +++ b/scripts/dotnet-cli-build/MsiTargets.cs @@ -22,9 +22,13 @@ namespace Microsoft.DotNet.Cli.Build } } - private static string Msi { get; set; } + private static string SdkMsi { get; set; } - private static string Bundle { get; set; } + private static string SdkBundle { get; set; } + + private static string SharedHostMsi { get; set; } + + private static string SharedFrameworkMsi { get; set; } private static string Engine { get; set; } @@ -60,9 +64,12 @@ namespace Microsoft.DotNet.Cli.Build [BuildPlatforms(BuildPlatform.Windows)] public static BuildTargetResult InitMsi(BuildTargetContext c) { - Bundle = c.BuildContext.Get("InstallerFile"); - Msi = Path.ChangeExtension(Bundle, "msi"); - Engine = Path.Combine(Path.GetDirectoryName(Bundle), ENGINE); + SdkBundle = c.BuildContext.Get("CombinedFrameworkSDKHostInstallerFile"); + SdkMsi = Path.ChangeExtension(SdkBundle, "msi"); + Engine = Path.Combine(Path.GetDirectoryName(SdkBundle), ENGINE); + + SharedHostMsi = Path.ChangeExtension(c.BuildContext.Get("SharedHostInstallerFile"), "msi"); + SharedFrameworkMsi = Path.ChangeExtension(c.BuildContext.Get("SharedFrameworkInstallerFile"), "msi"); var buildVersion = c.BuildContext.Get("BuildVersion"); MsiVersion = buildVersion.GenerateMsiVersion(); @@ -74,9 +81,9 @@ namespace Microsoft.DotNet.Cli.Build } [Target(nameof(MsiTargets.InitMsi), - nameof(GenerateDotnetMuxerMsi), - nameof(GenerateDotnetSharedFxMsi), - nameof(GenerateCLISDKMsi))] + nameof(GenerateDotnetSharedHostMsi), + nameof(GenerateDotnetSharedFrameworkMsi), + nameof(GenerateCliSdkMsi))] [BuildPlatforms(BuildPlatform.Windows)] public static BuildTargetResult GenerateMsis(BuildTargetContext c) { @@ -85,11 +92,12 @@ namespace Microsoft.DotNet.Cli.Build [Target] [BuildPlatforms(BuildPlatform.Windows)] - public static BuildTargetResult GenerateCLISDKMsi(BuildTargetContext c) + public static BuildTargetResult GenerateCliSdkMsi(BuildTargetContext c) { + var cliSdkRoot = c.BuildContext.Get("CLISDKRoot"); Cmd("powershell", "-NoProfile", "-NoLogo", Path.Combine(Dirs.RepoRoot, "packaging", "windows", "generatemsi.ps1"), - Dirs.Stage2, Msi, WixRoot, MsiVersion, CliVersion, Arch, Channel) + cliSdkRoot, SdkMsi, WixRoot, MsiVersion, CliVersion, Arch, Channel) .Execute() .EnsureSuccessful(); return c.Success(); @@ -97,15 +105,46 @@ namespace Microsoft.DotNet.Cli.Build [Target] [BuildPlatforms(BuildPlatform.Windows)] - public static BuildTargetResult GenerateDotnetMuxerMsi(BuildTargetContext c) + public static BuildTargetResult GenerateDotnetSharedHostMsi(BuildTargetContext c) { + var inputDir = c.BuildContext.Get("SharedHostPublishRoot"); + var wixObjRoot = Path.Combine(Dirs.Output, "obj", "wix", "sharedhost"); + + if (Directory.Exists(wixObjRoot)) + { + Utils.DeleteDirectory(wixObjRoot); + } + Directory.CreateDirectory(wixObjRoot); + + Cmd("powershell", "-NoProfile", "-NoLogo", + Path.Combine(Dirs.RepoRoot, "packaging", "host", "windows", "generatemsi.ps1"), + inputDir, SharedHostMsi, WixRoot, MsiVersion, CliVersion, Arch, wixObjRoot) + .Execute() + .EnsureSuccessful(); return c.Success(); } [Target] [BuildPlatforms(BuildPlatform.Windows)] - public static BuildTargetResult GenerateDotnetSharedFxMsi(BuildTargetContext c) + public static BuildTargetResult GenerateDotnetSharedFrameworkMsi(BuildTargetContext c) { + var inputDir = c.BuildContext.Get("SharedFrameworkPublishRoot"); + var sharedFrameworkNuGetName = Monikers.SharedFrameworkName; + var sharedFrameworkNuGetVersion = c.BuildContext.Get("SharedFrameworkNugetVersion"); + var upgradeCode = Utils.GenerateGuidFromName($"{sharedFrameworkNuGetName}-{sharedFrameworkNuGetVersion}-{Arch}").ToString().ToUpper(); + var wixObjRoot = Path.Combine(Dirs.Output, "obj", "wix", "sharedframework"); + + if (Directory.Exists(wixObjRoot)) + { + Utils.DeleteDirectory(wixObjRoot); + } + Directory.CreateDirectory(wixObjRoot); + + Cmd("powershell", "-NoProfile", "-NoLogo", + Path.Combine(Dirs.RepoRoot, "packaging", "sharedframework", "windows", "generatemsi.ps1"), + inputDir, SharedFrameworkMsi, WixRoot, MsiVersion, sharedFrameworkNuGetName, sharedFrameworkNuGetVersion, upgradeCode, Arch, wixObjRoot) + .Execute() + .EnsureSuccessful(); return c.Success(); } @@ -116,7 +155,7 @@ namespace Microsoft.DotNet.Cli.Build { Cmd("powershell", "-NoProfile", "-NoLogo", Path.Combine(Dirs.RepoRoot, "packaging", "windows", "generatebundle.ps1"), - Msi, Bundle, WixRoot, MsiVersion, CliVersion, Arch, Channel) + SdkMsi, SharedFrameworkMsi, SharedHostMsi, SdkBundle, WixRoot, MsiVersion, CliVersion, Arch, Channel) .EnvironmentVariable("Stage2Dir", Dirs.Stage2) .Execute() .EnsureSuccessful(); @@ -127,7 +166,7 @@ namespace Microsoft.DotNet.Cli.Build [BuildPlatforms(BuildPlatform.Windows)] public static BuildTargetResult ExtractEngineFromBundle(BuildTargetContext c) { - Cmd($"{WixRoot}\\insignia.exe", "-ib", Bundle, "-o", Engine) + Cmd($"{WixRoot}\\insignia.exe", "-ib", SdkBundle, "-o", Engine) .Execute() .EnsureSuccessful(); return c.Success(); @@ -137,7 +176,7 @@ namespace Microsoft.DotNet.Cli.Build [BuildPlatforms(BuildPlatform.Windows)] public static BuildTargetResult ReattachEngineToBundle(BuildTargetContext c) { - Cmd($"{WixRoot}\\insignia.exe", "-ab", Engine, Bundle, "-o", Bundle) + Cmd($"{WixRoot}\\insignia.exe", "-ab", Engine, SdkBundle, "-o", SdkBundle) .Execute() .EnsureSuccessful(); return c.Success(); diff --git a/scripts/dotnet-cli-build/PackageTargets.cs b/scripts/dotnet-cli-build/PackageTargets.cs index c43b201e1..2719439e4 100644 --- a/scripts/dotnet-cli-build/PackageTargets.cs +++ b/scripts/dotnet-cli-build/PackageTargets.cs @@ -12,7 +12,11 @@ namespace Microsoft.DotNet.Cli.Build { public static class PackageTargets { - [Target] + [Target(nameof(PackageTargets.CopyCLISDKLayout), + nameof(PackageTargets.CopySharedHostLayout), + nameof(PackageTargets.CopySharedFxLayout), + nameof(PackageTargets.CopyCombinedFrameworkSDKHostLayout), + nameof(PackageTargets.CopyCombinedFrameworkHostLayout))] public static BuildTargetResult InitPackage(BuildTargetContext c) { Directory.CreateDirectory(Dirs.Packages); @@ -45,6 +49,99 @@ namespace Microsoft.DotNet.Cli.Build return c.Success(); } + [Target] + public static BuildTargetResult CopyCLISDKLayout(BuildTargetContext c) + { + var cliSdkRoot = Path.Combine(Dirs.Output, "obj", "clisdk"); + if (Directory.Exists(cliSdkRoot)) + { + Utils.DeleteDirectory(cliSdkRoot); + } + + Directory.CreateDirectory(cliSdkRoot); + Utils.CopyDirectoryRecursively(Path.Combine(Dirs.Stage2, "sdk"), cliSdkRoot, true); + c.BuildContext["CLISDKRoot"] = cliSdkRoot; + return c.Success(); + } + + [Target] + public static BuildTargetResult CopySharedHostLayout(BuildTargetContext c) + { + var sharedHostRoot = Path.Combine(Dirs.Output, "obj", "sharedHost"); + if (Directory.Exists(sharedHostRoot)) + { + Utils.DeleteDirectory(sharedHostRoot); + } + + Directory.CreateDirectory(sharedHostRoot); + + foreach (var file in Directory.GetFiles(Dirs.Stage2, "*", SearchOption.TopDirectoryOnly)) + { + var destFile = file.Replace(Dirs.Stage2, sharedHostRoot); + File.Copy(file, destFile, true); + } + + c.BuildContext["SharedHostPublishRoot"] = sharedHostRoot; + return c.Success(); + } + + [Target] + public static BuildTargetResult CopySharedFxLayout(BuildTargetContext c) + { + var sharedFxRoot = Path.Combine(Dirs.Output, "obj", "sharedFx"); + if (Directory.Exists(sharedFxRoot)) + { + Utils.DeleteDirectory(sharedFxRoot); + } + + Directory.CreateDirectory(sharedFxRoot); + Utils.CopyDirectoryRecursively(Path.Combine(Dirs.Stage2, "shared"), sharedFxRoot, true); + c.BuildContext["SharedFrameworkPublishRoot"] = sharedFxRoot; + return c.Success(); + } + + [Target] + public static BuildTargetResult CopyCombinedFrameworkSDKHostLayout(BuildTargetContext c) + { + var combinedRoot = Path.Combine(Dirs.Output, "obj", "combined-framework-sdk-host"); + if (Directory.Exists(combinedRoot)) + { + Utils.DeleteDirectory(combinedRoot); + } + + string sdkPublishRoot = c.BuildContext.Get("CLISDKRoot"); + Utils.CopyDirectoryRecursively(sdkPublishRoot, combinedRoot); + + string sharedFrameworkPublishRoot = c.BuildContext.Get("SharedFrameworkPublishRoot"); + Utils.CopyDirectoryRecursively(sharedFrameworkPublishRoot, combinedRoot); + + string sharedHostPublishRoot = c.BuildContext.Get("SharedHostPublishRoot"); + Utils.CopyDirectoryRecursively(sharedHostPublishRoot, combinedRoot); + + c.BuildContext["CombinedFrameworkSDKHostRoot"] = combinedRoot; + return c.Success(); + } + + [Target] + public static BuildTargetResult CopyCombinedFrameworkHostLayout(BuildTargetContext c) + { + var combinedRoot = Path.Combine(Dirs.Output, "obj", "combined-framework-host"); + if (Directory.Exists(combinedRoot)) + { + Utils.DeleteDirectory(combinedRoot); + } + + + string sharedFrameworkPublishRoot = c.BuildContext.Get("SharedFrameworkPublishRoot"); + Utils.CopyDirectoryRecursively(sharedFrameworkPublishRoot, combinedRoot); + + string sharedHostPublishRoot = c.BuildContext.Get("SharedHostPublishRoot"); + Utils.CopyDirectoryRecursively(sharedHostPublishRoot, combinedRoot); + + c.BuildContext["CombinedFrameworkHostRoot"] = combinedRoot; + return c.Success(); + } + [Target(nameof(PackageTargets.GenerateZip), nameof(PackageTargets.GenerateTarBall))] public static BuildTargetResult GenerateCompressedFile(BuildTargetContext c) { @@ -55,14 +152,9 @@ namespace Microsoft.DotNet.Cli.Build [BuildPlatforms(BuildPlatform.Windows)] public static BuildTargetResult GenerateZip(BuildTargetContext c) { - var zipFile = c.BuildContext.Get("CompressedFile"); + CreateZipFromDirectory(c.BuildContext.Get("CombinedFrameworkSDKHostRoot"), c.BuildContext.Get("CombinedFrameworkSDKHostCompressedFile")); + CreateZipFromDirectory(c.BuildContext.Get("CombinedFrameworkHostRoot"), c.BuildContext.Get("CombinedFrameworkHostCompressedFile")); - if (File.Exists(zipFile)) - { - File.Delete(zipFile); - } - - ZipFile.CreateFromDirectory(Dirs.Stage2, zipFile, CompressionLevel.Optimal, false); return c.Success(); } @@ -70,16 +162,9 @@ namespace Microsoft.DotNet.Cli.Build [BuildPlatforms(BuildPlatform.Unix)] public static BuildTargetResult GenerateTarBall(BuildTargetContext c) { - var tarFile = c.BuildContext.Get("CompressedFile"); + CreateTarBallFromDirectory(c.BuildContext.Get("CombinedFrameworkSDKHostRoot"), c.BuildContext.Get("CombinedFrameworkSDKHostCompressedFile")); + CreateTarBallFromDirectory(c.BuildContext.Get("CombinedFrameworkHostRoot"), c.BuildContext.Get("CombinedFrameworkHostCompressedFile")); - if (File.Exists(tarFile)) - { - File.Delete(tarFile); - } - - Cmd("tar", "-czf", tarFile, "-C", Dirs.Stage2, ".") - .Execute() - .EnsureSuccessful(); return c.Success(); } @@ -90,7 +175,7 @@ namespace Microsoft.DotNet.Cli.Build var versionSuffix = c.BuildContext.Get("BuildVersion").VersionSuffix; var env = GetCommonEnvVars(c); Cmd("powershell", "-NoProfile", "-NoLogo", - Path.Combine(Dirs.RepoRoot, "packaging", "nuget", "package.ps1"), Path.Combine(Dirs.Stage2, "bin"), versionSuffix) + Path.Combine(Dirs.RepoRoot, "packaging", "nuget", "package.ps1"), Dirs.Stage2, versionSuffix) .Environment(env) .Execute() .EnsureSuccessful(); @@ -134,5 +219,27 @@ namespace Microsoft.DotNet.Cli.Build return env; } + + private static void CreateZipFromDirectory(string directory, string artifactPath) + { + if (File.Exists(artifactPath)) + { + File.Delete(artifactPath); + } + + ZipFile.CreateFromDirectory(directory, artifactPath, CompressionLevel.Optimal, false); + } + + private static void CreateTarBallFromDirectory(string directory, string artifactPath) + { + if (File.Exists(artifactPath)) + { + File.Delete(artifactPath); + } + + Cmd("tar", "-czf", artifactPath, "-C", directory, ".") + .Execute() + .EnsureSuccessful(); + } } } diff --git a/scripts/dotnet-cli-build/PkgTargets.cs b/scripts/dotnet-cli-build/PkgTargets.cs new file mode 100644 index 000000000..34220aac1 --- /dev/null +++ b/scripts/dotnet-cli-build/PkgTargets.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Runtime.InteropServices; +using Microsoft.DotNet.Cli.Build.Framework; +using Microsoft.Extensions.PlatformAbstractions; + +using static Microsoft.DotNet.Cli.Build.Framework.BuildHelpers; + +namespace Microsoft.DotNet.Cli.Build +{ + public class PkgTargets + { + public static string PkgsIntermediateDir { get; set; } + [Target] + [BuildPlatforms(BuildPlatform.OSX)] + public static BuildTargetResult InitPkg(BuildTargetContext c) + { + PkgsIntermediateDir = Path.Combine(Dirs.Packages, "intermediate"); + Directory.CreateDirectory(PkgsIntermediateDir); + return c.Success(); + } + + [Target(nameof(InitPkg), nameof(GenerateSharedFrameworkProductArchive), nameof(GenerateCLISdkProductArchive))] + [BuildPlatforms(BuildPlatform.OSX)] + public static BuildTargetResult GeneratePkgs(BuildTargetContext c) + { + return c.Success(); + } + + [Target(nameof(GenerateCLISdkPkg))] + [BuildPlatforms(BuildPlatform.OSX)] + public static BuildTargetResult GenerateCLISdkProductArchive(BuildTargetContext c) + { + string sharedFrameworkNugetVersion = c.BuildContext.Get("SharedFrameworkNugetVersion"); + string version = c.BuildContext.Get("BuildVersion").SimpleVersion; + string id = $"com.microsoft.dotnet.dev.{version}.osx.x64"; + string resourcePath = Path.Combine(Dirs.RepoRoot, "packaging", "osx", "resources"); + string outFilePath = Path.Combine(Dirs.Packages, c.BuildContext.Get("CombinedFrameworkSDKHostInstallerFile")); + + string inputDistTemplatePath = Path.Combine( + Dirs.RepoRoot, + "packaging", + "osx", + "clisdk", + "Distribution-Template"); + string distTemplate = File.ReadAllText(inputDistTemplatePath); + string distributionPath = Path.Combine(PkgsIntermediateDir, "CLI-SDK-Formatted-Distribution-Template.xml"); + string formattedDistContents = + distTemplate.Replace("{SharedFrameworkNugetVersion}", sharedFrameworkNugetVersion) + .Replace("{SharedFrameworkNugetName}", Monikers.SharedFrameworkName) + .Replace("{VERSION}", version); + File.WriteAllText(distributionPath, formattedDistContents); + + Cmd("productbuild", + "--version", version, + "--identifier", id, + "--package-path", PkgsIntermediateDir, + "--resources", resourcePath, + "--distribution", distributionPath, + outFilePath) + .Execute() + .EnsureSuccessful(); + + return c.Success(); + } + + [Target] + [BuildPlatforms(BuildPlatform.OSX)] + public static BuildTargetResult GenerateCLISdkPkg(BuildTargetContext c) + { + string version = c.BuildContext.Get("BuildVersion").SimpleVersion; + string id = $"com.microsoft.dotnet.sdk.osx.x64"; + string outFilePath = Path.Combine(PkgsIntermediateDir, id + ".pkg"); + string installLocation = "/usr/local/share/dotnet"; + string scriptsLocation = Path.Combine(Dirs.RepoRoot, "packaging", "osx", "clisdk", "scripts"); + + Cmd("pkgbuild", + "--root", c.BuildContext.Get("CLISDKRoot"), + "--identifier", id, + "--version", version, + "--install-location", installLocation, + "--scripts", scriptsLocation, + outFilePath) + .Execute() + .EnsureSuccessful(); + + return c.Success(); + } + + [Target(nameof(GenerateSharedFrameworkPkg), nameof(GenerateSharedHostPkg))] + [BuildPlatforms(BuildPlatform.OSX)] + public static BuildTargetResult GenerateSharedFrameworkProductArchive(BuildTargetContext c) + { + string sharedFrameworkNugetName = Monikers.SharedFrameworkName; + string sharedFrameworkNugetVersion = c.BuildContext.Get("SharedFrameworkNugetVersion"); + string version = c.BuildContext.Get("BuildVersion").SimpleVersion; + string id = $"com.microsoft.dotnet.{sharedFrameworkNugetName}.{sharedFrameworkNugetVersion}.osx.x64"; + string resourcePath = Path.Combine(Dirs.RepoRoot, "packaging", "osx", "resources"); + string outFilePath = Path.Combine(PkgsIntermediateDir, c.BuildContext.Get("CombinedFrameworkHostInstallerFile")); + + string inputDistTemplatePath = Path.Combine( + Dirs.RepoRoot, + "packaging", + "osx", + "sharedframework", + "shared-framework-distribution-template.xml"); + string distTemplate = File.ReadAllText(inputDistTemplatePath); + string distributionPath = Path.Combine(PkgsIntermediateDir, "shared-framework-formatted-distribution.xml"); + string formattedDistContents = + distTemplate.Replace("{SharedFrameworkNugetVersion}", sharedFrameworkNugetVersion) + .Replace("{SharedFrameworkNugetName}", Monikers.SharedFrameworkName) + .Replace("{VERSION}", version); + File.WriteAllText(distributionPath, formattedDistContents); + + Cmd("productbuild", + "--version", version, + "--identifier", id, + "--package-path", PkgsIntermediateDir, + "--resources", resourcePath, + "--distribution", distributionPath, + outFilePath) + .Execute() + .EnsureSuccessful(); + + return c.Success(); + } + + [Target] + [BuildPlatforms(BuildPlatform.OSX)] + public static BuildTargetResult GenerateSharedFrameworkPkg(BuildTargetContext c) + { + string sharedFrameworkNugetName = Monikers.SharedFrameworkName; + string sharedFrameworkNugetVersion = c.BuildContext.Get("SharedFrameworkNugetVersion"); + string version = c.BuildContext.Get("BuildVersion").SimpleVersion; + string id = $"com.microsoft.dotnet.sharedframework.{sharedFrameworkNugetName}.{sharedFrameworkNugetVersion}.component.osx.x64"; + string outFilePath = Path.Combine(PkgsIntermediateDir, id + ".pkg"); + string installLocation = "/usr/local/share/dotnet"; + string scriptsLocation = Path.Combine(Dirs.RepoRoot, "packaging", "osx", "sharedframework", "scripts"); + + Cmd("pkgbuild", + "--root", c.BuildContext.Get("SharedFrameworkPublishRoot"), + "--identifier", id, + "--version", version, + "--install-location", installLocation, + "--scripts", scriptsLocation, + outFilePath) + .Execute() + .EnsureSuccessful(); + + return c.Success(); + } + + [Target] + [BuildPlatforms(BuildPlatform.OSX)] + public static BuildTargetResult GenerateSharedHostPkg(BuildTargetContext c) + { + string version = c.BuildContext.Get("BuildVersion").SimpleVersion; + string id = $"com.microsoft.dotnet.sharedhost.osx.x64"; + string outFilePath = Path.Combine(PkgsIntermediateDir, id + ".pkg"); + string installLocation = "/usr/local/share/dotnet"; + string scriptsLocation = Path.Combine(Dirs.RepoRoot, "packaging", "osx", "sharedhost", "scripts"); + + Cmd("pkgbuild", + "--root", c.BuildContext.Get("SharedHostPublishRoot"), + "--identifier", id, + "--version", version, + "--install-location", installLocation, + "--scripts", scriptsLocation, + outFilePath) + .Execute() + .EnsureSuccessful(); + + return c.Success(); + } + } +} diff --git a/scripts/dotnet-cli-build/PrepareTargets.cs b/scripts/dotnet-cli-build/PrepareTargets.cs index 9a11d5b7e..33a2eaf43 100644 --- a/scripts/dotnet-cli-build/PrepareTargets.cs +++ b/scripts/dotnet-cli-build/PrepareTargets.cs @@ -10,6 +10,7 @@ using System.Text; using static Microsoft.DotNet.Cli.Build.FS; using static Microsoft.DotNet.Cli.Build.Utils; using static Microsoft.DotNet.Cli.Build.Framework.BuildHelpers; +using System.Text.RegularExpressions; namespace Microsoft.DotNet.Cli.Build { @@ -45,6 +46,7 @@ namespace Microsoft.DotNet.Cli.Build c.BuildContext["Configuration"] = configEnv; c.BuildContext["Channel"] = Environment.GetEnvironmentVariable("CHANNEL"); + c.BuildContext["SharedFrameworkNugetVersion"] = GetVersionFromProjectJson(Path.Combine(Dirs.RepoRoot, "src", "sharedframework", "framework", "project.json")); c.Info($"Building {c.BuildContext["Configuration"]} to: {Dirs.Output}"); c.Info("Build Environment:"); @@ -108,34 +110,15 @@ namespace Microsoft.DotNet.Cli.Build [Target] public static BuildTargetResult ExpectedBuildArtifacts(BuildTargetContext c) { - var productName = Monikers.GetProductMoniker(c); var config = Environment.GetEnvironmentVariable("CONFIGURATION"); var versionBadgeName = $"{CurrentPlatform.Current}_{CurrentArchitecture.Current}_{config}_version_badge.svg"; c.BuildContext["VersionBadge"] = Path.Combine(Dirs.Output, versionBadgeName); - var extension = CurrentPlatform.IsWindows ? ".zip" : ".tar.gz"; - c.BuildContext["CompressedFile"] = Path.Combine(Dirs.Packages, productName + extension); - - string installer = ""; - switch (CurrentPlatform.Current) - { - case BuildPlatform.Windows: - installer = productName + ".exe"; - break; - case BuildPlatform.OSX: - installer = productName + ".pkg"; - break; - case BuildPlatform.Ubuntu: - installer = productName + ".deb"; - break; - default: - break; - } - - if (!string.IsNullOrEmpty(installer)) - { - c.BuildContext["InstallerFile"] = Path.Combine(Dirs.Packages, installer); - } + AddInstallerArtifactToContext(c, "dotnet-sdk", "Sdk"); + AddInstallerArtifactToContext(c, "dotnet-host", "SharedHost"); + AddInstallerArtifactToContext(c, "dotnet-sharedframework", "SharedFramework"); + AddInstallerArtifactToContext(c, "dotnet-dev", "CombinedFrameworkSDKHost"); + AddInstallerArtifactToContext(c, "dotnet", "CombinedFrameworkHost"); return c.Success(); } @@ -211,8 +194,8 @@ namespace Microsoft.DotNet.Cli.Build { var dotnet = DotNetCli.Stage0; - dotnet.Restore().WorkingDirectory(Path.Combine(c.BuildContext.BuildDirectory, "src")).Execute().EnsureSuccessful(); - dotnet.Restore().WorkingDirectory(Path.Combine(c.BuildContext.BuildDirectory, "tools")).Execute().EnsureSuccessful(); + dotnet.Restore("--verbosity", "verbose", "--disable-parallel").WorkingDirectory(Path.Combine(c.BuildContext.BuildDirectory, "src")).Execute().EnsureSuccessful(); + dotnet.Restore("--verbosity", "verbose", "--disable-parallel").WorkingDirectory(Path.Combine(c.BuildContext.BuildDirectory, "tools")).Execute().EnsureSuccessful(); return c.Success(); } @@ -334,6 +317,23 @@ cmake is required to build the native host 'corehost'"; return c.Success(); } + private static string GetVersionFromProjectJson(string pathToProjectJson) + { + Regex r = new Regex($"\"{Regex.Escape(Monikers.SharedFrameworkName)}\"\\s*:\\s*\"(?'version'[^\"]*)\""); + + foreach (var line in File.ReadAllLines(pathToProjectJson)) + { + var m = r.Match(line); + + if (m.Success) + { + return m.Groups["version"].Value; + } + } + + throw new InvalidOperationException("Unable to match the version name from " + pathToProjectJson); + } + private static bool AptPackageIsInstalled(string packageName) { var result = Command.Create("dpkg", "-s", packageName) @@ -361,5 +361,35 @@ cmake is required to build the native host 'corehost'"; } return dict; } + + private static void AddInstallerArtifactToContext(BuildTargetContext c, string artifactPrefix, string contextPrefix) + { + var productName = Monikers.GetProductMoniker(c, artifactPrefix); + + var extension = CurrentPlatform.IsWindows ? ".zip" : ".tar.gz"; + c.BuildContext[contextPrefix + "CompressedFile"] = Path.Combine(Dirs.Packages, productName + extension); + + string installer = ""; + switch (CurrentPlatform.Current) + { + case BuildPlatform.Windows: + installer = productName + ".exe"; + break; + case BuildPlatform.OSX: + installer = productName + ".pkg"; + break; + case BuildPlatform.Ubuntu: + installer = productName + ".deb"; + break; + default: + break; + } + + if (!string.IsNullOrEmpty(installer)) + { + c.BuildContext[contextPrefix + "InstallerFile"] = Path.Combine(Dirs.Packages, installer); + } + + } } } diff --git a/scripts/dotnet-cli-build/PublishTargets.cs b/scripts/dotnet-cli-build/PublishTargets.cs index 06dca3485..b57ed91fa 100644 --- a/scripts/dotnet-cli-build/PublishTargets.cs +++ b/scripts/dotnet-cli-build/PublishTargets.cs @@ -1,12 +1,9 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Runtime.InteropServices; +using System.Net.Http; +using System.Text; using Microsoft.DotNet.Cli.Build.Framework; -using Microsoft.Extensions.PlatformAbstractions; -using Microsoft.WindowsAzure; using Microsoft.WindowsAzure.Storage; -using Microsoft.WindowsAzure.Storage.Auth; using Microsoft.WindowsAzure.Storage.Blob; using static Microsoft.DotNet.Cli.Build.Framework.BuildHelpers; @@ -36,7 +33,8 @@ namespace Microsoft.DotNet.Cli.Build [Target(nameof(PrepareTargets.Init), nameof(PublishTargets.InitPublish), - nameof(PublishTargets.PublishArtifacts))] + nameof(PublishTargets.PublishArtifacts), + nameof(PublishTargets.TriggerDockerHubBuilds))] [Environment("PUBLISH_TO_AZURE_BLOB", "1", "true")] // This is set by CI systems public static BuildTargetResult Publish(BuildTargetContext c) { @@ -44,8 +42,10 @@ namespace Microsoft.DotNet.Cli.Build } [Target(nameof(PublishTargets.PublishVersionBadge), - nameof(PublishTargets.PublishCompressedFile), - nameof(PublishTargets.PublishInstallerFile), + nameof(PublishTargets.PublishSdkInstallerFile), + nameof(PublishTargets.PublishDebFileToDebianRepo), + nameof(PublishTargets.PublishCombinedFrameworkSDKHostFile), + nameof(PublishTargets.PublishCombinedFrameworkHostFile), nameof(PublishTargets.PublishLatestVersionTextFile))] public static BuildTargetResult PublishArtifacts(BuildTargetContext c) { @@ -64,24 +64,11 @@ namespace Microsoft.DotNet.Cli.Build return c.Success(); } - [Target] - public static BuildTargetResult PublishCompressedFile(BuildTargetContext c) - { - var compressedFile = c.BuildContext.Get("CompressedFile"); - var compressedFileBlob = $"{Channel}/Binaries/{Version}/{Path.GetFileName(compressedFile)}"; - var latestCompressedFile = compressedFile.Replace(Version, "latest"); - var latestCompressedFileBlob = $"{Channel}/Binaries/Latest/{Path.GetFileName(latestCompressedFile)}"; - - PublishFileAzure(compressedFileBlob, compressedFile); - PublishFileAzure(latestCompressedFileBlob, compressedFile); - return c.Success(); - } - [Target] [BuildPlatforms(BuildPlatform.Windows, BuildPlatform.OSX, BuildPlatform.Ubuntu)] - public static BuildTargetResult PublishInstallerFile(BuildTargetContext c) + public static BuildTargetResult PublishSdkInstallerFile(BuildTargetContext c) { - var installerFile = c.BuildContext.Get("InstallerFile"); + var installerFile = c.BuildContext.Get("CombinedFrameworkSDKHostInstallerFile"); var installerFileBlob = $"{Channel}/Installers/{Version}/{Path.GetFileName(installerFile)}"; var latestInstallerFile = installerFile.Replace(Version, "latest"); var latestInstallerFileBlob = $"{Channel}/Installers/Latest/{Path.GetFileName(latestInstallerFile)}"; @@ -102,13 +89,13 @@ namespace Microsoft.DotNet.Cli.Build return c.Success(); } - [Target(nameof(PublishInstallerFile))] + [Target(nameof(PublishSdkInstallerFile))] [BuildPlatforms(BuildPlatform.Ubuntu)] public static BuildTargetResult PublishDebFileToDebianRepo(BuildTargetContext c) { var packageName = Monikers.GetDebianPackageName(c); - var installerFile = c.BuildContext.Get("InstallerFile"); - var uploadUrl = $"https://dotnetcli.blob.core.windows.net/dotnet/{Channel}/Installers/{Version}/{Path.GetFileName(installerFile)}"; + var installerFile = c.BuildContext.Get("SdkInstallerFile"); + var uploadUrl = $"https://dotnetcli.blob.core.windows.net/dotnet/{Channel}/Installers/{Version}/{Path.GetFileName(installerFile)}"; var uploadJson = GenerateUploadJsonFile(packageName, Version, uploadUrl); Cmd(Path.Combine(Dirs.RepoRoot, "scripts", "publish", "repoapi_client.sh"), "-addpkg", uploadJson) @@ -118,6 +105,45 @@ namespace Microsoft.DotNet.Cli.Build return c.Success(); } + [Target] + [Environment("DOCKER_HUB_REPO")] + [Environment("DOCKER_HUB_TRIGGER_TOKEN")] + public static BuildTargetResult TriggerDockerHubBuilds(BuildTargetContext c) + { + string dockerHubRepo = Environment.GetEnvironmentVariable("DOCKER_HUB_REPO"); + string dockerHubTriggerToken = Environment.GetEnvironmentVariable("DOCKER_HUB_TRIGGER_TOKEN"); + + Uri baseDockerHubUri = new Uri("https://registry.hub.docker.com/u/"); + Uri dockerHubTriggerUri; + if (!Uri.TryCreate(baseDockerHubUri, $"{dockerHubRepo}/trigger/{dockerHubTriggerToken}/", out dockerHubTriggerUri)) + { + return c.Failed("Invalid DOCKER_HUB_REPO and/or DOCKER_HUB_TRIGGER_TOKEN"); + } + + c.Info($"Triggering automated DockerHub builds for {dockerHubRepo}"); + using (HttpClient client = new HttpClient()) + { + StringContent requestContent = new StringContent("{\"build\": true}", Encoding.UTF8, "application/json"); + try + { + HttpResponseMessage response = client.PostAsync(dockerHubTriggerUri, requestContent).Result; + if (!response.IsSuccessStatusCode) + { + StringBuilder sb = new StringBuilder(); + sb.AppendLine($"HTTP request to {dockerHubTriggerUri.ToString()} was unsuccessful."); + sb.AppendLine($"Response status code: {response.StatusCode}. Reason phrase: {response.ReasonPhrase}."); + sb.Append($"Respone content: {response.Content.ReadAsStringAsync().Result}"); + return c.Failed(sb.ToString()); + } + } + catch (AggregateException e) + { + return c.Failed($"HTTP request to {dockerHubTriggerUri.ToString()} failed. {e.ToString()}"); + } + } + return c.Success(); + } + private static string GenerateUploadJsonFile(string packageName, string version, string uploadUrl) { var repoID = Environment.GetEnvironmentVariable("REPO_ID"); @@ -128,18 +154,44 @@ namespace Microsoft.DotNet.Cli.Build { using (StreamWriter sw = new StreamWriter(fileStream)) { - sw.WriteLine("{"); - sw.WriteLine($" \"name\":\"{packageName}\","); - sw.WriteLine($" \"version\":\"{version}\","); - sw.WriteLine($" \"repositoryId\":\"{repoID}\","); - sw.WriteLine($" \"sourceUrl\":\"{uploadUrl}\""); - sw.WriteLine("}"); + sw.WriteLine("{"); + sw.WriteLine($" \"name\":\"{packageName}\","); + sw.WriteLine($" \"version\":\"{version}\","); + sw.WriteLine($" \"repositoryId\":\"{repoID}\","); + sw.WriteLine($" \"sourceUrl\":\"{uploadUrl}\""); + sw.WriteLine("}"); } } return uploadJson; } + [Target] + public static BuildTargetResult PublishCombinedFrameworkSDKHostFile(BuildTargetContext c) + { + var compressedFile = c.BuildContext.Get("CombinedFrameworkSDKHostCompressedFile"); + var compressedFileBlob = $"{Channel}/Binaries/{Version}/{Path.GetFileName(compressedFile)}"; + var latestCompressedFile = compressedFile.Replace(Version, "latest"); + var latestCompressedFileBlob = $"{Channel}/Binaries/Latest/{Path.GetFileName(latestCompressedFile)}"; + + PublishFileAzure(compressedFileBlob, compressedFile); + PublishFileAzure(latestCompressedFileBlob, compressedFile); + return c.Success(); + } + + [Target] + public static BuildTargetResult PublishCombinedFrameworkHostFile(BuildTargetContext c) + { + var compressedFile = c.BuildContext.Get("CombinedFrameworkHostCompressedFile"); + var compressedFileBlob = $"{Channel}/Binaries/{Version}/{Path.GetFileName(compressedFile)}"; + var latestCompressedFile = compressedFile.Replace(Version, "latest"); + var latestCompressedFileBlob = $"{Channel}/Binaries/Latest/{Path.GetFileName(latestCompressedFile)}"; + + PublishFileAzure(compressedFileBlob, compressedFile); + PublishFileAzure(latestCompressedFileBlob, compressedFile); + return c.Success(); + } + private static BuildTargetResult PublishFile(BuildTargetContext c, string file) { var env = PackageTargets.GetCommonEnvVars(c); diff --git a/scripts/dotnet-cli-build/TestTargets.cs b/scripts/dotnet-cli-build/TestTargets.cs index b1a783be8..abedc6663 100644 --- a/scripts/dotnet-cli-build/TestTargets.cs +++ b/scripts/dotnet-cli-build/TestTargets.cs @@ -16,7 +16,8 @@ namespace Microsoft.DotNet.Cli.Build public static readonly string[] TestPackageProjects = new[] { "dotnet-hello/v1/dotnet-hello", - "dotnet-hello/v2/dotnet-hello" + "dotnet-hello/v2/dotnet-hello", + "dotnet-portable" }; public static readonly string[] TestProjects = new[] @@ -36,7 +37,8 @@ namespace Microsoft.DotNet.Cli.Build "Microsoft.DotNet.ProjectModel.Tests", "Microsoft.Extensions.DependencyModel.Tests", "ArgumentForwardingTests", - "dotnet-test.UnitTests" + "dotnet-test.UnitTests", + "dotnet-test.Tests" }; [Target(nameof(PrepareTargets.Init), nameof(SetupTests), nameof(RestoreTests), nameof(BuildTests), nameof(RunTests), nameof(ValidateDependencies))] @@ -44,7 +46,7 @@ namespace Microsoft.DotNet.Cli.Build [Target(nameof(SetupTestPackages), nameof(SetupTestProjects))] public static BuildTargetResult SetupTests(BuildTargetContext c) => c.Success(); - + [Target(nameof(RestoreTestAssetPackages), nameof(BuildTestAssetPackages))] public static BuildTargetResult SetupTestPackages(BuildTargetContext c) => c.Success(); @@ -60,11 +62,11 @@ namespace Microsoft.DotNet.Cli.Build CleanNuGetTempCache(); var dotnet = DotNetCli.Stage2; - dotnet.Restore().WorkingDirectory(Path.Combine(c.BuildContext.BuildDirectory, "TestAssets", "TestPackages")).Execute().EnsureSuccessful(); + dotnet.Restore("--verbosity", "verbose", "--disable-parallel").WorkingDirectory(Path.Combine(c.BuildContext.BuildDirectory, "TestAssets", "TestPackages")).Execute().EnsureSuccessful(); return c.Success(); } - + [Target] public static BuildTargetResult RestoreTestAssetProjects(BuildTargetContext c) { @@ -74,17 +76,17 @@ namespace Microsoft.DotNet.Cli.Build CleanNuGetTempCache(); var dotnet = DotNetCli.Stage2; - - dotnet.Restore("--fallbacksource", Dirs.TestPackages) + + dotnet.Restore("--verbosity", "verbose", "--disable-parallel", "--fallbacksource", Dirs.TestPackages) .WorkingDirectory(Path.Combine(c.BuildContext.BuildDirectory, "TestAssets", "TestProjects")) .Execute().EnsureSuccessful(); - + // The 'ProjectModelServer' directory contains intentionally-unresolved dependencies, so don't check for success. Also, suppress the output - dotnet.Restore() + dotnet.Restore("--verbosity", "verbose", "--disable-parallel") .WorkingDirectory(Path.Combine(c.BuildContext.BuildDirectory, "TestAssets", "ProjectModelServer", "DthTestProjects")) .Execute(); - - dotnet.Restore() + + dotnet.Restore("--verbosity", "verbose", "--disable-parallel") .WorkingDirectory(Path.Combine(c.BuildContext.BuildDirectory, "TestAssets", "ProjectModelServer", "DthUpdateSearchPathSample")) .Execute(); @@ -94,6 +96,8 @@ namespace Microsoft.DotNet.Cli.Build [Target(nameof(CleanTestPackages))] public static BuildTargetResult BuildTestAssetPackages(BuildTargetContext c) { + CleanBinObj(c, Path.Combine(c.BuildContext.BuildDirectory, "TestAssets", "TestPackages")); + var dotnet = DotNetCli.Stage2; Rmdir(Dirs.TestPackages); @@ -108,21 +112,23 @@ namespace Microsoft.DotNet.Cli.Build .Execute() .EnsureSuccessful(); } - + return c.Success(); } - + [Target] public static BuildTargetResult CleanTestPackages(BuildTargetContext c) { Rmdir(Path.Combine(Dirs.NuGetPackages, "dotnet-hello")); - + return c.Success(); } [Target] public static BuildTargetResult BuildTestAssetProjects(BuildTargetContext c) { + CleanBinObj(c, Path.Combine(c.BuildContext.BuildDirectory, "TestAssets", "TestProjects")); + var dotnet = DotNetCli.Stage2; var nobuildFileName = ".noautobuild"; string testProjectsRoot = Path.Combine(c.BuildContext.BuildDirectory, "TestAssets", "TestProjects"); @@ -137,7 +143,7 @@ namespace Microsoft.DotNet.Cli.Build .Execute() .EnsureSuccessful(); } - + return c.Success(); } @@ -148,7 +154,7 @@ namespace Microsoft.DotNet.Cli.Build CleanBinObj(c, Path.Combine(c.BuildContext.BuildDirectory, "test")); CleanNuGetTempCache(); - DotNetCli.Stage2.Restore("--fallbacksource", Dirs.TestPackages) + DotNetCli.Stage2.Restore("--verbosity", "verbose", "--disable-parallel", "--fallbacksource", Dirs.TestPackages) .WorkingDirectory(Path.Combine(c.BuildContext.BuildDirectory, "test")) .Execute() .EnsureSuccessful(); @@ -246,7 +252,7 @@ namespace Microsoft.DotNet.Cli.Build { return new Dictionary(); } - + c.Verbose("Start Collecting Visual Studio Environment Variables"); var vsvarsPath = Path.GetFullPath(Path.Combine(Environment.GetEnvironmentVariable("VS140COMNTOOLS"), "..", "..", "VC")); @@ -273,14 +279,14 @@ set"); File.Delete(temp); } } - + result.EnsureSuccessful(); - + var vars = new Dictionary(); foreach (var line in result.StdOut.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)) { var splat = line.Split(new[] { '=' }, 2); - + if (splat.Length == 2) { c.Verbose($"Adding variable '{line}'"); @@ -291,9 +297,8 @@ set"); c.Info($"Skipping VS Env Variable. Unknown format: '{line}'"); } } - + c.Verbose("Finish Collecting Visual Studio Environment Variables"); - return vars; } } diff --git a/scripts/dotnet-cli-build/Utils/BuildVersion.cs b/scripts/dotnet-cli-build/Utils/BuildVersion.cs index df2b32c34..c7e2f50c7 100644 --- a/scripts/dotnet-cli-build/Utils/BuildVersion.cs +++ b/scripts/dotnet-cli-build/Utils/BuildVersion.cs @@ -12,6 +12,7 @@ public string SimpleVersion => $"{Major}.{Minor}.{Patch}.{CommitCountString}"; public string VersionSuffix => $"{ReleaseSuffix}-{CommitCountString}"; public string NuGetVersion => $"{Major}.{Minor}.{Patch}-{VersionSuffix}"; + public string ProductionVersion => $"{Major}.{Minor}.{Patch}"; public string GenerateMsiVersion() { diff --git a/scripts/dotnet-cli-build/Utils/Crossgen.cs b/scripts/dotnet-cli-build/Utils/Crossgen.cs new file mode 100644 index 000000000..37cf836aa --- /dev/null +++ b/scripts/dotnet-cli-build/Utils/Crossgen.cs @@ -0,0 +1,50 @@ +using System.IO; +using System.Linq; +using System; +using System.Runtime.InteropServices; +using Microsoft.DotNet.Cli.Build.Framework; +using Microsoft.Extensions.PlatformAbstractions; + +namespace Microsoft.DotNet.Cli.Build +{ + internal static class Crossgen + { + public static string GetCrossgenPathForVersion(string coreClrVersion) + { + string arch = PlatformServices.Default.Runtime.RuntimeArchitecture; + string packageId; + if (CurrentPlatform.IsWindows) + { + packageId = $"runtime.win7-{arch}.Microsoft.NETCore.Runtime.CoreCLR"; + } + else if (CurrentPlatform.IsUbuntu) + { + packageId = "runtime.ubuntu.14.04-x64.Microsoft.NETCore.Runtime.CoreCLR"; + } + else if (CurrentPlatform.IsCentOS || CurrentPlatform.IsRHEL) + { + // CentOS runtime is in the runtime.rhel.7-x64... package. + packageId = "runtime.rhel.7-x64.Microsoft.NETCore.Runtime.CoreCLR"; + } + else if (CurrentPlatform.IsOSX) + { + packageId = "runtime.osx.10.10-x64.Microsoft.NETCore.Runtime.CoreCLR"; + } + else if (CurrentPlatform.IsDebian) + { + packageId = "runtime.debian.8.2-x64.Microsoft.NETCore.Runtime.CoreCLR"; + } + else + { + return null; + } + + return Path.Combine( + Dirs.NuGetPackages, + packageId, + coreClrVersion, + "tools", + $"crossgen{Constants.ExeSuffix}"); + } + } +} diff --git a/scripts/dotnet-cli-build/Utils/DotNetCli.cs b/scripts/dotnet-cli-build/Utils/DotNetCli.cs index a84ec89dc..01e618fac 100644 --- a/scripts/dotnet-cli-build/Utils/DotNetCli.cs +++ b/scripts/dotnet-cli-build/Utils/DotNetCli.cs @@ -7,11 +7,11 @@ using Microsoft.Extensions.PlatformAbstractions; namespace Microsoft.DotNet.Cli.Build { - internal class DotNetCli + public class DotNetCli { public static readonly DotNetCli Stage0 = new DotNetCli(GetStage0Path()); - public static readonly DotNetCli Stage1 = new DotNetCli(Path.Combine(Dirs.Stage1, "bin")); - public static readonly DotNetCli Stage2 = new DotNetCli(Path.Combine(Dirs.Stage2, "bin")); + public static readonly DotNetCli Stage1 = new DotNetCli(Dirs.Stage1); + public static readonly DotNetCli Stage2 = new DotNetCli(Dirs.Stage2); public string BinPath { get; } diff --git a/scripts/dotnet-cli-build/Utils/Monikers.cs b/scripts/dotnet-cli-build/Utils/Monikers.cs index ae8052636..7fb3562e4 100644 --- a/scripts/dotnet-cli-build/Utils/Monikers.cs +++ b/scripts/dotnet-cli-build/Utils/Monikers.cs @@ -8,12 +8,14 @@ namespace Microsoft.DotNet.Cli.Build { public class Monikers { - public static string GetProductMoniker(BuildTargetContext c) + public const string SharedFrameworkName = "Microsoft.NETCore.App"; + + public static string GetProductMoniker(BuildTargetContext c, string artifactPrefix) { string osname = GetOSShortName(); var arch = CurrentArchitecture.Current.ToString(); var version = c.BuildContext.Get("BuildVersion").SimpleVersion; - return $"dotnet-{osname}-{arch}.{version}"; + return $"{artifactPrefix}-{osname}-{arch}.{version}"; } public static string GetDebianPackageName(BuildTargetContext c) @@ -31,13 +33,20 @@ namespace Microsoft.DotNet.Cli.Build case "rtm": packageName = "dotnet"; break; - default: + default: throw new Exception($"Unknown channel - {channel}"); } return packageName; } + public static string GetDebianSharedFrameworkPackageName(BuildTargetContext c) + { + var sharedFrameworkNugetVersion = c.BuildContext.Get("SharedFrameworkNugetVersion"); + + return $"dotnet-sharedframework-{SharedFrameworkName}-{sharedFrameworkNugetVersion}".ToLower(); + } + public static string GetOSShortName() { string osname = ""; diff --git a/scripts/dotnet-cli-build/Utils/Utils.cs b/scripts/dotnet-cli-build/Utils/Utils.cs index edbee23d1..b94343249 100644 --- a/scripts/dotnet-cli-build/Utils/Utils.cs +++ b/scripts/dotnet-cli-build/Utils/Utils.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Runtime.InteropServices; +using System.Security.Cryptography; namespace Microsoft.DotNet.Cli.Build { @@ -34,5 +35,87 @@ namespace Microsoft.DotNet.Cli.Build throw new PlatformNotSupportedException(); } } + + // Generate a Version 5 (SHA1 Name Based) Guid from a name. + public static Guid GenerateGuidFromName(string name) + { + // Any fixed GUID will do for a namespace. + Guid namespaceId = new Guid("28F1468D-672B-489A-8E0C-7C5B3030630C"); + + using (SHA1 hasher = SHA1.Create()) + { + var nameBytes = System.Text.Encoding.UTF8.GetBytes(name ?? string.Empty); + var namespaceBytes = namespaceId.ToByteArray(); + + SwapGuidByteOrder(namespaceBytes); + + var streamToHash = new byte[namespaceBytes.Length + nameBytes.Length]; + + Array.Copy(namespaceBytes, streamToHash, namespaceBytes.Length); + Array.Copy(nameBytes, 0, streamToHash, namespaceBytes.Length, nameBytes.Length); + + var hashResult = hasher.ComputeHash(streamToHash); + + var res = new byte[16]; + + Array.Copy(hashResult, res, res.Length); + + unchecked { res[6] = (byte)(0x50 | (res[6] & 0x0F)); } + unchecked { res[8] = (byte)(0x40 | (res[8] & 0x3F)); } + + SwapGuidByteOrder(res); + + return new Guid(res); + } + } + + // Do a byte order swap, .NET GUIDs store multi byte components in little + // endian. + private static void SwapGuidByteOrder(byte[] b) + { + Swap(b, 0, 3); + Swap(b, 1, 2); + Swap(b, 5, 6); + Swap(b, 7, 8); + } + + private static void Swap(byte[] b, int x, int y) + { + byte t = b[x]; + b[x] = b[y]; + b[y] = t; + } + + public static void DeleteDirectory(string path) + { + if (Directory.Exists(path)) + { + string[] files = Directory.GetFiles(path, "*", SearchOption.AllDirectories); + foreach (string file in files) + { + File.SetAttributes(file, FileAttributes.Normal); + File.Delete(file); + } + + Directory.Delete(path, true); + } + } + + public static void CopyDirectoryRecursively(string path, string destination, bool keepParentDir = false) + { + if (keepParentDir) + { + path = path.TrimEnd(Path.DirectorySeparatorChar); + destination = Path.Combine(destination, Path.GetFileName(path)); + Directory.CreateDirectory(destination); + } + + foreach (var file in Directory.GetFiles(path, "*", SearchOption.AllDirectories)) + { + string destFile = file.Replace(path, destination); + Directory.CreateDirectory(Path.GetDirectoryName(destFile)); + File.Copy(file, destFile, true); + } + } } } diff --git a/scripts/dotnet-cli-build/project.json b/scripts/dotnet-cli-build/project.json index e76a7a1c3..72295b954 100755 --- a/scripts/dotnet-cli-build/project.json +++ b/scripts/dotnet-cli-build/project.json @@ -1,21 +1,18 @@ { - "version": "1.0.0-*", - "description": "Build scripts for dotnet-cli", - "compilationOptions": { - "emitEntryPoint": true - }, - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "System.IO.Compression.ZipFile": "4.0.1-rc2-23901", - "Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-16537", - "Microsoft.DotNet.Cli.Build.Framework": "1.0.0-*", - "WindowsAzure.Storage" : "6.2.2-preview" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": ["dnxcore50", "portable-net45+win8"] - } + "version": "1.0.0-*", + "description": "Build scripts for dotnet-cli", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "Microsoft.DotNet.Cli.Build.Framework": "1.0.0-*", + "WindowsAzure.Storage": "6.2.2-preview", + "System.Reflection.Metadata": "1.2.0" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": [ "dnxcore50", "portable-net45+win8" ] } + } } diff --git a/scripts/obtain/install.sh b/scripts/obtain/install.sh index c212eae44..706b4c718 100755 --- a/scripts/obtain/install.sh +++ b/scripts/obtain/install.sh @@ -84,7 +84,7 @@ current_os() elif [ "$(cat /etc/*-release | grep -cim1 centos)" -eq 1 ]; then echo "centos" elif [ "$(cat /etc/*-release | grep -cim1 rhel)" -eq 1 ]; then - echo "rhel.7" + echo "rhel" elif [ "$(cat /etc/*-release | grep -cim1 debian)" -eq 1 ]; then echo "debian" fi @@ -189,7 +189,7 @@ install_dotnet() local localVersion=$(tail -n 1 "$installLocation/cli/.version" 2>/dev/null) if [ "$VERSION" == "Latest" ]; then # Check if we need to bother - local remoteData="$(curl -s https://dotnetcli.blob.core.windows.net/dotnet/$CHANNEL/dnvm/latest.$os.version)" + local remoteData="$(curl -s https://dotnetcli.blob.core.windows.net/dotnet/$CHANNEL/dnvm/latest.$os.x64.version)" [ $? != 0 ] && say_err "Unable to determine latest version." && return 1 local remoteVersion=$(IFS="\n" && echo $remoteData | tail -n 1) diff --git a/scripts/package/package-debian.sh b/scripts/package/package-debian.sh index e06748739..1b54bfb02 100755 --- a/scripts/package/package-debian.sh +++ b/scripts/package/package-debian.sh @@ -29,6 +29,7 @@ help(){ echo " --output The full path to which the package will be written." echo " --package-name Package to identify during installation. Example - 'dotnet-nightly', 'dotnet'" echo " --previous-version-url Url to the previous version of the debian packge against which to run the upgrade tests." + echo " --obj-root Root folder for intermediate objects." exit 1 } @@ -61,6 +62,10 @@ parseargs(){ PREVIOUS_VERSION_URL=$2 shift ;; + --obj-root) + OBJECT_DIR=$2 + shift + ;; --help) help ;; @@ -103,9 +108,9 @@ parseargs $@ PACKAGING_ROOT="$REPOROOT/packaging/debian" PACKAGING_TOOL_DIR="$REPOROOT/tools/DebianPackageTool" -PACKAGE_OUTPUT_DIR=$(dirname "${OUTPUT_DEBIAN_FILE}") -PACKAGE_LAYOUT_DIR="$PACKAGE_OUTPUT_DIR/deb_intermediate" -TEST_STAGE_DIR="$PACKAGE_OUTPUT_DIR/debian_tests" +PACKAGE_OUTPUT_DIR="$OBJECT_DIR/deb_output" +PACKAGE_LAYOUT_DIR="$OBJECT_DIR/deb_intermediate" +TEST_STAGE_DIR="$OBJECT_DIR/debian_tests" # remove any residual deb files from earlier builds rm -f "$PACKAGE_OUTPUT_DIR/*.deb" diff --git a/scripts/package/package-sharedframework-debian.sh b/scripts/package/package-sharedframework-debian.sh new file mode 100755 index 000000000..ef904c0ee --- /dev/null +++ b/scripts/package/package-sharedframework-debian.sh @@ -0,0 +1,154 @@ +#!/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. + +# Debian Packaging Script +# Currently Intended to build on ubuntu14.04 + +set -e + +SOURCE="${BASH_SOURCE[0]}" +while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink + DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + SOURCE="$(readlink "$SOURCE")" + [[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located +done +DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + +source "$DIR/../common/_common.sh" +REPOROOT="$DIR/../.." + +help(){ + echo "Usage: $0" + echo "" + echo "Options:" + echo " --input Package the entire contents of the directory tree." + echo " --output The full path to which the package will be written." + echo " --package-name Package to identify during installation. Example - 'dotnet-sharedframework'" + echo " --framework-nuget-name The name of the nuget package that produced this shared framework." + echo " --framework-nuget-version The versionf of the nuget package that produced this shared framework." + echo " --obj-root Root folder for intermediate objects." + echo " --version Version for the debain package." + exit 1 +} + +while [[ $# > 0 ]]; do + lowerI="$(echo $1 | awk '{print tolower($0)}')" + case $lowerI in + -o|--output) + OUTPUT_DEBIAN_FILE=$2 + shift + ;; + -i|--input) + REPO_BINARIES_DIR=$2 + shift + ;; + -p|--package-name) + SHARED_FRAMEWORK_DEBIAN_PACKAGE_NAME=$2 + shift + ;; + --framework-nuget-name) + SHARED_FRAMEWORK_NUGET_NAME=$2 + shift + ;; + --framework-nuget-version) + SHARED_FRAMEWORK_NUGET_VERSION=$2 + shift + ;; + --obj-root) + OBJECT_DIR=$2 + shift + ;; + --version) + SHARED_FRAMEWORK_DEBIAN_VERSION=$2 + shift + ;; + --help) + help + ;; + *) + break + ;; + esac + shift +done + +PACKAGING_ROOT="$REPOROOT/packaging/sharedframework/debian" +PACKAGING_TOOL_DIR="$REPOROOT/tools/DebianPackageTool" + +PACKAGE_OUTPUT_DIR="$OBJECT_DIR/deb_output" +PACKAGE_LAYOUT_DIR="$OBJECT_DIR/deb_intermediate" +TEST_STAGE_DIR="$OBJECT_DIR/debian_tests" + +execute_build(){ + create_empty_debian_layout + copy_files_to_debian_layout + update_debian_json + create_debian_package +} + +create_empty_debian_layout(){ + header "Creating empty debian package layout" + + rm -rf "$PACKAGE_LAYOUT_DIR" + mkdir -p "$PACKAGE_LAYOUT_DIR" + + mkdir "$PACKAGE_LAYOUT_DIR/\$" + mkdir "$PACKAGE_LAYOUT_DIR/package_root" + mkdir "$PACKAGE_LAYOUT_DIR/samples" + mkdir "$PACKAGE_LAYOUT_DIR/docs" +} + +copy_files_to_debian_layout(){ + header "Copying files to debian layout" + + # Copy Built Binaries + cp -a "$REPO_BINARIES_DIR/." "$PACKAGE_LAYOUT_DIR/package_root" + + # Copy config file + cp "$PACKAGING_ROOT/dotnet-sharedframework-debian_config.json" "$PACKAGE_LAYOUT_DIR/debian_config.json" +} + +create_debian_package(){ + header "Packing .deb" + + mkdir -p "$PACKAGE_OUTPUT_DIR" + + "$PACKAGING_TOOL_DIR/package_tool" -i "$PACKAGE_LAYOUT_DIR" -o "$PACKAGE_OUTPUT_DIR" -n "$SHARED_FRAMEWORK_DEBIAN_PACKAGE_NAME" -v "$SHARED_FRAMEWORK_DEBIAN_VERSION" +} + +update_debian_json() +{ + header "Updating debian.json file" + sed -i "s/%SHARED_FRAMEWORK_DEBIAN_PACKAGE_NAME%/$SHARED_FRAMEWORK_DEBIAN_PACKAGE_NAME/g" "$PACKAGE_LAYOUT_DIR"/debian_config.json + sed -i "s/%SHARED_FRAMEWORK_NUGET_NAME%/$SHARED_FRAMEWORK_NUGET_NAME/g" "$PACKAGE_LAYOUT_DIR"/debian_config.json + sed -i "s/%SHARED_FRAMEWORK_NUGET_VERSION%/$SHARED_FRAMEWORK_NUGET_VERSION/g" "$PACKAGE_LAYOUT_DIR"/debian_config.json +} + +test_debian_package(){ + header "Testing debian package" + + install_bats + run_package_integrity_tests +} + +install_bats() { + rm -rf $TEST_STAGE_DIR + git clone https://github.com/sstephenson/bats.git $TEST_STAGE_DIR +} + +run_package_integrity_tests() { + # Set LAST_VERSION_URL to enable upgrade tests + # export LAST_VERSION_URL="$PREVIOUS_VERSION_URL" + + $TEST_STAGE_DIR/bin/bats $PACKAGE_OUTPUT_DIR/test_package.bats +} + +execute_build + +DEBIAN_FILE=$(find $PACKAGE_OUTPUT_DIR -iname "*.deb") + +test_debian_package + +mv -f "$DEBIAN_FILE" "$OUTPUT_DEBIAN_FILE" diff --git a/scripts/package/package-sharedhost-debian.sh b/scripts/package/package-sharedhost-debian.sh new file mode 100755 index 000000000..4c4ecb8db --- /dev/null +++ b/scripts/package/package-sharedhost-debian.sh @@ -0,0 +1,130 @@ +#!/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. + +# Debian Packaging Script +# Currently Intended to build on ubuntu14.04 + +set -e + +SOURCE="${BASH_SOURCE[0]}" +while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symlink + DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + SOURCE="$(readlink "$SOURCE")" + [[ "$SOURCE" != /* ]] && SOURCE="$DIR/$SOURCE" # if $SOURCE was a relative symlink, we need to resolve it relative to the path where the symlink file was located +done +DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" + +source "$DIR/../common/_common.sh" +REPOROOT="$DIR/../.." + +help(){ + echo "Usage: $0" + echo "" + echo "Options:" + echo " --input Package the entire contents of the directory tree." + echo " --output The full path to which the package will be written." + echo " --obj-root Root folder for intermediate objects." + echo " --version Version for the debain package." + exit 1 +} + +while [[ $# > 0 ]]; do + lowerI="$(echo $1 | awk '{print tolower($0)}')" + case $lowerI in + -o|--output) + OUTPUT_DEBIAN_FILE=$2 + shift + ;; + -i|--input) + REPO_BINARIES_DIR=$2 + shift + ;; + --obj-root) + OBJECT_DIR=$2 + shift + ;; + --version) + SHARED_HOST_DEBIAN_VERSION=$2 + shift + ;; + --help) + help + ;; + *) + break + ;; + esac + shift +done + +PACKAGING_ROOT="$REPOROOT/packaging/host/debian" +PACKAGING_TOOL_DIR="$REPOROOT/tools/DebianPackageTool" + +PACKAGE_OUTPUT_DIR="$OBJECT_DIR/deb_output" +PACKAGE_LAYOUT_DIR="$OBJECT_DIR/deb_intermediate" +TEST_STAGE_DIR="$OBJECT_DIR/debian_tests" + +execute_build(){ + create_empty_debian_layout + copy_files_to_debian_layout + create_debian_package +} + +create_empty_debian_layout(){ + header "Creating empty debian package layout" + + rm -rf "$PACKAGE_LAYOUT_DIR" + mkdir -p "$PACKAGE_LAYOUT_DIR" + + mkdir "$PACKAGE_LAYOUT_DIR/\$" + mkdir "$PACKAGE_LAYOUT_DIR/package_root" + mkdir "$PACKAGE_LAYOUT_DIR/samples" + mkdir "$PACKAGE_LAYOUT_DIR/docs" +} + +copy_files_to_debian_layout(){ + header "Copying files to debian layout" + + # Copy Built Binaries + cp -a "$REPO_BINARIES_DIR/." "$PACKAGE_LAYOUT_DIR/package_root" + + # Copy config file + cp "$PACKAGING_ROOT/dotnet-sharedhost-debian_config.json" "$PACKAGE_LAYOUT_DIR/debian_config.json" +} + +create_debian_package(){ + header "Packing .deb" + + mkdir -p "$PACKAGE_OUTPUT_DIR" + + "$PACKAGING_TOOL_DIR/package_tool" -i "$PACKAGE_LAYOUT_DIR" -o "$PACKAGE_OUTPUT_DIR" -v "$SHARED_HOST_DEBIAN_VERSION" +} + +test_debian_package(){ + header "Testing debian package" + + install_bats + run_package_integrity_tests +} + +install_bats() { + rm -rf $TEST_STAGE_DIR + git clone https://github.com/sstephenson/bats.git $TEST_STAGE_DIR +} + +run_package_integrity_tests() { + # Set LAST_VERSION_URL to enable upgrade tests + # export LAST_VERSION_URL="$PREVIOUS_VERSION_URL" + + $TEST_STAGE_DIR/bin/bats $PACKAGE_OUTPUT_DIR/test_package.bats +} + +execute_build + +DEBIAN_FILE=$(find $PACKAGE_OUTPUT_DIR -iname "*.deb") + +test_debian_package + +mv -f "$DEBIAN_FILE" "$OUTPUT_DEBIAN_FILE" diff --git a/scripts/package/projectsToPack.ps1 b/scripts/package/projectsToPack.ps1 index 1b8cbe54f..5c08c9573 100644 --- a/scripts/package/projectsToPack.ps1 +++ b/scripts/package/projectsToPack.ps1 @@ -3,10 +3,13 @@ $ProjectsToPack = @( "Microsoft.DotNet.Cli.Utils", + "Microsoft.DotNet.Compiler.Common", + "Microsoft.DotNet.Files", + "Microsoft.DotNet.InternalAbstractions", "Microsoft.DotNet.ProjectModel", "Microsoft.DotNet.ProjectModel.Loader", "Microsoft.DotNet.ProjectModel.Workspaces", - "Microsoft.DotNet.InternalAbstractions", + "Microsoft.DotNet.TestFramework", "Microsoft.Extensions.DependencyModel", "Microsoft.Extensions.Testing.Abstractions" ) \ No newline at end of file diff --git a/scripts/run-build.ps1 b/scripts/run-build.ps1 index 79809e4c6..125a63aa9 100644 --- a/scripts/run-build.ps1 +++ b/scripts/run-build.ps1 @@ -72,7 +72,7 @@ $env:PATH = "$env:DOTNET_INSTALL_DIR\cli\bin;$env:PATH" # Restore the build scripts Write-Host "Restoring Build Script projects..." pushd $PSScriptRoot -dotnet restore +dotnet restore --disable-parallel if($LASTEXITCODE -ne 0) { throw "Failed to restore" } popd diff --git a/scripts/run-build.sh b/scripts/run-build.sh index 076abfdb2..63f28ea7a 100755 --- a/scripts/run-build.sh +++ b/scripts/run-build.sh @@ -102,7 +102,7 @@ fi echo "Restoring Build Script projects..." ( cd $DIR - dotnet restore + dotnet restore --disable-parallel ) # Build the builder @@ -114,7 +114,7 @@ echo "Invoking Build Scripts..." echo "Configuration: $CONFIGURATION" if [ -f "$DIR/dotnet-cli-build/bin/dotnet-cli-build" ]; then - $DIR/dotnet-cli-build/bin/dotnet-cli-build "${targets[@]}" + $DIR/dotnet-cli-build/bin/dotnet-cli-build ${targets[@]} exit $? else # We're on an older CLI. This is temporary while Ubuntu and CentOS VSO builds are stalled. diff --git a/scripts/update-dependencies.ps1 b/scripts/update-dependencies.ps1 new file mode 100644 index 000000000..e7050cec1 --- /dev/null +++ b/scripts/update-dependencies.ps1 @@ -0,0 +1,51 @@ +# +# 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. +# + +param( + [string[]]$Targets=@("Default"), + [switch]$Help) + +if($Help) +{ + Write-Host "Usage: .\update-dependencies.ps1 [-Targets ]" + Write-Host "" + Write-Host "Options:" + Write-Host " -Targets Comma separated build targets to run (UpdateFiles, PushPR; Default is everything)" + Write-Host " -Help Display this help message" + exit 0 +} + +# Use a repo-local install directory (but not the artifacts directory because that gets cleaned a lot +if (!$env:DOTNET_INSTALL_DIR) +{ + $env:DOTNET_INSTALL_DIR="$PSScriptRoot\..\.dotnet_stage0\Windows\$Architecture" +} + +# Install a stage 0 +Write-Host "Installing .NET Core CLI Stage 0" +& "$PSScriptRoot\obtain\install.ps1" -Architecture x64 + +# Put the stage0 on the path +$env:PATH = "$env:DOTNET_INSTALL_DIR\cli\bin;$env:PATH" + +$appPath = "$PSScriptRoot\update-dependencies" + +# Restore the build scripts +Write-Host "Restoring Build Script projects..." +pushd $PSScriptRoot +dotnet restore +if($LASTEXITCODE -ne 0) { throw "Failed to restore" } +popd + +# Publish the app +Write-Host "Compiling App $appPath..." +dotnet publish "$appPath" -o "$appPath\bin" --framework netstandardapp1.5 +if($LASTEXITCODE -ne 0) { throw "Failed to compile build scripts" } + +# Run the app +Write-Host "Invoking App $appPath..." +Write-Host " Configuration: $env:CONFIGURATION" +& "$appPath\bin\update-dependencies.exe" @Targets +if($LASTEXITCODE -ne 0) { throw "Build failed" } diff --git a/scripts/update-dependencies/Config.cs b/scripts/update-dependencies/Config.cs new file mode 100644 index 000000000..c0e446ec3 --- /dev/null +++ b/scripts/update-dependencies/Config.cs @@ -0,0 +1,74 @@ +// 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 Microsoft.DotNet.Cli.Build.Framework; + +namespace Microsoft.DotNet.Scripts +{ + /// + /// Holds the configuration information for the update-dependencies script. + /// + /// + /// The following Environment Variables are required by this script: + /// + /// GITHUB_USER - The user to commit the changes as. + /// GITHUB_EMAIL - The user's email to commit the changes as. + /// GITHUB_PASSWORD - The password/personal access token of the GitHub user. + /// + /// The following Environment Variables can optionally be specified: + /// + /// COREFX_VERSION_URL - The Url to get the current CoreFx version. (ex. "https://raw.githubusercontent.com/dotnet/versions/master/dotnet/corefx/release/1.0.0-rc2/LKG.txt") + /// GITHUB_ORIGIN_OWNER - The owner of the GitHub fork to push the commit and create the PR from. (ex. "dotnet-bot") + /// GITHUB_UPSTREAM_OWNER - The owner of the GitHub base repo to create the PR to. (ex. "dotnet") + /// GITHUB_PROJECT - The repo name under the ORIGIN and UPSTREAM owners. (ex. "cli") + /// GITHUB_UPSTREAM_BRANCH - The branch in the GitHub base repo to create the PR to. (ex. "rel/1.0.0") + /// + public class Config + { + public static Config Instance { get; } = Read(); + + public string UserName { get; set; } + public string Email { get; set; } + public string Password { get; set; } + public string CoreFxVersionUrl { get; set; } + public string GitHubOriginOwner { get; set; } + public string GitHubUpstreamOwner { get; set; } + public string GitHubProject { get; set; } + public string GitHubUpstreamBranch { get; set; } + + private static Config Read() + { + string userName = GetEnvironmentVariable("GITHUB_USER"); + + return new Config + { + UserName = userName, + Email = GetEnvironmentVariable("GITHUB_EMAIL"), + Password = GetEnvironmentVariable("GITHUB_PASSWORD"), + + CoreFxVersionUrl = GetEnvironmentVariable("COREFX_VERSION_URL", "https://raw.githubusercontent.com/dotnet/versions/master/dotnet/corefx/release/1.0.0-rc2/LKG.txt"), + GitHubOriginOwner = GetEnvironmentVariable("GITHUB_ORIGIN_OWNER", userName), + GitHubUpstreamOwner = GetEnvironmentVariable("GITHUB_UPSTREAM_OWNER", "dotnet"), + GitHubProject = GetEnvironmentVariable("GITHUB_PROJECT", "cli"), + GitHubUpstreamBranch = GetEnvironmentVariable("GITHUB_UPSTREAM_BRANCH", "rel/1.0.0"), + }; + } + + private static string GetEnvironmentVariable(string name, string defaultValue = null) + { + string value = Environment.GetEnvironmentVariable(name); + if (string.IsNullOrEmpty(value)) + { + value = defaultValue; + } + + if (string.IsNullOrEmpty(value)) + { + throw new BuildFailureException($"Can't find environment variable '{name}'."); + } + + return value; + } + } +} diff --git a/scripts/update-dependencies/Dirs.cs b/scripts/update-dependencies/Dirs.cs new file mode 100644 index 000000000..302928f02 --- /dev/null +++ b/scripts/update-dependencies/Dirs.cs @@ -0,0 +1,12 @@ +// 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.IO; + +namespace Microsoft.DotNet.Scripts +{ + public static class Dirs + { + public static readonly string RepoRoot = Directory.GetCurrentDirectory(); + } +} diff --git a/scripts/update-dependencies/Program.cs b/scripts/update-dependencies/Program.cs new file mode 100644 index 000000000..f0fc8f8d1 --- /dev/null +++ b/scripts/update-dependencies/Program.cs @@ -0,0 +1,25 @@ +// 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.Build.Framework; + +namespace Microsoft.DotNet.Scripts +{ + public class Program + { + public static int Main(string[] args) + { + DebugHelper.HandleDebugSwitch(ref args); + + return BuildSetup.Create(".NET CLI Dependency Updater") + .UseTargets(new[] + { + new BuildTarget("Default", "Dependency Updater Goals", new [] { "UpdateFiles", "PushPR" }), + new BuildTarget("UpdateFiles", "Dependency Updater Goals"), + new BuildTarget("PushPR", "Dependency Updater Goals"), + }) + .UseAllTargetsFromAssembly() + .Run(args); + } + } +} diff --git a/scripts/update-dependencies/PushPRTargets.cs b/scripts/update-dependencies/PushPRTargets.cs new file mode 100644 index 000000000..4d9ca44c1 --- /dev/null +++ b/scripts/update-dependencies/PushPRTargets.cs @@ -0,0 +1,108 @@ +// 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 Microsoft.DotNet.Cli.Build.Framework; +using Octokit; + +using static Microsoft.DotNet.Cli.Build.Framework.BuildHelpers; + +namespace Microsoft.DotNet.Scripts +{ + /// + /// Creates a GitHub Pull Request for the current changes in the repo. + /// + public static class PushPRTargets + { + private const string PullRequestTitle = "Updating dependencies from last known good builds"; + + private static readonly Config s_config = Config.Instance; + + [Target(nameof(CommitChanges), nameof(CreatePR))] + public static BuildTargetResult PushPR(BuildTargetContext c) => c.Success(); + + /// + /// Commits all the current changes in the repo and pushes the commit to a remote + /// so a PR can be created for it. + /// + [Target] + public static BuildTargetResult CommitChanges(BuildTargetContext c) + { + Cmd("git", "add", ".") + .Execute() + .EnsureSuccessful(); + + string userName = s_config.UserName; + string email = s_config.Email; + + Cmd("git", "commit", "-m", PullRequestTitle, "--author", $"{userName} <{email}>") + .EnvironmentVariable("GIT_COMMITTER_NAME", userName) + .EnvironmentVariable("GIT_COMMITTER_EMAIL", email) + .Execute() + .EnsureSuccessful(); + + string remoteUrl = $"github.com/{s_config.GitHubOriginOwner}/{s_config.GitHubProject}.git"; + string remoteBranchName = $"UpdateDependencies{DateTime.UtcNow.ToString("yyyyMMddhhmmss")}"; + string refSpec = $"HEAD:refs/heads/{remoteBranchName}"; + + string logMessage = $"git push https://{remoteUrl} {refSpec}"; + BuildReporter.BeginSection("EXEC", logMessage); + + CommandResult pushResult = + Cmd("git", "push", $"https://{userName}:{s_config.Password}@{remoteUrl}", refSpec) + .QuietBuildReporter() // we don't want secrets showing up in our logs + .CaptureStdErr() // git push will write to StdErr upon success, disable that + .CaptureStdOut() + .Execute(); + + var message = logMessage + $" exited with {pushResult.ExitCode}"; + if (pushResult.ExitCode == 0) + { + BuildReporter.EndSection("EXEC", message.Green(), success: true); + } + else + { + BuildReporter.EndSection("EXEC", message.Red().Bold(), success: false); + } + + pushResult.EnsureSuccessful(suppressOutput: true); + + c.SetRemoteBranchName(remoteBranchName); + + return c.Success(); + } + + /// + /// Creates a GitHub PR for the remote branch created above. + /// + [Target] + public static BuildTargetResult CreatePR(BuildTargetContext c) + { + string remoteBranchName = c.GetRemoteBranchName(); + + NewPullRequest prInfo = new NewPullRequest( + PullRequestTitle, + s_config.GitHubOriginOwner + ":" + remoteBranchName, + s_config.GitHubUpstreamBranch); + + GitHubClient gitHub = new GitHubClient(new ProductHeaderValue("dotnetDependencyUpdater")); + + gitHub.Credentials = new Credentials(s_config.Password); + + PullRequest createdPR = gitHub.PullRequest.Create(s_config.GitHubUpstreamOwner, s_config.GitHubProject, prInfo).Result; + c.Info($"Created Pull Request: {createdPR.HtmlUrl}"); + + return c.Success(); + } + + private static string GetRemoteBranchName(this BuildTargetContext c) + { + return (string)c.BuildContext["RemoteBranchName"]; + } + + private static void SetRemoteBranchName(this BuildTargetContext c, string value) + { + c.BuildContext["RemoteBranchName"] = value; + } + } +} diff --git a/scripts/update-dependencies/UpdateFilesTargets.cs b/scripts/update-dependencies/UpdateFilesTargets.cs new file mode 100644 index 000000000..31527ce59 --- /dev/null +++ b/scripts/update-dependencies/UpdateFilesTargets.cs @@ -0,0 +1,233 @@ +// 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.Net.Http; +using System.Text; +using System.Text.RegularExpressions; +using Microsoft.DotNet.Cli.Build.Framework; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NuGet.Versioning; + +namespace Microsoft.DotNet.Scripts +{ + public static class UpdateFilesTargets + { + private static HttpClient s_client = new HttpClient(); + + [Target(nameof(GetDependencies), nameof(ReplaceVersions))] + public static BuildTargetResult UpdateFiles(BuildTargetContext c) => c.Success(); + + /// + /// Gets all the dependency information and puts it in the build properties. + /// + [Target] + public static BuildTargetResult GetDependencies(BuildTargetContext c) + { + string coreFxLkgVersion = s_client.GetStringAsync(Config.Instance.CoreFxVersionUrl).Result; + coreFxLkgVersion = coreFxLkgVersion.Trim(); + + const string coreFxIdPattern = @"^(?i)((System\..*)|(NETStandard\.Library)|(Microsoft\.CSharp)|(Microsoft\.NETCore.*)|(Microsoft\.TargetingPack\.Private\.(CoreCLR|NETNative))|(Microsoft\.Win32\..*)|(Microsoft\.VisualBasic))$"; + const string coreFxIdExclusionPattern = @"System.CommandLine"; + + List dependencyInfos = c.GetDependencyInfo(); + dependencyInfos.Add(new DependencyInfo() + { + Name = "CoreFx", + IdPattern = coreFxIdPattern, + IdExclusionPattern = coreFxIdExclusionPattern, + NewReleaseVersion = coreFxLkgVersion + }); + + return c.Success(); + } + + private static List GetDependencyInfo(this BuildTargetContext c) + { + const string propertyName = "DependencyInfo"; + + List dependencyInfos; + object dependencyInfosObj; + if (c.BuildContext.Properties.TryGetValue(propertyName, out dependencyInfosObj)) + { + dependencyInfos = (List)dependencyInfosObj; + } + else + { + dependencyInfos = new List(); + c.BuildContext[propertyName] = dependencyInfos; + } + + return dependencyInfos; + } + + [Target(nameof(ReplaceProjectJson), nameof(ReplaceCrossGen))] + public static BuildTargetResult ReplaceVersions(BuildTargetContext c) => c.Success(); + + /// + /// Replaces all the dependency versions in the project.json files. + /// + [Target] + public static BuildTargetResult ReplaceProjectJson(BuildTargetContext c) + { + List dependencyInfos = c.GetDependencyInfo(); + + IEnumerable projectJsonFiles = Enumerable.Union( + Directory.GetFiles(Dirs.RepoRoot, "project.json", SearchOption.AllDirectories), + Directory.GetFiles(Path.Combine(Dirs.RepoRoot, @"src\dotnet\commands\dotnet-new"), "project.json.template", SearchOption.AllDirectories)); + + JObject projectRoot; + foreach (string projectJsonFile in projectJsonFiles) + { + try + { + projectRoot = ReadProject(projectJsonFile); + } + catch (Exception e) + { + c.Warn($"Non-fatal exception occurred reading '{projectJsonFile}'. Skipping file. Exception: {e}. "); + continue; + } + + bool changedAnyPackage = FindAllDependencyProperties(projectRoot) + .Select(dependencyProperty => ReplaceDependencyVersion(dependencyProperty, dependencyInfos)) + .ToArray() + .Any(shouldWrite => shouldWrite); + + if (changedAnyPackage) + { + c.Info($"Writing changes to {projectJsonFile}"); + WriteProject(projectRoot, projectJsonFile); + } + } + + return c.Success(); + } + + /// + /// Replaces the single dependency with the updated version, if it matches any of the dependencies that need to be updated. + /// + private static bool ReplaceDependencyVersion(JProperty dependencyProperty, List dependencyInfos) + { + string id = dependencyProperty.Name; + foreach (DependencyInfo dependencyInfo in dependencyInfos) + { + if (Regex.IsMatch(id, dependencyInfo.IdPattern)) + { + if (string.IsNullOrEmpty(dependencyInfo.IdExclusionPattern) || !Regex.IsMatch(id, dependencyInfo.IdExclusionPattern)) + { + string version; + if (dependencyProperty.Value is JObject) + { + version = dependencyProperty.Value["version"].Value(); + } + else if (dependencyProperty.Value is JValue) + { + version = dependencyProperty.Value.ToString(); + } + else + { + throw new Exception($"Invalid package project.json version {dependencyProperty}"); + } + + VersionRange dependencyVersionRange = VersionRange.Parse(version); + NuGetVersion dependencyVersion = dependencyVersionRange.MinVersion; + + string newReleaseVersion = dependencyInfo.NewReleaseVersion; + + if (!string.IsNullOrEmpty(dependencyVersion.Release) && dependencyVersion.Release != newReleaseVersion) + { + string newVersion = new NuGetVersion( + dependencyVersion.Major, + dependencyVersion.Minor, + dependencyVersion.Patch, + newReleaseVersion, + dependencyVersion.Metadata).ToNormalizedString(); + + if (dependencyProperty.Value is JObject) + { + dependencyProperty.Value["version"] = newVersion; + } + else + { + dependencyProperty.Value = newVersion; + } + + return true; + } + } + } + } + + return false; + } + + private static JObject ReadProject(string projectJsonPath) + { + using (TextReader projectFileReader = File.OpenText(projectJsonPath)) + { + var projectJsonReader = new JsonTextReader(projectFileReader); + + var serializer = new JsonSerializer(); + return serializer.Deserialize(projectJsonReader); + } + } + + private static void WriteProject(JObject projectRoot, string projectJsonPath) + { + string projectJson = JsonConvert.SerializeObject(projectRoot, Formatting.Indented); + + File.WriteAllText(projectJsonPath, projectJson + Environment.NewLine); + } + + private static IEnumerable FindAllDependencyProperties(JObject projectJsonRoot) + { + return projectJsonRoot + .Descendants() + .OfType() + .Where(property => property.Name == "dependencies") + .Select(property => property.Value) + .SelectMany(o => o.Children()); + } + + private class DependencyInfo + { + public string Name { get; set; } + public string IdPattern { get; set; } + public string IdExclusionPattern { get; set; } + public string NewReleaseVersion { get; set; } + } + + /// + /// Replaces version number that is hard-coded in the CrossGen script. + /// + [Target] + public static BuildTargetResult ReplaceCrossGen(BuildTargetContext c) + { + DependencyInfo coreFXInfo = c.GetDependencyInfo().Single(d => d.Name == "CoreFx"); + + string compileTargetsPath = Path.Combine(Dirs.RepoRoot, @"scripts\dotnet-cli-build\CompileTargets.cs"); + string compileTargetsContent = File.ReadAllText(compileTargetsPath); + + Regex regex = new Regex(@"CoreCLRVersion = ""(?\d.\d.\d)-(?.*)"";"); + compileTargetsContent = regex.Replace(compileTargetsContent, m => + { + string replacedValue = m.Value; + Group releaseGroup = m.Groups["release"]; + + replacedValue = replacedValue.Remove(releaseGroup.Index - m.Index, releaseGroup.Length); + replacedValue = replacedValue.Insert(releaseGroup.Index - m.Index, coreFXInfo.NewReleaseVersion); + + return replacedValue; + }); + + File.WriteAllText(compileTargetsPath, compileTargetsContent, Encoding.UTF8); + + return c.Success(); + } + } +} diff --git a/scripts/update-dependencies/project.json b/scripts/update-dependencies/project.json new file mode 100644 index 000000000..d5a24cc82 --- /dev/null +++ b/scripts/update-dependencies/project.json @@ -0,0 +1,25 @@ +{ + "version": "1.0.0-*", + "description": "Updates the repos dependencies", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "Microsoft.CSharp": "4.0.1-rc2-23911", + "System.Runtime.Serialization.Primitives": "4.1.1-rc2-23911", + "Microsoft.DotNet.Cli.Build.Framework": "1.0.0-*", + "NuGet.Versioning": "3.4.0-rtm-0764", + "Newtonsoft.Json": "7.0.1", + "Octokit": "0.18.0", + "Microsoft.Net.Http": "2.2.29" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": [ + "dnxcore50", + "portable-net45+win" + ] + } + } +} diff --git a/scripts/update-dependencies/update-dependencies.xproj b/scripts/update-dependencies/update-dependencies.xproj new file mode 100644 index 000000000..07cff1695 --- /dev/null +++ b/scripts/update-dependencies/update-dependencies.xproj @@ -0,0 +1,18 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + a28bd8ac-df15-4f58-8299-98a9ae2b8726 + Microsoft.DotNet.Scripts + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin + + + 2.0 + + + \ No newline at end of file diff --git a/src/Microsoft.DotNet.Cli.Utils/ArgumentEscaper.cs b/src/Microsoft.DotNet.Cli.Utils/ArgumentEscaper.cs index 4a5d0f4fe..c8469a9ac 100644 --- a/src/Microsoft.DotNet.Cli.Utils/ArgumentEscaper.cs +++ b/src/Microsoft.DotNet.Cli.Utils/ArgumentEscaper.cs @@ -54,7 +54,7 @@ namespace Microsoft.DotNet.Cli.Utils foreach (var arg in args) { - escapedArgs.Add(EscapeArg(arg)); + escapedArgs.Add(EscapeSingleArg(arg)); } return escapedArgs; @@ -82,7 +82,7 @@ namespace Microsoft.DotNet.Cli.Utils return escapedArgs; } - private static string EscapeArg(string arg) + public static string EscapeSingleArg(string arg) { var sb = new StringBuilder(); diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/IPackagedCommandSpecFactory.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/IPackagedCommandSpecFactory.cs index 34c14e56c..626dcf9cc 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/IPackagedCommandSpecFactory.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/IPackagedCommandSpecFactory.cs @@ -2,7 +2,9 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.DotNet.ProjectModel; using Microsoft.DotNet.ProjectModel.Graph; +using Microsoft.DotNet.ProjectModel.Compilation; namespace Microsoft.DotNet.Cli.Utils { diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/PackagedCommandSpecFactory.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/PackagedCommandSpecFactory.cs index ee5bd7ecb..8b3ff0ca3 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/PackagedCommandSpecFactory.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/PackagedCommandSpecFactory.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Runtime.InteropServices; using Microsoft.DotNet.ProjectModel; using Microsoft.DotNet.ProjectModel.Graph; +using Microsoft.DotNet.ProjectModel.Compilation; using Microsoft.Extensions.PlatformAbstractions; using NuGet.Frameworks; using NuGet.Packaging; @@ -38,11 +39,15 @@ namespace Microsoft.DotNet.Cli.Utils var commandPath = Path.Combine(packageDirectory, commandFile); + var isPortable = DetermineIfPortableApp(commandPath); + return CreateCommandSpecWrappingWithCorehostfDll( commandPath, commandArguments, depsFilePath, - commandResolutionStrategy); + commandResolutionStrategy, + nugetPackagesRoot, + isPortable); } private string GetPackageDirectoryFullPath(LockFilePackageLibrary library, string nugetPackagesRoot) @@ -69,7 +74,9 @@ namespace Microsoft.DotNet.Cli.Utils string commandPath, IEnumerable commandArguments, string depsFilePath, - CommandResolutionStrategy commandResolutionStrategy) + CommandResolutionStrategy commandResolutionStrategy, + string nugetPackagesRoot, + bool isPortable) { var commandExtension = Path.GetExtension(commandPath); @@ -79,7 +86,9 @@ namespace Microsoft.DotNet.Cli.Utils commandPath, commandArguments, depsFilePath, - commandResolutionStrategy); + commandResolutionStrategy, + nugetPackagesRoot, + isPortable); } return CreateCommandSpec(commandPath, commandArguments, commandResolutionStrategy); @@ -89,21 +98,44 @@ namespace Microsoft.DotNet.Cli.Utils string commandPath, IEnumerable commandArguments, string depsFilePath, - CommandResolutionStrategy commandResolutionStrategy) + CommandResolutionStrategy commandResolutionStrategy, + string nugetPackagesRoot, + bool isPortable) { - var corehost = CoreHost.HostExePath; - + string host = string.Empty; var arguments = new List(); + + if (isPortable) + { + var muxer = new Muxer(); + + host = muxer.MuxerPath; + if (host == null) + { + throw new Exception("Unable to locate dotnet multiplexer"); + } + + arguments.Add("exec"); + } + else + { + host = CoreHost.LocalHostExePath; + } + arguments.Add(commandPath); if (depsFilePath != null) { - arguments.Add($"--depsfile:{depsFilePath}"); + arguments.Add("--depsfile"); + arguments.Add(depsFilePath); } + arguments.Add("--additionalprobingpath"); + arguments.Add(nugetPackagesRoot); + arguments.AddRange(commandArguments); - return CreateCommandSpec(corehost, arguments, commandResolutionStrategy); + return CreateCommandSpec(host, arguments, commandResolutionStrategy); } private CommandSpec CreateCommandSpec( @@ -114,6 +146,23 @@ namespace Microsoft.DotNet.Cli.Utils var escapedArgs = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(commandArguments); return new CommandSpec(commandPath, escapedArgs, commandResolutionStrategy); - } + } + + private bool DetermineIfPortableApp(string commandPath) + { + var commandDir = Path.GetDirectoryName(commandPath); + + var runtimeConfigPath = Directory.EnumerateFiles(commandDir) + .FirstOrDefault(x => x.EndsWith("runtimeconfig.json")); + + if (runtimeConfigPath == null) + { + return false; + } + + var runtimeConfig = new RuntimeConfig(runtimeConfigPath); + + return runtimeConfig.IsPortable; + } } } diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectDependenciesCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectDependenciesCommandResolver.cs index f5436186c..608beb231 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectDependenciesCommandResolver.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectDependenciesCommandResolver.cs @@ -25,12 +25,12 @@ namespace Microsoft.DotNet.Cli.Utils { if (environment == null) { - throw new ArgumentNullException("environment"); + throw new ArgumentNullException(nameof(environment)); } if (packagedCommandSpecFactory == null) { - throw new ArgumentNullException("packagedCommandSpecFactory"); + throw new ArgumentNullException(nameof(packagedCommandSpecFactory)); } _environment = environment; @@ -75,7 +75,7 @@ namespace Microsoft.DotNet.Cli.Utils return null; } - var depsFilePath = projectContext.GetOutputPaths(configuration, outputPath: outputPath).RuntimeFiles.Deps; + var depsFilePath = projectContext.GetOutputPaths(configuration, outputPath: outputPath).RuntimeFiles.DepsJson; var dependencyLibraries = GetAllDependencyLibraries(projectContext); diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsCommandResolver.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsCommandResolver.cs index 906d660e9..e49c4192e 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsCommandResolver.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ProjectToolsCommandResolver.cs @@ -5,18 +5,28 @@ using System.Linq; using System.Runtime.InteropServices; using Microsoft.DotNet.ProjectModel; using Microsoft.DotNet.ProjectModel.Graph; +using Microsoft.DotNet.ProjectModel.Compilation; +using Microsoft.Extensions.DependencyModel; using Microsoft.Extensions.PlatformAbstractions; using NuGet.Frameworks; using NuGet.Packaging; +using NuGet.ProjectModel; + +using LockFile = Microsoft.DotNet.ProjectModel.Graph.LockFile; +using FileFormatException = Microsoft.DotNet.ProjectModel.FileFormatException; namespace Microsoft.DotNet.Cli.Utils { public class ProjectToolsCommandResolver : ICommandResolver { private static readonly NuGetFramework s_toolPackageFramework = FrameworkConstants.CommonFrameworks.NetStandardApp15; + private static readonly CommandResolutionStrategy s_commandResolutionStrategy = CommandResolutionStrategy.ProjectToolsPackage; + private static readonly string s_currentRuntimeIdentifier = PlatformServices.Default.Runtime.GetLegacyRestoreRuntimeIdentifier(); + + private List _allowedCommandExtensions; private IPackagedCommandSpecFactory _packagedCommandSpecFactory; @@ -90,23 +100,21 @@ namespace Microsoft.DotNet.Cli.Utils IEnumerable args, ProjectContext projectContext) { - //todo: change this for new resolution strategy - var lockFilePath = Path.Combine( - projectContext.ProjectDirectory, - "artifacts", "Tools", toolLibrary.Name, - "project.lock.json"); - - if (!File.Exists(lockFilePath)) - { - return null; - } - - var lockFile = LockFileReader.Read(lockFilePath); - - var lockFilePackageLibrary = lockFile.PackageLibraries.FirstOrDefault(l => l.Name == toolLibrary.Name); - var nugetPackagesRoot = projectContext.PackagesDirectory; + var lockFile = GetToolLockFile(toolLibrary, nugetPackagesRoot); + var lockFilePackageLibrary = lockFile.PackageLibraries.FirstOrDefault(l => l.Name == toolLibrary.Name); + + var depsFileRoot = Path.GetDirectoryName(lockFile.LockFilePath); + var depsFilePath = GetToolDepsFilePath(toolLibrary, lockFile, depsFileRoot); + + var toolProjectContext = new ProjectContextBuilder() + .WithLockFile(lockFile) + .WithTargetFramework(s_toolPackageFramework.ToString()) + .Build(); + + var exporter = toolProjectContext.CreateExporter(Constants.DefaultConfiguration); + return _packagedCommandSpecFactory.CreateCommandSpecFromLibrary( lockFilePackageLibrary, commandName, @@ -114,7 +122,44 @@ namespace Microsoft.DotNet.Cli.Utils _allowedCommandExtensions, projectContext.PackagesDirectory, s_commandResolutionStrategy, - null); + depsFilePath); + } + + private LockFile GetToolLockFile( + LibraryRange toolLibrary, + string nugetPackagesRoot) + { + var lockFilePath = GetToolLockFilePath(toolLibrary, nugetPackagesRoot); + + if (!File.Exists(lockFilePath)) + { + return null; + } + + LockFile lockFile = null; + + try + { + lockFile = LockFileReader.Read(lockFilePath); + } + catch (FileFormatException ex) + { + throw ex; + } + + return lockFile; + } + + private string GetToolLockFilePath( + LibraryRange toolLibrary, + string nugetPackagesRoot) + { + var toolPathCalculator = new ToolPathCalculator(nugetPackagesRoot); + + return toolPathCalculator.GetBestLockFilePath( + toolLibrary.Name, + toolLibrary.VersionRange, + s_toolPackageFramework); } private ProjectContext GetProjectContextFromDirectory(string directory, NuGetFramework framework) @@ -143,5 +188,50 @@ namespace Microsoft.DotNet.Cli.Utils return projectContext; } + + private string GetToolDepsFilePath( + LibraryRange toolLibrary, + LockFile toolLockFile, + string depsPathRoot) + { + var depsJsonPath = Path.Combine( + depsPathRoot, + toolLibrary.Name + FileNameSuffixes.DepsJson); + + EnsureToolJsonDepsFileExists(toolLibrary, toolLockFile, depsJsonPath); + + return depsJsonPath; + } + + private void EnsureToolJsonDepsFileExists( + LibraryRange toolLibrary, + LockFile toolLockFile, + string depsPath) + { + if (!File.Exists(depsPath)) + { + var projectContext = new ProjectContextBuilder() + .WithLockFile(toolLockFile) + .WithTargetFramework(s_toolPackageFramework.ToString()) + .Build(); + + var exporter = projectContext.CreateExporter(Constants.DefaultConfiguration); + + var dependencyContext = new DependencyContextBuilder() + .Build(null, + null, + exporter.GetAllExports(), + true, + s_toolPackageFramework, + string.Empty); + + using (var fileStream = File.Create(depsPath)) + { + var dependencyContextWriter = new DependencyContextWriter(); + + dependencyContextWriter.Write(dependencyContext, fileStream); + } + } + } } } diff --git a/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ToolPathCalculator.cs b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ToolPathCalculator.cs new file mode 100644 index 000000000..5dd24c0c5 --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/CommandResolution/ToolPathCalculator.cs @@ -0,0 +1,70 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.IO; +using System.Collections.Generic; +using NuGet.Frameworks; +using NuGet.Versioning; + +namespace Microsoft.DotNet.Cli.Utils +{ + public class ToolPathCalculator + { + private readonly string _packagesDirectory; + + public ToolPathCalculator(string packagesDirectory) + { + _packagesDirectory = packagesDirectory; + } + + public string GetBestLockFilePath(string packageId, VersionRange versionRange, NuGetFramework framework) + { + var availableToolVersions = GetAvailableToolVersions(packageId); + + var bestVersion = versionRange.FindBestMatch(availableToolVersions); + + return GetLockFilePath(packageId, bestVersion, framework); + } + + public string GetLockFilePath(string packageId, NuGetVersion version, NuGetFramework framework) + { + return Path.Combine( + GetBaseToolPath(packageId), + version.ToNormalizedString(), + framework.GetShortFolderName(), + "project.lock.json"); + } + + private string GetBaseToolPath(string packageId) + { + return Path.Combine( + _packagesDirectory, + ".tools", + packageId); + } + + private IEnumerable GetAvailableToolVersions(string packageId) + { + var availableVersions = new List(); + + var toolBase = GetBaseToolPath(packageId); + var versionDirectories = Directory.EnumerateDirectories(toolBase); + + foreach (var versionDirectory in versionDirectories) + { + var version = Path.GetFileName(versionDirectory); + + NuGetVersion nugetVersion = null; + NuGetVersion.TryParse(version, out nugetVersion); + + if (nugetVersion != null) + { + availableVersions.Add(nugetVersion); + } + } + + return availableVersions; + } + + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.Cli.Utils/Constants.cs b/src/Microsoft.DotNet.Cli.Utils/Constants.cs index 9bbb22325..13a349b13 100644 --- a/src/Microsoft.DotNet.Cli.Utils/Constants.cs +++ b/src/Microsoft.DotNet.Cli.Utils/Constants.cs @@ -37,7 +37,8 @@ namespace Microsoft.DotNet.Cli.Utils public static readonly string HostExecutableName = "corehost" + ExeSuffix; public static readonly string[] HostBinaryNames = new string[] { HostExecutableName, - (CurrentPlatform == Platform.Windows ? "hostpolicy" : "libhostpolicy") + DynamicLibSuffix + (CurrentPlatform == Platform.Windows ? "hostpolicy" : "libhostpolicy") + DynamicLibSuffix, + (CurrentPlatform == Platform.Windows ? "hostfxr" : "libhostfxr") + DynamicLibSuffix }; } diff --git a/src/Microsoft.DotNet.Cli.Utils/CoreHost.cs b/src/Microsoft.DotNet.Cli.Utils/CoreHost.cs index 2dae756b0..5808c5140 100644 --- a/src/Microsoft.DotNet.Cli.Utils/CoreHost.cs +++ b/src/Microsoft.DotNet.Cli.Utils/CoreHost.cs @@ -49,6 +49,9 @@ namespace Microsoft.DotNet.Cli.Utils var outputBinaryPath = Path.Combine(destinationPath, outputBinaryName); var hostBinaryPath = Path.Combine(HostDir, binaryName); File.Copy(hostBinaryPath, outputBinaryPath, overwrite: true); + + // Update the last write time so this file can be treated as an output of a build + File.SetLastWriteTimeUtc(outputBinaryPath, DateTime.UtcNow); } } } diff --git a/src/Microsoft.DotNet.Cli.Utils/Muxer.cs b/src/Microsoft.DotNet.Cli.Utils/Muxer.cs new file mode 100644 index 000000000..c279f3e5a --- /dev/null +++ b/src/Microsoft.DotNet.Cli.Utils/Muxer.cs @@ -0,0 +1,65 @@ +using System; +using System.IO; +using Microsoft.Extensions.PlatformAbstractions; + +namespace Microsoft.DotNet.Cli.Utils +{ + public class Muxer + { + private static readonly string s_muxerName = "dotnet"; + private static readonly string s_muxerFileName = s_muxerName + Constants.ExeSuffix; + + private string _muxerPath; + + public string MuxerPath + { + get + { + return _muxerPath; + } + } + + public Muxer() + { + if (!TryResolveMuxerFromParentDirectories()) + { + TryResolverMuxerFromPath(); + } + } + + private bool TryResolveMuxerFromParentDirectories() + { + var appBase = new DirectoryInfo(PlatformServices.Default.Application.ApplicationBasePath); + var muxerDir = appBase.Parent?.Parent; + + if (muxerDir == null) + { + return false; + } + + var muxerCandidate = Path.Combine(muxerDir.FullName, s_muxerFileName); + + if (!File.Exists(muxerCandidate)) + { + return false; + } + + _muxerPath = muxerCandidate; + return true; + } + + private bool TryResolverMuxerFromPath() + { + var muxerPath = Env.GetCommandPath(s_muxerName, Constants.ExeSuffix); + + if (muxerPath == null || !File.Exists(muxerPath)) + { + return false; + } + + _muxerPath = muxerPath; + + return true; + } + } +} diff --git a/src/Microsoft.DotNet.Cli.Utils/project.json b/src/Microsoft.DotNet.Cli.Utils/project.json index 625b48bf4..e6cdae2f9 100644 --- a/src/Microsoft.DotNet.Cli.Utils/project.json +++ b/src/Microsoft.DotNet.Cli.Utils/project.json @@ -1,27 +1,31 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "keyFile": "../../tools/Key.snk", - "warningsAsErrors": true - }, - "dependencies": { - "Microsoft.DotNet.ProjectModel": "1.0.0-*", - "Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-16537" - }, - "frameworks": { - "net451": { - "frameworkAssemblies": { - "System.Runtime": { - "type": "build" - } - } - }, - "netstandard1.3": { - "imports": "dnxcore50", - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "System.Diagnostics.Process": "4.1.0-rc2-23901" - } + "version": "1.0.0-*", + "compilationOptions": { + "keyFile": "../../tools/Key.snk", + "warningsAsErrors": true + }, + "dependencies": { + "Microsoft.DotNet.ProjectModel": "1.0.0-*", + "Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-16537", + "NuGet.Versioning": "3.4.0-rtm-0763", + "NuGet.Packaging": "3.4.0-rtm-0763", + "NuGet.Frameworks": "3.4.0-rtm-0763", + "NuGet.ProjectModel": "3.4.0-rtm-0763" + }, + "frameworks": { + "net451": { + "frameworkAssemblies": { + "System.Runtime": { + "type": "build" } + } + }, + "netstandard1.3": { + "imports": "dnxcore50", + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "System.Diagnostics.Process": "4.1.0-rc2-23911" + } } + } } diff --git a/src/Microsoft.DotNet.Compiler.Common/Executable.cs b/src/Microsoft.DotNet.Compiler.Common/Executable.cs index 03c89273c..3fa63b148 100644 --- a/src/Microsoft.DotNet.Compiler.Common/Executable.cs +++ b/src/Microsoft.DotNet.Compiler.Common/Executable.cs @@ -14,11 +14,16 @@ using Microsoft.DotNet.ProjectModel.Compilation; using Microsoft.DotNet.ProjectModel.Graph; using Microsoft.Extensions.DependencyModel; using NuGet.Frameworks; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; namespace Microsoft.Dotnet.Cli.Compiler.Common { public class Executable { + // GROOOOOSS + private static readonly string RedistPackageName = "Microsoft.NETCore.App"; + private readonly ProjectContext _context; private readonly LibraryExporter _exporter; @@ -71,7 +76,8 @@ namespace Microsoft.Dotnet.Cli.Compiler.Common { WriteDepsFileAndCopyProjectDependencies(_exporter); - if (!string.IsNullOrEmpty(_context.RuntimeIdentifier)) + var emitEntryPoint = _context.ProjectFile.GetCompilerOptions(_context.TargetFramework, _configuration).EmitEntryPoint ?? false; + if (emitEntryPoint && !string.IsNullOrEmpty(_context.RuntimeIdentifier)) { // TODO: Pick a host based on the RID CoreHost.CopyTo(_runtimeOutputPath, _context.ProjectFile.Name + Constants.ExeSuffix); @@ -106,6 +112,7 @@ namespace Microsoft.Dotnet.Cli.Compiler.Common private void WriteDepsFileAndCopyProjectDependencies(LibraryExporter exporter) { WriteDeps(exporter); + WriteRuntimeConfig(exporter); var projectExports = exporter.GetDependencies(LibraryType.Project); CopyAssemblies(projectExports); @@ -115,6 +122,37 @@ namespace Microsoft.Dotnet.Cli.Compiler.Common CopyAssets(packageExports); } + private void WriteRuntimeConfig(LibraryExporter exporter) + { + if (!_context.TargetFramework.IsDesktop()) + { + // TODO: Suppress this file if there's nothing to write? RuntimeOutputFiles would have to be updated + // in order to prevent breaking incremental compilation... + + var json = new JObject(); + var runtimeOptions = new JObject(); + json.Add("runtimeOptions", runtimeOptions); + + var redistExport = exporter + .GetAllExports() + .FirstOrDefault(l => l.Library.Identity.Name.Equals(RedistPackageName, StringComparison.OrdinalIgnoreCase)); + if (redistExport != null) + { + var framework = new JObject( + new JProperty("name", redistExport.Library.Identity.Name), + new JProperty("version", redistExport.Library.Identity.Version.ToNormalizedString())); + runtimeOptions.Add("framework", framework); + } + + var runtimeConfigJsonFile = Path.Combine(_runtimeOutputPath, _context.ProjectFile.Name + FileNameSuffixes.RuntimeConfigJson); + using (var writer = new JsonTextWriter(new StreamWriter(File.Create(runtimeConfigJsonFile)))) + { + writer.Formatting = Formatting.Indented; + json.WriteTo(writer); + } + } + } + public void WriteDeps(LibraryExporter exporter) { var path = Path.Combine(_runtimeOutputPath, _context.ProjectFile.Name + FileNameSuffixes.Deps); @@ -128,7 +166,7 @@ namespace Microsoft.Dotnet.Cli.Compiler.Common var exports = exporter.GetAllExports().ToArray(); var dependencyContext = new DependencyContextBuilder().Build( - compilerOptions: includeCompile? compilerOptions: null, + compilerOptions: includeCompile ? compilerOptions : null, compilationExports: includeCompile ? exports : null, runtimeExports: exports, portable: string.IsNullOrEmpty(_context.RuntimeIdentifier), @@ -141,10 +179,8 @@ namespace Microsoft.Dotnet.Cli.Compiler.Common { writer.Write(dependencyContext, fileStream); } - } - public void GenerateBindingRedirects(LibraryExporter exporter) { var outputName = _outputPaths.RuntimeFiles.Assembly; diff --git a/src/Microsoft.DotNet.Compiler.Common/project.json b/src/Microsoft.DotNet.Compiler.Common/project.json index 0e15d8637..f0376403a 100644 --- a/src/Microsoft.DotNet.Compiler.Common/project.json +++ b/src/Microsoft.DotNet.Compiler.Common/project.json @@ -1,23 +1,23 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "keyFile": "../../tools/Key.snk" - }, - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "System.CommandLine": "0.1.0-e160119-1", - "Microsoft.CodeAnalysis.CSharp": "1.2.0-beta1-20160202-02", - "Microsoft.DotNet.ProjectModel": "1.0.0-*", - "Microsoft.DotNet.Cli.Utils": "1.0.0-*", - "Microsoft.DotNet.Files": "1.0.0-*" - }, - "frameworks": { - "netstandard1.3": { - "imports": [ - "dnxcore50", - "portable-net45+win8" - ] - } - }, - "scripts": {} -} \ No newline at end of file + "version": "1.0.0-*", + "compilationOptions": { + "keyFile": "../../tools/Key.snk" + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "System.CommandLine": "0.1.0-e160119-1", + "Microsoft.CodeAnalysis.CSharp": "1.2.0-beta1-20160202-02", + "Microsoft.DotNet.ProjectModel": "1.0.0-*", + "Microsoft.DotNet.Cli.Utils": "1.0.0-*", + "Microsoft.DotNet.Files": "1.0.0-*" + }, + "frameworks": { + "netstandard1.3": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ] + } + }, + "scripts": {} +} diff --git a/src/Microsoft.DotNet.Files/project.json b/src/Microsoft.DotNet.Files/project.json index 82f1a2b51..1131165e9 100644 --- a/src/Microsoft.DotNet.Files/project.json +++ b/src/Microsoft.DotNet.Files/project.json @@ -1,20 +1,20 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "keyFile": "../../tools/Key.snk" - }, - "description": "Abstraction to interact with the file system and file paths.", - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "System.Linq.Expressions": "4.0.11-rc2-23901", - "Microsoft.Extensions.FileSystemGlobbing": "1.0.0-rc2-15996", - "Microsoft.DotNet.Cli.Utils": "1.0.0-*", - "Microsoft.DotNet.ProjectModel": "1.0.0-*" - }, - "frameworks": { - "netstandard1.3": { - "imports": "dnxcore50" - } - }, - "scripts": {} -} \ No newline at end of file + "version": "1.0.0-*", + "compilationOptions": { + "keyFile": "../../tools/Key.snk" + }, + "description": "Abstraction to interact with the file system and file paths.", + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "System.Linq.Expressions": "4.0.11-rc2-23911", + "Microsoft.Extensions.FileSystemGlobbing": "1.0.0-rc2-15996", + "Microsoft.DotNet.Cli.Utils": "1.0.0-*", + "Microsoft.DotNet.ProjectModel": "1.0.0-*" + }, + "frameworks": { + "netstandard1.3": { + "imports": "dnxcore50" + } + }, + "scripts": {} +} diff --git a/src/Microsoft.DotNet.InternalAbstractions/FileWrapper.cs b/src/Microsoft.DotNet.InternalAbstractions/FileWrapper.cs index 8b6efbeac..867d4eab1 100644 --- a/src/Microsoft.DotNet.InternalAbstractions/FileWrapper.cs +++ b/src/Microsoft.DotNet.InternalAbstractions/FileWrapper.cs @@ -16,5 +16,10 @@ namespace Microsoft.Extensions.EnvironmentAbstractions { return File.ReadAllText(path); } + + public Stream OpenRead(string path) + { + return File.OpenRead(path); + } } } \ No newline at end of file diff --git a/src/Microsoft.DotNet.InternalAbstractions/IFile.cs b/src/Microsoft.DotNet.InternalAbstractions/IFile.cs index 26d029eec..5b0328de9 100644 --- a/src/Microsoft.DotNet.InternalAbstractions/IFile.cs +++ b/src/Microsoft.DotNet.InternalAbstractions/IFile.cs @@ -1,6 +1,8 @@ // 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.IO; + namespace Microsoft.Extensions.EnvironmentAbstractions { internal interface IFile @@ -8,5 +10,7 @@ namespace Microsoft.Extensions.EnvironmentAbstractions bool Exists(string path); string ReadAllText(string path); + + Stream OpenRead(string path); } } \ No newline at end of file diff --git a/src/Microsoft.DotNet.InternalAbstractions/project.json b/src/Microsoft.DotNet.InternalAbstractions/project.json index 70b5e7221..3daa0c061 100644 --- a/src/Microsoft.DotNet.InternalAbstractions/project.json +++ b/src/Microsoft.DotNet.InternalAbstractions/project.json @@ -13,14 +13,13 @@ "Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-16537" }, "frameworks": { - "net451": { }, + "net451": {}, "netstandard1.3": { "imports": "dnxcore50", "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" + "NETStandard.Library": "1.5.0-rc2-23911" } } }, - "scripts": { - } + "scripts": {} } diff --git a/src/Microsoft.DotNet.ProjectModel.Loader/project.json b/src/Microsoft.DotNet.ProjectModel.Loader/project.json index efe082435..9d2eca854 100644 --- a/src/Microsoft.DotNet.ProjectModel.Loader/project.json +++ b/src/Microsoft.DotNet.ProjectModel.Loader/project.json @@ -1,16 +1,16 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "keyFile": "../../tools/Key.snk" - }, - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "Microsoft.DotNet.ProjectModel": "1.0.0-*", - "System.Runtime.Loader": "4.0.0-rc2-23901" - }, - "frameworks": { - "netstandard1.3": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "compilationOptions": { + "keyFile": "../../tools/Key.snk" + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "Microsoft.DotNet.ProjectModel": "1.0.0-*", + "System.Runtime.Loader": "4.0.0-rc2-23911" + }, + "frameworks": { + "netstandard1.3": { + "imports": "dnxcore50" } -} \ No newline at end of file + } +} diff --git a/src/Microsoft.DotNet.ProjectModel.Workspaces/project.json b/src/Microsoft.DotNet.ProjectModel.Workspaces/project.json index 83f7a2d6e..e14bbe01b 100644 --- a/src/Microsoft.DotNet.ProjectModel.Workspaces/project.json +++ b/src/Microsoft.DotNet.ProjectModel.Workspaces/project.json @@ -1,20 +1,20 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "keyFile": "../../tools/Key.snk" - }, - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "Microsoft.DotNet.ProjectModel": "1.0.0-*", - "Microsoft.DotNet.Compiler.Common": "1.0.0-*", - "Microsoft.CodeAnalysis.CSharp.Workspaces": "1.2.0-beta1-20160202-02" - }, - "frameworks": { - "netstandard1.3": { - "imports": [ - "dnxcore50", - "portable-net45+win8" - ] - } + "version": "1.0.0-*", + "compilationOptions": { + "keyFile": "../../tools/Key.snk" + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "Microsoft.DotNet.ProjectModel": "1.0.0-*", + "Microsoft.DotNet.Compiler.Common": "1.0.0-*", + "Microsoft.CodeAnalysis.CSharp.Workspaces": "1.2.0-beta1-20160202-02" + }, + "frameworks": { + "netstandard1.3": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ] } -} \ No newline at end of file + } +} diff --git a/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryAsset.cs b/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryAsset.cs index 6186f26e5..b4565dd19 100644 --- a/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryAsset.cs +++ b/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryAsset.cs @@ -15,6 +15,7 @@ namespace Microsoft.DotNet.ProjectModel.Compilation public string Name { get; } public string RelativePath { get; } public string ResolvedPath { get; } + public string FileName => Path.GetFileName(RelativePath); public Action Transform { get; set; } public LibraryAsset(string name, string relativePath, string resolvedPath, Action transform = null) diff --git a/src/Microsoft.DotNet.ProjectModel/DependencyContextBuilder.cs b/src/Microsoft.DotNet.ProjectModel/DependencyContextBuilder.cs index 8b088e88c..d36ac6f82 100644 --- a/src/Microsoft.DotNet.ProjectModel/DependencyContextBuilder.cs +++ b/src/Microsoft.DotNet.ProjectModel/DependencyContextBuilder.cs @@ -55,7 +55,7 @@ namespace Microsoft.Extensions.DependencyModel compilationOptions, GetLibraries(compilationExports, dependencyLookup, runtime: false).Cast(), GetLibraries(runtimeExports, dependencyLookup, runtime: true).Cast(), - new KeyValuePair[0]); + new RuntimeFallbacks[] {}); } private static CompilationOptions GetCompilationOptions(CommonCompilerOptions compilerOptions) @@ -129,6 +129,7 @@ namespace Microsoft.Extensions.DependencyModel export.Library.Identity.Version.ToString(), export.Library.Hash, assemblies.Select(RuntimeAssembly.Create), + export.NativeLibraries.Select(l => l.RelativePath), export.ResourceAssemblies.Select(CreateResourceAssembly), export.RuntimeTargets.Select(CreateRuntimeTarget), libraryDependencies, diff --git a/src/Microsoft.DotNet.ProjectModel/FileNameSuffixes.cs b/src/Microsoft.DotNet.ProjectModel/FileNameSuffixes.cs index 12bedfdb4..781e3cbed 100644 --- a/src/Microsoft.DotNet.ProjectModel/FileNameSuffixes.cs +++ b/src/Microsoft.DotNet.ProjectModel/FileNameSuffixes.cs @@ -7,6 +7,7 @@ namespace Microsoft.DotNet.ProjectModel { public const string Deps = ".deps"; public const string DepsJson = ".deps.json"; + public const string RuntimeConfigJson = ".runtimeconfig.json"; public static PlatformFileNameSuffixes CurrentPlatform { diff --git a/src/Microsoft.DotNet.ProjectModel/OutputPathsCalculator.cs b/src/Microsoft.DotNet.ProjectModel/OutputPathsCalculator.cs index fdaa7c5e3..f2a25bb45 100644 --- a/src/Microsoft.DotNet.ProjectModel/OutputPathsCalculator.cs +++ b/src/Microsoft.DotNet.ProjectModel/OutputPathsCalculator.cs @@ -70,11 +70,7 @@ namespace Microsoft.DotNet.ProjectModel var compilationFiles = new CompilationOutputFiles(compilationOutputPath, project, configuration, framework); - RuntimeOutputFiles runtimeFiles = null; - if (runtimeOutputPath != null) - { - runtimeFiles = new RuntimeOutputFiles(runtimeOutputPath, project, configuration, framework); - } + RuntimeOutputFiles runtimeFiles = new RuntimeOutputFiles(runtimeOutputPath, project, configuration, framework, runtimeIdentifier); return new OutputPaths(intermediateOutputPath, compilationOutputPath, runtimeOutputPath, compilationFiles, runtimeFiles); } } diff --git a/src/Microsoft.DotNet.ProjectModel/ProjectContext.cs b/src/Microsoft.DotNet.ProjectModel/ProjectContext.cs index 99f869f36..b920e857c 100644 --- a/src/Microsoft.DotNet.ProjectModel/ProjectContext.cs +++ b/src/Microsoft.DotNet.ProjectModel/ProjectContext.cs @@ -22,7 +22,7 @@ namespace Microsoft.DotNet.ProjectModel public string RuntimeIdentifier { get; } - public Project ProjectFile => RootProject.Project; + public Project ProjectFile => RootProject?.Project; public LockFile LockFile { get; } @@ -140,6 +140,12 @@ namespace Microsoft.DotNet.ProjectModel public ProjectContext CreateRuntimeContext(IEnumerable runtimeIdentifiers) { + // Temporary until we have removed RID inference from NuGet + if(TargetFramework.IsCompileOnly) + { + return this; + } + // Check if there are any runtime targets (i.e. are we portable) var standalone = LockFile.Targets .Where(t => t.TargetFramework.Equals(TargetFramework)) diff --git a/src/Microsoft.DotNet.ProjectModel/ProjectContextBuilder.cs b/src/Microsoft.DotNet.ProjectModel/ProjectContextBuilder.cs index 8d617df63..6e2d6b58f 100644 --- a/src/Microsoft.DotNet.ProjectModel/ProjectContextBuilder.cs +++ b/src/Microsoft.DotNet.ProjectModel/ProjectContextBuilder.cs @@ -140,7 +140,7 @@ namespace Microsoft.DotNet.ProjectModel { ProjectDirectory = Project?.ProjectDirectory ?? ProjectDirectory; - if (GlobalSettings == null) + if (GlobalSettings == null && ProjectDirectory != null) { RootDirectory = ProjectRootResolver.ResolveRootDirectory(ProjectDirectory); @@ -157,7 +157,6 @@ namespace Microsoft.DotNet.ProjectModel var frameworkReferenceResolver = new FrameworkReferenceResolver(ReferenceAssembliesPath); LockFileLookup lockFileLookup = null; - EnsureProjectLoaded(); LockFile = LockFile ?? LockFileResolver(ProjectDirectory); @@ -167,7 +166,10 @@ namespace Microsoft.DotNet.ProjectModel if (LockFile != null) { - validLockFile = LockFile.IsValidForProject(Project, out lockFileValidationMessage); + if (Project != null) + { + validLockFile = LockFile.IsValidForProject(Project, out lockFileValidationMessage); + } lockFileLookup = new LockFileLookup(LockFile); } @@ -175,10 +177,14 @@ namespace Microsoft.DotNet.ProjectModel var libraries = new Dictionary(); var projectResolver = new ProjectDependencyProvider(ProjectResolver); - var mainProject = projectResolver.GetDescription(TargetFramework, Project, targetLibrary: null); + ProjectDescription mainProject = null; + if (Project != null) + { + mainProject = projectResolver.GetDescription(TargetFramework, Project, targetLibrary: null); - // Add the main project - libraries.Add(new LibraryKey(mainProject.Identity.Name), mainProject); + // Add the main project + libraries.Add(new LibraryKey(mainProject.Identity.Name), mainProject); + } LockFileTarget target = null; if (lockFileLookup != null) @@ -251,7 +257,7 @@ namespace Microsoft.DotNet.ProjectModel } // Create a library manager - var libraryManager = new LibraryManager(libraries.Values.ToList(), diagnostics, Project.ProjectFilePath); + var libraryManager = new LibraryManager(libraries.Values.ToList(), diagnostics, Project?.ProjectFilePath); return new ProjectContext( GlobalSettings, @@ -375,13 +381,9 @@ namespace Microsoft.DotNet.ProjectModel private void EnsureProjectLoaded() { - if (Project == null) + if (Project == null && ProjectDirectory != null) { Project = ProjectResolver(ProjectDirectory); - if (Project == null) - { - throw new InvalidOperationException($"Unable to resolve project from {ProjectDirectory}"); - } } } diff --git a/src/Microsoft.DotNet.ProjectModel/Resolution/FrameworkReferenceResolver.cs b/src/Microsoft.DotNet.ProjectModel/Resolution/FrameworkReferenceResolver.cs index 17458d0eb..e75860ed1 100644 --- a/src/Microsoft.DotNet.ProjectModel/Resolution/FrameworkReferenceResolver.cs +++ b/src/Microsoft.DotNet.ProjectModel/Resolution/FrameworkReferenceResolver.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Runtime.Versioning; using System.Xml.Linq; using Microsoft.DotNet.ProjectModel.Utilities; +using Microsoft.Extensions.DependencyModel.Resolution; using Microsoft.Extensions.PlatformAbstractions; using NuGet.Frameworks; @@ -53,7 +54,7 @@ namespace Microsoft.DotNet.ProjectModel.Resolution public static string GetDefaultReferenceAssembliesPath() { // Allow setting the reference assemblies path via an environment variable - var referenceAssembliesPath = Environment.GetEnvironmentVariable("DOTNET_REFERENCE_ASSEMBLIES_PATH"); + var referenceAssembliesPath = DotNetReferenceAssembliesPathResolver.Resolve(); if (!string.IsNullOrEmpty(referenceAssembliesPath)) { @@ -207,7 +208,9 @@ namespace Microsoft.DotNet.ProjectModel.Resolution private static FrameworkInformation GetFrameworkInformation(NuGetFramework targetFramework, string referenceAssembliesPath) { // Check for legacy frameworks - if (targetFramework.IsDesktop() && targetFramework.Version <= new Version(3, 5)) + if (PlatformServices.Default.Runtime.OperatingSystemPlatform == Platform.Windows && + targetFramework.IsDesktop() && + targetFramework.Version <= new Version(3, 5)) { return GetLegacyFrameworkInformation(targetFramework, referenceAssembliesPath); } diff --git a/src/Microsoft.DotNet.ProjectModel/RuntimeConfig/RuntimeConfig.cs b/src/Microsoft.DotNet.ProjectModel/RuntimeConfig/RuntimeConfig.cs new file mode 100644 index 000000000..0d2d7431d --- /dev/null +++ b/src/Microsoft.DotNet.ProjectModel/RuntimeConfig/RuntimeConfig.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 Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.IO; + +namespace Microsoft.DotNet.ProjectModel +{ + public class RuntimeConfig + { + public bool IsPortable { get; } + public RuntimeConfigFramework Framework { get; } + + public RuntimeConfig(string runtimeConfigPath) + { + var runtimeConfigJson = OpenRuntimeConfig(runtimeConfigPath); + + Framework = ParseFramework(runtimeConfigJson); + + IsPortable = Framework != null; + } + + private JObject OpenRuntimeConfig(string runtimeConfigPath) + { + return JObject.Parse(File.ReadAllText(runtimeConfigPath)); + } + + private RuntimeConfigFramework ParseFramework(JObject runtimeConfigRoot) + { + var runtimeOptionsRoot = runtimeConfigRoot["runtimeOptions"]; + if (runtimeOptionsRoot == null) + { + return null; + } + + var framework = (JObject) runtimeOptionsRoot["framework"]; + if (framework == null) + { + return null; + } + + return RuntimeConfigFramework.ParseFromFrameworkRoot(framework); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectModel/RuntimeConfig/RuntimeConfigFramework.cs b/src/Microsoft.DotNet.ProjectModel/RuntimeConfig/RuntimeConfigFramework.cs new file mode 100644 index 000000000..d6a0c6f63 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectModel/RuntimeConfig/RuntimeConfigFramework.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; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.DotNet.ProjectModel +{ + public class RuntimeConfigFramework + { + public string Name { get; set; } + public string Version { get; set; } + + public static RuntimeConfigFramework ParseFromFrameworkRoot(JObject framework) + { + var properties = framework.Properties(); + + var name = properties.FirstOrDefault(p => p.Name.Equals("name", StringComparison.OrdinalIgnoreCase)); + var version = properties.FirstOrDefault(p => p.Name.Equals("version", StringComparison.OrdinalIgnoreCase)); + + if (name == null || version == null) + { + return null; + } + + return new RuntimeConfigFramework + { + Name = name.Value.ToString(), + Version = version.Value.ToString() + }; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectModel/RuntimeOutputFiles.cs b/src/Microsoft.DotNet.ProjectModel/RuntimeOutputFiles.cs index 7ade31bc1..6368f173b 100644 --- a/src/Microsoft.DotNet.ProjectModel/RuntimeOutputFiles.cs +++ b/src/Microsoft.DotNet.ProjectModel/RuntimeOutputFiles.cs @@ -9,11 +9,15 @@ namespace Microsoft.DotNet.ProjectModel { public class RuntimeOutputFiles : CompilationOutputFiles { + private readonly string _runtimeIdentifier; + public RuntimeOutputFiles(string basePath, Project project, string configuration, - NuGetFramework framework) : base(basePath, project, configuration, framework) + NuGetFramework framework, + string runtimeIdentifier) : base(basePath, project, configuration, framework) { + _runtimeIdentifier = runtimeIdentifier; } public string Executable @@ -22,12 +26,18 @@ namespace Microsoft.DotNet.ProjectModel { var extension = FileNameSuffixes.CurrentPlatform.Exe; - // This is the check for mono, if we're not on windows and producing outputs for - // the desktop framework then it's an exe if (Framework.IsDesktop()) { + // This is the check for mono, if we're not on windows and producing outputs for + // the desktop framework then it's an exe extension = FileNameSuffixes.DotNet.Exe; } + else if (string.IsNullOrEmpty(_runtimeIdentifier)) + { + // The executable is a DLL in this case + extension = FileNameSuffixes.DotNet.DynamicLib; + } + return Path.Combine(BasePath, Project.Name + extension); } } @@ -39,6 +49,7 @@ namespace Microsoft.DotNet.ProjectModel return Path.ChangeExtension(Assembly, FileNameSuffixes.Deps); } } + public string DepsJson { get @@ -47,6 +58,14 @@ namespace Microsoft.DotNet.ProjectModel } } + public string RuntimeConfigJson + { + get + { + return Path.ChangeExtension(Assembly, FileNameSuffixes.RuntimeConfigJson); + } + } + public string Config { get { return Assembly + ".config"; } @@ -59,20 +78,28 @@ namespace Microsoft.DotNet.ProjectModel yield return file; } + if (Project.HasRuntimeOutput(Config)) + { + if (!Framework.IsDesktop()) + { + yield return Deps; + yield return DepsJson; + yield return RuntimeConfigJson; + } + + // If the project actually has an entry point + var hasEntryPoint = Project.GetCompilerOptions(targetFramework: null, configurationName: Configuration).EmitEntryPoint ?? false; + if (hasEntryPoint) + { + // Yield the executable + yield return Executable; + } + } + if (File.Exists(Config)) { yield return Config; } - - if (File.Exists(Deps)) - { - yield return Deps; - } - - if (File.Exists(DepsJson)) - { - yield return DepsJson; - } } } -} \ No newline at end of file +} diff --git a/src/Microsoft.DotNet.ProjectModel/project.json b/src/Microsoft.DotNet.ProjectModel/project.json index 9479b166a..46cd0f97c 100644 --- a/src/Microsoft.DotNet.ProjectModel/project.json +++ b/src/Microsoft.DotNet.ProjectModel/project.json @@ -1,47 +1,47 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "keyFile": "../../tools/Key.snk" + "version": "1.0.0-*", + "compilationOptions": { + "keyFile": "../../tools/Key.snk" + }, + "description": "Types to model a .NET Project", + "dependencies": { + "System.Reflection.Metadata": "1.2.0-rc2-23911", + "NuGet.Packaging": "3.4.0-rtm-0763", + "Microsoft.Extensions.FileSystemGlobbing": "1.0.0-rc2-15996", + "Microsoft.Extensions.JsonParser.Sources": { + "type": "build", + "version": "1.0.0-rc2-16453" }, - "description": "Types to model a .NET Project", - "dependencies": { - "System.Reflection.Metadata": "1.2.0-rc2-23901", - "NuGet.Packaging": "3.4.0-rtm-0733", - "Microsoft.Extensions.FileSystemGlobbing": "1.0.0-rc2-15996", - "Microsoft.Extensions.JsonParser.Sources": { - "type": "build", - "version": "1.0.0-rc2-16453" - }, - "Microsoft.Extensions.HashCodeCombiner.Sources": { - "type": "build", - "version": "1.0.0-rc2-16054" - }, - "Microsoft.Extensions.DependencyModel": "1.0.0-*" + "Microsoft.Extensions.HashCodeCombiner.Sources": { + "type": "build", + "version": "1.0.0-rc2-16054" }, - "frameworks": { - "net451": { - "frameworkAssemblies": { - "System.Runtime": { - "type": "build" - }, - "System.Collections": { - "type": "build" - }, - "System.IO": { - "type": "build" - } - } + "Microsoft.Extensions.DependencyModel": "1.0.0-*" + }, + "frameworks": { + "net451": { + "frameworkAssemblies": { + "System.Runtime": { + "type": "build" }, - "netstandard1.3": { - "imports": "dnxcore50", - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "System.Dynamic.Runtime": "4.0.11-rc2-23901", - "System.Runtime.Loader": "4.0.0-rc2-23901", - "System.Security.Cryptography.Algorithms": "4.0.0-rc2-23901", - "Microsoft.CSharp": "4.0.1-rc2-23901", - "System.Xml.XDocument": "4.0.11-rc2-23901" - } + "System.Collections": { + "type": "build" + }, + "System.IO": { + "type": "build" } + } + }, + "netstandard1.3": { + "imports": "dnxcore50", + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "System.Dynamic.Runtime": "4.0.11-rc2-23911", + "System.Runtime.Loader": "4.0.0-rc2-23911", + "System.Security.Cryptography.Algorithms": "4.0.0-rc2-23911", + "Microsoft.CSharp": "4.0.1-rc2-23911", + "System.Xml.XDocument": "4.0.11-rc2-23911" + } } + } } diff --git a/src/Microsoft.DotNet.TestFramework/Microsoft.DotNet.TestFramework.TestAssetsManager.cs b/src/Microsoft.DotNet.TestFramework/Microsoft.DotNet.TestFramework.TestAssetsManager.cs index 9d4e59313..aa1e6ba56 100644 --- a/src/Microsoft.DotNet.TestFramework/Microsoft.DotNet.TestFramework.TestAssetsManager.cs +++ b/src/Microsoft.DotNet.TestFramework/Microsoft.DotNet.TestFramework.TestAssetsManager.cs @@ -92,7 +92,7 @@ namespace Microsoft.DotNet.TestFramework { throw new Exception($"Cannot find '{testProjectName}' at '{AssetsRoot}'"); } - + string testDestination = Path.Combine(AppContext.BaseDirectory, callingMethod + identifier, testProjectName); var testInstance = new TestInstance(testProjectDir, testDestination); return testInstance; diff --git a/src/Microsoft.DotNet.TestFramework/project.json b/src/Microsoft.DotNet.TestFramework/project.json index e04e63cb5..2e096995e 100644 --- a/src/Microsoft.DotNet.TestFramework/project.json +++ b/src/Microsoft.DotNet.TestFramework/project.json @@ -1,4 +1,4 @@ -{ +{ "version": "1.0.0-*", "description": "Microsoft.DotNet.TestFramework Class Library", "authors": [ @@ -11,7 +11,7 @@ "licenseUrl": "", "dependencies": { "Microsoft.DotNet.Cli.Utils": "1.0.0-*", - "NETStandard.Library": "1.0.0-rc2-23901" + "NETStandard.Library": "1.5.0-rc2-23911" }, "frameworks": { "netstandard1.3": { diff --git a/src/Microsoft.Extensions.DependencyModel/CompilationLibrary.cs b/src/Microsoft.Extensions.DependencyModel/CompilationLibrary.cs index 2a85c2f8e..435068ceb 100644 --- a/src/Microsoft.Extensions.DependencyModel/CompilationLibrary.cs +++ b/src/Microsoft.Extensions.DependencyModel/CompilationLibrary.cs @@ -19,6 +19,10 @@ namespace Microsoft.Extensions.DependencyModel bool serviceable) : base(type, name, version, hash, dependencies, serviceable) { + if (assemblies == null) + { + throw new ArgumentNullException(nameof(assemblies)); + } Assemblies = assemblies.ToArray(); } diff --git a/src/Microsoft.Extensions.DependencyModel/CompilationOptions.cs b/src/Microsoft.Extensions.DependencyModel/CompilationOptions.cs index d887671cc..f37e553cb 100644 --- a/src/Microsoft.Extensions.DependencyModel/CompilationOptions.cs +++ b/src/Microsoft.Extensions.DependencyModel/CompilationOptions.cs @@ -1,6 +1,7 @@ // 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; @@ -8,7 +9,7 @@ namespace Microsoft.Extensions.DependencyModel { public class CompilationOptions { - public IEnumerable Defines { get; } + public IReadOnlyList Defines { get; } public string LanguageVersion { get; } @@ -26,7 +27,7 @@ namespace Microsoft.Extensions.DependencyModel public bool? PublicSign { get; } - public string DebugType { get; } + public string DebugType { get; } public bool? EmitEntryPoint { get; } @@ -59,7 +60,11 @@ namespace Microsoft.Extensions.DependencyModel bool? emitEntryPoint, bool? generateXmlDocumentation) { - Defines = defines; + if (defines == null) + { + throw new ArgumentNullException(nameof(defines)); + } + Defines = defines.ToArray(); LanguageVersion = languageVersion; Platform = platform; AllowUnsafe = allowUnsafe; diff --git a/src/Microsoft.Extensions.DependencyModel/Dependency.cs b/src/Microsoft.Extensions.DependencyModel/Dependency.cs index 8060cb4b1..76a75fa74 100644 --- a/src/Microsoft.Extensions.DependencyModel/Dependency.cs +++ b/src/Microsoft.Extensions.DependencyModel/Dependency.cs @@ -1,6 +1,7 @@ // 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 Microsoft.Extensions.Internal; namespace Microsoft.Extensions.DependencyModel @@ -9,6 +10,14 @@ namespace Microsoft.Extensions.DependencyModel { public Dependency(string name, string version) { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(nameof(name)); + } + if (string.IsNullOrEmpty(version)) + { + throw new ArgumentException(nameof(version)); + } Name = name; Version = version; } diff --git a/src/Microsoft.Extensions.DependencyModel/DependencyContext.cs b/src/Microsoft.Extensions.DependencyModel/DependencyContext.cs index c31ea8dc6..d955b02e1 100644 --- a/src/Microsoft.Extensions.DependencyModel/DependencyContext.cs +++ b/src/Microsoft.Extensions.DependencyModel/DependencyContext.cs @@ -11,9 +11,6 @@ namespace Microsoft.Extensions.DependencyModel { public class DependencyContext { - private const string DepsJsonExtension = ".deps.json"; - private const string DepsFileExtension = ".deps"; - private static readonly Lazy _defaultContext = new Lazy(LoadDefault); public DependencyContext(string targetFramework, @@ -22,15 +19,15 @@ namespace Microsoft.Extensions.DependencyModel CompilationOptions compilationOptions, IEnumerable compileLibraries, IEnumerable runtimeLibraries, - IEnumerable> runtimeGraph) + IEnumerable runtimeGraph) { - if (targetFramework == null) + if (string.IsNullOrEmpty(targetFramework)) { - throw new ArgumentNullException(nameof(targetFramework)); + throw new ArgumentException(nameof(targetFramework)); } - if (runtime == null) + if (compilationOptions == null) { - throw new ArgumentNullException(nameof(runtime)); + throw new ArgumentNullException(nameof(compilationOptions)); } if (compileLibraries == null) { @@ -68,48 +65,47 @@ namespace Microsoft.Extensions.DependencyModel public IReadOnlyList RuntimeLibraries { get; } - public IReadOnlyList> RuntimeGraph { get; } + public IReadOnlyList RuntimeGraph { get; } + + public DependencyContext Merge(DependencyContext other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + return new DependencyContext( + TargetFramework, + Runtime, + IsPortable, + CompilationOptions, + CompileLibraries.Union(other.CompileLibraries, new LibraryMergeEqualityComparer()), + RuntimeLibraries.Union(other.RuntimeLibraries, new LibraryMergeEqualityComparer()), + RuntimeGraph.Union(other.RuntimeGraph) + ); + } private static DependencyContext LoadDefault() { - var entryAssembly = Assembly.GetEntryAssembly(); - return Load(entryAssembly); + return DependencyContextLoader.Default.Load(Assembly.GetEntryAssembly()); } public static DependencyContext Load(Assembly assembly) { - if (assembly == null) + return DependencyContextLoader.Default.Load(assembly); + } + + private class LibraryMergeEqualityComparer: IEqualityComparer where T:Library + { + public bool Equals(T x, T y) { - throw new ArgumentNullException(nameof(assembly)); + return string.Equals(x.Name, y.Name, StringComparison.Ordinal); } - using (var stream = assembly.GetManifestResourceStream(assembly.GetName().Name + DepsJsonExtension)) + public int GetHashCode(T obj) { - if (stream != null) - { - return new DependencyContextJsonReader().Read(stream); - } + return obj.Name.GetHashCode(); } - - var depsJsonFile = Path.ChangeExtension(assembly.Location, DepsJsonExtension); - if (File.Exists(depsJsonFile)) - { - using (var stream = File.OpenRead(depsJsonFile)) - { - return new DependencyContextJsonReader().Read(stream); - } - } - - var depsFile = Path.ChangeExtension(assembly.Location, DepsFileExtension); - if (File.Exists(depsFile)) - { - using (var stream = File.OpenRead(depsFile)) - { - return new DependencyContextCsvReader().Read(stream); - } - } - - return null; } } } diff --git a/src/Microsoft.Extensions.DependencyModel/DependencyContextCsvReader.cs b/src/Microsoft.Extensions.DependencyModel/DependencyContextCsvReader.cs deleted file mode 100644 index 69c4b2bf2..000000000 --- a/src/Microsoft.Extensions.DependencyModel/DependencyContextCsvReader.cs +++ /dev/null @@ -1,134 +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. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; - -namespace Microsoft.Extensions.DependencyModel -{ - public class DependencyContextCsvReader - { - public DependencyContext Read(Stream stream) - { - var lines = new List(); - using (var reader = new StreamReader(stream)) - { - while (!reader.EndOfStream) - { - var line = new DepsFileLine(); - line.LibraryType = ReadValue(reader); - line.PackageName = ReadValue(reader); - line.PackageVersion = ReadValue(reader); - line.PackageHash = ReadValue(reader); - line.AssetType = ReadValue(reader); - line.AssetName = ReadValue(reader); - line.AssetPath = ReadValue(reader); - - if (line.AssetType == "runtime" && - !line.AssetPath.EndsWith(".ni.dll")) - { - lines.Add(line); - } - SkipWhitespace(reader); - } - } - - var runtimeLibraries = new List(); - var packageGroups = lines.GroupBy(PackageIdentity); - foreach (var packageGroup in packageGroups) - { - var identity = packageGroup.Key; - runtimeLibraries.Add(new RuntimeLibrary( - type: identity.Item1, - name: identity.Item2, - version: identity.Item3, - hash: identity.Item4, - assemblies: packageGroup.Select(l => RuntimeAssembly.Create(l.AssetPath)), - resourceAssemblies: Enumerable.Empty(), - subTargets: Enumerable.Empty(), - dependencies: Enumerable.Empty(), - serviceable: false - )); - } - - return new DependencyContext( - targetFramework: string.Empty, - runtime: string.Empty, - isPortable: false, - compilationOptions: CompilationOptions.Default, - compileLibraries: Enumerable.Empty(), - runtimeLibraries: runtimeLibraries.ToArray(), - runtimeGraph: Enumerable.Empty>()); - } - - private Tuple PackageIdentity(DepsFileLine line) - { - return Tuple.Create(line.LibraryType, line.PackageName, line.PackageVersion, line.PackageHash); - } - - private void SkipWhitespace(StreamReader reader) - { - // skip all whitespace - while (!reader.EndOfStream && char.IsWhiteSpace((char)reader.Peek())) - { - reader.Read(); - } - } - - private string ReadValue(StreamReader reader) - { - SkipWhitespace(reader); - - var c = ReadSucceed(reader.Read()); - if (c != '"') - { - throw new FormatException("Deps file value should start with '\"'"); - } - - var value = new StringBuilder(); - while (ReadSucceed(reader.Peek()) != '"') - { - c = ReadSucceed(reader.Read()); - if (c == '\\') - { - value.Append(ReadSucceed(reader.Read())); - } - else - { - value.Append(c); - } - } - // Read last " - ReadSucceed(reader.Read()); - // Read comment - if (reader.Peek() == ',') - { - reader.Read(); - } - return value.ToString(); - } - - private char ReadSucceed(int c) - { - if (c == -1) - { - throw new FormatException("Unexpected end of file"); - } - return (char) c; - } - - private struct DepsFileLine - { - public string LibraryType; - public string PackageName; - public string PackageVersion; - public string PackageHash; - public string AssetType; - public string AssetName; - public string AssetPath; - } - } -} \ No newline at end of file diff --git a/src/Microsoft.Extensions.DependencyModel/DependencyContextJsonReader.cs b/src/Microsoft.Extensions.DependencyModel/DependencyContextJsonReader.cs index 3dc15822f..ceb2f5c2d 100644 --- a/src/Microsoft.Extensions.DependencyModel/DependencyContextJsonReader.cs +++ b/src/Microsoft.Extensions.DependencyModel/DependencyContextJsonReader.cs @@ -10,10 +10,14 @@ using Newtonsoft.Json.Linq; namespace Microsoft.Extensions.DependencyModel { - public class DependencyContextJsonReader + public class DependencyContextJsonReader: IDependencyContextReader { public DependencyContext Read(Stream stream) { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } using (var streamReader = new StreamReader(stream)) { using (var reader = new JsonTextReader(streamReader)) @@ -39,35 +43,60 @@ namespace Microsoft.Extensions.DependencyModel JObject runtimeTarget = null; JObject compileTarget = null; - if (targetsObject != null) + + if (targetsObject == null) { - var compileTargetProperty = targetsObject.Properties() - .FirstOrDefault(p => !IsRuntimeTarget(p.Name)); + throw new FormatException("Dependency file does not have 'targets' section"); + } - compileTarget = (JObject)compileTargetProperty.Value; - target = compileTargetProperty.Name; - - if (!string.IsNullOrEmpty(runtimeTargetName)) + if (!string.IsNullOrEmpty(runtimeTargetName)) + { + runtimeTarget = (JObject) targetsObject[runtimeTargetName]; + if (runtimeTarget == null) { - runtimeTarget = (JObject) targetsObject[runtimeTargetName]; - if (runtimeTarget == null) - { - throw new FormatException($"Target with name {runtimeTargetName} not found"); - } + throw new FormatException($"Target with name {runtimeTargetName} not found"); + } + } + else + { + var runtimeTargetProperty = targetsObject.Properties() + .FirstOrDefault(p => IsRuntimeTarget(p.Name)); - var seperatorIndex = runtimeTargetName.IndexOf(DependencyContextStrings.VersionSeperator); - if (seperatorIndex > -1 && seperatorIndex < runtimeTargetName.Length) - { - runtime = runtimeTargetName.Substring(seperatorIndex + 1); - isPortable = false; - } + runtimeTarget = (JObject)runtimeTargetProperty?.Value; + runtimeTargetName = runtimeTargetProperty?.Name; + } + + if (runtimeTargetName != null) + { + var seperatorIndex = runtimeTargetName.IndexOf(DependencyContextStrings.VersionSeperator); + if (seperatorIndex > -1 && seperatorIndex < runtimeTargetName.Length) + { + runtime = runtimeTargetName.Substring(seperatorIndex + 1); + target = runtimeTargetName.Substring(0, seperatorIndex); + isPortable = false; } else { - runtimeTarget = compileTarget; + target = runtimeTargetName; } } + var ridlessTargetProperty = targetsObject.Properties().FirstOrDefault(p => !IsRuntimeTarget(p.Name)); + if (ridlessTargetProperty != null) + { + compileTarget = (JObject)ridlessTargetProperty.Value; + if (runtimeTarget == null) + { + runtimeTarget = compileTarget; + target = ridlessTargetProperty.Name; + } + } + + if (runtimeTarget == null) + { + throw new FormatException("No runtime target found"); + } + return new DependencyContext( target, runtime, @@ -79,18 +108,16 @@ namespace Microsoft.Extensions.DependencyModel ); } - private IEnumerable> ReadRuntimeGraph(JObject runtimes) + private IEnumerable ReadRuntimeGraph(JObject runtimes) { if (runtimes == null) { yield break; } - var targets = runtimes.Children(); - var runtime = (JProperty)targets.Single(); - foreach (var pair in (JObject)runtime.Value) + foreach (var pair in runtimes) { - yield return new KeyValuePair(pair.Key, pair.Value.Values().ToArray()); + yield return new RuntimeFallbacks(pair.Key, pair.Value.Values().ToArray()); } } @@ -102,7 +129,7 @@ namespace Microsoft.Extensions.DependencyModel } return new CompilationOptions( - compilationOptionsObject[DependencyContextStrings.DefinesPropertyName]?.Values(), + compilationOptionsObject[DependencyContextStrings.DefinesPropertyName]?.Values() ?? Enumerable.Empty(), compilationOptionsObject[DependencyContextStrings.LanguageVersionPropertyName]?.Value(), compilationOptionsObject[DependencyContextStrings.PlatformPropertyName]?.Value(), compilationOptionsObject[DependencyContextStrings.AllowUnsafePropertyName]?.Value(), @@ -169,10 +196,12 @@ namespace Microsoft.Extensions.DependencyModel )); } - var assemblies = ReadAssemblies(libraryObject, DependencyContextStrings.RuntimeAssembliesKey) + var assemblies = ReadAssetList(libraryObject, DependencyContextStrings.RuntimeAssembliesKey) .Select(RuntimeAssembly.Create) .ToArray(); + var nativeLibraries = ReadAssetList(libraryObject, DependencyContextStrings.NativeLibrariesKey); + var resourceAssemblies = ReadResourceAssemblies((JObject)libraryObject[DependencyContextStrings.ResourceAssembliesPropertyName]); return new RuntimeLibrary( @@ -181,6 +210,7 @@ namespace Microsoft.Extensions.DependencyModel version: version, hash: stub.Hash, assemblies: assemblies, + nativeLibraries: nativeLibraries, resourceAssemblies: resourceAssemblies, subTargets: runtimeTargets.ToArray(), dependencies: dependencies, @@ -188,7 +218,7 @@ namespace Microsoft.Extensions.DependencyModel } else { - var assemblies = ReadAssemblies(libraryObject, DependencyContextStrings.CompileTimeAssembliesKey); + var assemblies = ReadAssetList(libraryObject, DependencyContextStrings.CompileTimeAssembliesKey); return new CompilationLibrary(stub.Type, name, version, stub.Hash, assemblies, dependencies, stub.Serviceable); } } @@ -226,7 +256,7 @@ namespace Microsoft.Extensions.DependencyModel } } - private static string[] ReadAssemblies(JObject libraryObject, string name) + private static string[] ReadAssetList(JObject libraryObject, string name) { var assembliesObject = (JObject) libraryObject[name]; diff --git a/src/Microsoft.Extensions.DependencyModel/DependencyContextLoader.cs b/src/Microsoft.Extensions.DependencyModel/DependencyContextLoader.cs new file mode 100644 index 000000000..9cc4d5f6c --- /dev/null +++ b/src/Microsoft.Extensions.DependencyModel/DependencyContextLoader.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.Extensions.EnvironmentAbstractions; + +namespace Microsoft.Extensions.DependencyModel +{ + public class DependencyContextLoader + { + private static Lazy _depsFiles = new Lazy(GetHostDepsList); + + private const string DepsJsonExtension = ".deps.json"; + + private readonly string _entryPointDepsLocation; + private readonly string _runtimeDepsLocation; + private readonly IFileSystem _fileSystem; + private readonly IDependencyContextReader _jsonReader; + + public DependencyContextLoader() : this( + GetDefaultEntrypointDepsLocation(), + GetDefaultRuntimeDepsLocation(), + FileSystemWrapper.Default, + new DependencyContextJsonReader()) + { + } + + internal DependencyContextLoader( + string entryPointDepsLocation, + string runtimeDepsLocation, + IFileSystem fileSystem, + IDependencyContextReader jsonReader) + { + _entryPointDepsLocation = entryPointDepsLocation; + _runtimeDepsLocation = runtimeDepsLocation; + _fileSystem = fileSystem; + _jsonReader = jsonReader; + } + + public static DependencyContextLoader Default { get; } = new DependencyContextLoader(); + + internal virtual bool IsEntryAssembly(Assembly assembly) + { + return assembly.GetName() == Assembly.GetEntryAssembly()?.GetName(); + } + + internal virtual Stream GetResourceStream(Assembly assembly, string name) + { + return assembly.GetManifestResourceStream(name); + } + + public DependencyContext Load(Assembly assembly) + { + if (assembly == null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + DependencyContext context = null; + + if (IsEntryAssembly(assembly)) + { + context = LoadEntryAssemblyContext(); + } + + if (context == null) + { + context = LoadAssemblyContext(assembly); + } + + if (context?.IsPortable == true) + { + var runtimeContext = LoadRuntimeContext(); + if (runtimeContext != null) + { + context = context.Merge(runtimeContext); + } + } + return context; + } + + private DependencyContext LoadEntryAssemblyContext() + { + if (!string.IsNullOrEmpty(_entryPointDepsLocation)) + { + Debug.Assert(File.Exists(_entryPointDepsLocation)); + using (var stream = _fileSystem.File.OpenRead(_entryPointDepsLocation)) + { + return _jsonReader.Read(stream); + } + } + return null; + } + + private DependencyContext LoadRuntimeContext() + { + if (!string.IsNullOrEmpty(_runtimeDepsLocation)) + { + Debug.Assert(File.Exists(_runtimeDepsLocation)); + using (var stream = _fileSystem.File.OpenRead(_runtimeDepsLocation)) + { + return _jsonReader.Read(stream); + } + } + return null; + } + + private DependencyContext LoadAssemblyContext(Assembly assembly) + { + using (var stream = GetResourceStream(assembly, assembly.GetName().Name + DepsJsonExtension)) + { + if (stream != null) + { + return _jsonReader.Read(stream); + } + } + + var depsJsonFile = Path.ChangeExtension(assembly.Location, DepsJsonExtension); + if (_fileSystem.File.Exists(depsJsonFile)) + { + using (var stream = _fileSystem.File.OpenRead(depsJsonFile)) + { + return _jsonReader.Read(stream); + } + } + + return null; + } + + private static string GetDefaultRuntimeDepsLocation() + { + var deps = _depsFiles.Value; + if (deps != null && deps.Length > 1) + { + return deps[1]; + } + return null; + } + + private static string GetDefaultEntrypointDepsLocation() + { + var deps = _depsFiles.Value; + if (deps != null && deps.Length > 0) + { + return deps[0]; + } + return null; + } + + private static string[] GetHostDepsList() + { + // TODO: We're going to replace this with AppContext.GetData + var appDomainType = typeof(object).GetTypeInfo().Assembly?.GetType("System.AppDomain"); + var currentDomain = appDomainType?.GetProperty("CurrentDomain")?.GetValue(null); + var deps = appDomainType?.GetMethod("GetData")?.Invoke(currentDomain, new[] { "APP_CONTEXT_DEPS_FILES" }); + + return (deps as string)?.Split(new [] { ';' }, StringSplitOptions.RemoveEmptyEntries); + } + + } +} diff --git a/src/Microsoft.Extensions.DependencyModel/DependencyContextStrings.cs b/src/Microsoft.Extensions.DependencyModel/DependencyContextStrings.cs index 7f098877e..b5df3f7a3 100644 --- a/src/Microsoft.Extensions.DependencyModel/DependencyContextStrings.cs +++ b/src/Microsoft.Extensions.DependencyModel/DependencyContextStrings.cs @@ -11,6 +11,8 @@ namespace Microsoft.Extensions.DependencyModel internal const string RuntimeAssembliesKey = "runtime"; + internal const string NativeLibrariesKey = "native"; + internal const string RuntimeTargetPropertyName = "runtimeTarget"; internal const string LibrariesPropertyName = "libraries"; diff --git a/src/Microsoft.Extensions.DependencyModel/DependencyContextWriter.cs b/src/Microsoft.Extensions.DependencyModel/DependencyContextWriter.cs index 2f26e8c49..1a6015359 100644 --- a/src/Microsoft.Extensions.DependencyModel/DependencyContextWriter.cs +++ b/src/Microsoft.Extensions.DependencyModel/DependencyContextWriter.cs @@ -16,6 +16,14 @@ namespace Microsoft.Extensions.DependencyModel { public void Write(DependencyContext context, Stream stream) { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } using (var writer = new StreamWriter(stream)) { using (var jsonWriter = new JsonTextWriter(writer) { Formatting = Formatting.Indented }) @@ -50,7 +58,7 @@ namespace Microsoft.Extensions.DependencyModel private JObject WriteRuntimeGraph(DependencyContext context) { return new JObject( - context.RuntimeGraph.Select(g => new JProperty(g.Key, new JArray(g.Value))) + context.RuntimeGraph.Select(g => new JProperty(g.Runtime, new JArray(g.Fallbacks))) ); } @@ -79,7 +87,7 @@ namespace Microsoft.Extensions.DependencyModel { if (value != null) { - o[name] = value.ToString(); + o.Add(new JProperty(name, value)); } } @@ -147,7 +155,7 @@ namespace Microsoft.Extensions.DependencyModel return; } libraryObject.Add(new JProperty(DependencyContextStrings.CompileTimeAssembliesKey, - WriteAssemblies(compilationAssemblies)) + WriteAssetList(compilationAssemblies)) ); } @@ -158,7 +166,7 @@ namespace Microsoft.Extensions.DependencyModel return; } libraryObject.Add(new JProperty(DependencyContextStrings.RuntimeAssembliesKey, - WriteAssemblies(runtimeAssemblies.Select(a => a.Path))) + WriteAssetList(runtimeAssemblies.Select(a => a.Path))) ); } @@ -168,7 +176,7 @@ namespace Microsoft.Extensions.DependencyModel { return; } - libraryObject.Add( + libraryObject.AddFirst( new JProperty(DependencyContextStrings.DependenciesPropertyName, new JObject( dependencies.Select(dependency => new JProperty(dependency.Name, dependency.Version)))) @@ -197,6 +205,12 @@ namespace Microsoft.Extensions.DependencyModel AddDependencies(libraryObject, runtimeLibrary.Dependencies); AddRuntimeAssemblies(libraryObject, runtimeLibrary.Assemblies); AddResourceAssemblies(libraryObject, runtimeLibrary.ResourceAssemblies); + + if (runtimeLibrary.NativeLibraries.Any()) + { + libraryObject.Add(DependencyContextStrings.NativeLibrariesKey, WriteAssetList(runtimeLibrary.NativeLibraries)); + } + return libraryObject; } @@ -213,7 +227,6 @@ namespace Microsoft.Extensions.DependencyModel private JObject WritePortableTargetLibrary(RuntimeLibrary runtimeLibrary, CompilationLibrary compilationLibrary) { - var libraryObject = new JObject(); var dependencies = new HashSet(); @@ -227,8 +240,9 @@ namespace Microsoft.Extensions.DependencyModel new JObject(runtimeLibrary.RuntimeTargets.SelectMany(WriteRuntimeTarget))) ); } - AddResourceAssemblies(libraryObject, runtimeLibrary.ResourceAssemblies); AddRuntimeAssemblies(libraryObject, runtimeLibrary.Assemblies); + AddResourceAssemblies(libraryObject, runtimeLibrary.ResourceAssemblies); + libraryObject.Add(DependencyContextStrings.NativeLibrariesKey, WriteAssetList(runtimeLibrary.NativeLibraries)); dependencies.UnionWith(runtimeLibrary.Dependencies); } @@ -272,9 +286,9 @@ namespace Microsoft.Extensions.DependencyModel } } - private JObject WriteAssemblies(IEnumerable assemblies) + private JObject WriteAssetList(IEnumerable assetPaths) { - return new JObject(assemblies.Select(assembly => new JProperty(NormalizePath(assembly), new JObject()))); + return new JObject(assetPaths.Select(assembly => new JProperty(NormalizePath(assembly), new JObject()))); } private JObject WriteLibraries(DependencyContext context) diff --git a/src/Microsoft.Extensions.DependencyModel/IDependencyContextReader.cs b/src/Microsoft.Extensions.DependencyModel/IDependencyContextReader.cs new file mode 100644 index 000000000..57b2a442e --- /dev/null +++ b/src/Microsoft.Extensions.DependencyModel/IDependencyContextReader.cs @@ -0,0 +1,9 @@ +using System.IO; + +namespace Microsoft.Extensions.DependencyModel +{ + public interface IDependencyContextReader + { + DependencyContext Read(Stream stream); + } +} \ No newline at end of file diff --git a/src/Microsoft.Extensions.DependencyModel/Library.cs b/src/Microsoft.Extensions.DependencyModel/Library.cs index a3fefef42..5e9b074e5 100644 --- a/src/Microsoft.Extensions.DependencyModel/Library.cs +++ b/src/Microsoft.Extensions.DependencyModel/Library.cs @@ -1,6 +1,7 @@ // 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.Linq; using System.Collections.Generic; @@ -10,6 +11,22 @@ namespace Microsoft.Extensions.DependencyModel { public Library(string type, string name, string version, string hash, IEnumerable dependencies, bool serviceable) { + if (string.IsNullOrEmpty(type)) + { + throw new ArgumentException(nameof(type)); + } + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(nameof(name)); + } + if (string.IsNullOrEmpty(version)) + { + throw new ArgumentException(nameof(version)); + } + if (dependencies == null) + { + throw new ArgumentNullException(nameof(dependencies)); + } Type = type; Name = name; Version = version; diff --git a/src/Microsoft.Extensions.DependencyModel/Resolution/DotNetReferenceAssembliesPathResolver.cs b/src/Microsoft.Extensions.DependencyModel/Resolution/DotNetReferenceAssembliesPathResolver.cs new file mode 100644 index 000000000..b44668ede --- /dev/null +++ b/src/Microsoft.Extensions.DependencyModel/Resolution/DotNetReferenceAssembliesPathResolver.cs @@ -0,0 +1,59 @@ +// 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 Microsoft.Extensions.EnvironmentAbstractions; +using Microsoft.Extensions.PlatformAbstractions; + +namespace Microsoft.Extensions.DependencyModel.Resolution +{ + public class DotNetReferenceAssembliesPathResolver + { + public static readonly string DotNetReferenceAssembliesPathEnv = "DOTNET_REFERENCE_ASSEMBLIES_PATH"; + + internal static string Resolve(IEnvironment envirnment, IFileSystem fileSystem, IRuntimeEnvironment runtimeEnvironment) + { + var path = envirnment.GetEnvironmentVariable(DotNetReferenceAssembliesPathEnv); + if (!string.IsNullOrEmpty(path)) + { + return path; + } + + return GetDefaultDotNetReferenceAssembliesPath(fileSystem, runtimeEnvironment); + } + + public static string Resolve() + { + return Resolve(EnvironmentWrapper.Default, FileSystemWrapper.Default, PlatformServices.Default.Runtime); + } + + private static string GetDefaultDotNetReferenceAssembliesPath(IFileSystem fileSystem, IRuntimeEnvironment runtimeEnvironment) + { + var os = runtimeEnvironment.OperatingSystemPlatform; + + if (os == Platform.Windows) + { + return null; + } + + if (os == Platform.Darwin && + fileSystem.Directory.Exists("/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/xbuild-frameworks")) + { + return "/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/xbuild-frameworks"; + } + + if (fileSystem.Directory.Exists("/usr/local/lib/mono/xbuild-frameworks")) + { + return "/usr/local/lib/mono/xbuild-frameworks"; + } + + if (fileSystem.Directory.Exists("/usr/lib/mono/xbuild-frameworks")) + { + return "/usr/lib/mono/xbuild-frameworks"; + } + + return null; + } + } +} diff --git a/src/Microsoft.Extensions.DependencyModel/Resolution/ReferenceAssemblyPathResolver.cs b/src/Microsoft.Extensions.DependencyModel/Resolution/ReferenceAssemblyPathResolver.cs index 523b20eca..55f212660 100644 --- a/src/Microsoft.Extensions.DependencyModel/Resolution/ReferenceAssemblyPathResolver.cs +++ b/src/Microsoft.Extensions.DependencyModel/Resolution/ReferenceAssemblyPathResolver.cs @@ -27,7 +27,7 @@ namespace Microsoft.Extensions.DependencyModel.Resolution internal ReferenceAssemblyPathResolver(IFileSystem fileSystem, IRuntimeEnvironment runtimeEnvironment, IEnvironment environment) : this(fileSystem, - GetDefaultReferenceAssembliesPath(runtimeEnvironment, environment), + GetDefaultReferenceAssembliesPath(runtimeEnvironment, fileSystem, environment), GetFallbackSearchPaths(fileSystem, runtimeEnvironment, environment)) { } @@ -101,11 +101,10 @@ namespace Microsoft.Extensions.DependencyModel.Resolution return new[] { net20Dir }; } - internal static string GetDefaultReferenceAssembliesPath(IRuntimeEnvironment runtimeEnvironment, IEnvironment environment) + internal static string GetDefaultReferenceAssembliesPath(IRuntimeEnvironment runtimeEnvironment, IFileSystem fileSystem, IEnvironment environment) { // Allow setting the reference assemblies path via an environment variable - var referenceAssembliesPath = environment.GetEnvironmentVariable("DOTNET_REFERENCE_ASSEMBLIES_PATH"); - + var referenceAssembliesPath = DotNetReferenceAssembliesPathResolver.Resolve(environment, fileSystem, runtimeEnvironment); if (!string.IsNullOrEmpty(referenceAssembliesPath)) { return referenceAssembliesPath; @@ -138,6 +137,5 @@ namespace Microsoft.Extensions.DependencyModel.Resolution programFiles, "Reference Assemblies", "Microsoft", "Framework"); } - } } \ No newline at end of file diff --git a/src/Microsoft.Extensions.DependencyModel/ResourceAssembly.cs b/src/Microsoft.Extensions.DependencyModel/ResourceAssembly.cs index 416ed20b1..4a59c054a 100644 --- a/src/Microsoft.Extensions.DependencyModel/ResourceAssembly.cs +++ b/src/Microsoft.Extensions.DependencyModel/ResourceAssembly.cs @@ -1,12 +1,22 @@ // 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; + namespace Microsoft.Extensions.DependencyModel { public class ResourceAssembly { public ResourceAssembly(string path, string locale) { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentException(nameof(path)); + } + if (string.IsNullOrEmpty(locale)) + { + throw new ArgumentException(nameof(locale)); + } Locale = locale; Path = path; } diff --git a/src/Microsoft.Extensions.DependencyModel/RuntimeAssembly.cs b/src/Microsoft.Extensions.DependencyModel/RuntimeAssembly.cs index 4cfc002eb..196a36c17 100644 --- a/src/Microsoft.Extensions.DependencyModel/RuntimeAssembly.cs +++ b/src/Microsoft.Extensions.DependencyModel/RuntimeAssembly.cs @@ -15,6 +15,14 @@ namespace Microsoft.Extensions.DependencyModel public RuntimeAssembly(string assemblyName, string path) { + if (string.IsNullOrEmpty(assemblyName)) + { + throw new ArgumentException(nameof(assemblyName)); + } + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentException(nameof(path)); + } _assemblyName = assemblyName; Path = path; } diff --git a/src/Microsoft.Extensions.DependencyModel/RuntimeFallbacks.cs b/src/Microsoft.Extensions.DependencyModel/RuntimeFallbacks.cs new file mode 100644 index 000000000..d5ff52403 --- /dev/null +++ b/src/Microsoft.Extensions.DependencyModel/RuntimeFallbacks.cs @@ -0,0 +1,29 @@ +// 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; + +namespace Microsoft.Extensions.DependencyModel +{ + public class RuntimeFallbacks + { + public string Runtime { get; set; } + public IReadOnlyList Fallbacks { get; set; } + + public RuntimeFallbacks(string runtime, IEnumerable fallbacks) + { + if (string.IsNullOrEmpty(runtime)) + { + throw new ArgumentException(nameof(runtime)); + } + if (fallbacks == null) + { + throw new ArgumentNullException(nameof(fallbacks)); + } + Runtime = runtime; + Fallbacks = fallbacks.ToArray(); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Extensions.DependencyModel/RuntimeLibrary.cs b/src/Microsoft.Extensions.DependencyModel/RuntimeLibrary.cs index a3ee2332f..cc7accd3e 100644 --- a/src/Microsoft.Extensions.DependencyModel/RuntimeLibrary.cs +++ b/src/Microsoft.Extensions.DependencyModel/RuntimeLibrary.cs @@ -1,6 +1,7 @@ // 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; @@ -14,19 +15,39 @@ namespace Microsoft.Extensions.DependencyModel string version, string hash, IEnumerable assemblies, + IEnumerable nativeLibraries, IEnumerable resourceAssemblies, IEnumerable subTargets, IEnumerable dependencies, bool serviceable) : base(type, name, version, hash, dependencies, serviceable) { + if (assemblies == null) + { + throw new ArgumentNullException(nameof(assemblies)); + } + if (nativeLibraries == null) + { + throw new ArgumentNullException(nameof(nativeLibraries)); + } + if (resourceAssemblies == null) + { + throw new ArgumentNullException(nameof(resourceAssemblies)); + } + if (subTargets == null) + { + throw new ArgumentNullException(nameof(subTargets)); + } Assemblies = assemblies.ToArray(); ResourceAssemblies = resourceAssemblies.ToArray(); RuntimeTargets = subTargets.ToArray(); + NativeLibraries = nativeLibraries.ToArray(); } public IReadOnlyList Assemblies { get; } + public IReadOnlyList NativeLibraries { get; } + public IReadOnlyList ResourceAssemblies { get; } public IReadOnlyList RuntimeTargets { get; } diff --git a/src/Microsoft.Extensions.DependencyModel/RuntimeTarget.cs b/src/Microsoft.Extensions.DependencyModel/RuntimeTarget.cs index 81771662b..709e1073d 100644 --- a/src/Microsoft.Extensions.DependencyModel/RuntimeTarget.cs +++ b/src/Microsoft.Extensions.DependencyModel/RuntimeTarget.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; @@ -7,6 +8,18 @@ namespace Microsoft.Extensions.DependencyModel { public RuntimeTarget(string runtime, IEnumerable assemblies, IEnumerable nativeLibraries) { + if (string.IsNullOrEmpty(runtime)) + { + throw new ArgumentException(nameof(runtime)); + } + if (assemblies == null) + { + throw new ArgumentNullException(nameof(assemblies)); + } + if (nativeLibraries == null) + { + throw new ArgumentNullException(nameof(nativeLibraries)); + } Runtime = runtime; Assemblies = assemblies.ToArray(); NativeLibraries = nativeLibraries.ToArray(); diff --git a/src/Microsoft.Extensions.DependencyModel/project.json b/src/Microsoft.Extensions.DependencyModel/project.json index 1ad395b56..65fd21652 100644 --- a/src/Microsoft.Extensions.DependencyModel/project.json +++ b/src/Microsoft.Extensions.DependencyModel/project.json @@ -1,39 +1,38 @@ { - "description": "Abstractions for reading `.deps` files.", - "version": "1.0.0-*", - "repository": { - "type": "git", - "url": "git://github.com/dotnet/cli" + "description": "Abstractions for reading `.deps` files.", + "version": "1.0.0-*", + "repository": { + "type": "git", + "url": "git://github.com/dotnet/cli" + }, + "compilationOptions": { + "warningsAsErrors": true, + "keyFile": "../../tools/Key.snk" + }, + "dependencies": { + "Microsoft.Extensions.HashCodeCombiner.Sources": { + "type": "build", + "version": "1.0.0-rc2-16054" }, - "compilationOptions": { - "warningsAsErrors": true, - "keyFile": "../../tools/Key.snk" + "Microsoft.DotNet.InternalAbstractions": { + "target": "project", + "version": "1.0.0-*" }, - "dependencies": { - "Microsoft.Extensions.HashCodeCombiner.Sources": { - "type": "build", - "version": "1.0.0-rc2-16054" - }, - "Microsoft.DotNet.InternalAbstractions": { - "target": "project", - "version": "1.0.0-*" - }, - - "Newtonsoft.Json": "7.0.1", - "Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-16537" - }, - "frameworks": { - "net451": {}, - "netstandard1.3": { - "imports": "dnxcore50", - "dependencies": { - "System.IO.FileSystem": "4.0.1-rc2-23901", - "System.Linq": "4.0.1-rc2-23901", - "System.Runtime": "4.0.21-rc2-23901", - "System.Reflection": "4.1.0-rc2-23901", - "System.Dynamic.Runtime": "4.0.11-rc2-23901" - } - } - }, - "scripts": {} -} \ No newline at end of file + "Newtonsoft.Json": "7.0.1", + "Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-16537" + }, + "frameworks": { + "net451": {}, + "netstandard1.3": { + "imports": "dnxcore50", + "dependencies": { + "System.IO.FileSystem": "4.0.1-rc2-23911", + "System.Linq": "4.0.1-rc2-23911", + "System.Runtime": "4.0.21-rc2-23911", + "System.Reflection": "4.1.0-rc2-23911", + "System.Dynamic.Runtime": "4.0.11-rc2-23911" + } + } + }, + "scripts": {} +} diff --git a/src/Microsoft.Extensions.Testing.Abstractions/project.json b/src/Microsoft.Extensions.Testing.Abstractions/project.json index 7ecb6232e..e392da6d6 100644 --- a/src/Microsoft.Extensions.Testing.Abstractions/project.json +++ b/src/Microsoft.Extensions.Testing.Abstractions/project.json @@ -1,29 +1,29 @@ { - "description": "Abstractions for test runners to communicate to a tool, such as Visual Studio.", - "version": "1.0.0-*", - "repository": { - "type": "git", - "url": "git://github.com/dotnet/cli" - }, - "compilationOptions": { - "warningsAsErrors": true, - "keyFile": "../../tools/Key.snk" - }, - "dependencies": { - "Newtonsoft.Json": "7.0.1", - "Microsoft.DotNet.ProjectModel": "1.0.0-*", - "Microsoft.Extensions.Logging.Abstractions": "1.0.0-rc2-16040" - }, - "frameworks": { - "net451": {}, - "netstandard1.3": { - "imports": "dnxcore50", - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "System.Resources.ResourceManager": "4.0.1-rc2-23901", - "System.Runtime.Serialization.Primitives": "4.1.0-rc2-23901" - } - } - }, - "scripts": {} -} \ No newline at end of file + "description": "Abstractions for test runners to communicate to a tool, such as Visual Studio.", + "version": "1.0.0-*", + "repository": { + "type": "git", + "url": "git://github.com/dotnet/cli" + }, + "compilationOptions": { + "warningsAsErrors": true, + "keyFile": "../../tools/Key.snk" + }, + "dependencies": { + "Newtonsoft.Json": "7.0.1", + "Microsoft.DotNet.ProjectModel": "1.0.0-*", + "Microsoft.Extensions.Logging.Abstractions": "1.0.0-rc2-16040" + }, + "frameworks": { + "net451": {}, + "netstandard1.3": { + "imports": "dnxcore50", + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "System.Resources.ResourceManager": "4.0.1-rc2-23911", + "System.Reflection.TypeExtensions": "4.1.0-rc2-23911" + } + } + }, + "scripts": {} +} diff --git a/src/corehost/build.sh b/src/corehost/build.sh index 339838348..7717bdc15 100755 --- a/src/corehost/build.sh +++ b/src/corehost/build.sh @@ -14,6 +14,46 @@ while [ -h "$SOURCE" ]; do # resolve $SOURCE until the file is no longer a symli done DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" +__build_arch= +__runtime_id= + +while [ "$1" != "" ]; do + lowerI="$(echo $1 | awk '{print tolower($0)}')" + case $lowerI in + -h|--help) + usage + exit 1 + ;; + --arch) + shift + __build_arch=$1 + ;; + --rid) + shift + __runtime_id=$1 + ;; + *) + echo "Unknown argument to build.sh $1"; exit 1 + esac + shift +done + +__cmake_defines= + +case $__build_arch in + amd64) + __define=-DCLI_CMAKE_PLATFORM_ARCH_AMD64=1 + ;; + x86) + __define=-DCLI_CMAKE_PLATFORM_ARCH_I386=1 + ;; + *) + echo "Unknown architecture $__build_arch"; exit 1 + ;; +esac +__cmake_defines="${__cmake_defines} ${__define}" + + echo "Building Corehost from $DIR to $(pwd)" -cmake "$DIR" -G "Unix Makefiles" +cmake "$DIR" -G "Unix Makefiles" $__cmake_defines -DCLI_CMAKE_RUNTIME_ID:STRING=$__runtime_id make diff --git a/src/corehost/cli/CMakeLists.txt b/src/corehost/cli/CMakeLists.txt index 9c22fc834..12636ae6b 100644 --- a/src/corehost/cli/CMakeLists.txt +++ b/src/corehost/cli/CMakeLists.txt @@ -14,13 +14,26 @@ include(setup.cmake) set (CMAKE_CXX_STANDARD 11) +include_directories(..) include_directories(../common) include_directories(.) +include_directories(./fxr) +include_directories(./json/casablanca/include) # CMake does not recommend using globbing since it messes with the freshness checks set(SOURCES - ../corehost.cpp + libhost.cpp + #deps_format.cpp + #./json/casablanca/src/json/json.cpp + #./json/casablanca/src/json/json_parsing.cpp + #./json/casablanca/src/json/json_serialization.cpp + #./json/casablanca/src/utilities/asyncrt_utils.cpp + + + ./fxr/fx_ver.cpp + ../corehost.cpp + ../policy_load.cpp ../common/trace.cpp ../common/utils.cpp) @@ -32,6 +45,9 @@ else() endif() add_executable(corehost ${SOURCES}) +install(TARGETS corehost DESTINATION bin) +add_definitions(-D_NO_ASYNCRTIMP) +add_definitions(-D_NO_PPLXIMP) # Older CMake doesn't support CMAKE_CXX_STANDARD and GCC/Clang need a switch to enable C++ 11 if(${CMAKE_CXX_COMPILER_ID} MATCHES "(Clang|GNU)") @@ -44,3 +60,4 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") endif() add_subdirectory(dll) +add_subdirectory(fxr) diff --git a/src/corehost/cli/args.cpp b/src/corehost/cli/args.cpp index 086d05fab..40ebd24be 100644 --- a/src/corehost/cli/args.cpp +++ b/src/corehost/cli/args.cpp @@ -4,6 +4,7 @@ #include "args.h" #include "utils.h" #include "coreclr.h" +#include "libhost.h" arguments_t::arguments_t() : managed_application(_X("")), @@ -11,11 +12,8 @@ arguments_t::arguments_t() : app_dir(_X("")), app_argc(0), app_argv(nullptr), - nuget_packages(_X("")), dotnet_packages_cache(_X("")), dotnet_servicing(_X("")), - dotnet_runtime_servicing(_X("")), - dotnet_home(_X("")), deps_path(_X("")) { } @@ -26,12 +24,13 @@ void display_help() _X("Usage: " HOST_EXE_NAME " [ASSEMBLY] [ARGUMENTS]\n") _X("Execute the specified managed assembly with the passed in arguments\n\n") _X("The Host's behavior can be altered using the following environment variables:\n") - _X(" DOTNET_HOME Set the dotnet home directory. The CLR is expected to be in the runtime subdirectory of this directory. Overrides all other values for CLR search paths\n") _X(" COREHOST_TRACE Set to affect trace levels (0 = Errors only (default), 1 = Warnings, 2 = Info, 3 = Verbose)\n"); } -bool parse_arguments(const int argc, const pal::char_t* argv[], arguments_t& args) +bool parse_arguments(const pal::string_t& deps_path, const pal::string_t& probe_dir, host_mode_t mode, + const int argc, const pal::char_t* argv[], arguments_t* arg_out) { + arguments_t& args = *arg_out; // Get the full name of the application if (!pal::get_own_executable_path(&args.own_path) || !pal::realpath(&args.own_path)) { @@ -41,8 +40,8 @@ bool parse_arguments(const int argc, const pal::char_t* argv[], arguments_t& arg auto own_name = get_filename(args.own_path); auto own_dir = get_directory(args.own_path); - - if (own_name.compare(HOST_EXE_NAME) == 0) + + if (mode != host_mode_t::standalone) { // corerun mode. First argument is managed app if (argc < 2) @@ -78,23 +77,28 @@ bool parse_arguments(const int argc, const pal::char_t* argv[], arguments_t& arg args.app_argc = argc - 1; } - if (args.app_argc > 0) + std::unordered_map opts; + std::vector known_opts = { _X("--depsfile"), _X("--additionalprobingpath") }; + int num_args = 0; + if (!parse_known_args(args.app_argc, args.app_argv, known_opts, &opts, &num_args)) { - auto depsfile_candidate = pal::string_t(args.app_argv[0]); - - if (starts_with(depsfile_candidate, s_deps_arg_prefix, false)) - { - args.deps_path = depsfile_candidate.substr(s_deps_arg_prefix.length()); - if (!pal::realpath(&args.deps_path)) - { - trace::error(_X("Failed to locate deps file: %s"), args.deps_path.c_str()); - return false; - } - args.app_dir = get_directory(args.deps_path); - args.app_argc = args.app_argc - 1; - args.app_argv = &args.app_argv[1]; - } + return false; } + + args.app_argc -= num_args; + args.app_argv += num_args; + pal::string_t opts_deps_file = _X("--depsfile"); + pal::string_t opts_probe_path = _X("--additionalprobingpath"); + pal::string_t deps_file = opts.count(opts_deps_file) ? opts[opts_deps_file] : deps_path; + pal::string_t probe_path = opts.count(opts_probe_path) ? opts[opts_probe_path] : probe_dir; + + if (!deps_file.empty()) + { + args.deps_path = deps_file; + args.app_dir = get_directory(args.deps_path); + } + + args.probe_dir = probe_path; if (args.deps_path.empty()) { @@ -105,13 +109,10 @@ bool parse_arguments(const int argc, const pal::char_t* argv[], arguments_t& arg args.deps_path.append(app_base); args.deps_path.push_back(DIR_SEPARATOR); args.deps_path.append(app_name, 0, app_name.find_last_of(_X("."))); - args.deps_path.append(_X(".deps")); + args.deps_path.append(_X(".deps.json")); } - pal::getenv(_X("NUGET_PACKAGES"), &args.nuget_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/cli/args.h b/src/corehost/cli/args.h index d510cd6e8..bec56128c 100644 --- a/src/corehost/cli/args.h +++ b/src/corehost/cli/args.h @@ -7,8 +7,7 @@ #include "utils.h" #include "pal.h" #include "trace.h" - -static const pal::string_t s_deps_arg_prefix = _X("--depsfile:"); +#include "libhost.h" struct arguments_t { @@ -18,7 +17,7 @@ struct arguments_t pal::string_t dotnet_servicing; pal::string_t dotnet_runtime_servicing; pal::string_t dotnet_home; - pal::string_t nuget_packages; + pal::string_t probe_dir; pal::string_t dotnet_packages_cache; pal::string_t managed_application; @@ -28,6 +27,6 @@ struct arguments_t arguments_t(); }; -bool parse_arguments(const int argc, const pal::char_t* argv[], arguments_t& args); +bool parse_arguments(const pal::string_t& deps_path, const pal::string_t& probe_dir, host_mode_t mode, const int argc, const pal::char_t* argv[], arguments_t* args); #endif // ARGS_H diff --git a/src/corehost/cli/deps_entry.cpp b/src/corehost/cli/deps_entry.cpp new file mode 100644 index 000000000..ff5ebbbe3 --- /dev/null +++ b/src/corehost/cli/deps_entry.cpp @@ -0,0 +1,144 @@ +// 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 "pal.h" +#include "utils.h" +#include "deps_entry.h" +#include "trace.h" + +// ----------------------------------------------------------------------------- +// 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(); + + // Base directory must be present to obtain full path + if (base.empty()) + { + return false; + } + + // 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); +} diff --git a/src/corehost/cli/deps_entry.h b/src/corehost/cli/deps_entry.h new file mode 100644 index 000000000..81e30e541 --- /dev/null +++ b/src/corehost/cli/deps_entry.h @@ -0,0 +1,38 @@ +// 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_ENTRY_H_ +#define __DEPS_ENTRY_H_ + +#include +#include +#include "pal.h" + +struct deps_entry_t +{ + enum asset_types + { + runtime = 0, + resources, + native, + count + }; + + 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; +}; + +#endif // __DEPS_ENTRY_H_ diff --git a/src/corehost/cli/deps_format.cpp b/src/corehost/cli/deps_format.cpp new file mode 100644 index 000000000..f9e02ffa4 --- /dev/null +++ b/src/corehost/cli/deps_format.cpp @@ -0,0 +1,331 @@ +// 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 "deps_format.h" +#include "utils.h" +#include "trace.h" +#include +#include +#include +#include +#include +#include + +const std::array deps_json_t::s_known_asset_types = { + _X("runtime"), _X("resources"), _X("native") }; + +const deps_entry_t& deps_json_t::try_ni(const deps_entry_t& entry) const +{ + if (m_ni_entries.count(entry.asset_name)) + { + int index = m_ni_entries.at(entry.asset_name); + return m_deps_entries[deps_entry_t::asset_types::runtime][index]; + } + return entry; +} + +void deps_json_t::reconcile_libraries_with_targets( + const json_value& json, + const std::function& library_exists_fn, + const std::function&(const pal::string_t&, int)>& get_rel_paths_by_asset_type_fn) +{ + const auto& libraries = json.at(_X("libraries")).as_object(); + for (const auto& library : libraries) + { + trace::info(_X("Reconciling library %s"), library.first.c_str()); + + if (pal::to_lower(library.second.at(_X("type")).as_string()) != _X("package")) + { + trace::info(_X("Library %s is not a package"), library.first.c_str()); + continue; + } + if (!library_exists_fn(library.first)) + { + trace::info(_X("Library %s does not exist"), library.first.c_str()); + continue; + } + + const auto& properties = library.second.as_object(); + + const pal::string_t& hash = properties.at(_X("sha512")).as_string(); + bool serviceable = properties.at(_X("serviceable")).as_bool(); + + for (int i = 0; i < s_known_asset_types.size(); ++i) + { + for (const auto& rel_path : get_rel_paths_by_asset_type_fn(library.first, i)) + { + bool ni_dll = false; + auto asset_name = get_filename_without_ext(rel_path); + if (ends_with(asset_name, _X(".ni"), false)) + { + ni_dll = true; + asset_name = strip_file_ext(asset_name); + } + + deps_entry_t entry; + size_t pos = library.first.find(_X("/")); + entry.library_name = library.first.substr(0, pos); + entry.library_version = library.first.substr(pos + 1); + entry.library_type = _X("package"); + entry.library_hash = hash; + entry.asset_name = asset_name; + entry.asset_type = s_known_asset_types[i]; + entry.relative_path = rel_path; + entry.is_serviceable = serviceable; + + // TODO: Deps file does not follow spec. It uses '\\', should use '/' + replace_char(&entry.relative_path, _X('\\'), _X('/')); + + m_deps_entries[i].push_back(entry); + + if (ni_dll) + { + m_ni_entries[entry.asset_name] = m_deps_entries + [deps_entry_t::asset_types::runtime].size() - 1; + } + + trace::info(_X("Added %s %s deps entry [%d] [%s, %s, %s]"), s_known_asset_types[i], entry.asset_name.c_str(), m_deps_entries[i].size() - 1, entry.library_name.c_str(), entry.library_version.c_str(), entry.relative_path.c_str()); + + if (i == deps_entry_t::asset_types::native && + entry.asset_name == LIBCORECLR_FILENAME) + { + m_coreclr_index = m_deps_entries[i].size() - 1; + trace::verbose(_X("Found coreclr from deps %d [%s, %s, %s]"), + m_coreclr_index, + entry.library_name.c_str(), + entry.library_version.c_str(), + entry.relative_path.c_str()); + } + + } + } + } +} + +pal::string_t get_own_rid() +{ +#define _STRINGIFY(s) _X(s) +#if defined(TARGET_RUNTIME_ID) + return _STRINGIFY(TARGET_RUNTIME_ID); +#else +#error "Cannot build the host without knowing host's root RID" +#endif +} + +bool deps_json_t::perform_rid_fallback(rid_specific_assets_t* portable_assets, const rid_fallback_graph_t& rid_fallback_graph) +{ + pal::string_t host_rid = get_own_rid(); + for (auto& package : *portable_assets) + { + pal::string_t matched_rid = package.second.count(host_rid) ? host_rid : _X(""); + if (matched_rid.empty()) + { + if (rid_fallback_graph.count(host_rid) == 0) + { + trace::error(_X("Did not find fallback rids for package %s for the host rid %s"), package.first.c_str(), host_rid.c_str()); + return false; + } + const auto& fallback_rids = rid_fallback_graph.find(host_rid)->second; + auto iter = std::find_if(fallback_rids.begin(), fallback_rids.end(), [&package](const pal::string_t& rid) { + return package.second.count(rid); + }); + if (iter != fallback_rids.end()) + { + matched_rid = *iter; + } + } + + if (matched_rid.empty()) + { + package.second.clear(); + } + + for (auto iter = package.second.begin(); iter != package.second.end(); /* */) + { + if (iter->first != matched_rid) + { + trace::verbose(_X("Chose %s, so removing rid (%s) specific assets for package %s"), matched_rid.c_str(), iter->first.c_str(), package.first.c_str()); + iter = package.second.erase(iter); + } + else + { + ++iter; + } + } + } + return true; +} + + +bool deps_json_t::process_runtime_targets(const json_value& json, const pal::string_t& target_name, const rid_fallback_graph_t& rid_fallback_graph, rid_specific_assets_t* p_assets) +{ + rid_specific_assets_t& assets = *p_assets; + for (const auto& package : json.at(_X("targets")).at(target_name).as_object()) + { + const auto& targets = package.second.as_object(); + auto iter = targets.find(_X("runtimeTargets")); + if (iter == targets.end()) + { + continue; + } + + const auto& files = iter->second.as_object(); + for (const auto& file : files) + { + const auto& type = file.second.at(_X("assetType")).as_string(); + for (int i = 0; i < s_known_asset_types.size(); ++i) + { + if (pal::strcasecmp(type.c_str(), s_known_asset_types[i]) == 0) + { + const auto& rid = file.second.at(_X("rid")).as_string(); + assets[package.first][rid][i].push_back(file.first); + } + } + } + } + + if (!perform_rid_fallback(&assets, rid_fallback_graph)) + { + return false; + } + + return true; +} + +bool deps_json_t::process_targets(const json_value& json, const pal::string_t& target_name, deps_assets_t* p_assets) +{ + deps_assets_t& assets = *p_assets; + for (const auto& package : json.at(_X("targets")).at(target_name).as_object()) + { + // if (package.second.at(_X("type")).as_string() != _X("package")) continue; + const auto& asset_types = package.second.as_object(); + for (int i = 0; i < s_known_asset_types.size(); ++i) + { + auto iter = asset_types.find(s_known_asset_types[i]); + if (iter != asset_types.end()) + { + for (const auto& file : iter->second.as_object()) + { + trace::info(_X("Adding %s asset %s from %s"), s_known_asset_types[i], file.first.c_str(), package.first.c_str()); + assets[package.first][i].push_back(file.first); + } + } + } + } + return true; +} + +bool deps_json_t::load_portable(const json_value& json, const pal::string_t& target_name, const rid_fallback_graph_t& rid_fallback_graph) +{ + rid_specific_assets_t rid_assets; + if (!process_runtime_targets(json, target_name, rid_fallback_graph, &rid_assets)) + { + return false; + } + + deps_assets_t non_rid_assets; + if (!process_targets(json, target_name, &non_rid_assets)) + { + return false; + } + + auto package_exists = [&rid_assets, &non_rid_assets](const pal::string_t& package) -> bool { + return rid_assets.count(package) || non_rid_assets.count(package); + }; + + std::vector empty; + auto get_relpaths = [&rid_assets, &non_rid_assets, &empty](const pal::string_t& package, int type_index) -> const std::vector& { + + // Is there any rid specific assets for this type ("native" or "runtime" or "resources") + if (rid_assets.count(package) && !rid_assets[package].empty()) + { + const auto& assets_by_type = rid_assets[package].begin()->second[type_index]; + if (!assets_by_type.empty()) + { + return assets_by_type; + } + + trace::verbose(_X("There were no rid specific %s asset for %s"), deps_json_t::s_known_asset_types[type_index], package.c_str()); + } + + if (non_rid_assets.count(package)) + { + return non_rid_assets[package][type_index]; + } + + return empty; + }; + + reconcile_libraries_with_targets(json, package_exists, get_relpaths); + + return true; +} + +bool deps_json_t::load_standalone(const json_value& json, const pal::string_t& target_name) +{ + deps_assets_t assets; + + if (!process_targets(json, target_name, &assets)) + { + return false; + } + + auto package_exists = [&assets](const pal::string_t& package) -> bool { + return assets.count(package); + }; + + auto get_relpaths = [&assets](const pal::string_t& package, int type_index) -> const std::vector& { + return assets[package][type_index]; + }; + + reconcile_libraries_with_targets(json, package_exists, get_relpaths); + + const auto& json_object = json.as_object(); + const auto iter = json_object.find(_X("runtimes")); + if (iter != json_object.end()) + { + for (const auto& rid : iter->second.as_object()) + { + auto& vec = m_rid_fallback_graph[rid.first]; + for (const auto& fallback : rid.second.as_array()) + { + vec.push_back(fallback.as_string()); + } + } + } + return true; +} + +// ----------------------------------------------------------------------------- +// Load the deps file and parse its "entry" lines which contain the "fields" of +// the entry. Populate an array of these entries. +// +bool deps_json_t::load(bool portable, const pal::string_t& deps_path, const rid_fallback_graph_t& rid_fallback_graph) +{ + // If file doesn't exist, then assume parsed. + if (!pal::file_exists(deps_path)) + { + return true; + } + + // Somehow the file stream could not be opened. This is an error. + pal::ifstream_t file(deps_path); + if (!file.good()) + { + return false; + } + + try + { + const auto json = json_value::parse(file); + + const auto& runtime_target = json.at(_X("runtimeTarget")); + const pal::string_t& name = runtime_target.as_string(); + + return (portable) ? load_portable(json, name, rid_fallback_graph) : load_standalone(json, name); + } + catch (...) + { + return false; + } +} diff --git a/src/corehost/cli/deps_format.h b/src/corehost/cli/deps_format.h new file mode 100644 index 000000000..e9c8a0cea --- /dev/null +++ b/src/corehost/cli/deps_format.h @@ -0,0 +1,113 @@ +// 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_FORMAT_H_ +#define __DEPS_FORMAT_H_ + +#include +#include +#include +#include "pal.h" +#include "deps_entry.h" +#include "cpprest/json.h" + +class deps_json_t +{ + typedef web::json::value json_value; + typedef std::array, deps_entry_t::asset_types::count> vectors_t; + typedef std::unordered_map str_to_vectors_map_t; + typedef std::unordered_map> str_to_vector_map_t; + + typedef str_to_vector_map_t rid_fallback_graph_t; + typedef str_to_vectors_map_t deps_assets_t; + typedef std::unordered_map rid_specific_assets_t; + +public: + deps_json_t() + : m_valid(false) + , m_coreclr_index(-1) + { + } + + deps_json_t(bool portable, const pal::string_t& deps_path) + : deps_json_t(portable, deps_path, m_rid_fallback_graph /* dummy */) + { + } + + deps_json_t(bool portable, const pal::string_t& deps_path, const rid_fallback_graph_t& graph) + : deps_json_t() + { + m_valid = load(portable, deps_path, graph); + } + + const std::vector& get_entries(deps_entry_t::asset_types type) + { + assert(type < deps_entry_t::asset_types::count); + return m_deps_entries[type]; + } + + bool has_coreclr_entry() + { + return m_coreclr_index >= 0; + } + + const deps_entry_t& get_coreclr_entry() + { + assert(has_coreclr_entry()); + return m_deps_entries[deps_entry_t::asset_types::native][m_coreclr_index]; + } + + bool is_valid() + { + return m_valid; + } + + const rid_fallback_graph_t& get_rid_fallback_graph() + { + return m_rid_fallback_graph; + } + + const deps_entry_t& try_ni(const deps_entry_t& entry) const; + +private: + bool load_standalone(const json_value& json, const pal::string_t& target_name); + bool load_portable(const json_value& json, const pal::string_t& target_name, const rid_fallback_graph_t& rid_fallback_graph); + bool load(bool portable, const pal::string_t& deps_path, const rid_fallback_graph_t& rid_fallback_graph); + bool process_runtime_targets(const json_value& json, const pal::string_t& target_name, const rid_fallback_graph_t& rid_fallback_graph, rid_specific_assets_t* p_assets); + bool process_targets(const json_value& json, const pal::string_t& target_name, deps_assets_t* p_assets); + + void reconcile_libraries_with_targets( + const json_value& json, + const std::function& library_exists_fn, + const std::function&(const pal::string_t&, int)>& get_rel_paths_by_asset_type_fn); + + bool perform_rid_fallback(rid_specific_assets_t* portable_assets, const rid_fallback_graph_t& rid_fallback_graph); + + static const std::array s_known_asset_types; + + std::vector m_deps_entries[deps_entry_t::asset_types::count]; + + std::unordered_map m_ni_entries; + rid_fallback_graph_t m_rid_fallback_graph; + int m_coreclr_index; + bool m_valid; +}; + +class deps_text_t +{ +public: + deps_text_t(const pal::string_t& deps_path) + : m_valid(load(deps_path)) + { + } + + bool load(const pal::string_t& deps_path); + bool is_valid() { return m_valid; } + const std::vector& get_entries() { return m_deps_entries; } + +private: + std::vector m_deps_entries; + bool m_valid; +}; + +#endif // __DEPS_FORMAT_H_ diff --git a/src/corehost/cli/deps_resolver.cpp b/src/corehost/cli/deps_resolver.cpp index a43fdab34..fd2901d8a 100644 --- a/src/corehost/cli/deps_resolver.cpp +++ b/src/corehost/cli/deps_resolver.cpp @@ -6,80 +6,12 @@ #include #include "trace.h" +#include "deps_format.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. @@ -106,28 +38,6 @@ void add_tpa_asset( 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_path, items, output); - return; - } -} - // ----------------------------------------------------------------------------- // A uniqifying append helper that doesn't let two "paths" to be identical in // the "output" string. @@ -157,234 +67,16 @@ void add_unique_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); - - trace::verbose(_X("Added deps entry [%d] [%s, %s, %s]"), m_deps_entries.size() - 1, entry.library_name.c_str(), entry.library_version.c_str(), entry.relative_path.c_str()); - - static_assert(std::is_same, decltype(m_deps_entries)>::value, "decltype(m_deps_entries) not a vector, took index based on size."); - if (entry.asset_type == _X("native") && - entry.asset_name == LIBCORECLR_FILENAME) - { - m_coreclr_index = m_deps_entries.size() - 1; - trace::verbose(_X("Found coreclr from deps entry [%d] [%s, %s, %s]"), - m_coreclr_index, - entry.library_name.c_str(), - entry.library_version.c_str(), - entry.relative_path.c_str()); - } - } - return true; -} - -// ----------------------------------------------------------------------------- -// 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) -{ - m_deps_path = args.deps_path; - - 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) +void deps_resolver_t::get_dir_assemblies( + const pal::string_t& dir, + const pal::string_t& dir_name, + std::unordered_map* dir_assemblies) { - trace::verbose(_X("Adding files from dir %s"), dir.c_str()); + trace::verbose(_X("Adding files from %s dir %s"), dir_name.c_str(), 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") }; @@ -412,18 +104,17 @@ void deps_resolver_t::get_local_assemblies(const pal::string_t& dir) 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)) + if (dir_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()); + trace::verbose(_X("Skipping %s because the %s already exists in %s assemblies"), file.c_str(), dir_assemblies->find(file_name)->second.c_str(), dir_name.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); + trace::verbose(_X("Adding %s to %s assembly set from %s"), file_name.c_str(), dir_name.c_str(), file_path.c_str()); + dir_assemblies->emplace(file_name, file_path); } } } @@ -440,43 +131,93 @@ pal::string_t deps_resolver_t::resolve_coreclr_dir( const pal::string_t& package_dir, const pal::string_t& package_cache_dir) { - // Runtime servicing - trace::verbose(_X("Probing for CoreCLR in servicing dir=[%s]"), m_runtime_svc.c_str()); - if (!m_runtime_svc.empty()) + auto process_coreclr = [&] + (bool is_portable, const pal::string_t& deps_dir, deps_json_t* deps) -> pal::string_t { - pal::string_t svc_clr = m_runtime_svc; - append_path(&svc_clr, _X("runtime")); - append_path(&svc_clr, _X("coreclr")); + pal::string_t candidate; - if (coreclr_exists_in_dir(svc_clr)) + // Servicing override. + if (deps->has_coreclr_entry()) { - return svc_clr; + const deps_entry_t& entry = deps->get_coreclr_entry(); + trace::verbose(_X("Probing for CoreCLR package=[%s][%s] in servicing"), entry.library_name.c_str(), entry.library_version.c_str()); + if (entry.is_serviceable && m_svc.find_redirection(entry.library_name, entry.library_version, entry.relative_path, &candidate)) + { + return get_directory(candidate); + } + } + else + { + trace::verbose(_X("Deps has no coreclr entry.")); } - } - // Package cache. - trace::verbose(_X("Probing for CoreCLR in package cache=[%s] deps index: [%d]"), package_cache_dir.c_str(), m_coreclr_index); - pal::string_t coreclr_cache; - if (m_coreclr_index >= 0 && !package_cache_dir.empty() && - m_deps_entries[m_coreclr_index].to_hash_matched_path(package_cache_dir, &coreclr_cache)) - { - return get_directory(coreclr_cache); - } + // Package cache. + pal::string_t coreclr_cache; + if (!package_cache_dir.empty()) + { + if (deps->has_coreclr_entry()) + { + const deps_entry_t& entry = deps->get_coreclr_entry(); + trace::verbose(_X("Probing for CoreCLR package=[%s][%s] in package cache=[%s]"), entry.library_name.c_str(), entry.library_version.c_str(), package_cache_dir.c_str()); + if (entry.to_hash_matched_path(package_cache_dir, &coreclr_cache)) + { + return get_directory(coreclr_cache); + } + } + } - // App dir. - trace::verbose(_X("Probing for CoreCLR in app directory=[%s]"), app_dir.c_str()); - if (coreclr_exists_in_dir(app_dir)) - { - return app_dir; - } + // Deps directory: lookup relative path if portable, else look sxs. + if (is_portable) + { + pal::string_t coreclr_portable; + if (deps->has_coreclr_entry()) + { + const deps_entry_t& entry = deps->get_coreclr_entry(); + trace::verbose(_X("Probing for CoreCLR package=[%s][%s] in portable app dir=[%s]"), entry.library_name.c_str(), entry.library_version.c_str(), deps_dir.c_str()); + if (entry.to_full_path(deps_dir, &coreclr_portable)) + { + return get_directory(coreclr_portable); + } + } + } + else + { + // App main dir or standalone app dir. + trace::verbose(_X("Probing for CoreCLR in deps directory=[%s]"), deps_dir.c_str()); + if (coreclr_exists_in_dir(deps_dir)) + { + return deps_dir; + } + } - // Packages dir - trace::verbose(_X("Probing for CoreCLR in packages=[%s] deps index: [%d]"), package_dir.c_str(), m_coreclr_index); - pal::string_t coreclr_package; - if (m_coreclr_index >= 0 && !package_dir.empty() && - m_deps_entries[m_coreclr_index].to_full_path(package_dir, &coreclr_package)) + // Packages dir. + pal::string_t coreclr_package; + if (!package_dir.empty()) + { + if (deps->has_coreclr_entry()) + { + const deps_entry_t& entry = deps->get_coreclr_entry(); + trace::verbose(_X("Probing for CoreCLR package=[%s][%s] in packages dir=[%s]"), entry.library_name.c_str(), entry.library_version.c_str(), package_dir.c_str()); + if (entry.to_full_path(package_dir, &coreclr_package)) + { + return get_directory(coreclr_package); + } + } + } + + return pal::string_t(); + }; + + trace::info(_X("--- Starting CoreCLR Proble from app deps.json")); + pal::string_t clr_dir = process_coreclr(m_portable, app_dir, m_deps.get()); + if (clr_dir.empty() && m_portable) { - return get_directory(coreclr_package); + trace::info(_X("--- Starting CoreCLR Proble from FX deps.json")); + clr_dir = process_coreclr(false, m_fx_dir, m_fx_deps.get()); + } + if (!clr_dir.empty()) + { + return clr_dir; } // Use platform-specific search algorithm @@ -516,19 +257,33 @@ void deps_resolver_t::resolve_tpa_list( const pal::string_t& clr_dir, pal::string_t* output) { + std::vector empty(0); + + pal::string_t ni_package_cache_dir; + if (!package_cache_dir.empty()) + { + ni_package_cache_dir = package_cache_dir; + append_path(&ni_package_cache_dir, get_arch()); + } + // Obtain the local assemblies in the app dir. - get_local_assemblies(app_dir); + if (m_portable) + { + get_dir_assemblies(m_fx_dir, _X("fx"), &m_sxs_assemblies); + } + else + { + get_dir_assemblies(app_dir, _X("local"), &m_sxs_assemblies); + } std::set items; - add_mscorlib_to_tpa(clr_dir, &items, output); - - for (const deps_entry_t& entry : m_deps_entries) + auto process_entry = [&](bool is_portable, deps_json_t* deps, const deps_entry_t& entry) { // Is this asset a "runtime" type? - if (entry.asset_type != _X("runtime") || items.count(entry.asset_name)) + if (items.count(entry.asset_name)) { - continue; + return; } pal::string_t candidate; @@ -539,45 +294,63 @@ void deps_resolver_t::resolve_tpa_list( { add_tpa_asset(entry.asset_name, candidate, &items, output); } - // Is this entry present in the secondary package cache? + // Is an NI image for this entry present in the secondary package cache? + else if (entry.to_hash_matched_path(ni_package_cache_dir, &candidate)) + { + add_tpa_asset(entry.asset_name, candidate, &items, output); + } + // Is this entry present in the secondary package cache? (note: no .ni extension) else if (entry.to_hash_matched_path(package_cache_dir, &candidate)) { add_tpa_asset(entry.asset_name, candidate, &items, output); } // Is this entry present locally? - else if (m_local_assemblies.count(entry.asset_name)) + else if (!is_portable && m_sxs_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); + add_tpa_asset(entry.asset_name, m_sxs_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)) + // The app is portable so the asset should be picked up from relative subpath. + else if (is_portable && deps->try_ni(entry).to_full_path(app_dir, &candidate)) { add_tpa_asset(entry.asset_name, candidate, &items, output); } - } + // Is this entry present in the package restore dir? + else if (!package_dir.empty() && deps->try_ni(entry).to_full_path(package_dir, &candidate)) + { + add_tpa_asset(entry.asset_name, candidate, &items, output); + } + }; + + const auto& deps_entries = m_deps->get_entries(deps_entry_t::asset_types::runtime); + std::for_each(deps_entries.begin(), deps_entries.end(), [&](const deps_entry_t& entry) { + process_entry(m_portable, m_deps.get(), entry); + }); + const auto& fx_entries = m_portable ? m_fx_deps->get_entries(deps_entry_t::asset_types::runtime) : empty; + std::for_each(fx_entries.begin(), fx_entries.end(), [&](const deps_entry_t& entry) { + process_entry(false, m_fx_deps.get(), entry); + }); // 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) + for (const auto& kv : m_sxs_assemblies) { add_tpa_asset(kv.first, kv.second, &items, output); } } // ----------------------------------------------------------------------------- -// Resolve the directories order for culture/native lookup +// Resolve the directories order for resources/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 +// for both native images and resources specific resource images. Lookup for +// resources 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" +// supports "resources" 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 @@ -594,60 +367,89 @@ void deps_resolver_t::resolve_probe_dirs( const pal::string_t& clr_dir, pal::string_t* output) { - assert(asset_type == _X("culture") || asset_type == _X("native")); + assert(asset_type == _X("resources") || asset_type == _X("native")); - // For culture assemblies, we need to provide the base directory of the culture path. + // For resources assemblies, we need to provide the base directory of the resources path. // For example: .../Foo/en-US/Bar.dll, then, the resolved path is .../Foo - std::function culture = [] (const pal::string_t& str) { + std::function resources = [] (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::function& action = (asset_type == _X("resources")) ? resources : native; + deps_entry_t::asset_types entry_type = (asset_type == _X("resources")) ? deps_entry_t::asset_types::resources : deps_entry_t::asset_types::native; std::set items; + std::vector empty(0); + const auto& entries = m_deps->get_entries(entry_type); + const auto& fx_entries = m_portable ? m_fx_deps->get_entries(entry_type) : empty; + // 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) + auto add_serviced_entry = [&](const deps_entry_t& entry) { pal::string_t redirection_path; - if (entry.is_serviceable && entry.asset_type == asset_type && entry.library_type == _X("Package") && + if (entry.is_serviceable && 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); } - } + }; + + std::for_each(entries.begin(), entries.end(), add_serviced_entry); + std::for_each(fx_entries.begin(), fx_entries.end(), add_serviced_entry); pal::string_t candidate; // Take care of the secondary cache path if (!package_cache_dir.empty()) { - for (const deps_entry_t& entry : m_deps_entries) + auto add_package_cache_entry = [&](const deps_entry_t& entry) { - if (entry.asset_type == asset_type && entry.to_hash_matched_path(package_cache_dir, &candidate)) + if (entry.to_hash_matched_path(package_cache_dir, &candidate)) { add_unique_path(asset_type, action(candidate), &items, output); } - } + }; + std::for_each(entries.begin(), entries.end(), add_package_cache_entry); + std::for_each(fx_entries.begin(), fx_entries.end(), add_package_cache_entry); } // App local path add_unique_path(asset_type, app_dir, &items, output); - // Take care of the package restore path - if (!package_dir.empty()) + // For portable path, the app relative directory must be used. + if (m_portable) { - for (const deps_entry_t& entry : m_deps_entries) + std::for_each(entries.begin(), entries.end(), [&](const deps_entry_t& entry) { if (entry.asset_type == asset_type && entry.to_full_path(package_dir, &candidate)) { add_unique_path(asset_type, action(candidate), &items, output); } - } + }); + } + + // FX path if present + if (!m_fx_dir.empty()) + { + add_unique_path(asset_type, m_fx_dir, &items, output); + } + + // Take care of the package restore path + if (!package_dir.empty()) + { + auto add_packages_entry = [&](const deps_entry_t& entry) + { + if (entry.asset_type == asset_type && entry.to_full_path(package_dir, &candidate)) + { + add_unique_path(asset_type, action(candidate), &items, output); + } + }; + std::for_each(entries.begin(), entries.end(), add_packages_entry); + std::for_each(fx_entries.begin(), fx_entries.end(), add_packages_entry); } // CLR path @@ -656,7 +458,7 @@ void deps_resolver_t::resolve_probe_dirs( // ----------------------------------------------------------------------------- -// Entrypoint to resolve TPA, native and culture path ordering to pass to CoreCLR. +// Entrypoint to resolve TPA, native and resources path ordering to pass to CoreCLR. // // Parameters: // app_dir - The application local directory @@ -676,6 +478,6 @@ bool deps_resolver_t::resolve_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); + resolve_probe_dirs(_X("resources"), app_dir, package_dir, package_cache_dir, clr_dir, &probe_paths->resources); return true; } diff --git a/src/corehost/cli/deps_resolver.h b/src/corehost/cli/deps_resolver.h index 309fce686..ce3d7c429 100644 --- a/src/corehost/cli/deps_resolver.h +++ b/src/corehost/cli/deps_resolver.h @@ -8,48 +8,45 @@ #include "pal.h" #include "trace.h" - +#include "deps_format.h" +#include "deps_entry.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; -}; +#include "runtime_config.h" // Probe paths to be resolved for ordering struct probe_paths_t { pal::string_t tpa; pal::string_t native; - pal::string_t culture; + pal::string_t resources; }; class deps_resolver_t { public: - deps_resolver_t(const arguments_t& args) + deps_resolver_t(const pal::string_t& fx_dir, const runtime_config_t* config, const arguments_t& args) : m_svc(args.dotnet_servicing) - , m_runtime_svc(args.dotnet_runtime_servicing) + , m_fx_dir(fx_dir) , m_coreclr_index(-1) + , m_portable(config->get_portable()) + , m_deps(nullptr) + , m_fx_deps(nullptr) { - m_deps_valid = parse_deps_file(args); + m_deps_file = args.deps_path; + if (m_portable) + { + m_fx_deps_file = get_fx_deps(fx_dir, config->get_fx_name()); + m_fx_deps = std::unique_ptr(new deps_json_t(false, m_fx_deps_file)); + m_deps = std::unique_ptr(new deps_json_t(true, m_deps_file, m_fx_deps->get_rid_fallback_graph())); + } + else + { + m_deps = std::unique_ptr(new deps_json_t(false, m_deps_file)); + } } - bool valid() { return m_deps_valid; } + + bool valid() { return m_deps->is_valid() && (!m_portable || m_fx_deps->is_valid()); } bool resolve_probe_paths( const pal::string_t& app_dir, @@ -63,11 +60,24 @@ public: const pal::string_t& package_dir, const pal::string_t& package_cache_dir); + const pal::string_t& get_fx_deps_file() const + { + return m_fx_deps_file; + } + + const pal::string_t& get_deps_file() const + { + return m_deps_file; + } private: - bool load(); - - bool parse_deps_file(const arguments_t& args); + static pal::string_t get_fx_deps(const pal::string_t& fx_dir, const pal::string_t& fx_name) + { + pal::string_t fx_deps = fx_dir; + pal::string_t fx_deps_name = pal::to_lower(fx_name) + _X(".deps.json"); + append_path(&fx_deps, fx_deps_name.c_str()); + return fx_deps; + } // Resolve order for TPA lookup. void resolve_tpa_list( @@ -86,30 +96,42 @@ private: 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); + // Populate assemblies from the directory. + void get_dir_assemblies( + const pal::string_t& dir, + const pal::string_t& dir_name, + std::unordered_map* dir_assemblies); // Servicing index to resolve serviced assembly paths. servicing_index_t m_svc; - // Runtime servicing directory. - pal::string_t m_runtime_svc; + // Framework deps file. + pal::string_t m_fx_dir; - // 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; + // Map of simple name -> full path of local/fx assemblies populated + // in priority order of their extensions. + std::unordered_map m_sxs_assemblies; // Special entry for coreclr in the deps entries int m_coreclr_index; - // The dep file path - pal::string_t m_deps_path; + // The filepath for the app deps + pal::string_t m_deps_file; + + // The filepath for the fx deps + pal::string_t m_fx_deps_file; + + // Deps files for the fx + std::unique_ptr m_fx_deps; + + // Deps files for the app + std::unique_ptr m_deps; // Is the deps file valid bool m_deps_valid; + + // Is the deps file portable app? + bool m_portable; }; #endif // DEPS_RESOLVER_H diff --git a/src/corehost/cli/dll/CMakeLists.txt b/src/corehost/cli/dll/CMakeLists.txt index 21248df7c..fd19ec602 100644 --- a/src/corehost/cli/dll/CMakeLists.txt +++ b/src/corehost/cli/dll/CMakeLists.txt @@ -13,16 +13,24 @@ endif() include(../setup.cmake) include_directories(../../common) +include_directories(../json/casablanca/include) # CMake does not recommend using globbing since it messes with the freshness checks set(SOURCES ../../common/trace.cpp ../../common/utils.cpp - + ../libhost.cpp + ../runtime_config.cpp + ../json/casablanca/src/json/json.cpp + ../json/casablanca/src/json/json_parsing.cpp + ../json/casablanca/src/json/json_serialization.cpp + ../json/casablanca/src/utilities/asyncrt_utils.cpp ../args.cpp ../hostpolicy.cpp ../coreclr.cpp ../deps_resolver.cpp + ../deps_format.cpp + ../deps_entry.cpp ../servicing_index.cpp) @@ -32,6 +40,8 @@ else() list(APPEND SOURCES ../../common/pal.unix.cpp) endif() +add_definitions(-D_NO_ASYNCRTIMP) +add_definitions(-D_NO_PPLXIMP) add_definitions(-DCOREHOST_MAKE_DLL=1) add_library(hostpolicy SHARED ${SOURCES}) diff --git a/src/corehost/cli/fxr/CMakeLists.txt b/src/corehost/cli/fxr/CMakeLists.txt new file mode 100644 index 000000000..43e0b54ca --- /dev/null +++ b/src/corehost/cli/fxr/CMakeLists.txt @@ -0,0 +1,45 @@ +# 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. + +cmake_minimum_required (VERSION 2.6) +project(hostpolicy) + +if(WIN32) + add_compile_options($<$:/MT>) + add_compile_options($<$:/MT>) + add_compile_options($<$:/MTd>) +endif() + +include(../setup.cmake) + +include_directories(../../common) +include_directories(../json/casablanca/include) + +# CMake does not recommend using globbing since it messes with the freshness checks +set(SOURCES + ../../common/trace.cpp + ../../common/utils.cpp + ../../policy_load.cpp + ../libhost.cpp + ../runtime_config.cpp + ../json/casablanca/src/json/json.cpp + ../json/casablanca/src/json/json_parsing.cpp + ../json/casablanca/src/json/json_serialization.cpp + ../json/casablanca/src/utilities/asyncrt_utils.cpp + ./fx_ver.cpp + ./fx_muxer.cpp) + + +if(WIN32) + list(APPEND SOURCES ../../common/pal.windows.cpp) +else() + list(APPEND SOURCES ../../common/pal.unix.cpp) +endif() + +add_definitions(-D_NO_ASYNCRTIMP) +add_definitions(-D_NO_PPLXIMP) +add_definitions(-DCOREHOST_MAKE_DLL=1) + +add_library(hostfxr SHARED ${SOURCES}) + + diff --git a/src/corehost/cli/fxr/fx_muxer.cpp b/src/corehost/cli/fxr/fx_muxer.cpp new file mode 100644 index 000000000..5638043a7 --- /dev/null +++ b/src/corehost/cli/fxr/fx_muxer.cpp @@ -0,0 +1,385 @@ +// 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 "pal.h" +#include "utils.h" +#include "libhost.h" +#include "args.h" +#include "fx_ver.h" +#include "fx_muxer.h" +#include "trace.h" +#include "runtime_config.h" +#include "cpprest/json.h" +#include "corehost.h" +#include "policy_load.h" + +typedef web::json::value json_value; + +pal::string_t fx_muxer_t::resolve_fx_dir(const pal::string_t& muxer_dir, runtime_config_t* runtime) +{ + trace::verbose(_X("--- Resolving FX directory from muxer dir [%s]"), muxer_dir.c_str()); + const auto fx_name = runtime->get_fx_name(); + const auto fx_ver = runtime->get_fx_version(); + const auto roll_fwd = runtime->get_fx_roll_fwd(); + + fx_ver_t specified(-1, -1, -1); + if (!fx_ver_t::parse(fx_ver, &specified, false)) + { + trace::info(_X("The specified runtimeconfig.json version [%s] could not be parsed"), fx_ver.c_str()); + return pal::string_t(); + } + + auto fx_dir = muxer_dir; + append_path(&fx_dir, _X("shared")); + append_path(&fx_dir, fx_name.c_str()); + + // If not roll forward or if pre-release, just return. + if (!roll_fwd || specified.is_prerelease()) + { + trace::verbose(_X("Did not roll forward because rollfwd=%d and [%s] is prerelease=%d"), + roll_fwd, fx_ver.c_str(), specified.is_prerelease()); + append_path(&fx_dir, fx_ver.c_str()); + } + else + { + trace::verbose(_X("Attempting production FX roll forward starting from [%s]"), fx_ver.c_str()); + + std::vector list; + pal::readdir(fx_dir, &list); + fx_ver_t max_specified = specified; + for (const auto& version : list) + { + trace::verbose(_X("Inspecting version... [%s]"), version.c_str()); + fx_ver_t ver(-1, -1, -1); + if (fx_ver_t::parse(version, &ver, true) && + ver.get_major() == max_specified.get_major() && + ver.get_minor() == max_specified.get_minor()) + { + max_specified.set_patch(std::max(ver.get_patch(), max_specified.get_patch())); + } + } + pal::string_t max_specified_str = max_specified.as_str(); + append_path(&fx_dir, max_specified_str.c_str()); + } + + trace::verbose(_X("Chose FX version [%s]"), fx_dir.c_str()); + return pal::directory_exists(fx_dir) ? fx_dir : pal::string_t(); +} + +pal::string_t fx_muxer_t::resolve_cli_version(const pal::string_t& global_json) +{ + trace::verbose(_X("--- Resolving CLI version from global json [%s]"), global_json.c_str()); + + pal::string_t retval; + if (!pal::file_exists(global_json)) + { + trace::verbose(_X("[%s] does not exist"), global_json.c_str()); + return retval; + } + + pal::ifstream_t file(global_json); + if (!file.good()) + { + trace::verbose(_X("[%s] could not be opened"), global_json.c_str()); + return retval; + } + + try + { + const auto root = json_value::parse(file); + const auto& json = root.as_object(); + const auto sdk_iter = json.find(_X("sdk")); + if (sdk_iter == json.end() || sdk_iter->second.is_null()) + { + trace::verbose(_X("CLI '/sdk/version' field not present/null in [%s]"), global_json.c_str()); + return retval; + } + + const auto& sdk_obj = sdk_iter->second.as_object(); + const auto ver_iter = sdk_obj.find(_X("version")); + if (ver_iter == sdk_obj.end() || ver_iter->second.is_null()) + { + trace::verbose(_X("CLI 'sdk/version' field not present/null in [%s]"), global_json.c_str()); + return retval; + } + retval = ver_iter->second.as_string(); + } + catch (...) + { + trace::verbose(_X("A JSON parsing exception occurred")); + } + trace::verbose(_X("CLI version is [%s] in global json file [%s]"), retval.c_str(), global_json.c_str()); + return retval; +} + +pal::string_t resolve_sdk_version(pal::string_t sdk_path) +{ + trace::verbose(_X("--- Resolving SDK version from SDK dir [%s]"), sdk_path.c_str()); + + pal::string_t retval; + std::vector versions; + + pal::readdir(sdk_path, &versions); + fx_ver_t max_ver(-1, -1, -1); + fx_ver_t max_pre(-1, -1, -1); + for (const auto& version : versions) + { + trace::verbose(_X("Considering version... [%s]"), version.c_str()); + + fx_ver_t ver(-1, -1, -1); + if (fx_ver_t::parse(version, &ver, true)) + { + max_ver = std::max(ver, max_ver); + } + if (fx_ver_t::parse(version, &ver, false)) + { + max_pre = std::max(ver, max_pre); + } + } + + // No production, use the max pre-release. + if (max_ver == fx_ver_t(-1, -1, -1)) + { + trace::verbose(_X("No production version found, so using latest prerelease")); + max_ver = max_pre; + } + + pal::string_t max_ver_str = max_ver.as_str(); + append_path(&sdk_path, max_ver_str.c_str()); + + trace::verbose(_X("Checking if resolved SDK dir [%s] exists"), sdk_path.c_str()); + if (pal::directory_exists(sdk_path)) + { + retval = sdk_path; + } + + trace::verbose(_X("Resolved SDK dir is [%s]"), retval.c_str()); + return retval; +} + +bool fx_muxer_t::resolve_sdk_dotnet_path(const pal::string_t& own_dir, pal::string_t* cli_sdk) +{ + trace::verbose(_X("--- Resolving dotnet from working dir")); + pal::string_t cwd; + pal::string_t global; + if (pal::getcwd(&cwd)) + { + for (pal::string_t parent_dir, cur_dir = cwd; true; cur_dir = parent_dir) + { + pal::string_t file = cur_dir; + append_path(&file, _X("global.json")); + + trace::verbose(_X("Probing path [%s] for global.json"), file.c_str()); + if (pal::file_exists(file)) + { + global = file; + trace::verbose(_X("Found global.json [%s]"), global.c_str()); + break; + } + parent_dir = get_directory(cur_dir); + if (parent_dir.empty() || parent_dir.size() == cur_dir.size()) + { + trace::verbose(_X("Terminating global.json search at [%s]"), parent_dir.c_str()); + break; + } + } + } + else + { + trace::verbose(_X("Failed to obtain current working dir")); + } + + pal::string_t retval; + if (!global.empty()) + { + pal::string_t cli_version = resolve_cli_version(global); + if (!cli_version.empty()) + { + pal::string_t sdk_path = own_dir; + append_path(&sdk_path, _X("sdk")); + append_path(&sdk_path, cli_version.c_str()); + + if (pal::directory_exists(sdk_path)) + { + trace::verbose(_X("CLI directory [%s] from global.json exists"), sdk_path.c_str()); + retval = sdk_path; + } + else + { + trace::verbose(_X("CLI directory [%s] from global.json doesn't exist"), sdk_path.c_str()); + } + } + } + if (retval.empty()) + { + pal::string_t sdk_path = own_dir; + append_path(&sdk_path, _X("sdk")); + retval = resolve_sdk_version(sdk_path); + } + cli_sdk->assign(retval); + trace::verbose(_X("Found CLI SDK in: %s"), cli_sdk->c_str()); + return !retval.empty(); +} + +/* static */ +int fx_muxer_t::execute(const int argc, const pal::char_t* argv[]) +{ + trace::verbose(_X("--- Executing in muxer mode...")); + + pal::string_t own_path; + + // Get the full name of the application + if (!pal::get_own_executable_path(&own_path) || !pal::realpath(&own_path)) + { + trace::error(_X("Failed to locate current executable")); + return StatusCode::LibHostCurExeFindFailure; + } + + auto own_dir = get_directory(own_path); + + if (argc <= 1) + { + trace::error(_X("Usage: dotnet [--help | app.dll]")); + return StatusCode::InvalidArgFailure; + } + if (ends_with(argv[1], _X(".dll"), false)) + { + pal::string_t app_path = argv[1]; + + if (!pal::realpath(&app_path)) + { + trace::error(_X("Could not resolve app's full path [%s]"), app_path.c_str()); + return StatusCode::LibHostExecModeFailure; + } + + auto config_file = get_runtime_config_from_file(app_path); + runtime_config_t config(config_file); + if (!config.is_valid()) + { + trace::error(_X("Invalid runtimeconfig.json [%s]"), config.get_path().c_str()); + return StatusCode::InvalidConfigFile; + } + if (config.get_portable()) + { + trace::verbose(_X("Executing as a portable app as per config file [%s]"), config_file.c_str()); + pal::string_t fx_dir = resolve_fx_dir(own_dir, &config); + corehost_init_t init(_X(""), _X(""), fx_dir, host_mode_t::muxer, &config); + return policy_load_t::execute_app(fx_dir, &init, argc, argv); + } + else + { + trace::verbose(_X("Executing as a standlone app as per config file [%s]"), config_file.c_str()); + corehost_init_t init(_X(""), _X(""), _X(""), host_mode_t::muxer, &config); + return policy_load_t::execute_app(get_directory(app_path), &init, argc, argv); + } + } + else + { + if (pal::strcasecmp(_X("exec"), argv[1]) == 0) + { + std::vector known_opts = { _X("--depsfile"), _X("--additionalprobingpath") }; + + int num_args = 0; + std::unordered_map opts; + if (!parse_known_args(argc - 2, &argv[2], known_opts, &opts, &num_args)) + { + trace::error(_X("Failed to parse known arguments.")); + return InvalidArgFailure; + } + int cur_i = 2 + num_args; + if (cur_i >= argc) + { + trace::error(_X("Parsed known args, but need more arguments.")); + return InvalidArgFailure; + } + + // Transform dotnet exec [--additionalprobingpath path] [--depsfile file] dll [args] -> dotnet dll [args] + + std::vector new_argv(argc - cur_i + 1); // +1 for dotnet + memcpy(new_argv.data() + 1, argv + cur_i, (argc - cur_i) * sizeof(pal::char_t*)); + new_argv[0] = argv[0]; + + pal::string_t opts_deps_file = _X("--depsfile"); + pal::string_t opts_probe_path = _X("--additionalprobingpath"); + pal::string_t deps_file = opts.count(opts_deps_file) ? opts[opts_deps_file] : _X(""); + pal::string_t probe_path = opts.count(opts_probe_path) ? opts[opts_probe_path] : _X(""); + + pal::string_t app_or_deps = deps_file.empty() ? argv[cur_i] : deps_file; + auto config_file = get_runtime_config_from_file(app_or_deps); + runtime_config_t config(config_file); + if (!config.is_valid()) + { + trace::error(_X("Invalid runtimeconfig.json [%s]"), config.get_path().c_str()); + return StatusCode::InvalidConfigFile; + } + if (!deps_file.empty() && !pal::file_exists(deps_file)) + { + trace::error(_X("Deps file [%s] specified but doesn't exist"), deps_file.c_str()); + return StatusCode::InvalidArgFailure; + } + if (config.get_portable()) + { + trace::verbose(_X("Executing as a portable app as per config file [%s]"), config_file.c_str()); + pal::string_t fx_dir = resolve_fx_dir(own_dir, &config); + corehost_init_t init(deps_file, probe_path, fx_dir, host_mode_t::muxer, &config); + return policy_load_t::execute_app(fx_dir, &init, new_argv.size(), new_argv.data()); + } + else + { + trace::verbose(_X("Executing as a standalone app as per config file [%s]"), config_file.c_str()); + corehost_init_t init(deps_file, probe_path, _X(""), host_mode_t::muxer, &config); + pal::string_t impl_dir = get_directory(app_or_deps); + return policy_load_t::execute_app(impl_dir, &init, new_argv.size(), new_argv.data()); + } + } + else + { + pal::string_t sdk_dotnet; + if (!resolve_sdk_dotnet_path(own_dir, &sdk_dotnet)) + { + trace::error(_X("Could not resolve SDK directory from [%s]"), own_dir.c_str()); + return StatusCode::LibHostSdkFindFailure; + } + append_path(&sdk_dotnet, _X("dotnet.dll")); + + if (!pal::file_exists(sdk_dotnet)) + { + trace::error(_X("Could not find dotnet.dll at [%s]"), sdk_dotnet.c_str()); + return StatusCode::LibHostSdkFindFailure; + } + + // Transform dotnet [command] [args] -> dotnet [dotnet.dll] [command] [args] + + std::vector new_argv(argc + 1); + memcpy(&new_argv.data()[2], argv + 1, (argc - 1) * sizeof(pal::char_t*)); + new_argv[0] = argv[0]; + new_argv[1] = sdk_dotnet.c_str(); + + trace::verbose(_X("Using dotnet SDK dll=[%s]"), sdk_dotnet.c_str()); + + auto config_file = get_runtime_config_from_file(sdk_dotnet); + runtime_config_t config(config_file); + + if (config.get_portable()) + { + trace::verbose(_X("Executing dotnet.dll as a portable app as per config file [%s]"), config_file.c_str()); + pal::string_t fx_dir = resolve_fx_dir(own_dir, &config); + corehost_init_t init(_X(""), _X(""), fx_dir, host_mode_t::muxer, &config); + return policy_load_t::execute_app(fx_dir, &init, new_argv.size(), new_argv.data()); + } + else + { + trace::verbose(_X("Executing dotnet.dll as a standalone app as per config file [%s]"), config_file.c_str()); + corehost_init_t init(_X(""), _X(""), _X(""), host_mode_t::muxer, &config); + return policy_load_t::execute_app(get_directory(sdk_dotnet), &init, new_argv.size(), new_argv.data()); + } + } + } +} + +SHARED_API int hostfxr_main(const int argc, const pal::char_t* argv[]) +{ + trace::setup(); + return fx_muxer_t().execute(argc, argv); +} diff --git a/src/corehost/cli/fxr/fx_muxer.h b/src/corehost/cli/fxr/fx_muxer.h new file mode 100644 index 000000000..5ea4cea8b --- /dev/null +++ b/src/corehost/cli/fxr/fx_muxer.h @@ -0,0 +1,16 @@ +// 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. + +class runtime_config_t; +struct fx_ver_t; + +class fx_muxer_t +{ +public: + static int execute(const int argc, const pal::char_t* argv[]); +private: + static pal::string_t resolve_fx_dir(const pal::string_t& muxer_path, runtime_config_t* runtime); + static pal::string_t resolve_cli_version(const pal::string_t& global); + static bool resolve_sdk_dotnet_path(const pal::string_t& own_dir, pal::string_t* cli_sdk); +}; + diff --git a/src/corehost/cli/fxr/fx_ver.cpp b/src/corehost/cli/fxr/fx_ver.cpp new file mode 100644 index 000000000..7c81fb287 --- /dev/null +++ b/src/corehost/cli/fxr/fx_ver.cpp @@ -0,0 +1,168 @@ +// 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 "pal.h" +#include "fx_ver.h" + +fx_ver_t::fx_ver_t(int major, int minor, int patch, const pal::string_t& pre, const pal::string_t& build) + : m_major(major) + , m_minor(minor) + , m_patch(patch) + , m_pre(pre) + , m_build(build) +{ +} + +fx_ver_t::fx_ver_t(int major, int minor, int patch, const pal::string_t& pre) + : fx_ver_t(major, minor, patch, pre, _X("")) +{ +} + +fx_ver_t::fx_ver_t(int major, int minor, int patch) + : fx_ver_t(major, minor, patch, _X(""), _X("")) +{ +} + +bool fx_ver_t::operator ==(const fx_ver_t& b) const +{ + return compare(*this, b) == 0; +} + +bool fx_ver_t::operator !=(const fx_ver_t& b) const +{ + return !operator ==(b); +} + +bool fx_ver_t::operator <(const fx_ver_t& b) const +{ + return compare(*this, b) < 0; +} + +bool fx_ver_t::operator >(const fx_ver_t& b) const +{ + return compare(*this, b) > 0; +} + +pal::string_t fx_ver_t::as_str() +{ + pal::stringstream_t stream; + stream << m_major << _X(".") << m_minor << _X(".") << m_patch; + if (!m_pre.empty()) + { + stream << m_pre; + } + if (!m_build.empty()) + { + stream << _X("+") << m_build; + } + return stream.str(); +} + +/* static */ +int fx_ver_t::compare(const fx_ver_t&a, const fx_ver_t& b, bool ignore_build) +{ + // compare(u.v.w-p+b, x.y.z-q+c) + return + (a.m_major == b.m_major) + ? ((a.m_minor == b.m_minor) + ? ((a.m_patch == b.m_patch) + ? ((a.m_pre.empty() == b.m_pre.empty()) + ? ((a.m_pre.empty()) + ? (ignore_build ? 0 : a.m_build.compare(b.m_build)) + : a.m_pre.compare(b.m_pre)) + : a.m_pre.empty() ? 1 : -1) + : (a.m_patch > b.m_patch ? 1 : -1)) + : (a.m_minor > b.m_minor ? 1 : -1)) + : ((a.m_major > b.m_major) ? 1 : -1) + ; +} + +bool try_stou(const pal::string_t& str, unsigned* num) +{ + if (str.empty()) + { + return false; + } + if (str.find_first_not_of(_X("0123456789")) != pal::string_t::npos) + { + return false; + } + *num = (unsigned) std::stoul(str); + return true; +} + +bool parse_internal(const pal::string_t& ver, fx_ver_t* fx_ver, bool parse_only_production) +{ + size_t maj_start = 0; + size_t maj_sep = ver.find(_X('.')); + if (maj_sep == pal::string_t::npos) + { + return false; + } + unsigned major = 0; + if (!try_stou(ver.substr(maj_start, maj_sep), &major)) + { + return false; + } + + size_t min_start = maj_sep + 1; + size_t min_sep = ver.find(_X('.'), min_start); + if (min_sep == pal::string_t::npos) + { + return false; + } + + unsigned minor = 0; + if (!try_stou(ver.substr(min_start, min_sep - min_start), &minor)) + { + return false; + } + + unsigned patch = 0; + size_t pat_start = min_sep + 1; + size_t pat_sep = ver.find_first_not_of(_X("0123456789"), pat_start); + if (pat_sep == pal::string_t::npos) + { + if (!try_stou(ver.substr(pat_start), &patch)) + { + return false; + } + + *fx_ver = fx_ver_t(major, minor, patch); + return true; + } + + if (parse_only_production) + { + // This is a prerelease or has build suffix. + return false; + } + + if (!try_stou(ver.substr(pat_start, pat_sep - pat_start), &patch)) + { + return false; + } + + size_t pre_start = pat_sep; + size_t pre_sep = ver.find(_X('+'), pre_start); + if (pre_sep == pal::string_t::npos) + { + *fx_ver = fx_ver_t(major, minor, patch, ver.substr(pre_start)); + return true; + } + else + { + size_t build_start = pre_sep + 1; + *fx_ver = fx_ver_t(major, minor, patch, ver.substr(pre_start, pre_sep - pre_start), ver.substr(build_start)); + return true; + } +} + +/* static */ +bool fx_ver_t::parse(const pal::string_t& ver, fx_ver_t* fx_ver, bool parse_only_production) +{ + bool valid = parse_internal(ver, fx_ver, parse_only_production); + assert(!valid || fx_ver->as_str() == ver); + return valid; +} diff --git a/src/corehost/cli/fxr/fx_ver.h b/src/corehost/cli/fxr/fx_ver.h new file mode 100644 index 000000000..180426737 --- /dev/null +++ b/src/corehost/cli/fxr/fx_ver.h @@ -0,0 +1,40 @@ +// 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 "pal.h" + +struct fx_ver_t +{ + fx_ver_t(int major, int minor, int patch); + fx_ver_t(int major, int minor, int patch, const pal::string_t& pre); + fx_ver_t(int major, int minor, int patch, const pal::string_t& pre, const pal::string_t& build); + + int get_major() { return m_major; } + int get_minor() { return m_minor; } + int get_patch() { return m_patch; } + + void set_major(int m) { m_major = m; } + void set_minor(int m) { m_minor = m; } + void set_patch(int p) { m_patch = p; } + + bool is_prerelease() { return !m_pre.empty(); } + + pal::string_t as_str(); + + bool operator ==(const fx_ver_t& b) const; + bool operator !=(const fx_ver_t& b) const; + bool operator <(const fx_ver_t& b) const; + bool operator >(const fx_ver_t& b) const; + + static bool parse(const pal::string_t& ver, fx_ver_t* fx_ver, bool parse_only_production = false); + +private: + int m_major; + int m_minor; + int m_patch; + pal::string_t m_pre; + pal::string_t m_build; + + static int compare(const fx_ver_t&a, const fx_ver_t& b, bool ignore_build = false); +}; + diff --git a/src/corehost/cli/hostpolicy.cpp b/src/corehost/cli/hostpolicy.cpp index b087357c8..7cf161fba 100644 --- a/src/corehost/cli/hostpolicy.cpp +++ b/src/corehost/cli/hostpolicy.cpp @@ -5,25 +5,21 @@ #include "args.h" #include "trace.h" #include "deps_resolver.h" +#include "fx_muxer.h" #include "utils.h" #include "coreclr.h" +#include "cpprest/json.h" +#include "libhost.h" +#include "error_codes.h" -enum StatusCode -{ - // 0x80 prefix to distinguish from corehost main's error codes. - InvalidArgFailure = 0x81, - CoreClrResolveFailure = 0x82, - CoreClrBindFailure = 0x83, - CoreClrInitFailure = 0x84, - CoreClrExeFailure = 0x85, - ResolverInitFailure = 0x86, - ResolverResolveFailure = 0x87, -}; -int run(const arguments_t& args) +corehost_init_t* g_init = nullptr; + +int run(const corehost_init_t* init, const runtime_config_t& config, const arguments_t& args) { // Load the deps resolver - deps_resolver_t resolver(args); + deps_resolver_t resolver(init->fx_dir(), &config, args); + if (!resolver.valid()) { trace::error(_X("Invalid .deps file")); @@ -31,8 +27,8 @@ int run(const arguments_t& args) } // Add packages directory - pal::string_t packages_dir = args.nuget_packages; - if (!pal::directory_exists(packages_dir)) + pal::string_t packages_dir = init->probe_dir(); + if (packages_dir.empty() || !pal::directory_exists(packages_dir)) { (void)pal::get_default_packages_directory(&packages_dir); } @@ -55,6 +51,8 @@ int run(const arguments_t& args) return StatusCode::ResolverResolveFailure; } + // TODO: config.get_runtime_properties(); + // Build CoreCLR properties const char* property_keys[] = { "TRUSTED_PLATFORM_ASSEMBLIES", @@ -63,20 +61,22 @@ int run(const arguments_t& args) "NATIVE_DLL_SEARCH_DIRECTORIES", "PLATFORM_RESOURCE_ROOTS", "AppDomainCompatSwitch", - // TODO: pipe this from corehost.json "SERVER_GC", // Workaround: mscorlib does not resolve symlinks for AppContext.BaseDirectory dotnet/coreclr/issues/2128 "APP_CONTEXT_BASE_DIRECTORY", + "APP_CONTEXT_DEPS_FILES" }; 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); + auto resources_dirs_cstr = pal::to_stdstring(probe_paths.resources); // Workaround for dotnet/cli Issue #488 and #652 pal::string_t server_gc; std::string server_gc_cstr = (pal::getenv(_X("COREHOST_SERVER_GC"), &server_gc) && !server_gc.empty()) ? pal::to_stdstring(server_gc) : "0"; + + std::string deps = pal::to_stdstring(resolver.get_deps_file() + _X(";") + resolver.get_fx_deps_file()); const char* property_values[] = { // TRUSTED_PLATFORM_ASSEMBLIES @@ -88,13 +88,15 @@ int run(const arguments_t& args) // NATIVE_DLL_SEARCH_DIRECTORIES native_dirs_cstr.c_str(), // PLATFORM_RESOURCE_ROOTS - culture_dirs_cstr.c_str(), + resources_dirs_cstr.c_str(), // AppDomainCompatSwitch "UseLatestBehaviorWhenTFMNotSpecified", // SERVER_GC server_gc_cstr.c_str(), // APP_CONTEXT_BASE_DIRECTORY - app_base_cstr.c_str() + app_base_cstr.c_str(), + // APP_CONTEXT_DEPS_FILES, + deps.c_str(), }; size_t property_size = sizeof(property_keys) / sizeof(property_keys[0]); @@ -188,16 +190,44 @@ int run(const arguments_t& args) return exit_code; } +SHARED_API int corehost_load(corehost_init_t* init) +{ + g_init = init; + return 0; +} + SHARED_API int corehost_main(const int argc, const pal::char_t* argv[]) { trace::setup(); + assert(g_init); + // Take care of arguments arguments_t args; - if (!parse_arguments(argc, argv, args)) + if (!parse_arguments(g_init->deps_file(), g_init->probe_dir(), g_init->host_mode(), argc, argv, &args)) { - return StatusCode::InvalidArgFailure; + return StatusCode::LibHostInvalidArgs; } - return run(args); + if (g_init->runtime_config()) + { + return run(g_init, *g_init->runtime_config(), args); + } + else + { + auto config_path = get_runtime_config_from_file(args.managed_application); + runtime_config_t config(config_path); + if (!config.is_valid()) + { + trace::error(_X("Invalid runtimeconfig.json [%s]"), config.get_path().c_str()); + return StatusCode::InvalidConfigFile; + } + return run(g_init, config, args); + } +} + +SHARED_API int corehost_unload() +{ + g_init = nullptr; + return 0; } diff --git a/src/corehost/cli/json/casablanca/LICENSE.txt b/src/corehost/cli/json/casablanca/LICENSE.txt new file mode 100644 index 000000000..314f8097f --- /dev/null +++ b/src/corehost/cli/json/casablanca/LICENSE.txt @@ -0,0 +1 @@ +https://github.com/Microsoft/cpprestsdk diff --git a/src/corehost/cli/json/casablanca/include/cpprest/asyncrt_utils.h b/src/corehost/cli/json/casablanca/include/cpprest/asyncrt_utils.h new file mode 100644 index 000000000..c100ece52 --- /dev/null +++ b/src/corehost/cli/json/casablanca/include/cpprest/asyncrt_utils.h @@ -0,0 +1,600 @@ +/*** +* ==++== +* +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* ==--== +* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +* +* Various common utilities. +* +* For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk +* +* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +****/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "cpprest/details/basic_types.h" + +#if !defined(_WIN32) || (_MSC_VER >= 1700) +#include +#endif + +#ifndef _WIN32 +//#include +#if !defined(ANDROID) && !defined(__ANDROID__) // CodePlex 269 +#include +#endif +#endif + +/// Various utilities for string conversions and date and time manipulation. +namespace utility +{ + +// Left over from VS2010 support, remains to avoid breaking. +typedef std::chrono::seconds seconds; + +/// Functions for converting to/from std::chrono::seconds to xml string. +namespace timespan +{ + /// + /// Converts a timespan/interval in seconds to xml duration string as specified by + /// http://www.w3.org/TR/xmlschema-2/#duration + /// + _ASYNCRTIMP utility::string_t __cdecl seconds_to_xml_duration(utility::seconds numSecs); + + /// + /// Converts an xml duration to timespan/interval in seconds + /// http://www.w3.org/TR/xmlschema-2/#duration + /// + _ASYNCRTIMP utility::seconds __cdecl xml_duration_to_seconds(const utility::string_t ×panString); +} + +/// Functions for Unicode string conversions. +namespace conversions +{ + /// + /// Converts a UTF-16 string to a UTF-8 string. + /// + /// A two byte character UTF-16 string. + /// A single byte character UTF-8 string. + _ASYNCRTIMP std::string __cdecl utf16_to_utf8(const utf16string &w); + + /// + /// Converts a UTF-8 string to a UTF-16 + /// + /// A single byte character UTF-8 string. + /// A two byte character UTF-16 string. + _ASYNCRTIMP utf16string __cdecl utf8_to_utf16(const std::string &s); + + /// + /// Converts a ASCII (us-ascii) string to a UTF-16 string. + /// + /// A single byte character us-ascii string. + /// A two byte character UTF-16 string. + _ASYNCRTIMP utf16string __cdecl usascii_to_utf16(const std::string &s); + + /// + /// Converts a Latin1 (iso-8859-1) string to a UTF-16 string. + /// + /// A single byte character UTF-8 string. + /// A two byte character UTF-16 string. + _ASYNCRTIMP utf16string __cdecl latin1_to_utf16(const std::string &s); + + /// + /// Converts a Latin1 (iso-8859-1) string to a UTF-8 string. + /// + /// A single byte character UTF-8 string. + /// A single byte character UTF-8 string. + _ASYNCRTIMP utf8string __cdecl latin1_to_utf8(const std::string &s); + + /// + /// Converts to a platform dependent Unicode string type. + /// + /// A single byte character UTF-8 string. + /// A platform dependent string type. + _ASYNCRTIMP utility::string_t __cdecl to_string_t(std::string &&s); + + /// + /// Converts to a platform dependent Unicode string type. + /// + /// A two byte character UTF-16 string. + /// A platform dependent string type. + _ASYNCRTIMP utility::string_t __cdecl to_string_t(utf16string &&s); + + /// + /// Converts to a platform dependent Unicode string type. + /// + /// A single byte character UTF-8 string. + /// A platform dependent string type. + _ASYNCRTIMP utility::string_t __cdecl to_string_t(const std::string &s); + + /// + /// Converts to a platform dependent Unicode string type. + /// + /// A two byte character UTF-16 string. + /// A platform dependent string type. + _ASYNCRTIMP utility::string_t __cdecl to_string_t(const utf16string &s); + + /// + /// Converts to a UTF-16 from string. + /// + /// A single byte character UTF-8 string. + /// A two byte character UTF-16 string. + _ASYNCRTIMP utf16string __cdecl to_utf16string(const std::string &value); + + /// + /// Converts to a UTF-16 from string. + /// + /// A two byte character UTF-16 string. + /// A two byte character UTF-16 string. + _ASYNCRTIMP utf16string __cdecl to_utf16string(utf16string value); + + /// + /// Converts to a UTF-8 string. + /// + /// A single byte character UTF-8 string. + /// A single byte character UTF-8 string. + _ASYNCRTIMP std::string __cdecl to_utf8string(std::string value); + + /// + /// Converts to a UTF-8 string. + /// + /// A two byte character UTF-16 string. + /// A single byte character UTF-8 string. + _ASYNCRTIMP std::string __cdecl to_utf8string(const utf16string &value); + + /// + /// Encode the given byte array into a base64 string + /// + _ASYNCRTIMP utility::string_t __cdecl to_base64(const std::vector& data); + + /// + /// Encode the given 8-byte integer into a base64 string + /// + _ASYNCRTIMP utility::string_t __cdecl to_base64(uint64_t data); + + /// + /// Decode the given base64 string to a byte array + /// + _ASYNCRTIMP std::vector __cdecl from_base64(const utility::string_t& str); + + template + utility::string_t print_string(const Source &val, const std::locale &loc) + { + utility::ostringstream_t oss; + oss.imbue(loc); + oss << val; + if (oss.bad()) + { + throw std::bad_cast(); + } + return oss.str(); + } + + template + utility::string_t print_string(const Source &val) + { + return print_string(val, std::locale()); + } + + template + Target scan_string(const utility::string_t &str, const std::locale &loc) + { + Target t; + utility::istringstream_t iss(str); + iss.imbue(loc); + iss >> t; + if (iss.bad()) + { + throw std::bad_cast(); + } + return t; + } + + template + Target scan_string(const utility::string_t &str) + { + return scan_string(str, std::locale()); + } +} + +namespace details +{ + /// + /// Cross platform RAII container for setting thread local locale. + /// + class scoped_c_thread_locale + { + public: + _ASYNCRTIMP scoped_c_thread_locale(); + _ASYNCRTIMP ~scoped_c_thread_locale(); + +#if !defined(ANDROID) && !defined(__ANDROID__) // CodePlex 269 +#ifdef _WIN32 + typedef _locale_t xplat_locale; +#else + typedef locale_t xplat_locale; +#endif + + static _ASYNCRTIMP xplat_locale __cdecl c_locale(); +#endif + private: +#ifdef _WIN32 + std::string m_prevLocale; + int m_prevThreadSetting; +#elif !(defined(ANDROID) || defined(__ANDROID__)) + locale_t m_prevLocale; +#endif + scoped_c_thread_locale(const scoped_c_thread_locale &); + scoped_c_thread_locale & operator=(const scoped_c_thread_locale &); + }; + + /// + /// Our own implementation of alpha numeric instead of std::isalnum to avoid + /// taking global lock for performance reasons. + /// + inline bool __cdecl is_alnum(char ch) + { + return (ch >= '0' && ch <= '9') + || (ch >= 'A' && ch <= 'Z') + || (ch >= 'a' && ch <= 'z'); + } + + /// + /// Simplistic implementation of make_unique. A better implementation would be based on variadic templates + /// and therefore not be compatible with Dev10. + /// + template + std::unique_ptr<_Type> make_unique() { + return std::unique_ptr<_Type>(new _Type()); + } + + template + std::unique_ptr<_Type> make_unique(_Arg1&& arg1) { + return std::unique_ptr<_Type>(new _Type(std::forward<_Arg1>(arg1))); + } + + template + std::unique_ptr<_Type> make_unique(_Arg1&& arg1, _Arg2&& arg2) { + return std::unique_ptr<_Type>(new _Type(std::forward<_Arg1>(arg1), std::forward<_Arg2>(arg2))); + } + + template + std::unique_ptr<_Type> make_unique(_Arg1&& arg1, _Arg2&& arg2, _Arg3&& arg3) { + return std::unique_ptr<_Type>(new _Type(std::forward<_Arg1>(arg1), std::forward<_Arg2>(arg2), std::forward<_Arg3>(arg3))); + } + + template + std::unique_ptr<_Type> make_unique(_Arg1&& arg1, _Arg2&& arg2, _Arg3&& arg3, _Arg4&& arg4) { + return std::unique_ptr<_Type>(new _Type(std::forward<_Arg1>(arg1), std::forward<_Arg2>(arg2), std::forward<_Arg3>(arg3), std::forward<_Arg4>(arg4))); + } + + /// + /// Cross platform utility function for performing case insensitive string comparision. + /// + /// First string to compare. + /// Second strong to compare. + /// true if the strings are equivalent, false otherwise +/* inline bool str_icmp(const utility::string_t &left, const utility::string_t &right) + { +#ifdef _WIN32 + return _wcsicmp(left.c_str(), right.c_str()) == 0; +#else + return boost::iequals(left, right); +#endif + } +*/ +#ifdef _WIN32 + +/// +/// Category error type for Windows OS errors. +/// +class windows_category_impl : public std::error_category +{ +public: + virtual const char *name() const CPPREST_NOEXCEPT { return "windows"; } + + _ASYNCRTIMP virtual std::string message(int errorCode) const CPPREST_NOEXCEPT; + + _ASYNCRTIMP virtual std::error_condition default_error_condition(int errorCode) const CPPREST_NOEXCEPT; +}; + +/// +/// Gets the one global instance of the windows error category. +/// +/// An error category instance. +_ASYNCRTIMP const std::error_category & __cdecl windows_category(); + +#else + +/// +/// Gets the one global instance of the linux error category. +/// +/// An error category instance. +_ASYNCRTIMP const std::error_category & __cdecl linux_category(); + +#endif + +/// +/// Gets the one global instance of the current platform's error category. +/// +_ASYNCRTIMP const std::error_category & __cdecl platform_category(); + +/// +/// Creates an instance of std::system_error from a OS error code. +/// +inline std::system_error __cdecl create_system_error(unsigned long errorCode) +{ + std::error_code code((int)errorCode, platform_category()); + return std::system_error(code, code.message()); +} + +/// +/// Creates a std::error_code from a OS error code. +/// +inline std::error_code __cdecl create_error_code(unsigned long errorCode) +{ + return std::error_code((int)errorCode, platform_category()); +} + +/// +/// Creates the corresponding error message from a OS error code. +/// +inline utility::string_t __cdecl create_error_message(unsigned long errorCode) +{ + return utility::conversions::to_string_t(create_error_code(errorCode).message()); +} + +} + +class datetime +{ +public: + typedef uint64_t interval_type; + + /// + /// Defines the supported date and time string formats. + /// + enum date_format { RFC_1123, ISO_8601 }; + + /// + /// Returns the current UTC time. + /// + // static _ASYNCRTIMP datetime __cdecl utc_now(); + + /// + /// An invalid UTC timestamp value. + /// + enum:interval_type { utc_timestamp_invalid = static_cast(-1) }; + + /// + /// Returns seconds since Unix/POSIX time epoch at 01-01-1970 00:00:00. + /// If time is before epoch, utc_timestamp_invalid is returned. + /// + /* + static interval_type utc_timestamp() + { + const auto seconds = utc_now().to_interval() / _secondTicks; + if (seconds >= 11644473600LL) + { + return seconds - 11644473600LL; + } + else + { + return utc_timestamp_invalid; + } + } + */ + + datetime() : m_interval(0) + { + } + + /// + /// Creates datetime from a string representing time in UTC in RFC 1123 format. + /// + /// Returns a datetime of zero if not successful. + // static _ASYNCRTIMP datetime __cdecl from_string(const utility::string_t& timestring, date_format format = RFC_1123); + + /// + /// Returns a string representation of the datetime. + /// + _ASYNCRTIMP utility::string_t to_string(date_format format = RFC_1123) const; + + /// + /// Returns the integral time value. + /// + interval_type to_interval() const + { + return m_interval; + } + + datetime operator- (interval_type value) const + { + return datetime(m_interval - value); + } + + datetime operator+ (interval_type value) const + { + return datetime(m_interval + value); + } + + bool operator== (datetime dt) const + { + return m_interval == dt.m_interval; + } + + bool operator!= (const datetime& dt) const + { + return !(*this == dt); + } + + static interval_type from_milliseconds(unsigned int milliseconds) + { + return milliseconds*_msTicks; + } + + static interval_type from_seconds(unsigned int seconds) + { + return seconds*_secondTicks; + } + + static interval_type from_minutes(unsigned int minutes) + { + return minutes*_minuteTicks; + } + + static interval_type from_hours(unsigned int hours) + { + return hours*_hourTicks; + } + + static interval_type from_days(unsigned int days) + { + return days*_dayTicks; + } + + bool is_initialized() const + { + return m_interval != 0; + } + +private: + + friend int operator- (datetime t1, datetime t2); + + static const interval_type _msTicks = static_cast(10000); + static const interval_type _secondTicks = 1000*_msTicks; + static const interval_type _minuteTicks = 60*_secondTicks; + static const interval_type _hourTicks = 60*60*_secondTicks; + static const interval_type _dayTicks = 24*60*60*_secondTicks; + + +#ifdef _WIN32 + // void* to avoid pulling in windows.h + static _ASYNCRTIMP bool __cdecl datetime::system_type_to_datetime(/*SYSTEMTIME*/ void* psysTime, uint64_t seconds, datetime * pdt); +#else + static datetime timeval_to_datetime(const timeval &time); +#endif + + // Private constructor. Use static methods to create an instance. + datetime(interval_type interval) : m_interval(interval) + { + } + + // Storing as hundreds of nanoseconds 10e-7, i.e. 1 here equals 100ns. + interval_type m_interval; +}; + +#ifndef _WIN32 + +// temporary workaround for the fact that +// utf16char is not fully supported in GCC +class cmp +{ +public: + + static int icmp(std::string left, std::string right) + { + size_t i; + for (i = 0; i < left.size(); ++i) + { + if (i == right.size()) return 1; + + auto l = cmp::tolower(left[i]); + auto r = cmp::tolower(right[i]); + if (l > r) return 1; + if (l < r) return -1; + } + if (i < right.size()) return -1; + return 0; + } + +private: + static char tolower(char c) + { + if (c >= 'A' && c <= 'Z') + return static_cast(c - 'A' + 'a'); + return c; + } +}; + +#endif + +inline int operator- (datetime t1, datetime t2) +{ + auto diff = (t1.m_interval - t2.m_interval); + + // Round it down to seconds + diff /= 10 * 1000 * 1000; + + return static_cast(diff); +} + +/* +/// +/// Nonce string generator class. +/// +class nonce_generator +{ +public: + + /// + /// Define default nonce length. + /// + enum { default_length = 32 }; + + /// + /// Nonce generator constructor. + /// + /// Length of the generated nonce string. + nonce_generator(int length=default_length) : + m_random(static_cast(utility::datetime::utc_timestamp())), + m_length(length) + {} + + /// + /// Generate a nonce string containing random alphanumeric characters (A-Za-z0-9). + /// Length of the generated string is set by length(). + /// + /// The generated nonce string. + _ASYNCRTIMP utility::string_t generate(); + + /// + /// Get length of generated nonce string. + /// + /// Nonce string length. + int length() const { return m_length; } + + /// + /// Set length of the generated nonce string. + /// + /// Lenght of nonce string. + void set_length(int length) { m_length = length; } + +private: + static const utility::string_t c_allowed_chars; + std::mt19937 m_random; + int m_length; +}; +*/ +} // namespace utility; diff --git a/src/corehost/cli/json/casablanca/include/cpprest/details/SafeInt3.hpp b/src/corehost/cli/json/casablanca/include/cpprest/details/SafeInt3.hpp new file mode 100755 index 000000000..798012bed --- /dev/null +++ b/src/corehost/cli/json/casablanca/include/cpprest/details/SafeInt3.hpp @@ -0,0 +1,7048 @@ +/*----------------------------------------------------------------------------------------------------------- +SafeInt.hpp +Version 3.0.18p + +This software is licensed under the Microsoft Public License (Ms-PL). +For more information about Microsoft open source licenses, refer to +http://www.microsoft.com/opensource/licenses.mspx + +This license governs use of the accompanying software. If you use the software, you accept this license. +If you do not accept the license, do not use the software. + +Definitions +The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here +as under U.S. copyright law. A "contribution" is the original software, or any additions or changes to +the software. A "contributor" is any person that distributes its contribution under this license. +"Licensed patents" are a contributor's patent claims that read directly on its contribution. + +Grant of Rights +(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations +in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to +reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution +or any derivative works that you create. + +(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in +section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed +patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution +in the software or derivative works of the contribution in the software. + +Conditions and Limitations +(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, + or trademarks. +(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the + software, your patent license from such contributor to the software ends automatically. +(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and + attribution notices that are present in the software. +(D) If you distribute any portion of the software in source code form, you may do so only under this license + by including a complete copy of this license with your distribution. If you distribute any portion of the + software in compiled or object code form, you may only do so under a license that complies with this license. +(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, + guarantees, or conditions. You may have additional consumer rights under your local laws which this license + cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties + of merchantability, fitness for a particular purpose and non-infringement. + + +Copyright (c) Microsoft Corporation. All rights reserved. + +This header implements an integer handling class designed to catch +unsafe integer operations + +This header compiles properly at Wall on Visual Studio, -Wall on gcc, and -Weverything on clang. + +Please read the leading comments before using the class. +---------------------------------------------------------------*/ +#pragma once + +// It is a bit tricky to sort out what compiler we are actually using, +// do this once here, and avoid cluttering the code +#define VISUAL_STUDIO_COMPILER 0 +#define CLANG_COMPILER 1 +#define GCC_COMPILER 2 +#define UNKNOWN_COMPILER -1 + +// Clang will sometimes pretend to be Visual Studio +// and does pretend to be gcc. Check it first, as nothing else pretends to be clang +#if defined __clang__ +#define SAFEINT_COMPILER CLANG_COMPILER +#elif defined __GNUC__ +#define SAFEINT_COMPILER GCC_COMPILER +#elif defined _MSC_VER +#define SAFEINT_COMPILER VISUAL_STUDIO_COMPILER +#else +#define SAFEINT_COMPILER UNKNOWN_COMPILER +#endif + +// Enable compiling with /Wall under VC +#if SAFEINT_COMPILER == VISUAL_STUDIO_COMPILER +#pragma warning( push ) +// Disable warnings coming from headers +#pragma warning( disable:4987 4820 4987 4820 ) + +#endif + +// Need this for ptrdiff_t on some compilers +#include +#include + +#if SAFEINT_COMPILER == VISUAL_STUDIO_COMPILER && defined _M_AMD64 + #include + #define SAFEINT_USE_INTRINSICS 1 +#else + #define SAFEINT_USE_INTRINSICS 0 +#endif + +#if SAFEINT_COMPILER == VISUAL_STUDIO_COMPILER +#pragma warning( pop ) +#endif + +// Various things needed for GCC +#if SAFEINT_COMPILER == GCC_COMPILER || SAFEINT_COMPILER == CLANG_COMPILER + +#define NEEDS_INT_DEFINED + +#if !defined NULL +#define NULL 0 +#endif + +// GCC warning suppression +#if SAFEINT_COMPILER == GCC_COMPILER +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-local-typedefs" +#endif + +#include + +// clang only +#if SAFEINT_COMPILER == CLANG_COMPILER + +#if __has_feature(cxx_nullptr) + #define NEEDS_NULLPTR_DEFINED 0 +#endif + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++11-long-long" +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wunused-local-typedef" +#endif + +#endif + +// If the user made a choice, respect it #if !defined +#if !defined NEEDS_NULLPTR_DEFINED + // Visual Studio 2010 and higher support this + #if SAFEINT_COMPILER == VISUAL_STUDIO_COMPILER + #if (_MSC_VER < 1600) + #define NEEDS_NULLPTR_DEFINED 1 + #else + #define NEEDS_NULLPTR_DEFINED 0 + #endif + #else + // Let everything else trigger based on whether we use c++11 or above + #if __cplusplus >= 201103L + #define NEEDS_NULLPTR_DEFINED 0 + #else + #define NEEDS_NULLPTR_DEFINED 1 + #endif + #endif +#endif + +#if NEEDS_NULLPTR_DEFINED +#define nullptr NULL +#endif + +#ifndef C_ASSERT +#define C_ASSERT(e) typedef char __C_ASSERT__[(e)?1:-1] +#endif + +// Let's test some assumptions +// We're assuming two's complement negative numbers +C_ASSERT( -1 == static_cast(0xffffffff) ); + +/************* Compiler Options ***************************************************************************************************** + +SafeInt supports several compile-time options that can change the behavior of the class. + +Compiler options: +SAFEINT_WARN_64BIT_PORTABILITY - this re-enables various warnings that happen when /Wp64 is used. Enabling this option is not + recommended. +NEEDS_INT_DEFINED - if your compiler does not support __int8, __int16, __int32 and __int64, you can enable this. +SAFEINT_ASSERT_ON_EXCEPTION - it is often easier to stop on an assert and figure out a problem than to try and figure out + how you landed in the catch block. +SafeIntDefaultExceptionHandler - if you'd like to replace the exception handlers SafeInt provides, define your replacement and + define this. Note - two built in (Windows-specific) options exist: + - SAFEINT_FAILFAST - bypasses all exception handlers, exits the app with an exception + - SAFEINT_RAISE_EXCEPTION - throws Win32 exceptions, which can be caught +SAFEINT_DISALLOW_UNSIGNED_NEGATION - Invoking the unary negation operator creates warnings, but if you'd like it to completely fail + to compile, define this. +ANSI_CONVERSIONS - This changes the class to use default comparison behavior, which may be unsafe. Enabling this + option is not recommended. +SAFEINT_DISABLE_BINARY_ASSERT - binary AND, OR or XOR operations on mixed size types can produce unexpected results. If you do + this, the default is to assert. Set this if you prefer not to assert under these conditions. +SIZE_T_CAST_NEEDED - some compilers complain if there is not a cast to size_t, others complain if there is one. + This lets you not have your compiler complain. +SAFEINT_DISABLE_SHIFT_ASSERT - Set this option if you don't want to assert when shifting more bits than the type has. Enabling + this option is not recommended. + +************************************************************************************************************************************/ + +/* +* The SafeInt class is designed to have as low an overhead as possible +* while still ensuring that all integer operations are conducted safely. +* Nearly every operator has been overloaded, with a very few exceptions. +* +* A usability-safety trade-off has been made to help ensure safety. This +* requires that every operation return either a SafeInt or a bool. If we +* allowed an operator to return a base integer type T, then the following +* can happen: +* +* char i = SafeInt(32) * 2 + SafeInt(16) * 4; +* +* The * operators take precedence, get overloaded, return a char, and then +* you have: +* +* char i = (char)64 + (char)64; //overflow! +* +* This situation would mean that safety would depend on usage, which isn't +* acceptable. +* +* One key operator that is missing is an implicit cast to type T. The reason for +* this is that if there is an implicit cast operator, then we end up with +* an ambiguous compile-time precedence. Because of this amiguity, there +* are two methods that are provided: +* +* Casting operators for every native integer type +* Version 3 note - it now compiles correctly for size_t without warnings +* +* SafeInt::Ptr() - returns the address of the internal integer +* Note - the '&' (address of) operator has been overloaded and returns +* the address of the internal integer. +* +* The SafeInt class should be used in any circumstances where ensuring +* integrity of the calculations is more important than performance. See Performance +* Notes below for additional information. +* +* Many of the conditionals will optimize out or be inlined for a release +* build (especially with /Ox), but it does have significantly more overhead, +* especially for signed numbers. If you do not _require_ negative numbers, use +* unsigned integer types - certain types of problems cannot occur, and this class +* performs most efficiently. +* +* Here's an example of when the class should ideally be used - +* +* void* AllocateMemForStructs(int StructSize, int HowMany) +* { +* SafeInt s(StructSize); +* +* s *= HowMany; +* +* return malloc(s); +* +* } +* +* Here's when it should NOT be used: +* +* void foo() +* { +* int i; +* +* for(i = 0; i < 0xffff; i++) +* .... +* } +* +* Error handling - a SafeInt class will throw exceptions if something +* objectionable happens. The exceptions are SafeIntException classes, +* which contain an enum as a code. +* +* Typical usage might be: +* +* bool foo() +* { +* SafeInt s; //note that s == 0 unless set +* +* try{ +* s *= 23; +* .... +* } +* catch(SafeIntException err) +* { +* //handle errors here +* } +* } +* +* Update for 3.0 - the exception class is now a template parameter. +* You can replace the exception class with any exception class you like. This is accomplished by: +* 1) Create a class that has the following interface: +* + template <> class YourSafeIntExceptionHandler < YourException > + { + public: + static __declspec(noreturn) void __stdcall SafeIntOnOverflow() + { + throw YourException( YourSafeIntArithmeticOverflowError ); + } + + static __declspec(noreturn) void __stdcall SafeIntOnDivZero() + { + throw YourException( YourSafeIntDivideByZeroError ); + } + }; +* +* Note that you don't have to throw C++ exceptions, you can throw Win32 exceptions, or do +* anything you like, just don't return from the call back into the code. +* +* 2) Either explicitly declare SafeInts like so: +* SafeInt< int, YourSafeIntExceptionHandler > si; +* or +* #define SafeIntDefaultExceptionHandler YourSafeIntExceptionHandler +* +* Performance: +* +* Due to the highly nested nature of this class, you can expect relatively poor +* performance in unoptimized code. In tests of optimized code vs. correct inline checks +* in native code, this class has been found to take approximately 8% more CPU time (this varies), +* most of which is due to exception handling. Solutions: +* +* 1) Compile optimized code - /Ox is best, /O2 also performs well. Interestingly, /O1 +* (optimize for size) does not work as well. +* 2) If that 8% hit is really a serious problem, walk through the code and inline the +* exact same checks as the class uses. +* 3) Some operations are more difficult than others - avoid using signed integers, and if +* possible keep them all the same size. 64-bit integers are also expensive. Mixing +* different integer sizes and types may prove expensive. Be aware that literals are +* actually ints. For best performance, cast literals to the type desired. +* +* +* Performance update +* The current version of SafeInt uses template specialization to force the compiler to invoke only the +* operator implementation needed for any given pair of types. This will dramatically improve the perf +* of debug builds. +* +* 3.0 update - not only have we maintained the specialization, there were some cases that were overly complex, +* and using some additional cases (e.g. signed __int64 and unsigned __int64) resulted in some simplification. +* Additionally, there was a lot of work done to better optimize the 64-bit multiplication. +* +* Binary Operators +* +* All of the binary operators have certain assumptions built into the class design. +* This is to ensure correctness. Notes on each class of operator follow: +* +* Arithmetic Operators (*,/,+,-,%) +* There are three possible variants: +* SafeInt< T, E > op SafeInt< T, E > +* SafeInt< T, E > op U +* U op SafeInt< T, E > +* +* The SafeInt< T, E > op SafeInt< U, E > variant is explicitly not supported, and if you try to do +* this the compiler with throw the following error: +* +* error C2593: 'operator *' is ambiguous +* +* This is because the arithmetic operators are required to return a SafeInt of some type. +* The compiler cannot know whether you'd prefer to get a type T or a type U returned. If +* you need to do this, you need to extract the value contained within one of the two using +* the casting operator. For example: +* +* SafeInt< T, E > t, result; +* SafeInt< U, E > u; +* +* result = t * (U)u; +* +* Comparison Operators +* Because each of these operators return type bool, mixing SafeInts of differing types is +* allowed. +* +* Shift Operators +* Shift operators always return the type on the left hand side of the operator. Mixed type +* operations are allowed because the return type is always known. +* +* Boolean Operators +* Like comparison operators, these overloads always return type bool, and mixed-type SafeInts +* are allowed. Additionally, specific overloads exist for type bool on both sides of the +* operator. +* +* Binary Operators +* Mixed-type operations are discouraged, however some provision has been made in order to +* enable things like: +* +* SafeInt c = 2; +* +* if(c & 0x02) +* ... +* +* The "0x02" is actually an int, and it needs to work. +* In the case of binary operations on integers smaller than 32-bit, or of mixed type, corner +* cases do exist where you could get unexpected results. In any case where SafeInt returns a different +* result than the underlying operator, it will call assert(). You should examine your code and cast things +* properly so that you are not programming with side effects. +* +* Documented issues: +* +* This header compiles correctly at /W4 using VC++ 8 (Version 14.00.50727.42) and later. +* As of this writing, I believe it will also work for VC 7.1, but not for VC 7.0 or below. +* If you need a version that will work with lower level compilers, try version 1.0.7. None +* of them work with Visual C++ 6, and gcc didn't work very well, either, though this hasn't +* been tried recently. +* +* It is strongly recommended that any code doing integer manipulation be compiled at /W4 +* - there are a number of warnings which pertain to integer manipulation enabled that are +* not enabled at /W3 (default for VC++) +* +* Perf note - postfix operators are slightly more costly than prefix operators. +* Unless you're actually assigning it to something, ++SafeInt is less expensive than SafeInt++ +* +* The comparison operator behavior in this class varies from the ANSI definition, which is +* arguably broken. As an example, consider the following: +* +* unsigned int l = 0xffffffff; +* char c = -1; +* +* if(c == l) +* printf("Why is -1 equal to 4 billion???\n"); +* +* The problem here is that c gets cast to an int, now has a value of 0xffffffff, and then gets +* cast again to an unsigned int, losing the true value. This behavior is despite the fact that +* an __int64 exists, and the following code will yield a different (and intuitively correct) +* answer: +* +* if((__int64)c == (__int64)l)) +* printf("Why is -1 equal to 4 billion???\n"); +* else +* printf("Why doesn't the compiler upcast to 64-bits when needed?\n"); +* +* Note that combinations with smaller integers won't display the problem - if you +* changed "unsigned int" above to "unsigned short", you'd get the right answer. +* +* If you prefer to retain the ANSI standard behavior insert +* #define ANSI_CONVERSIONS +* into your source. Behavior differences occur in the following cases: +* 8, 16, and 32-bit signed int, unsigned 32-bit int +* any signed int, unsigned 64-bit int +* Note - the signed int must be negative to show the problem +* +* +* Revision history: +* +* Oct 12, 2003 - Created +* Author - David LeBlanc - dleblanc@microsoft.com +* +* Oct 27, 2003 - fixed numerous items pointed out by michmarc and bdawson +* Dec 28, 2003 - 1.0 +* added support for mixed-type operations +* thanks to vikramh +* also fixed broken __int64 multiplication section +* added extended support for mixed-type operations where possible +* Jan 28, 2004 - 1.0.1 +* changed WCHAR to wchar_t +* fixed a construct in two mixed-type assignment overloads that was +* not compiling on some compilers +* Also changed name of private method to comply with standards on +* reserved names +* Thanks to Niels Dekker for the input +* Feb 12, 2004 - 1.0.2 +* Minor changes to remove dependency on Windows headers +* Consistently used __int16, __int32 and __int64 to ensure +* portability +* May 10, 2004 - 1.0.3 +* Corrected bug in one case of GreaterThan +* July 22, 2004 - 1.0.4 +* Tightened logic in addition check (saving 2 instructions) +* Pulled error handler out into function to enable user-defined replacement +* Made internal type of SafeIntException an enum (as per Niels' suggestion) +* Added casts for base integer types (as per Scott Meyers' suggestion) +* Updated usage information - see important new perf notes. +* Cleaned up several const issues (more thanks to Niels) +* +* Oct 1, 2004 - 1.0.5 +* Added support for SEH exceptions instead of C++ exceptions - Win32 only +* Made handlers for DIV0 and overflows individually overridable +* Commented out the destructor - major perf gains here +* Added cast operator for type long, since long != __int32 +* Corrected a couple of missing const modifiers +* Fixed broken >= and <= operators for type U op SafeInt< T, E > +* Nov 5, 2004 - 1.0.6 +* Implemented new logic in binary operators to resolve issues with +* implicit casts +* Fixed casting operator because char != signed char +* Defined __int32 as int instead of long +* Removed unsafe SafeInt::Value method +* Re-implemented casting operator as a result of removing Value method +* Dec 1, 2004 - 1.0.7 +* Implemented specialized operators for pointer arithmetic +* Created overloads for cases of U op= SafeInt. What you do with U +* after that may be dangerous. +* Fixed bug in corner case of MixedSizeModulus +* Fixed bug in MixedSizeMultiply and MixedSizeDivision with input of 0 +* Added throw() decorations +* +* Apr 12, 2005 - 2.0 +* Extensive revisions to leverage template specialization. +* April, 2007 Extensive revisions for version 3.0 +* Nov 22, 2009 Forked from MS internal code +* Changes needed to support gcc compiler - many thanks to Niels Dekker +* for determining not just the issues, but also suggesting fixes. +* Also updating some of the header internals to be the same as the upcoming Visual Studio version. +* +* Jan 16, 2010 64-bit gcc has long == __int64, which means that many of the existing 64-bit +* templates are over-specialized. This forces a redefinition of all the 64-bit +* multiplication routines to use pointers instead of references for return +* values. Also, let's use some intrinsics for x64 Microsoft compiler to +* reduce code size, and hopefully improve efficiency. +* +* June 21, 2014 Better support for clang, higher warning levels supported for all 3 primary supported + compilers (Visual Studio, clang, gcc). + Also started to converge the code base such that the public CodePlex version will + be a drop-in replacement for the Visual Studio version. + +* Note about code style - throughout this class, casts will be written using C-style (T), +* not C++ style static_cast< T >. This is because the class is nearly always dealing with integer +* types, and in this case static_cast and a C cast are equivalent. Given the large number of casts, +* the code is a little more readable this way. In the event a cast is needed where static_cast couldn't +* be substituted, we'll use the new templatized cast to make it explicit what the operation is doing. +* +************************************************************************************************************ +* Version 3.0 changes: +* +* 1) The exception type thrown is now replacable, and you can throw your own exception types. This should help +* those using well-developed exception classes. +* 2) The 64-bit multiplication code has had a lot of perf work done, and should be faster than 2.0. +* 3) There is now limited floating point support. You can initialize a SafeInt with a floating point type, +* and you can cast it out (or assign) to a float as well. +* 4) There is now an Align method. I noticed people use this a lot, and rarely check errors, so now you have one. +* +* Another major improvement is the addition of external functions - if you just want to check an operation, this can now happen: +* All of the following can be invoked without dealing with creating a class, or managing exceptions. This is especially handy +* for 64-bit porting, since SafeCast compiles away for a 32-bit cast from size_t to unsigned long, but checks it for 64-bit. +* +* inline bool SafeCast( const T From, U& To ) throw() +* inline bool SafeEquals( const T t, const U u ) throw() +* inline bool SafeNotEquals( const T t, const U u ) throw() +* inline bool SafeGreaterThan( const T t, const U u ) throw() +* inline bool SafeGreaterThanEquals( const T t, const U u ) throw() +* inline bool SafeLessThan( const T t, const U u ) throw() +* inline bool SafeLessThanEquals( const T t, const U u ) throw() +* inline bool SafeModulus( const T& t, const U& u, T& result ) throw() +* inline bool SafeMultiply( T t, U u, T& result ) throw() +* inline bool SafeDivide( T t, U u, T& result ) throw() +* inline bool SafeAdd( T t, U u, T& result ) throw() +* inline bool SafeSubtract( T t, U u, T& result ) throw() +* +*/ + +//use these if the compiler does not support _intXX +#ifdef NEEDS_INT_DEFINED +#define __int8 char +#define __int16 short +#define __int32 int +#define __int64 long long +#endif + +namespace msl +{ + +namespace safeint3 +{ + +// catch these to handle errors +// Currently implemented code values: +// ERROR_ARITHMETIC_OVERFLOW +// EXCEPTION_INT_DIVIDE_BY_ZERO +enum SafeIntError +{ + SafeIntNoError = 0, + SafeIntArithmeticOverflow, + SafeIntDivideByZero +}; + +} // safeint3 +} // msl + + +/* +* Error handler classes +* Using classes to deal with exceptions is going to allow the most +* flexibility, and we can mix different error handlers in the same project +* or even the same file. It isn't advisable to do this in the same function +* because a SafeInt< int, MyExceptionHandler > isn't the same thing as +* SafeInt< int, YourExceptionHander >. +* If for some reason you have to translate between the two, cast one of them back to its +* native type. +* +* To use your own exception class with SafeInt, first create your exception class, +* which may look something like the SafeIntException class below. The second step is to +* create a template specialization that implements SafeIntOnOverflow and SafeIntOnDivZero. +* For example: +* +* template <> class SafeIntExceptionHandler < YourExceptionClass > +* { +* static __declspec(noreturn) void __stdcall SafeIntOnOverflow() +* { +* throw YourExceptionClass( EXCEPTION_INT_OVERFLOW ); +* } +* +* static __declspec(noreturn) void __stdcall SafeIntOnDivZero() +* { +* throw YourExceptionClass( EXCEPTION_INT_DIVIDE_BY_ZERO ); +* } +* }; +* +* typedef SafeIntExceptionHandler < YourExceptionClass > YourSafeIntExceptionHandler +* You'd then declare your SafeInt objects like this: +* SafeInt< int, YourSafeIntExceptionHandler > +* +* Unfortunately, there is no such thing as partial template specialization in typedef +* statements, so you have three options if you find this cumbersome: +* +* 1) Create a holder class: +* +* template < typename T > +* class MySafeInt +* { +* public: +* SafeInt< T, MyExceptionClass> si; +* }; +* +* You'd then declare an instance like so: +* MySafeInt< int > i; +* +* You'd lose handy things like initialization - it would have to be initialized as: +* +* i.si = 0; +* +* 2) You could create a typedef for every int type you deal with: +* +* typedef SafeInt< int, MyExceptionClass > MySafeInt; +* typedef SafeInt< char, MyExceptionClass > MySafeChar; +* +* and so on. The second approach is probably more usable, and will just drop into code +* better, which is the original intent of the SafeInt class. +* +* 3) If you're going to consistently use a different class to handle your exceptions, +* you can override the default typedef like so: +* +* #define SafeIntDefaultExceptionHandler YourSafeIntExceptionHandler +* +* Overall, this is probably the best approach. +* */ + +// On the Microsoft compiler, violating a throw() annotation is a silent error. +// Other compilers might turn these into exceptions, and some users may want to not have throw() enabled. +// In addition, some error handlers may not throw C++ exceptions, which makes everything no throw. +#if defined SAFEINT_REMOVE_NOTHROW +#define SAFEINT_NOTHROW +#else +#define SAFEINT_NOTHROW throw() +#endif + +namespace msl +{ + +namespace safeint3 +{ + +// If you would like to use your own custom assert +// Define SAFEINT_ASSERT +#if !defined SAFEINT_ASSERT +#include +#define SAFEINT_ASSERT(x) assert(x) +#endif + +#if defined SAFEINT_ASSERT_ON_EXCEPTION + inline void SafeIntExceptionAssert() SAFEINT_NOTHROW { SAFEINT_ASSERT(false); } +#else + inline void SafeIntExceptionAssert() SAFEINT_NOTHROW {} +#endif + +#if SAFEINT_COMPILER == GCC_COMPILER || SAFEINT_COMPILER == CLANG_COMPILER + #define SAFEINT_NORETURN __attribute__((noreturn)) + #define SAFEINT_STDCALL + #define SAFEINT_VISIBLE __attribute__ ((__visibility__("default"))) + #define SAFEINT_WEAK __attribute__ ((weak)) +#else + #define SAFEINT_NORETURN __declspec(noreturn) + #define SAFEINT_STDCALL __stdcall + #define SAFEINT_VISIBLE + #define SAFEINT_WEAK +#endif + +class SAFEINT_VISIBLE SafeIntException +{ +public: + SafeIntException() SAFEINT_NOTHROW { m_code = SafeIntNoError; } + SafeIntException( SafeIntError code ) SAFEINT_NOTHROW + { + m_code = code; + } + SafeIntError m_code; +}; + +namespace SafeIntInternal +{ + // Visual Studio version of SafeInt provides for two possible error + // handlers: + // SafeIntErrorPolicy_SafeIntException - C++ exception, default if not otherwise defined + // SafeIntErrorPolicy_InvalidParameter - Calls fail fast (Windows-specific), bypasses any exception handlers, + // exits the app with a crash + template < typename E > class SafeIntExceptionHandler; + + template <> class SafeIntExceptionHandler < SafeIntException > + { + public: + + static SAFEINT_NORETURN void SAFEINT_STDCALL SafeIntOnOverflow() + { + SafeIntExceptionAssert(); + throw SafeIntException( SafeIntArithmeticOverflow ); + } + + static SAFEINT_NORETURN void SAFEINT_STDCALL SafeIntOnDivZero() + { + SafeIntExceptionAssert(); + throw SafeIntException( SafeIntDivideByZero ); + } + }; + +#if !defined _CRT_SECURE_INVALID_PARAMETER + // Calling fail fast is somewhat more robust than calling abort, + // but abort is the closest we can manage without Visual Studio support + // Need the header for abort() + #include + #define _CRT_SECURE_INVALID_PARAMETER(msg) abort() +#endif + + class SafeInt_InvalidParameter + { + public: + static SAFEINT_NORETURN void SafeIntOnOverflow() SAFEINT_NOTHROW + { + SafeIntExceptionAssert(); + _CRT_SECURE_INVALID_PARAMETER("SafeInt Arithmetic Overflow"); + } + + static SAFEINT_NORETURN void SafeIntOnDivZero() SAFEINT_NOTHROW + { + SafeIntExceptionAssert(); + _CRT_SECURE_INVALID_PARAMETER("SafeInt Divide By Zero"); + } + }; + +#if defined _WINDOWS_ + + class SafeIntWin32ExceptionHandler + { + public: + static SAFEINT_NORETURN void SAFEINT_STDCALL SafeIntOnOverflow() SAFEINT_NOTHROW + { + SafeIntExceptionAssert(); + RaiseException( static_cast(EXCEPTION_INT_OVERFLOW), EXCEPTION_NONCONTINUABLE, 0, 0); + } + + static SAFEINT_NORETURN void SAFEINT_STDCALL SafeIntOnDivZero() SAFEINT_NOTHROW + { + SafeIntExceptionAssert(); + RaiseException( static_cast(EXCEPTION_INT_DIVIDE_BY_ZERO), EXCEPTION_NONCONTINUABLE, 0, 0); + } + }; + +#endif + +} // namespace SafeIntInternal + +// both of these have cross-platform support +typedef SafeIntInternal::SafeIntExceptionHandler < SafeIntException > CPlusPlusExceptionHandler; +typedef SafeIntInternal::SafeInt_InvalidParameter InvalidParameterExceptionHandler; + +// This exception handler is no longer recommended, but is left here in order not to break existing users +#if defined _WINDOWS_ +typedef SafeIntInternal::SafeIntWin32ExceptionHandler Win32ExceptionHandler; +#endif + +// For Visual Studio compatibility +#if defined VISUAL_STUDIO_SAFEINT_COMPAT + typedef CPlusPlusExceptionHandler SafeIntErrorPolicy_SafeIntException; + typedef InvalidParameterExceptionHandler SafeIntErrorPolicy_InvalidParameter; +#endif + +// If the user hasn't defined a default exception handler, +// define one now, depending on whether they would like Win32 or C++ exceptions + +// This library will use conditional noexcept soon, but not in this release +// Some users might mix exception handlers, which is not advised, but is supported +#if !defined SafeIntDefaultExceptionHandler + #if defined SAFEINT_RAISE_EXCEPTION + #if !defined _WINDOWS_ + #error Include windows.h in order to use Win32 exceptions + #endif + + #define SafeIntDefaultExceptionHandler Win32ExceptionHandler + #elif defined SAFEINT_FAILFAST + #define SafeIntDefaultExceptionHandler InvalidParameterExceptionHandler + #else + #define SafeIntDefaultExceptionHandler CPlusPlusExceptionHandler + #if !defined SAFEINT_EXCEPTION_HANDLER_CPP + #define SAFEINT_EXCEPTION_HANDLER_CPP 1 + #endif + #endif +#endif + +#if !defined SAFEINT_EXCEPTION_HANDLER_CPP +#define SAFEINT_EXCEPTION_HANDLER_CPP 0 +#endif + +// If an error handler is chosen other than C++ exceptions, such as Win32 exceptions, fail fast, +// or abort, then all methods become no throw. Some teams track throw() annotations closely, +// and the following option provides for this. +#if SAFEINT_EXCEPTION_HANDLER_CPP +#define SAFEINT_CPP_THROW +#else +#define SAFEINT_CPP_THROW SAFEINT_NOTHROW +#endif + +// Turns out we can fool the compiler into not seeing compile-time constants with +// a simple template specialization +template < int method > class CompileConst; +template <> class CompileConst { public: static bool Value() SAFEINT_NOTHROW { return true; } }; +template <> class CompileConst { public: static bool Value() SAFEINT_NOTHROW { return false; } }; + +// The following template magic is because we're now not allowed +// to cast a float to an enum. This means that if we happen to assign +// an enum to a SafeInt of some type, it won't compile, unless we prevent +// isFloat = ( (T)( (float)1.1 ) > (T)1 ) +// from compiling in the case of an enum, which is the point of the specialization +// that follows. + +// If we have support for std, then we can do this easily, and detect enums as well +template < typename T > class NumericType; + +#if defined _LIBCPP_TYPE_TRAITS || defined _TYPE_TRAITS_ +// Continue to special case bool +template <> class NumericType { public: enum{ isBool = true, isFloat = false, isInt = false }; }; +template < typename T > class NumericType +{ + public: + enum + { + isBool = false, // We specialized out a bool + isFloat = std::is_floating_point::value, + // If it is an enum, then consider it an int type + // This does allow someone to make a SafeInt from an enum type, which is not recommended, + // but it also allows someone to add an enum value to a SafeInt, which is handy. + isInt = std::is_integral::value || std::is_enum::value + }; +}; + +#else + +template <> class NumericType { public: enum{ isBool = true, isFloat = false, isInt = false }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +#if defined SAFEINT_USE_WCHAR_T || defined _NATIVE_WCHAR_T_DEFINED +template <> class NumericType { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +#endif +template <> class NumericType { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +template <> class NumericType<__int64> { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = true, isInt = false }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = true, isInt = false }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = true, isInt = false }; }; +// Catch-all for anything not supported +template < typename T > class NumericType +{ +public: + // We have some unknown type, which could be an enum. For parity with the code that uses , + // We can try a static_cast - it if compiles, then it might be an enum, and should work. + // If it is something else that just happens to have a constructor that takes an int, and a casting operator, + // then it is possible something will go wrong, and for best results, cast it directly to an int before letting it + // interact with a SafeInt + + enum + { + isBool = false, + isFloat = false, + isInt = static_cast( static_cast(0) ) == 0 + }; +}; +#endif // type traits + +// Use this to avoid compile-time const truncation warnings +template < int fSigned, int bits > class SafeIntMinMax; + +template <> class SafeIntMinMax< true, 8 > { public: const static signed __int8 min = (-0x7f - 1); + const static signed __int8 max = 0x7f; }; +template <> class SafeIntMinMax< true, 16 > { public: const static __int16 min = ( -0x7fff - 1 ); + const static __int16 max = 0x7fff; }; +template <> class SafeIntMinMax< true, 32 > { public: const static __int32 min = ( -0x7fffffff -1 ); + const static __int32 max = 0x7fffffff; }; +template <> class SafeIntMinMax< true, 64 > { public: const static __int64 min = static_cast<__int64>(0x8000000000000000LL); + const static __int64 max = 0x7fffffffffffffffLL; }; + +template <> class SafeIntMinMax< false, 8 > { public: const static unsigned __int8 min = 0; + const static unsigned __int8 max = 0xff; }; +template <> class SafeIntMinMax< false, 16 > { public: const static unsigned __int16 min = 0; + const static unsigned __int16 max = 0xffff; }; +template <> class SafeIntMinMax< false, 32 > { public: const static unsigned __int32 min = 0; + const static unsigned __int32 max = 0xffffffff; }; +template <> class SafeIntMinMax< false, 64 > { public: const static unsigned __int64 min = 0; + const static unsigned __int64 max = 0xffffffffffffffffULL; }; + +template < typename T > class IntTraits +{ +public: + C_ASSERT( NumericType::isInt ); + enum + { + isSigned = ( (T)(-1) < 0 ), + is64Bit = ( sizeof(T) == 8 ), + is32Bit = ( sizeof(T) == 4 ), + is16Bit = ( sizeof(T) == 2 ), + is8Bit = ( sizeof(T) == 1 ), + isLT32Bit = ( sizeof(T) < 4 ), + isLT64Bit = ( sizeof(T) < 8 ), + isInt8 = ( sizeof(T) == 1 && isSigned ), + isUint8 = ( sizeof(T) == 1 && !isSigned ), + isInt16 = ( sizeof(T) == 2 && isSigned ), + isUint16 = ( sizeof(T) == 2 && !isSigned ), + isInt32 = ( sizeof(T) == 4 && isSigned ), + isUint32 = ( sizeof(T) == 4 && !isSigned ), + isInt64 = ( sizeof(T) == 8 && isSigned ), + isUint64 = ( sizeof(T) == 8 && !isSigned ), + bitCount = ( sizeof(T)*8 ), + isBool = ( (T)2 == (T)1 ) + }; + + // On version 13.10 enums cannot define __int64 values + // so we'll use const statics instead! + // These must be cast to deal with the possibility of a SafeInt being given an enum as an argument + const static T maxInt = static_cast(SafeIntMinMax< isSigned, bitCount >::max); + const static T minInt = static_cast(SafeIntMinMax< isSigned, bitCount >::min); +}; + +template < typename T > +const T IntTraits< T >::maxInt; +template < typename T > +const T IntTraits< T >::minInt; + +template < typename T, typename U > class SafeIntCompare +{ +public: + enum + { + isBothSigned = (IntTraits< T >::isSigned && IntTraits< U >::isSigned), + isBothUnsigned = (!IntTraits< T >::isSigned && !IntTraits< U >::isSigned), + isLikeSigned = ((bool)(IntTraits< T >::isSigned) == (bool)(IntTraits< U >::isSigned)), + isCastOK = ((isLikeSigned && sizeof(T) >= sizeof(U)) || + (IntTraits< T >::isSigned && sizeof(T) > sizeof(U))), + isBothLT32Bit = (IntTraits< T >::isLT32Bit && IntTraits< U >::isLT32Bit), + isBothLT64Bit = (IntTraits< T >::isLT64Bit && IntTraits< U >::isLT64Bit) + }; +}; + +//all of the arithmetic operators can be solved by the same code within +//each of these regions without resorting to compile-time constant conditionals +//most operators collapse the problem into less than the 22 zones, but this is used +//as the first cut +//using this also helps ensure that we handle all of the possible cases correctly + +template < typename T, typename U > class IntRegion +{ +public: + enum + { + //unsigned-unsigned zone + IntZone_UintLT32_UintLT32 = SafeIntCompare< T,U >::isBothUnsigned && SafeIntCompare< T,U >::isBothLT32Bit, + IntZone_Uint32_UintLT64 = SafeIntCompare< T,U >::isBothUnsigned && IntTraits< T >::is32Bit && IntTraits< U >::isLT64Bit, + IntZone_UintLT32_Uint32 = SafeIntCompare< T,U >::isBothUnsigned && IntTraits< T >::isLT32Bit && IntTraits< U >::is32Bit, + IntZone_Uint64_Uint = SafeIntCompare< T,U >::isBothUnsigned && IntTraits< T >::is64Bit, + IntZone_UintLT64_Uint64 = SafeIntCompare< T,U >::isBothUnsigned && IntTraits< T >::isLT64Bit && IntTraits< U >::is64Bit, + //unsigned-signed + IntZone_UintLT32_IntLT32 = !IntTraits< T >::isSigned && IntTraits< U >::isSigned && SafeIntCompare< T,U >::isBothLT32Bit, + IntZone_Uint32_IntLT64 = IntTraits< T >::isUint32 && IntTraits< U >::isSigned && IntTraits< U >::isLT64Bit, + IntZone_UintLT32_Int32 = !IntTraits< T >::isSigned && IntTraits< T >::isLT32Bit && IntTraits< U >::isInt32, + IntZone_Uint64_Int = IntTraits< T >::isUint64 && IntTraits< U >::isSigned && IntTraits< U >::isLT64Bit, + IntZone_UintLT64_Int64 = !IntTraits< T >::isSigned && IntTraits< T >::isLT64Bit && IntTraits< U >::isInt64, + IntZone_Uint64_Int64 = IntTraits< T >::isUint64 && IntTraits< U >::isInt64, + //signed-signed + IntZone_IntLT32_IntLT32 = SafeIntCompare< T,U >::isBothSigned && SafeIntCompare< T, U >::isBothLT32Bit, + IntZone_Int32_IntLT64 = SafeIntCompare< T,U >::isBothSigned && IntTraits< T >::is32Bit && IntTraits< U >::isLT64Bit, + IntZone_IntLT32_Int32 = SafeIntCompare< T,U >::isBothSigned && IntTraits< T >::isLT32Bit && IntTraits< U >::is32Bit, + IntZone_Int64_Int64 = SafeIntCompare< T,U >::isBothSigned && IntTraits< T >::isInt64 && IntTraits< U >::isInt64, + IntZone_Int64_Int = SafeIntCompare< T,U >::isBothSigned && IntTraits< T >::is64Bit && IntTraits< U >::isLT64Bit, + IntZone_IntLT64_Int64 = SafeIntCompare< T,U >::isBothSigned && IntTraits< T >::isLT64Bit && IntTraits< U >::is64Bit, + //signed-unsigned + IntZone_IntLT32_UintLT32 = IntTraits< T >::isSigned && !IntTraits< U >::isSigned && SafeIntCompare< T,U >::isBothLT32Bit, + IntZone_Int32_UintLT32 = IntTraits< T >::isInt32 && !IntTraits< U >::isSigned && IntTraits< U >::isLT32Bit, + IntZone_IntLT64_Uint32 = IntTraits< T >::isSigned && IntTraits< T >::isLT64Bit && IntTraits< U >::isUint32, + IntZone_Int64_UintLT64 = IntTraits< T >::isInt64 && !IntTraits< U >::isSigned && IntTraits< U >::isLT64Bit, + IntZone_Int_Uint64 = IntTraits< T >::isSigned && IntTraits< U >::isUint64 && IntTraits< T >::isLT64Bit, + IntZone_Int64_Uint64 = IntTraits< T >::isInt64 && IntTraits< U >::isUint64 + }; +}; + + +// In all of the following functions, we have two versions +// One for SafeInt, which throws C++ (or possibly SEH) exceptions +// The non-throwing versions are for use by the helper functions that return success and failure. +// Some of the non-throwing functions are not used, but are maintained for completeness. + +// There's no real alternative to duplicating logic, but keeping the two versions +// immediately next to one another will help reduce problems + + +// useful function to help with getting the magnitude of a negative number +enum AbsMethod +{ + AbsMethodInt, + AbsMethodInt64, + AbsMethodNoop +}; + +template < typename T > +class GetAbsMethod +{ +public: + enum + { + method = IntTraits< T >::isLT64Bit && IntTraits< T >::isSigned ? AbsMethodInt : + IntTraits< T >::isInt64 ? AbsMethodInt64 : AbsMethodNoop + }; +}; + +// let's go ahead and hard-code a dependency on the +// representation of negative numbers to keep compilers from getting overly +// happy with optimizing away things like -MIN_INT. +template < typename T, int > class AbsValueHelper; + +template < typename T > class AbsValueHelper < T, AbsMethodInt> +{ +public: + static unsigned __int32 Abs( T t ) SAFEINT_NOTHROW + { + SAFEINT_ASSERT( t < 0 ); + return ~(unsigned __int32)t + 1; + } +}; + +template < typename T > class AbsValueHelper < T, AbsMethodInt64 > +{ +public: + static unsigned __int64 Abs( T t ) SAFEINT_NOTHROW + { + SAFEINT_ASSERT( t < 0 ); + return ~(unsigned __int64)t + 1; + } +}; + +template < typename T > class AbsValueHelper < T, AbsMethodNoop > +{ +public: + static T Abs( T t ) SAFEINT_NOTHROW + { + // Why are you calling Abs on an unsigned number ??? + SAFEINT_ASSERT( false ); + return t; + } +}; + +template < typename T, bool > class NegationHelper; +// Previous versions had an assert that the type being negated was 32-bit or higher +// In retrospect, this seems like something to just document +// Negation will normally upcast to int +// For example -(unsigned short)0xffff == (int)0xffff0001 +// This class will retain the type, and will truncate, which may not be what +// you wanted +// If you want normal operator casting behavior, do this: +// SafeInt ss = 0xffff; +// then: +// -(SafeInt(ss)) +// will then emit a signed int with the correct value and bitfield + +template < typename T > class NegationHelper // Signed +{ +public: + template + static T NegativeThrow( T t ) SAFEINT_CPP_THROW + { + // corner case + if( t != IntTraits< T >::minInt ) + { + // cast prevents unneeded checks in the case of small ints + return -t; + } + E::SafeIntOnOverflow(); + } + + static bool Negative( T t, T& ret ) SAFEINT_NOTHROW + { + // corner case + if( t != IntTraits< T >::minInt ) + { + // cast prevents unneeded checks in the case of small ints + ret = -t; + return true; + } + return false; + } +}; + +// Helper classes to work keep compilers from +// optimizing away negation +template < typename T > class SignedNegation; + +template <> +class SignedNegation +{ +public: + static signed __int32 Value(unsigned __int64 in) SAFEINT_NOTHROW + { + return (signed __int32)(~(unsigned __int32)in + 1); + } + + static signed __int32 Value(unsigned __int32 in) SAFEINT_NOTHROW + { + return (signed __int32)(~in + 1); + } +}; + +template <> +class SignedNegation +{ +public: + static signed __int64 Value(unsigned __int64 in) SAFEINT_NOTHROW + { + return (signed __int64)(~in + 1); + } +}; + +template < typename T > class NegationHelper // unsigned +{ +public: + template + static T NegativeThrow( T t ) SAFEINT_CPP_THROW + { +#if defined SAFEINT_DISALLOW_UNSIGNED_NEGATION + C_ASSERT( sizeof(T) == 0 ); +#endif + +#if SAFEINT_COMPILER == VISUAL_STUDIO_COMPILER +#pragma warning(push) +//this avoids warnings from the unary '-' operator being applied to unsigned numbers +#pragma warning(disable:4146) +#endif + // Note - this could be quenched on gcc + // by doing something like: + // return (T)-((__int64)t); + // but it seems like you would want a warning when doing this. + return (T)-t; + +#if SAFEINT_COMPILER == VISUAL_STUDIO_COMPILER +#pragma warning(pop) +#endif + } + + static bool Negative( T t, T& ret ) SAFEINT_NOTHROW + { + if( IntTraits::isLT32Bit ) + { + // See above + SAFEINT_ASSERT( false ); + } +#if defined SAFEINT_DISALLOW_UNSIGNED_NEGATION + C_ASSERT( sizeof(T) == 0 ); +#endif + // Do it this way to avoid warning + ret = -t; + return true; + } +}; + +//core logic to determine casting behavior +enum CastMethod +{ + CastOK = 0, + CastCheckLTZero, + CastCheckGTMax, + CastCheckSafeIntMinMaxUnsigned, + CastCheckSafeIntMinMaxSigned, + CastToFloat, + CastFromFloat, + CastToBool, + CastFromBool +}; + + +template < typename ToType, typename FromType > +class GetCastMethod +{ +public: + enum + { + method = ( IntTraits< FromType >::isBool && + !IntTraits< ToType >::isBool ) ? CastFromBool : + + ( !IntTraits< FromType >::isBool && + IntTraits< ToType >::isBool ) ? CastToBool : + + ( SafeIntCompare< ToType, FromType >::isCastOK ) ? CastOK : + + ( ( IntTraits< ToType >::isSigned && + !IntTraits< FromType >::isSigned && + sizeof( FromType ) >= sizeof( ToType ) ) || + ( SafeIntCompare< ToType, FromType >::isBothUnsigned && + sizeof( FromType ) > sizeof( ToType ) ) ) ? CastCheckGTMax : + + ( !IntTraits< ToType >::isSigned && + IntTraits< FromType >::isSigned && + sizeof( ToType ) >= sizeof( FromType ) ) ? CastCheckLTZero : + + ( !IntTraits< ToType >::isSigned ) ? CastCheckSafeIntMinMaxUnsigned + : CastCheckSafeIntMinMaxSigned + }; +}; + +template < typename FromType > class GetCastMethod < float, FromType > +{ +public: + enum{ method = CastOK }; +}; + +template < typename FromType > class GetCastMethod < double, FromType > +{ +public: + enum{ method = CastOK }; +}; + +template < typename FromType > class GetCastMethod < long double, FromType > +{ +public: + enum{ method = CastOK }; +}; + +template < typename ToType > class GetCastMethod < ToType, float > +{ +public: + enum{ method = CastFromFloat }; +}; + +template < typename ToType > class GetCastMethod < ToType, double > +{ +public: + enum{ method = CastFromFloat }; +}; + +template < typename ToType > class GetCastMethod < ToType, long double > +{ +public: + enum{ method = CastFromFloat }; +}; + +template < typename T, typename U, int > class SafeCastHelper; + +template < typename T, typename U > class SafeCastHelper < T, U, CastOK > +{ +public: + static bool Cast( U u, T& t ) SAFEINT_NOTHROW + { + t = (T)u; + return true; + } + + template < typename E > + static void CastThrow( U u, T& t ) SAFEINT_CPP_THROW + { + t = (T)u; + } +}; + +// special case floats and doubles +// tolerate loss of precision +template < typename T, typename U > class SafeCastHelper < T, U, CastFromFloat > +{ +public: + static bool Cast( U u, T& t ) SAFEINT_NOTHROW + { + if( u <= (U)IntTraits< T >::maxInt && + u >= (U)IntTraits< T >::minInt ) + { + t = (T)u; + return true; + } + return false; + } + + template < typename E > + static void CastThrow( U u, T& t ) SAFEINT_CPP_THROW + { + if( u <= (U)IntTraits< T >::maxInt && + u >= (U)IntTraits< T >::minInt ) + { + t = (T)u; + return; + } + E::SafeIntOnOverflow(); + } +}; + +// Match on any method where a bool is cast to type T +template < typename T > class SafeCastHelper < T, bool, CastFromBool > +{ +public: + static bool Cast( bool b, T& t ) SAFEINT_NOTHROW + { + t = (T)( b ? 1 : 0 ); + return true; + } + + template < typename E > + static void CastThrow( bool b, T& t ) SAFEINT_CPP_THROW + { + t = (T)( b ? 1 : 0 ); + } +}; + +template < typename T > class SafeCastHelper < bool, T, CastToBool > +{ +public: + static bool Cast( T t, bool& b ) SAFEINT_NOTHROW + { + b = !!t; + return true; + } + + template < typename E > + static void CastThrow( bool b, T& t ) SAFEINT_CPP_THROW + { + b = !!t; + } +}; + +template < typename T, typename U > class SafeCastHelper < T, U, CastCheckLTZero > +{ +public: + static bool Cast( U u, T& t ) SAFEINT_NOTHROW + { + if( u < 0 ) + return false; + + t = (T)u; + return true; + } + + template < typename E > + static void CastThrow( U u, T& t ) SAFEINT_CPP_THROW + { + if( u < 0 ) + E::SafeIntOnOverflow(); + + t = (T)u; + } +}; + +template < typename T, typename U > class SafeCastHelper < T, U, CastCheckGTMax > +{ +public: + static bool Cast( U u, T& t ) SAFEINT_NOTHROW + { + if( u > (U)IntTraits< T >::maxInt ) + return false; + + t = (T)u; + return true; + } + + template < typename E > + static void CastThrow( U u, T& t ) SAFEINT_CPP_THROW + { + if( u > (U)IntTraits< T >::maxInt ) + E::SafeIntOnOverflow(); + + t = (T)u; + } +}; + +template < typename T, typename U > class SafeCastHelper < T, U, CastCheckSafeIntMinMaxUnsigned > +{ +public: + static bool Cast( U u, T& t ) SAFEINT_NOTHROW + { + // U is signed - T could be either signed or unsigned + if( u > IntTraits< T >::maxInt || u < 0 ) + return false; + + t = (T)u; + return true; + } + + template < typename E > + static void CastThrow( U u, T& t ) SAFEINT_CPP_THROW + { + // U is signed - T could be either signed or unsigned + if( u > IntTraits< T >::maxInt || u < 0 ) + E::SafeIntOnOverflow(); + + t = (T)u; + } +}; + +template < typename T, typename U > class SafeCastHelper < T, U, CastCheckSafeIntMinMaxSigned > +{ +public: + static bool Cast( U u, T& t ) SAFEINT_NOTHROW + { + // T, U are signed + if( u > IntTraits< T >::maxInt || u < IntTraits< T >::minInt ) + return false; + + t = (T)u; + return true; + } + + template < typename E > + static void CastThrow( U u, T& t ) SAFEINT_CPP_THROW + { + //T, U are signed + if( u > IntTraits< T >::maxInt || u < IntTraits< T >::minInt ) + E::SafeIntOnOverflow(); + + t = (T)u; + } +}; + +//core logic to determine whether a comparison is valid, or needs special treatment +enum ComparisonMethod +{ + ComparisonMethod_Ok = 0, + ComparisonMethod_CastInt, + ComparisonMethod_CastInt64, + ComparisonMethod_UnsignedT, + ComparisonMethod_UnsignedU +}; + + // Note - the standard is arguably broken in the case of some integer + // conversion operations + // For example, signed char a = -1 = 0xff + // unsigned int b = 0xffffffff + // If you then test if a < b, a value-preserving cast + // is made, and you're essentially testing + // (unsigned int)a < b == false + // + // I do not think this makes sense - if you perform + // a cast to an __int64, which can clearly preserve both value and signedness + // then you get a different and intuitively correct answer + // IMHO, -1 should be less than 4 billion + // If you prefer to retain the ANSI standard behavior + // insert #define ANSI_CONVERSIONS into your source + // Behavior differences occur in the following cases: + // 8, 16, and 32-bit signed int, unsigned 32-bit int + // any signed int, unsigned 64-bit int + // Note - the signed int must be negative to show the problem + +template < typename T, typename U > +class ValidComparison +{ +public: + enum + { +#ifdef ANSI_CONVERSIONS + method = ComparisonMethod_Ok +#else + method = ( ( SafeIntCompare< T, U >::isLikeSigned ) ? ComparisonMethod_Ok : + ( ( IntTraits< T >::isSigned && sizeof(T) < 8 && sizeof(U) < 4 ) || + ( IntTraits< U >::isSigned && sizeof(T) < 4 && sizeof(U) < 8 ) ) ? ComparisonMethod_CastInt : + ( ( IntTraits< T >::isSigned && sizeof(U) < 8 ) || + ( IntTraits< U >::isSigned && sizeof(T) < 8 ) ) ? ComparisonMethod_CastInt64 : + ( !IntTraits< T >::isSigned ) ? ComparisonMethod_UnsignedT : + ComparisonMethod_UnsignedU ) +#endif + }; +}; + +template class EqualityTest; + +template < typename T, typename U > class EqualityTest< T, U, ComparisonMethod_Ok > +{ +public: + static bool IsEquals( const T t, const U u ) SAFEINT_NOTHROW { return ( t == u ); } +}; + +template < typename T, typename U > class EqualityTest< T, U, ComparisonMethod_CastInt > +{ +public: + static bool IsEquals( const T t, const U u ) SAFEINT_NOTHROW { return ( (int)t == (int)u ); } +}; + +template < typename T, typename U > class EqualityTest< T, U, ComparisonMethod_CastInt64 > +{ +public: + static bool IsEquals( const T t, const U u ) SAFEINT_NOTHROW { return ( (__int64)t == (__int64)u ); } +}; + +template < typename T, typename U > class EqualityTest< T, U, ComparisonMethod_UnsignedT > +{ +public: + static bool IsEquals( const T t, const U u ) SAFEINT_NOTHROW + { + //one operand is 32 or 64-bit unsigned, and the other is signed and the same size or smaller + if( u < 0 ) + return false; + + //else safe to cast to type T + return ( t == (T)u ); + } +}; + +template < typename T, typename U > class EqualityTest< T, U, ComparisonMethod_UnsignedU> +{ +public: + static bool IsEquals( const T t, const U u ) SAFEINT_NOTHROW + { + //one operand is 32 or 64-bit unsigned, and the other is signed and the same size or smaller + if( t < 0 ) + return false; + + //else safe to cast to type U + return ( (U)t == u ); + } +}; + +template class GreaterThanTest; + +template < typename T, typename U > class GreaterThanTest< T, U, ComparisonMethod_Ok > +{ +public: + static bool GreaterThan( const T t, const U u ) SAFEINT_NOTHROW { return ( t > u ); } +}; + +template < typename T, typename U > class GreaterThanTest< T, U, ComparisonMethod_CastInt > +{ +public: + static bool GreaterThan( const T t, const U u ) SAFEINT_NOTHROW { return ( (int)t > (int)u ); } +}; + +template < typename T, typename U > class GreaterThanTest< T, U, ComparisonMethod_CastInt64 > +{ +public: + static bool GreaterThan( const T t, const U u ) SAFEINT_NOTHROW { return ( (__int64)t > (__int64)u ); } +}; + +template < typename T, typename U > class GreaterThanTest< T, U, ComparisonMethod_UnsignedT > +{ +public: + static bool GreaterThan( const T t, const U u ) SAFEINT_NOTHROW + { + // one operand is 32 or 64-bit unsigned, and the other is signed and the same size or smaller + if( u < 0 ) + return true; + + // else safe to cast to type T + return ( t > (T)u ); + } +}; + +template < typename T, typename U > class GreaterThanTest< T, U, ComparisonMethod_UnsignedU > +{ +public: + static bool GreaterThan( const T t, const U u ) SAFEINT_NOTHROW + { + // one operand is 32 or 64-bit unsigned, and the other is signed and the same size or smaller + if( t < 0 ) + return false; + + // else safe to cast to type U + return ( (U)t > u ); + } +}; + +// Modulus is simpler than comparison, but follows much the same logic +// using this set of functions, it can't fail except in a div 0 situation +template class ModulusHelper; + +template class ModulusHelper +{ +public: + static SafeIntError Modulus( const T& t, const U& u, T& result ) SAFEINT_NOTHROW + { + if(u == 0) + return SafeIntDivideByZero; + + //trap corner case + if( CompileConst< IntTraits< U >::isSigned >::Value() ) + { + // Some compilers don't notice that this only compiles when u is signed + // Add cast to make them happy + if( u == (U)-1 ) + { + result = 0; + return SafeIntNoError; + } + } + + result = (T)(t % u); + return SafeIntNoError; + } + + template < typename E > + static void ModulusThrow( const T& t, const U& u, T& result ) SAFEINT_CPP_THROW + { + if(u == 0) + E::SafeIntOnDivZero(); + + //trap corner case + if( CompileConst< IntTraits< U >::isSigned >::Value() ) + { + if( u == (U)-1 ) + { + result = 0; + return; + } + } + + result = (T)(t % u); + } +}; + +template class ModulusHelper +{ +public: + static SafeIntError Modulus( const T& t, const U& u, T& result ) SAFEINT_NOTHROW + { + if(u == 0) + return SafeIntDivideByZero; + + //trap corner case + if( CompileConst< IntTraits< U >::isSigned >::Value() ) + { + if( u == (U)-1 ) + { + result = 0; + return SafeIntNoError; + } + } + + result = (T)(t % u); + return SafeIntNoError; + } + + template < typename E > + static void ModulusThrow( const T& t, const U& u, T& result ) SAFEINT_CPP_THROW + { + if(u == 0) + E::SafeIntOnDivZero(); + + //trap corner case + if( CompileConst< IntTraits< U >::isSigned >::Value() ) + { + if( u == (U)-1 ) + { + result = 0; + return; + } + } + + result = (T)(t % u); + } +}; + +template < typename T, typename U > class ModulusHelper< T, U, ComparisonMethod_CastInt64> +{ +public: + static SafeIntError Modulus( const T& t, const U& u, T& result ) SAFEINT_NOTHROW + { + if(u == 0) + return SafeIntDivideByZero; + + //trap corner case + if( CompileConst< IntTraits< U >::isSigned >::Value() ) + { + if( u == (U)-1 ) + { + result = 0; + return SafeIntNoError; + } + } + + result = (T)((__int64)t % (__int64)u); + return SafeIntNoError; + } + + template < typename E > + static void ModulusThrow( const T& t, const U& u, T& result ) SAFEINT_CPP_THROW + { + if(u == 0) + E::SafeIntOnDivZero(); + + if( CompileConst< IntTraits< U >::isSigned >::Value() ) + { + if( u == (U)-1 ) + { + result = 0; + return; + } + } + + result = (T)((__int64)t % (__int64)u); + } +}; + +// T is unsigned __int64, U is any signed int +template < typename T, typename U > class ModulusHelper< T, U, ComparisonMethod_UnsignedT> +{ +public: + static SafeIntError Modulus( const T& t, const U& u, T& result ) SAFEINT_NOTHROW + { + if(u == 0) + return SafeIntDivideByZero; + + // u could be negative - if so, need to convert to positive + // casts below are always safe due to the way modulus works + if(u < 0) + result = (T)(t % AbsValueHelper< U, GetAbsMethod< U >::method >::Abs(u)); + else + result = (T)(t % u); + + return SafeIntNoError; + } + + template < typename E > + static void ModulusThrow( const T& t, const U& u, T& result ) SAFEINT_CPP_THROW + { + if(u == 0) + E::SafeIntOnDivZero(); + + // u could be negative - if so, need to convert to positive + if(u < 0) + result = (T)(t % AbsValueHelper< U, GetAbsMethod< U >::method >::Abs( u )); + else + result = (T)(t % u); + } +}; + +// U is unsigned __int64, T any signed int +template < typename T, typename U > class ModulusHelper< T, U, ComparisonMethod_UnsignedU> +{ +public: + static SafeIntError Modulus( const T& t, const U& u, T& result ) SAFEINT_NOTHROW + { + if(u == 0) + return SafeIntDivideByZero; + + //t could be negative - if so, need to convert to positive + if(t < 0) + result = (T)( ~( AbsValueHelper< T, GetAbsMethod< T >::method >::Abs( t ) % u ) + 1 ); + else + result = (T)((T)t % u); + + return SafeIntNoError; + } + + template < typename E > + static void ModulusThrow( const T& t, const U& u, T& result ) SAFEINT_CPP_THROW + { + if(u == 0) + E::SafeIntOnDivZero(); + + //t could be negative - if so, need to convert to positive + if(t < 0) + result = (T)( ~( AbsValueHelper< T, GetAbsMethod< T >::method >::Abs( t ) % u ) + 1); + else + result = (T)( (T)t % u ); + } +}; + +//core logic to determine method to check multiplication +enum MultiplicationState +{ + MultiplicationState_CastInt = 0, // One or both signed, smaller than 32-bit + MultiplicationState_CastInt64, // One or both signed, smaller than 64-bit + MultiplicationState_CastUint, // Both are unsigned, smaller than 32-bit + MultiplicationState_CastUint64, // Both are unsigned, both 32-bit or smaller + MultiplicationState_Uint64Uint, // Both are unsigned, lhs 64-bit, rhs 32-bit or smaller + MultiplicationState_Uint64Uint64, // Both are unsigned int64 + MultiplicationState_Uint64Int, // lhs is unsigned int64, rhs int32 + MultiplicationState_Uint64Int64, // lhs is unsigned int64, rhs signed int64 + MultiplicationState_UintUint64, // Both are unsigned, lhs 32-bit or smaller, rhs 64-bit + MultiplicationState_UintInt64, // lhs unsigned 32-bit or less, rhs int64 + MultiplicationState_Int64Uint, // lhs int64, rhs unsigned int32 + MultiplicationState_Int64Int64, // lhs int64, rhs int64 + MultiplicationState_Int64Int, // lhs int64, rhs int32 + MultiplicationState_IntUint64, // lhs int, rhs unsigned int64 + MultiplicationState_IntInt64, // lhs int, rhs int64 + MultiplicationState_Int64Uint64, // lhs int64, rhs uint64 + MultiplicationState_Error +}; + +template < typename T, typename U > +class MultiplicationMethod +{ +public: + enum + { + // unsigned-unsigned + method = (IntRegion< T,U >::IntZone_UintLT32_UintLT32 ? MultiplicationState_CastUint : + (IntRegion< T,U >::IntZone_Uint32_UintLT64 || + IntRegion< T,U >::IntZone_UintLT32_Uint32) ? MultiplicationState_CastUint64 : + SafeIntCompare< T,U >::isBothUnsigned && + IntTraits< T >::isUint64 && IntTraits< U >::isUint64 ? MultiplicationState_Uint64Uint64 : + (IntRegion< T,U >::IntZone_Uint64_Uint) ? MultiplicationState_Uint64Uint : + (IntRegion< T,U >::IntZone_UintLT64_Uint64) ? MultiplicationState_UintUint64 : + // unsigned-signed + (IntRegion< T,U >::IntZone_UintLT32_IntLT32) ? MultiplicationState_CastInt : + (IntRegion< T,U >::IntZone_Uint32_IntLT64 || + IntRegion< T,U >::IntZone_UintLT32_Int32) ? MultiplicationState_CastInt64 : + (IntRegion< T,U >::IntZone_Uint64_Int) ? MultiplicationState_Uint64Int : + (IntRegion< T,U >::IntZone_UintLT64_Int64) ? MultiplicationState_UintInt64 : + (IntRegion< T,U >::IntZone_Uint64_Int64) ? MultiplicationState_Uint64Int64 : + // signed-signed + (IntRegion< T,U >::IntZone_IntLT32_IntLT32) ? MultiplicationState_CastInt : + (IntRegion< T,U >::IntZone_Int32_IntLT64 || + IntRegion< T,U >::IntZone_IntLT32_Int32) ? MultiplicationState_CastInt64 : + (IntRegion< T,U >::IntZone_Int64_Int64) ? MultiplicationState_Int64Int64 : + (IntRegion< T,U >::IntZone_Int64_Int) ? MultiplicationState_Int64Int : + (IntRegion< T,U >::IntZone_IntLT64_Int64) ? MultiplicationState_IntInt64 : + // signed-unsigned + (IntRegion< T,U >::IntZone_IntLT32_UintLT32) ? MultiplicationState_CastInt : + (IntRegion< T,U >::IntZone_Int32_UintLT32 || + IntRegion< T,U >::IntZone_IntLT64_Uint32) ? MultiplicationState_CastInt64 : + (IntRegion< T,U >::IntZone_Int64_UintLT64) ? MultiplicationState_Int64Uint : + (IntRegion< T,U >::IntZone_Int_Uint64) ? MultiplicationState_IntUint64 : + (IntRegion< T,U >::IntZone_Int64_Uint64 ? MultiplicationState_Int64Uint64 : + MultiplicationState_Error ) ) + }; +}; + +template class MultiplicationHelper; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_CastInt> +{ +public: + //accepts signed, both less than 32-bit + static bool Multiply( const T& t, const U& u, T& ret ) SAFEINT_NOTHROW + { + int tmp = t * u; + + if( tmp > IntTraits< T >::maxInt || tmp < IntTraits< T >::minInt ) + return false; + + ret = (T)tmp; + return true; + } + + template < typename E > + static void MultiplyThrow( const T& t, const U& u, T& ret ) SAFEINT_CPP_THROW + { + int tmp = t * u; + + if( tmp > IntTraits< T >::maxInt || tmp < IntTraits< T >::minInt ) + E::SafeIntOnOverflow(); + + ret = (T)tmp; + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_CastUint > +{ +public: + //accepts unsigned, both less than 32-bit + static bool Multiply( const T& t, const U& u, T& ret ) SAFEINT_NOTHROW + { + unsigned int tmp = (unsigned int)(t * u); + + if( tmp > IntTraits< T >::maxInt ) + return false; + + ret = (T)tmp; + return true; + } + + template < typename E > + static void MultiplyThrow( const T& t, const U& u, T& ret ) SAFEINT_CPP_THROW + { + unsigned int tmp = (unsigned int)( t * u ); + + if( tmp > IntTraits< T >::maxInt ) + E::SafeIntOnOverflow(); + + ret = (T)tmp; + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_CastInt64> +{ +public: + //mixed signed or both signed where at least one argument is 32-bit, and both a 32-bit or less + static bool Multiply( const T& t, const U& u, T& ret ) SAFEINT_NOTHROW + { + __int64 tmp = (__int64)t * (__int64)u; + + if(tmp > (__int64)IntTraits< T >::maxInt || tmp < (__int64)IntTraits< T >::minInt) + return false; + + ret = (T)tmp; + return true; + } + + template < typename E > + static void MultiplyThrow( const T& t, const U& u, T& ret ) SAFEINT_CPP_THROW + { + __int64 tmp = (__int64)t * (__int64)u; + + if(tmp > (__int64)IntTraits< T >::maxInt || tmp < (__int64)IntTraits< T >::minInt) + E::SafeIntOnOverflow(); + + ret = (T)tmp; + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_CastUint64> +{ +public: + //both unsigned where at least one argument is 32-bit, and both are 32-bit or less + static bool Multiply( const T& t, const U& u, T& ret ) SAFEINT_NOTHROW + { + unsigned __int64 tmp = (unsigned __int64)t * (unsigned __int64)u; + + if(tmp > (unsigned __int64)IntTraits< T >::maxInt) + return false; + + ret = (T)tmp; + return true; + } + + template < typename E > + static void MultiplyThrow( const T& t, const U& u, T& ret ) SAFEINT_CPP_THROW + { + unsigned __int64 tmp = (unsigned __int64)t * (unsigned __int64)u; + + if(tmp > (unsigned __int64)IntTraits< T >::maxInt) + E::SafeIntOnOverflow(); + + ret = (T)tmp; + } +}; + +// T = left arg and return type +// U = right arg +template < typename T, typename U > class LargeIntRegMultiply; + +#if SAFEINT_USE_INTRINSICS +// As usual, unsigned is easy +inline bool IntrinsicMultiplyUint64( const unsigned __int64& a, const unsigned __int64& b, unsigned __int64* pRet ) SAFEINT_NOTHROW +{ + unsigned __int64 ulHigh = 0; + *pRet = _umul128(a , b, &ulHigh); + return ulHigh == 0; +} + +// Signed, is not so easy +inline bool IntrinsicMultiplyInt64( const signed __int64& a, const signed __int64& b, signed __int64* pRet ) SAFEINT_NOTHROW +{ + __int64 llHigh = 0; + *pRet = _mul128(a , b, &llHigh); + + // Now we need to figure out what we expect + // If llHigh is 0, then treat *pRet as unsigned + // If llHigh is < 0, then treat *pRet as signed + + if( (a ^ b) < 0 ) + { + // Negative result expected + if( llHigh == -1 && *pRet < 0 || + llHigh == 0 && *pRet == 0 ) + { + // Everything is within range + return true; + } + } + else + { + // Result should be positive + // Check for overflow + if( llHigh == 0 && (unsigned __int64)*pRet <= IntTraits< signed __int64 >::maxInt ) + return true; + } + return false; +} + +#endif + +template<> class LargeIntRegMultiply< unsigned __int64, unsigned __int64 > +{ +public: + static bool RegMultiply( const unsigned __int64& a, const unsigned __int64& b, unsigned __int64* pRet ) SAFEINT_NOTHROW + { +#if SAFEINT_USE_INTRINSICS + return IntrinsicMultiplyUint64( a, b, pRet ); +#else + unsigned __int32 aHigh, aLow, bHigh, bLow; + + // Consider that a*b can be broken up into: + // (aHigh * 2^32 + aLow) * (bHigh * 2^32 + bLow) + // => (aHigh * bHigh * 2^64) + (aLow * bHigh * 2^32) + (aHigh * bLow * 2^32) + (aLow * bLow) + // Note - same approach applies for 128 bit math on a 64-bit system + + aHigh = (unsigned __int32)(a >> 32); + aLow = (unsigned __int32)a; + bHigh = (unsigned __int32)(b >> 32); + bLow = (unsigned __int32)b; + + *pRet = 0; + + if(aHigh == 0) + { + if(bHigh != 0) + { + *pRet = (unsigned __int64)aLow * (unsigned __int64)bHigh; + } + } + else if(bHigh == 0) + { + if(aHigh != 0) + { + *pRet = (unsigned __int64)aHigh * (unsigned __int64)bLow; + } + } + else + { + return false; + } + + if(*pRet != 0) + { + unsigned __int64 tmp; + + if((unsigned __int32)(*pRet >> 32) != 0) + return false; + + *pRet <<= 32; + tmp = (unsigned __int64)aLow * (unsigned __int64)bLow; + *pRet += tmp; + + if(*pRet < tmp) + return false; + + return true; + } + + *pRet = (unsigned __int64)aLow * (unsigned __int64)bLow; + return true; +#endif + } + + template < typename E > + static void RegMultiplyThrow( const unsigned __int64& a, const unsigned __int64& b, unsigned __int64* pRet ) SAFEINT_CPP_THROW + { +#if SAFEINT_USE_INTRINSICS + if( !IntrinsicMultiplyUint64( a, b, pRet ) ) + E::SafeIntOnOverflow(); +#else + unsigned __int32 aHigh, aLow, bHigh, bLow; + + // Consider that a*b can be broken up into: + // (aHigh * 2^32 + aLow) * (bHigh * 2^32 + bLow) + // => (aHigh * bHigh * 2^64) + (aLow * bHigh * 2^32) + (aHigh * bLow * 2^32) + (aLow * bLow) + // Note - same approach applies for 128 bit math on a 64-bit system + + aHigh = (unsigned __int32)(a >> 32); + aLow = (unsigned __int32)a; + bHigh = (unsigned __int32)(b >> 32); + bLow = (unsigned __int32)b; + + *pRet = 0; + + if(aHigh == 0) + { + if(bHigh != 0) + { + *pRet = (unsigned __int64)aLow * (unsigned __int64)bHigh; + } + } + else if(bHigh == 0) + { + if(aHigh != 0) + { + *pRet = (unsigned __int64)aHigh * (unsigned __int64)bLow; + } + } + else + { + E::SafeIntOnOverflow(); + } + + if(*pRet != 0) + { + unsigned __int64 tmp; + + if((unsigned __int32)(*pRet >> 32) != 0) + E::SafeIntOnOverflow(); + + *pRet <<= 32; + tmp = (unsigned __int64)aLow * (unsigned __int64)bLow; + *pRet += tmp; + + if(*pRet < tmp) + E::SafeIntOnOverflow(); + + return; + } + + *pRet = (unsigned __int64)aLow * (unsigned __int64)bLow; +#endif + } +}; + +template<> class LargeIntRegMultiply< unsigned __int64, unsigned __int32 > +{ +public: + static bool RegMultiply( const unsigned __int64& a, unsigned __int32 b, unsigned __int64* pRet ) SAFEINT_NOTHROW + { +#if SAFEINT_USE_INTRINSICS + return IntrinsicMultiplyUint64( a, (unsigned __int64)b, pRet ); +#else + unsigned __int32 aHigh, aLow; + + // Consider that a*b can be broken up into: + // (aHigh * 2^32 + aLow) * b + // => (aHigh * b * 2^32) + (aLow * b) + + aHigh = (unsigned __int32)(a >> 32); + aLow = (unsigned __int32)a; + + *pRet = 0; + + if(aHigh != 0) + { + *pRet = (unsigned __int64)aHigh * (unsigned __int64)b; + + unsigned __int64 tmp; + + if((unsigned __int32)(*pRet >> 32) != 0) + return false; + + *pRet <<= 32; + tmp = (unsigned __int64)aLow * (unsigned __int64)b; + *pRet += tmp; + + if(*pRet < tmp) + return false; + + return true; + } + + *pRet = (unsigned __int64)aLow * (unsigned __int64)b; + return true; +#endif + } + + template < typename E > + static void RegMultiplyThrow( const unsigned __int64& a, unsigned __int32 b, unsigned __int64* pRet ) SAFEINT_CPP_THROW + { +#if SAFEINT_USE_INTRINSICS + if( !IntrinsicMultiplyUint64( a, (unsigned __int64)b, pRet ) ) + E::SafeIntOnOverflow(); +#else + unsigned __int32 aHigh, aLow; + + // Consider that a*b can be broken up into: + // (aHigh * 2^32 + aLow) * b + // => (aHigh * b * 2^32) + (aLow * b) + + aHigh = (unsigned __int32)(a >> 32); + aLow = (unsigned __int32)a; + + *pRet = 0; + + if(aHigh != 0) + { + *pRet = (unsigned __int64)aHigh * (unsigned __int64)b; + + unsigned __int64 tmp; + + if((unsigned __int32)(*pRet >> 32) != 0) + E::SafeIntOnOverflow(); + + *pRet <<= 32; + tmp = (unsigned __int64)aLow * (unsigned __int64)b; + *pRet += tmp; + + if(*pRet < tmp) + E::SafeIntOnOverflow(); + + return; + } + + *pRet = (unsigned __int64)aLow * (unsigned __int64)b; + return; +#endif + } +}; + +template<> class LargeIntRegMultiply< unsigned __int64, signed __int32 > +{ +public: + // Intrinsic not needed + static bool RegMultiply( const unsigned __int64& a, signed __int32 b, unsigned __int64* pRet ) SAFEINT_NOTHROW + { + if( b < 0 && a != 0 ) + return false; + +#if SAFEINT_USE_INTRINSICS + return IntrinsicMultiplyUint64( a, (unsigned __int64)b, pRet ); +#else + return LargeIntRegMultiply< unsigned __int64, unsigned __int32 >::RegMultiply(a, (unsigned __int32)b, pRet); +#endif + } + + template < typename E > + static void RegMultiplyThrow( const unsigned __int64& a, signed __int32 b, unsigned __int64* pRet ) SAFEINT_CPP_THROW + { + if( b < 0 && a != 0 ) + E::SafeIntOnOverflow(); + +#if SAFEINT_USE_INTRINSICS + if( !IntrinsicMultiplyUint64( a, (unsigned __int64)b, pRet ) ) + E::SafeIntOnOverflow(); +#else + LargeIntRegMultiply< unsigned __int64, unsigned __int32 >::template RegMultiplyThrow< E >( a, (unsigned __int32)b, pRet ); +#endif + } +}; + +template<> class LargeIntRegMultiply< unsigned __int64, signed __int64 > +{ +public: + static bool RegMultiply( const unsigned __int64& a, signed __int64 b, unsigned __int64* pRet ) SAFEINT_NOTHROW + { + if( b < 0 && a != 0 ) + return false; + +#if SAFEINT_USE_INTRINSICS + return IntrinsicMultiplyUint64( a, (unsigned __int64)b, pRet ); +#else + return LargeIntRegMultiply< unsigned __int64, unsigned __int64 >::RegMultiply(a, (unsigned __int64)b, pRet); +#endif + } + + template < typename E > + static void RegMultiplyThrow( const unsigned __int64& a, signed __int64 b, unsigned __int64* pRet ) SAFEINT_CPP_THROW + { + if( b < 0 && a != 0 ) + E::SafeIntOnOverflow(); + +#if SAFEINT_USE_INTRINSICS + if( !IntrinsicMultiplyUint64( a, (unsigned __int64)b, pRet ) ) + E::SafeIntOnOverflow(); +#else + LargeIntRegMultiply< unsigned __int64, unsigned __int64 >::template RegMultiplyThrow< E >( a, (unsigned __int64)b, pRet ); +#endif + } +}; + +template<> class LargeIntRegMultiply< signed __int32, unsigned __int64 > +{ +public: + // Devolves into ordinary 64-bit calculation + static bool RegMultiply( signed __int32 a, const unsigned __int64& b, signed __int32* pRet ) SAFEINT_NOTHROW + { + unsigned __int32 bHigh, bLow; + bool fIsNegative = false; + + // Consider that a*b can be broken up into: + // (aHigh * 2^32 + aLow) * (bHigh * 2^32 + bLow) + // => (aHigh * bHigh * 2^64) + (aLow * bHigh * 2^32) + (aHigh * bLow * 2^32) + (aLow * bLow) + // aHigh == 0 implies: + // ( aLow * bHigh * 2^32 ) + ( aLow + bLow ) + // If the first part is != 0, fail + + bHigh = (unsigned __int32)(b >> 32); + bLow = (unsigned __int32)b; + + *pRet = 0; + + if(bHigh != 0 && a != 0) + return false; + + if( a < 0 ) + { + + a = (signed __int32)AbsValueHelper< signed __int32, GetAbsMethod< signed __int32 >::method >::Abs(a); + fIsNegative = true; + } + + unsigned __int64 tmp = (unsigned __int32)a * (unsigned __int64)bLow; + + if( !fIsNegative ) + { + if( tmp <= (unsigned __int64)IntTraits< signed __int32 >::maxInt ) + { + *pRet = (signed __int32)tmp; + return true; + } + } + else + { + if( tmp <= (unsigned __int64)IntTraits< signed __int32 >::maxInt+1 ) + { + *pRet = SignedNegation< signed __int32 >::Value( tmp ); + return true; + } + } + + return false; + } + + template < typename E > + static void RegMultiplyThrow( signed __int32 a, const unsigned __int64& b, signed __int32* pRet ) SAFEINT_CPP_THROW + { + unsigned __int32 bHigh, bLow; + bool fIsNegative = false; + + // Consider that a*b can be broken up into: + // (aHigh * 2^32 + aLow) * (bHigh * 2^32 + bLow) + // => (aHigh * bHigh * 2^64) + (aLow * bHigh * 2^32) + (aHigh * bLow * 2^32) + (aLow * bLow) + + bHigh = (unsigned __int32)(b >> 32); + bLow = (unsigned __int32)b; + + *pRet = 0; + + if(bHigh != 0 && a != 0) + E::SafeIntOnOverflow(); + + if( a < 0 ) + { + a = (signed __int32)AbsValueHelper< signed __int32, GetAbsMethod< signed __int32 >::method >::Abs(a); + fIsNegative = true; + } + + unsigned __int64 tmp = (unsigned __int32)a * (unsigned __int64)bLow; + + if( !fIsNegative ) + { + if( tmp <= (unsigned __int64)IntTraits< signed __int32 >::maxInt ) + { + *pRet = (signed __int32)tmp; + return; + } + } + else + { + if( tmp <= (unsigned __int64)IntTraits< signed __int32 >::maxInt+1 ) + { + *pRet = SignedNegation< signed __int32 >::Value( tmp ); + return; + } + } + + E::SafeIntOnOverflow(); + } +}; + +template<> class LargeIntRegMultiply< unsigned __int32, unsigned __int64 > +{ +public: + // Becomes ordinary 64-bit multiplication, intrinsic not needed + static bool RegMultiply( unsigned __int32 a, const unsigned __int64& b, unsigned __int32* pRet ) SAFEINT_NOTHROW + { + // Consider that a*b can be broken up into: + // (bHigh * 2^32 + bLow) * a + // => (bHigh * a * 2^32) + (bLow * a) + // In this case, the result must fit into 32-bits + // If bHigh != 0 && a != 0, immediate error. + + if( (unsigned __int32)(b >> 32) != 0 && a != 0 ) + return false; + + unsigned __int64 tmp = b * (unsigned __int64)a; + + if( (unsigned __int32)(tmp >> 32) != 0 ) // overflow + return false; + + *pRet = (unsigned __int32)tmp; + return true; + } + + template < typename E > + static void RegMultiplyThrow( unsigned __int32 a, const unsigned __int64& b, unsigned __int32* pRet ) SAFEINT_CPP_THROW + { + if( (unsigned __int32)(b >> 32) != 0 && a != 0 ) + E::SafeIntOnOverflow(); + + unsigned __int64 tmp = b * (unsigned __int64)a; + + if( (unsigned __int32)(tmp >> 32) != 0 ) // overflow + E::SafeIntOnOverflow(); + + *pRet = (unsigned __int32)tmp; + } +}; + +template<> class LargeIntRegMultiply< unsigned __int32, signed __int64 > +{ +public: + static bool RegMultiply( unsigned __int32 a, const signed __int64& b, unsigned __int32* pRet ) SAFEINT_NOTHROW + { + if( b < 0 && a != 0 ) + return false; + return LargeIntRegMultiply< unsigned __int32, unsigned __int64 >::RegMultiply( a, (unsigned __int64)b, pRet ); + } + + template < typename E > + static void RegMultiplyThrow( unsigned __int32 a, const signed __int64& b, unsigned __int32* pRet ) SAFEINT_CPP_THROW + { + if( b < 0 && a != 0 ) + E::SafeIntOnOverflow(); + + LargeIntRegMultiply< unsigned __int32, unsigned __int64 >::template RegMultiplyThrow< E >( a, (unsigned __int64)b, pRet ); + } +}; + +template<> class LargeIntRegMultiply< signed __int64, signed __int64 > +{ +public: + static bool RegMultiply( const signed __int64& a, const signed __int64& b, signed __int64* pRet ) SAFEINT_NOTHROW + { +#if SAFEINT_USE_INTRINSICS + return IntrinsicMultiplyInt64( a, b, pRet ); +#else + bool aNegative = false; + bool bNegative = false; + + unsigned __int64 tmp; + __int64 a1 = a; + __int64 b1 = b; + + if( a1 < 0 ) + { + aNegative = true; + a1 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(a1); + } + + if( b1 < 0 ) + { + bNegative = true; + b1 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(b1); + } + + if( LargeIntRegMultiply< unsigned __int64, unsigned __int64 >::RegMultiply( (unsigned __int64)a1, (unsigned __int64)b1, &tmp ) ) + { + // The unsigned multiplication didn't overflow + if( aNegative ^ bNegative ) + { + // Result must be negative + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::minInt ) + { + *pRet = SignedNegation< signed __int64 >::Value( tmp ); + return true; + } + } + else + { + // Result must be positive + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::maxInt ) + { + *pRet = (signed __int64)tmp; + return true; + } + } + } + + return false; +#endif + } + + template < typename E > + static void RegMultiplyThrow( const signed __int64& a, const signed __int64& b, signed __int64* pRet ) SAFEINT_CPP_THROW + { +#if SAFEINT_USE_INTRINSICS + if( !IntrinsicMultiplyInt64( a, b, pRet ) ) + E::SafeIntOnOverflow(); +#else + bool aNegative = false; + bool bNegative = false; + + unsigned __int64 tmp; + __int64 a1 = a; + __int64 b1 = b; + + if( a1 < 0 ) + { + aNegative = true; + a1 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(a1); + } + + if( b1 < 0 ) + { + bNegative = true; + b1 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(b1); + } + + LargeIntRegMultiply< unsigned __int64, unsigned __int64 >::template RegMultiplyThrow< E >( (unsigned __int64)a1, (unsigned __int64)b1, &tmp ); + + // The unsigned multiplication didn't overflow or we'd be in the exception handler + if( aNegative ^ bNegative ) + { + // Result must be negative + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::minInt ) + { + *pRet = SignedNegation< signed __int64 >::Value( tmp ); + return; + } + } + else + { + // Result must be positive + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::maxInt ) + { + *pRet = (signed __int64)tmp; + return; + } + } + + E::SafeIntOnOverflow(); +#endif + } +}; + +template<> class LargeIntRegMultiply< signed __int64, unsigned __int32 > +{ +public: + static bool RegMultiply( const signed __int64& a, unsigned __int32 b, signed __int64* pRet ) SAFEINT_NOTHROW + { +#if SAFEINT_USE_INTRINSICS + return IntrinsicMultiplyInt64( a, (signed __int64)b, pRet ); +#else + bool aNegative = false; + unsigned __int64 tmp; + __int64 a1 = a; + + if( a1 < 0 ) + { + aNegative = true; + a1 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(a1); + } + + if( LargeIntRegMultiply< unsigned __int64, unsigned __int32 >::RegMultiply( (unsigned __int64)a1, b, &tmp ) ) + { + // The unsigned multiplication didn't overflow + if( aNegative ) + { + // Result must be negative + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::minInt ) + { + *pRet = SignedNegation< signed __int64 >::Value( tmp ); + return true; + } + } + else + { + // Result must be positive + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::maxInt ) + { + *pRet = (signed __int64)tmp; + return true; + } + } + } + + return false; +#endif + } + + template < typename E > + static void RegMultiplyThrow( const signed __int64& a, unsigned __int32 b, signed __int64* pRet ) SAFEINT_CPP_THROW + { +#if SAFEINT_USE_INTRINSICS + if( !IntrinsicMultiplyInt64( a, (signed __int64)b, pRet ) ) + E::SafeIntOnOverflow(); +#else + bool aNegative = false; + unsigned __int64 tmp; + __int64 a1 = a; + + if( a1 < 0 ) + { + aNegative = true; + a1 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(a1); + } + + LargeIntRegMultiply< unsigned __int64, unsigned __int32 >::template RegMultiplyThrow< E >( (unsigned __int64)a1, b, &tmp ); + + // The unsigned multiplication didn't overflow + if( aNegative ) + { + // Result must be negative + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::minInt ) + { + *pRet = SignedNegation< signed __int64 >::Value( tmp ); + return; + } + } + else + { + // Result must be positive + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::maxInt ) + { + *pRet = (signed __int64)tmp; + return; + } + } + + E::SafeIntOnOverflow(); +#endif + } +}; + +template<> class LargeIntRegMultiply< signed __int64, signed __int32 > +{ +public: + static bool RegMultiply( const signed __int64& a, signed __int32 b, signed __int64* pRet ) SAFEINT_NOTHROW + { +#if SAFEINT_USE_INTRINSICS + return IntrinsicMultiplyInt64( a, (signed __int64)b, pRet ); +#else + bool aNegative = false; + bool bNegative = false; + + unsigned __int64 tmp; + __int64 a1 = a; + __int64 b1 = b; + + if( a1 < 0 ) + { + aNegative = true; + a1 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(a1); + } + + if( b1 < 0 ) + { + bNegative = true; + b1 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(b1); + } + + if( LargeIntRegMultiply< unsigned __int64, unsigned __int32 >::RegMultiply( (unsigned __int64)a1, (unsigned __int32)b1, &tmp ) ) + { + // The unsigned multiplication didn't overflow + if( aNegative ^ bNegative ) + { + // Result must be negative + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::minInt ) + { + *pRet = SignedNegation< signed __int64 >::Value( tmp ); + return true; + } + } + else + { + // Result must be positive + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::maxInt ) + { + *pRet = (signed __int64)tmp; + return true; + } + } + } + + return false; +#endif + } + + template < typename E > + static void RegMultiplyThrow( signed __int64 a, signed __int32 b, signed __int64* pRet ) SAFEINT_CPP_THROW + { +#if SAFEINT_USE_INTRINSICS + if( !IntrinsicMultiplyInt64( a, (signed __int64)b, pRet ) ) + E::SafeIntOnOverflow(); +#else + bool aNegative = false; + bool bNegative = false; + + unsigned __int64 tmp; + + if( a < 0 ) + { + aNegative = true; + a = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(a); + } + + if( b < 0 ) + { + bNegative = true; + b = (signed __int32)AbsValueHelper< signed __int32, GetAbsMethod< signed __int32 >::method >::Abs(b); + } + + LargeIntRegMultiply< unsigned __int64, unsigned __int32 >::template RegMultiplyThrow< E >( (unsigned __int64)a, (unsigned __int32)b, &tmp ); + + // The unsigned multiplication didn't overflow + if( aNegative ^ bNegative ) + { + // Result must be negative + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::minInt ) + { + *pRet = SignedNegation< signed __int64 >::Value( tmp ); + return; + } + } + else + { + // Result must be positive + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::maxInt ) + { + *pRet = (signed __int64)tmp; + return; + } + } + + E::SafeIntOnOverflow(); +#endif + } +}; + +template<> class LargeIntRegMultiply< signed __int32, signed __int64 > +{ +public: + static bool RegMultiply( signed __int32 a, const signed __int64& b, signed __int32* pRet ) SAFEINT_NOTHROW + { +#if SAFEINT_USE_INTRINSICS + __int64 tmp; + + if( IntrinsicMultiplyInt64( a, b, &tmp ) ) + { + if( tmp > IntTraits< signed __int32 >::maxInt || + tmp < IntTraits< signed __int32 >::minInt ) + { + return false; + } + + *pRet = (__int32)tmp; + return true; + } + return false; +#else + bool aNegative = false; + bool bNegative = false; + + unsigned __int32 tmp; + __int64 b1 = b; + + if( a < 0 ) + { + aNegative = true; + a = (signed __int32)AbsValueHelper< signed __int32, GetAbsMethod< signed __int32 >::method >::Abs(a); + } + + if( b1 < 0 ) + { + bNegative = true; + b1 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(b1); + } + + if( LargeIntRegMultiply< unsigned __int32, unsigned __int64 >::RegMultiply( (unsigned __int32)a, (unsigned __int64)b1, &tmp ) ) + { + // The unsigned multiplication didn't overflow + if( aNegative ^ bNegative ) + { + // Result must be negative + if( tmp <= (unsigned __int32)IntTraits< signed __int32 >::minInt ) + { + *pRet = SignedNegation< signed __int32 >::Value( tmp ); + return true; + } + } + else + { + // Result must be positive + if( tmp <= (unsigned __int32)IntTraits< signed __int32 >::maxInt ) + { + *pRet = (signed __int32)tmp; + return true; + } + } + } + + return false; +#endif + } + + template < typename E > + static void RegMultiplyThrow( signed __int32 a, const signed __int64& b, signed __int32* pRet ) SAFEINT_CPP_THROW + { +#if SAFEINT_USE_INTRINSICS + __int64 tmp; + + if( IntrinsicMultiplyInt64( a, b, &tmp ) ) + { + if( tmp > IntTraits< signed __int32 >::maxInt || + tmp < IntTraits< signed __int32 >::minInt ) + { + E::SafeIntOnOverflow(); + } + + *pRet = (__int32)tmp; + return; + } + E::SafeIntOnOverflow(); +#else + bool aNegative = false; + bool bNegative = false; + + unsigned __int32 tmp; + signed __int64 b2 = b; + + if( a < 0 ) + { + aNegative = true; + a = (signed __int32)AbsValueHelper< signed __int32, GetAbsMethod< signed __int32 >::method >::Abs(a); + } + + if( b < 0 ) + { + bNegative = true; + b2 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(b2); + } + + LargeIntRegMultiply< unsigned __int32, unsigned __int64 >::template RegMultiplyThrow< E >( (unsigned __int32)a, (unsigned __int64)b2, &tmp ); + + // The unsigned multiplication didn't overflow + if( aNegative ^ bNegative ) + { + // Result must be negative + if( tmp <= (unsigned __int32)IntTraits< signed __int32 >::minInt ) + { + *pRet = SignedNegation< signed __int32 >::Value( tmp ); + return; + } + } + else + { + // Result must be positive + if( tmp <= (unsigned __int32)IntTraits< signed __int32 >::maxInt ) + { + *pRet = (signed __int32)tmp; + return; + } + } + + E::SafeIntOnOverflow(); +#endif + } +}; + +template<> class LargeIntRegMultiply< signed __int64, unsigned __int64 > +{ +public: + // Leave this one as-is - will call unsigned intrinsic internally + static bool RegMultiply( const signed __int64& a, const unsigned __int64& b, signed __int64* pRet ) SAFEINT_NOTHROW + { + bool aNegative = false; + + unsigned __int64 tmp; + __int64 a1 = a; + + if( a1 < 0 ) + { + aNegative = true; + a1 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(a1); + } + + if( LargeIntRegMultiply< unsigned __int64, unsigned __int64 >::RegMultiply( (unsigned __int64)a1, (unsigned __int64)b, &tmp ) ) + { + // The unsigned multiplication didn't overflow + if( aNegative ) + { + // Result must be negative + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::minInt ) + { + *pRet = SignedNegation< signed __int64 >::Value( tmp ); + return true; + } + } + else + { + // Result must be positive + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::maxInt ) + { + *pRet = (signed __int64)tmp; + return true; + } + } + } + + return false; + } + + template < typename E > + static void RegMultiplyThrow( const signed __int64& a, const unsigned __int64& b, signed __int64* pRet ) SAFEINT_CPP_THROW + { + bool aNegative = false; + unsigned __int64 tmp; + __int64 a1 = a; + + if( a1 < 0 ) + { + aNegative = true; + a1 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(a1); + } + + if( LargeIntRegMultiply< unsigned __int64, unsigned __int64 >::RegMultiply( (unsigned __int64)a1, (unsigned __int64)b, &tmp ) ) + { + // The unsigned multiplication didn't overflow + if( aNegative ) + { + // Result must be negative + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::minInt ) + { + *pRet = SignedNegation< signed __int64 >::Value( tmp ); + return; + } + } + else + { + // Result must be positive + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::maxInt ) + { + *pRet = (signed __int64)tmp; + return; + } + } + } + + E::SafeIntOnOverflow(); + } +}; + +// In all of the following functions where LargeIntRegMultiply methods are called, +// we need to properly transition types. The methods need __int64, __int32, etc. +// but the variables being passed to us could be long long, long int, or long, depending on +// the compiler. Microsoft compiler knows that long long is the same type as __int64, but gcc doesn't + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_Uint64Uint64 > +{ +public: + // T, U are unsigned __int64 + static bool Multiply( const T& t, const U& u, T& ret ) SAFEINT_NOTHROW + { + C_ASSERT( IntTraits::isUint64 && IntTraits::isUint64 ); + unsigned __int64 t1 = t; + unsigned __int64 u1 = u; + return LargeIntRegMultiply< unsigned __int64, unsigned __int64 >::RegMultiply( t1, u1, reinterpret_cast(&ret) ); + } + + template < typename E > + static void MultiplyThrow(const unsigned __int64& t, const unsigned __int64& u, T& ret) SAFEINT_CPP_THROW + { + C_ASSERT( IntTraits::isUint64 && IntTraits::isUint64 ); + unsigned __int64 t1 = t; + unsigned __int64 u1 = u; + LargeIntRegMultiply< unsigned __int64, unsigned __int64 >::template RegMultiplyThrow< E >( t1, u1, reinterpret_cast(&ret) ); + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_Uint64Uint > +{ +public: + // T is unsigned __int64 + // U is any unsigned int 32-bit or less + static bool Multiply( const T& t, const U& u, T& ret ) SAFEINT_NOTHROW + { + C_ASSERT( IntTraits::isUint64 ); + unsigned __int64 t1 = t; + return LargeIntRegMultiply< unsigned __int64, unsigned __int32 >::RegMultiply( t1, (unsigned __int32)u, reinterpret_cast(&ret) ); + } + + template < typename E > + static void MultiplyThrow( const T& t, const U& u, T& ret ) SAFEINT_CPP_THROW + { + C_ASSERT( IntTraits::isUint64 ); + unsigned __int64 t1 = t; + LargeIntRegMultiply< unsigned __int64, unsigned __int32 >::template RegMultiplyThrow< E >( t1, (unsigned __int32)u, reinterpret_cast(&ret) ); + } +}; + +// converse of the previous function +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_UintUint64 > +{ +public: + // T is any unsigned int up to 32-bit + // U is unsigned __int64 + static bool Multiply(const T& t, const U& u, T& ret) SAFEINT_NOTHROW + { + C_ASSERT( IntTraits::isUint64 ); + unsigned __int64 u1 = u; + unsigned __int32 tmp; + + if( LargeIntRegMultiply< unsigned __int32, unsigned __int64 >::RegMultiply( t, u1, &tmp ) && + SafeCastHelper< T, unsigned __int32, GetCastMethod< T, unsigned __int32 >::method >::Cast(tmp, ret) ) + { + return true; + } + + return false; + } + + template < typename E > + static void MultiplyThrow(const T& t, const U& u, T& ret) SAFEINT_CPP_THROW + { + C_ASSERT( IntTraits::isUint64 ); + unsigned __int64 u1 = u; + unsigned __int32 tmp; + + LargeIntRegMultiply< unsigned __int32, unsigned __int64 >::template RegMultiplyThrow< E >( t, u1, &tmp ); + SafeCastHelper< T, unsigned __int32, GetCastMethod< T, unsigned __int32 >::method >::template CastThrow< E >(tmp, ret); + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_Uint64Int > +{ +public: + // T is unsigned __int64 + // U is any signed int, up to 64-bit + static bool Multiply(const T& t, const U& u, T& ret) SAFEINT_NOTHROW + { + C_ASSERT( IntTraits::isUint64 ); + unsigned __int64 t1 = t; + return LargeIntRegMultiply< unsigned __int64, signed __int32 >::RegMultiply(t1, (signed __int32)u, reinterpret_cast< unsigned __int64* >(&ret)); + } + + template < typename E > + static void MultiplyThrow(const T& t, const U& u, T& ret) SAFEINT_CPP_THROW + { + C_ASSERT( IntTraits::isUint64 ); + unsigned __int64 t1 = t; + LargeIntRegMultiply< unsigned __int64, signed __int32 >::template RegMultiplyThrow< E >(t1, (signed __int32)u, reinterpret_cast< unsigned __int64* >(&ret)); + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_Uint64Int64 > +{ +public: + // T is unsigned __int64 + // U is __int64 + static bool Multiply(const T& t, const U& u, T& ret) SAFEINT_NOTHROW + { + C_ASSERT( IntTraits::isUint64 && IntTraits::isInt64 ); + unsigned __int64 t1 = t; + __int64 u1 = u; + return LargeIntRegMultiply< unsigned __int64, __int64 >::RegMultiply(t1, u1, reinterpret_cast< unsigned __int64* >(&ret)); + } + + template < typename E > + static void MultiplyThrow(const T& t, const U& u, T& ret) SAFEINT_CPP_THROW + { + C_ASSERT( IntTraits::isUint64 && IntTraits::isInt64 ); + unsigned __int64 t1 = t; + __int64 u1 = u; + LargeIntRegMultiply< unsigned __int64, __int64 >::template RegMultiplyThrow< E >(t1, u1, reinterpret_cast< unsigned __int64* >(&ret)); + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_UintInt64 > +{ +public: + // T is unsigned up to 32-bit + // U is __int64 + static bool Multiply(const T& t, const U& u, T& ret) SAFEINT_NOTHROW + { + C_ASSERT( IntTraits::isInt64 ); + __int64 u1 = u; + unsigned __int32 tmp; + + if( LargeIntRegMultiply< unsigned __int32, __int64 >::RegMultiply( (unsigned __int32)t, u1, &tmp ) && + SafeCastHelper< T, unsigned __int32, GetCastMethod< T, unsigned __int32 >::method >::Cast(tmp, ret) ) + { + return true; + } + + return false; + } + + template < typename E > + static void MultiplyThrow(const T& t, const U& u, T& ret) SAFEINT_CPP_THROW + { + C_ASSERT( IntTraits::isInt64 ); + __int64 u1 = u; + unsigned __int32 tmp; + + LargeIntRegMultiply< unsigned __int32, __int64 >::template RegMultiplyThrow< E >( (unsigned __int32)t, u1, &tmp ); + SafeCastHelper< T, unsigned __int32, GetCastMethod< T, unsigned __int32 >::method >::template CastThrow< E >(tmp, ret); + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_Int64Uint > +{ +public: + // T is __int64 + // U is unsigned up to 32-bit + static bool Multiply( const T& t, const U& u, T& ret ) SAFEINT_NOTHROW + { + C_ASSERT( IntTraits::isInt64 ); + __int64 t1 = t; + return LargeIntRegMultiply< __int64, unsigned __int32 >::RegMultiply( t1, (unsigned __int32)u, reinterpret_cast< __int64* >(&ret) ); + } + + template < typename E > + static void MultiplyThrow( const T& t, const U& u, T& ret ) SAFEINT_CPP_THROW + { + C_ASSERT( IntTraits::isInt64 ); + __int64 t1 = t; + LargeIntRegMultiply< __int64, unsigned __int32 >::template RegMultiplyThrow< E >( t1, (unsigned __int32)u, reinterpret_cast< __int64* >(&ret) ); + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_Int64Int64 > +{ +public: + // T, U are __int64 + static bool Multiply( const T& t, const U& u, T& ret ) SAFEINT_NOTHROW + { + C_ASSERT( IntTraits::isInt64 && IntTraits::isInt64 ); + __int64 t1 = t; + __int64 u1 = u; + return LargeIntRegMultiply< __int64, __int64 >::RegMultiply( t1, u1, reinterpret_cast< __int64* >(&ret) ); + } + + template < typename E > + static void MultiplyThrow( const T& t, const U& u, T& ret ) SAFEINT_CPP_THROW + { + C_ASSERT( IntTraits::isInt64 && IntTraits::isInt64 ); + __int64 t1 = t; + __int64 u1 = u; + LargeIntRegMultiply< __int64, __int64 >::template RegMultiplyThrow< E >( t1, u1, reinterpret_cast< __int64* >(&ret)); + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_Int64Int > +{ +public: + // T is __int64 + // U is signed up to 32-bit + static bool Multiply( const T& t, U u, T& ret ) SAFEINT_NOTHROW + { + C_ASSERT( IntTraits::isInt64 ); + __int64 t1 = t; + return LargeIntRegMultiply< __int64, __int32 >::RegMultiply( t1, (__int32)u, reinterpret_cast< __int64* >(&ret)); + } + + template < typename E > + static void MultiplyThrow( const __int64& t, U u, T& ret ) SAFEINT_CPP_THROW + { + C_ASSERT( IntTraits::isInt64 ); + __int64 t1 = t; + LargeIntRegMultiply< __int64, __int32 >::template RegMultiplyThrow< E >(t1, (__int32)u, reinterpret_cast< __int64* >(&ret)); + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_IntUint64 > +{ +public: + // T is signed up to 32-bit + // U is unsigned __int64 + static bool Multiply(T t, const U& u, T& ret) SAFEINT_NOTHROW + { + C_ASSERT( IntTraits::isUint64 ); + unsigned __int64 u1 = u; + __int32 tmp; + + if( LargeIntRegMultiply< __int32, unsigned __int64 >::RegMultiply( (__int32)t, u1, &tmp ) && + SafeCastHelper< T, __int32, GetCastMethod< T, __int32 >::method >::Cast( tmp, ret ) ) + { + return true; + } + + return false; + } + + template < typename E > + static void MultiplyThrow(T t, const unsigned __int64& u, T& ret) SAFEINT_CPP_THROW + { + C_ASSERT( IntTraits::isUint64 ); + unsigned __int64 u1 = u; + __int32 tmp; + + LargeIntRegMultiply< __int32, unsigned __int64 >::template RegMultiplyThrow< E >( (__int32)t, u1, &tmp ); + SafeCastHelper< T, __int32, GetCastMethod< T, __int32 >::method >::template CastThrow< E >( tmp, ret ); + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_Int64Uint64> +{ +public: + // T is __int64 + // U is unsigned __int64 + static bool Multiply( const T& t, const U& u, T& ret ) SAFEINT_NOTHROW + { + C_ASSERT( IntTraits::isInt64 && IntTraits::isUint64 ); + __int64 t1 = t; + unsigned __int64 u1 = u; + return LargeIntRegMultiply< __int64, unsigned __int64 >::RegMultiply( t1, u1, reinterpret_cast< __int64* >(&ret) ); + } + + template < typename E > + static void MultiplyThrow( const __int64& t, const unsigned __int64& u, T& ret ) SAFEINT_CPP_THROW + { + C_ASSERT( IntTraits::isInt64 && IntTraits::isUint64 ); + __int64 t1 = t; + unsigned __int64 u1 = u; + LargeIntRegMultiply< __int64, unsigned __int64 >::template RegMultiplyThrow< E >( t1, u1, reinterpret_cast< __int64* >(&ret) ); + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_IntInt64> +{ +public: + // T is signed, up to 32-bit + // U is __int64 + static bool Multiply( T t, const U& u, T& ret ) SAFEINT_NOTHROW + { + C_ASSERT( IntTraits::isInt64 ); + __int64 u1 = u; + __int32 tmp; + + if( LargeIntRegMultiply< __int32, __int64 >::RegMultiply( (__int32)t, u1, &tmp ) && + SafeCastHelper< T, __int32, GetCastMethod< T, __int32 >::method >::Cast( tmp, ret ) ) + { + return true; + } + + return false; + } + + template < typename E > + static void MultiplyThrow(T t, const U& u, T& ret) SAFEINT_CPP_THROW + { + C_ASSERT( IntTraits::isInt64 ); + __int64 u1 = u; + __int32 tmp; + + LargeIntRegMultiply< __int32, __int64 >::template RegMultiplyThrow< E >( (__int32)t, u1, &tmp ); + SafeCastHelper< T, __int32, GetCastMethod< T, __int32 >::method >::template CastThrow< E >( tmp, ret ); + } +}; + +enum DivisionState +{ + DivisionState_OK, + DivisionState_UnsignedSigned, + DivisionState_SignedUnsigned32, + DivisionState_SignedUnsigned64, + DivisionState_SignedUnsigned, + DivisionState_SignedSigned +}; + +template < typename T, typename U > class DivisionMethod +{ +public: + enum + { + method = (SafeIntCompare< T, U >::isBothUnsigned ? DivisionState_OK : + (!IntTraits< T >::isSigned && IntTraits< U >::isSigned) ? DivisionState_UnsignedSigned : + (IntTraits< T >::isSigned && + IntTraits< U >::isUint32 && + IntTraits< T >::isLT64Bit) ? DivisionState_SignedUnsigned32 : + (IntTraits< T >::isSigned && IntTraits< U >::isUint64) ? DivisionState_SignedUnsigned64 : + (IntTraits< T >::isSigned && !IntTraits< U >::isSigned) ? DivisionState_SignedUnsigned : + DivisionState_SignedSigned) + }; +}; + +template < typename T, typename U, int state > class DivisionHelper; + +template < typename T, typename U > class DivisionHelper< T, U, DivisionState_OK > +{ +public: + static SafeIntError Divide( const T& t, const U& u, T& result ) SAFEINT_NOTHROW + { + if( u == 0 ) + return SafeIntDivideByZero; + + if( t == 0 ) + { + result = 0; + return SafeIntNoError; + } + + result = (T)( t/u ); + return SafeIntNoError; + } + + template < typename E > + static void DivideThrow( const T& t, const U& u, T& result ) SAFEINT_CPP_THROW + { + if( u == 0 ) + E::SafeIntOnDivZero(); + + if( t == 0 ) + { + result = 0; + return; + } + + result = (T)( t/u ); + } +}; + +template < typename T, typename U > class DivisionHelper< T, U, DivisionState_UnsignedSigned> +{ +public: + static SafeIntError Divide( const T& t, const U& u, T& result ) SAFEINT_NOTHROW + { + + if( u == 0 ) + return SafeIntDivideByZero; + + if( t == 0 ) + { + result = 0; + return SafeIntNoError; + } + + if( u > 0 ) + { + result = (T)( t/u ); + return SafeIntNoError; + } + + // it is always an error to try and divide an unsigned number by a negative signed number + // unless u is bigger than t + if( AbsValueHelper< U, GetAbsMethod< U >::method >::Abs( u ) > t ) + { + result = 0; + return SafeIntNoError; + } + + return SafeIntArithmeticOverflow; + } + + template < typename E > + static void DivideThrow( const T& t, const U& u, T& result ) SAFEINT_CPP_THROW + { + + if( u == 0 ) + E::SafeIntOnDivZero(); + + if( t == 0 ) + { + result = 0; + return; + } + + if( u > 0 ) + { + result = (T)( t/u ); + return; + } + + // it is always an error to try and divide an unsigned number by a negative signed number + // unless u is bigger than t + if( AbsValueHelper< U, GetAbsMethod< U >::method >::Abs( u ) > t ) + { + result = 0; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class DivisionHelper< T, U, DivisionState_SignedUnsigned32 > +{ +public: + static SafeIntError Divide( const T& t, const U& u, T& result ) SAFEINT_NOTHROW + { + if( u == 0 ) + return SafeIntDivideByZero; + + if( t == 0 ) + { + result = 0; + return SafeIntNoError; + } + + // Test for t > 0 + // If t < 0, must explicitly upcast, or implicit upcast to ulong will cause errors + // As it turns out, 32-bit division is about twice as fast, which justifies the extra conditional + + if( t > 0 ) + result = (T)( t/u ); + else + result = (T)( (__int64)t/(__int64)u ); + + return SafeIntNoError; + } + + template < typename E > + static void DivideThrow( const T& t, const U& u, T& result ) SAFEINT_CPP_THROW + { + if( u == 0 ) + { + E::SafeIntOnDivZero(); + } + + if( t == 0 ) + { + result = 0; + return; + } + + // Test for t > 0 + // If t < 0, must explicitly upcast, or implicit upcast to ulong will cause errors + // As it turns out, 32-bit division is about twice as fast, which justifies the extra conditional + + if( t > 0 ) + result = (T)( t/u ); + else + result = (T)( (__int64)t/(__int64)u ); + } +}; + +template < typename T, typename U > class DivisionHelper< T, U, DivisionState_SignedUnsigned64 > +{ +public: + static SafeIntError Divide( const T& t, const unsigned __int64& u, T& result ) SAFEINT_NOTHROW + { + C_ASSERT( IntTraits< U >::isUint64 ); + + if( u == 0 ) + { + return SafeIntDivideByZero; + } + + if( t == 0 ) + { + result = 0; + return SafeIntNoError; + } + + if( u <= (unsigned __int64)IntTraits< T >::maxInt ) + { + // Else u can safely be cast to T + if( CompileConst< sizeof( T ) < sizeof( __int64 )>::Value() ) + result = (T)( (int)t/(int)u ); + else + result = (T)((__int64)t/(__int64)u); + } + else // Corner case + if( t == IntTraits< T >::minInt && u == (unsigned __int64)IntTraits< T >::minInt ) + { + // Min int divided by it's own magnitude is -1 + result = -1; + } + else + { + result = 0; + } + return SafeIntNoError; + } + + template < typename E > + static void DivideThrow( const T& t, const unsigned __int64& u, T& result ) SAFEINT_CPP_THROW + { + C_ASSERT( IntTraits< U >::isUint64 ); + + if( u == 0 ) + { + E::SafeIntOnDivZero(); + } + + if( t == 0 ) + { + result = 0; + return; + } + + if( u <= (unsigned __int64)IntTraits< T >::maxInt ) + { + // Else u can safely be cast to T + if( CompileConst< sizeof( T ) < sizeof( __int64 ) >::Value() ) + result = (T)( (int)t/(int)u ); + else + result = (T)((__int64)t/(__int64)u); + } + else // Corner case + if( t == IntTraits< T >::minInt && u == (unsigned __int64)IntTraits< T >::minInt ) + { + // Min int divided by it's own magnitude is -1 + result = -1; + } + else + { + result = 0; + } + } +}; + +template < typename T, typename U > class DivisionHelper< T, U, DivisionState_SignedUnsigned> +{ +public: + // T is any signed, U is unsigned and smaller than 32-bit + // In this case, standard operator casting is correct + static SafeIntError Divide( const T& t, const U& u, T& result ) SAFEINT_NOTHROW + { + if( u == 0 ) + { + return SafeIntDivideByZero; + } + + if( t == 0 ) + { + result = 0; + return SafeIntNoError; + } + + result = (T)( t/u ); + return SafeIntNoError; + } + + template < typename E > + static void DivideThrow( const T& t, const U& u, T& result ) SAFEINT_CPP_THROW + { + if( u == 0 ) + { + E::SafeIntOnDivZero(); + } + + if( t == 0 ) + { + result = 0; + return; + } + + result = (T)( t/u ); + } +}; + +template < typename T, typename U > class DivisionHelper< T, U, DivisionState_SignedSigned> +{ +public: + static SafeIntError Divide( const T& t, const U& u, T& result ) SAFEINT_NOTHROW + { + if( u == 0 ) + { + return SafeIntDivideByZero; + } + + if( t == 0 ) + { + result = 0; + return SafeIntNoError; + } + + // Must test for corner case + if( t == IntTraits< T >::minInt && u == (U)-1 ) + return SafeIntArithmeticOverflow; + + result = (T)( t/u ); + return SafeIntNoError; + } + + template < typename E > + static void DivideThrow( const T& t, const U& u, T& result ) SAFEINT_CPP_THROW + { + if(u == 0) + { + E::SafeIntOnDivZero(); + } + + if( t == 0 ) + { + result = 0; + return; + } + + // Must test for corner case + if( t == IntTraits< T >::minInt && u == (U)-1 ) + E::SafeIntOnOverflow(); + + result = (T)( t/u ); + } +}; + +enum AdditionState +{ + AdditionState_CastIntCheckMax, + AdditionState_CastUintCheckOverflow, + AdditionState_CastUintCheckOverflowMax, + AdditionState_CastUint64CheckOverflow, + AdditionState_CastUint64CheckOverflowMax, + AdditionState_CastIntCheckSafeIntMinMax, + AdditionState_CastInt64CheckSafeIntMinMax, + AdditionState_CastInt64CheckMax, + AdditionState_CastUint64CheckSafeIntMinMax, + AdditionState_CastUint64CheckSafeIntMinMax2, + AdditionState_CastInt64CheckOverflow, + AdditionState_CastInt64CheckOverflowSafeIntMinMax, + AdditionState_CastInt64CheckOverflowMax, + AdditionState_ManualCheckInt64Uint64, + AdditionState_ManualCheck, + AdditionState_Error +}; + +template< typename T, typename U > +class AdditionMethod +{ +public: + enum + { + //unsigned-unsigned + method = (IntRegion< T,U >::IntZone_UintLT32_UintLT32 ? AdditionState_CastIntCheckMax : + (IntRegion< T,U >::IntZone_Uint32_UintLT64) ? AdditionState_CastUintCheckOverflow : + (IntRegion< T,U >::IntZone_UintLT32_Uint32) ? AdditionState_CastUintCheckOverflowMax : + (IntRegion< T,U >::IntZone_Uint64_Uint) ? AdditionState_CastUint64CheckOverflow : + (IntRegion< T,U >::IntZone_UintLT64_Uint64) ? AdditionState_CastUint64CheckOverflowMax : + //unsigned-signed + (IntRegion< T,U >::IntZone_UintLT32_IntLT32) ? AdditionState_CastIntCheckSafeIntMinMax : + (IntRegion< T,U >::IntZone_Uint32_IntLT64 || + IntRegion< T,U >::IntZone_UintLT32_Int32) ? AdditionState_CastInt64CheckSafeIntMinMax : + (IntRegion< T,U >::IntZone_Uint64_Int || + IntRegion< T,U >::IntZone_Uint64_Int64) ? AdditionState_CastUint64CheckSafeIntMinMax : + (IntRegion< T,U >::IntZone_UintLT64_Int64) ? AdditionState_CastUint64CheckSafeIntMinMax2 : + //signed-signed + (IntRegion< T,U >::IntZone_IntLT32_IntLT32) ? AdditionState_CastIntCheckSafeIntMinMax : + (IntRegion< T,U >::IntZone_Int32_IntLT64 || + IntRegion< T,U >::IntZone_IntLT32_Int32) ? AdditionState_CastInt64CheckSafeIntMinMax : + (IntRegion< T,U >::IntZone_Int64_Int || + IntRegion< T,U >::IntZone_Int64_Int64) ? AdditionState_CastInt64CheckOverflow : + (IntRegion< T,U >::IntZone_IntLT64_Int64) ? AdditionState_CastInt64CheckOverflowSafeIntMinMax : + //signed-unsigned + (IntRegion< T,U >::IntZone_IntLT32_UintLT32) ? AdditionState_CastIntCheckMax : + (IntRegion< T,U >::IntZone_Int32_UintLT32 || + IntRegion< T,U >::IntZone_IntLT64_Uint32) ? AdditionState_CastInt64CheckMax : + (IntRegion< T,U >::IntZone_Int64_UintLT64) ? AdditionState_CastInt64CheckOverflowMax : + (IntRegion< T,U >::IntZone_Int64_Uint64) ? AdditionState_ManualCheckInt64Uint64 : + (IntRegion< T,U >::IntZone_Int_Uint64) ? AdditionState_ManualCheck : + AdditionState_Error) + }; +}; + +template < typename T, typename U, int method > class AdditionHelper; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastIntCheckMax > +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + //16-bit or less unsigned addition + __int32 tmp = lhs + rhs; + + if( tmp <= (__int32)IntTraits< T >::maxInt ) + { + result = (T)tmp; + return true; + } + + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + //16-bit or less unsigned addition + __int32 tmp = lhs + rhs; + + if( tmp <= (__int32)IntTraits< T >::maxInt ) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastUintCheckOverflow > +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + // 32-bit or less - both are unsigned + unsigned __int32 tmp = (unsigned __int32)lhs + (unsigned __int32)rhs; + + //we added didn't get smaller + if( tmp >= lhs ) + { + result = (T)tmp; + return true; + } + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + // 32-bit or less - both are unsigned + unsigned __int32 tmp = (unsigned __int32)lhs + (unsigned __int32)rhs; + + //we added didn't get smaller + if( tmp >= lhs ) + { + result = (T)tmp; + return; + } + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastUintCheckOverflowMax> +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + // 32-bit or less - both are unsigned + unsigned __int32 tmp = (unsigned __int32)lhs + (unsigned __int32)rhs; + + // We added and it didn't get smaller or exceed maxInt + if( tmp >= lhs && tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return true; + } + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + //32-bit or less - both are unsigned + unsigned __int32 tmp = (unsigned __int32)lhs + (unsigned __int32)rhs; + + // We added and it didn't get smaller or exceed maxInt + if( tmp >= lhs && tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return; + } + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastUint64CheckOverflow> +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + // lhs unsigned __int64, rhs unsigned + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // We added and it didn't get smaller + if(tmp >= lhs) + { + result = (T)tmp; + return true; + } + + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + // lhs unsigned __int64, rhs unsigned + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // We added and it didn't get smaller + if(tmp >= lhs) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastUint64CheckOverflowMax > +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + //lhs unsigned __int64, rhs unsigned + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // We added and it didn't get smaller + if( tmp >= lhs && tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return true; + } + + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + //lhs unsigned __int64, rhs unsigned + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // We added and it didn't get smaller + if( tmp >= lhs && tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastIntCheckSafeIntMinMax > +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + // 16-bit or less - one or both are signed + __int32 tmp = lhs + rhs; + + if( tmp <= (__int32)IntTraits< T >::maxInt && tmp >= (__int32)IntTraits< T >::minInt ) + { + result = (T)tmp; + return true; + } + + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + // 16-bit or less - one or both are signed + __int32 tmp = lhs + rhs; + + if( tmp <= (__int32)IntTraits< T >::maxInt && tmp >= (__int32)IntTraits< T >::minInt ) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastInt64CheckSafeIntMinMax > +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + // 32-bit or less - one or both are signed + __int64 tmp = (__int64)lhs + (__int64)rhs; + + if( tmp <= (__int64)IntTraits< T >::maxInt && tmp >= (__int64)IntTraits< T >::minInt ) + { + result = (T)tmp; + return true; + } + + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + // 32-bit or less - one or both are signed + __int64 tmp = (__int64)lhs + (__int64)rhs; + + if( tmp <= (__int64)IntTraits< T >::maxInt && tmp >= (__int64)IntTraits< T >::minInt ) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastInt64CheckMax > +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + // 32-bit or less - lhs signed, rhs unsigned + __int64 tmp = (__int64)lhs + (__int64)rhs; + + if( tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return true; + } + + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + // 32-bit or less - lhs signed, rhs unsigned + __int64 tmp = (__int64)lhs + (__int64)rhs; + + if( tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastUint64CheckSafeIntMinMax > +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + // lhs is unsigned __int64, rhs signed + unsigned __int64 tmp; + + if( rhs < 0 ) + { + // So we're effectively subtracting + tmp = AbsValueHelper< U, GetAbsMethod< U >::method >::Abs( rhs ); + + if( tmp <= lhs ) + { + result = lhs - tmp; + return true; + } + } + else + { + // now we know that rhs can be safely cast into an unsigned __int64 + tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // We added and it did not become smaller + if( tmp >= lhs ) + { + result = (T)tmp; + return true; + } + } + + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + // lhs is unsigned __int64, rhs signed + unsigned __int64 tmp; + + if( rhs < 0 ) + { + // So we're effectively subtracting + tmp = AbsValueHelper< U, GetAbsMethod< U >::method >::Abs( rhs ); + + if( tmp <= lhs ) + { + result = lhs - tmp; + return; + } + } + else + { + // now we know that rhs can be safely cast into an unsigned __int64 + tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // We added and it did not become smaller + if( tmp >= lhs ) + { + result = (T)tmp; + return; + } + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastUint64CheckSafeIntMinMax2> +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + // lhs is unsigned and < 64-bit, rhs signed __int64 + if( rhs < 0 ) + { + if( lhs >= ~(unsigned __int64)( rhs ) + 1 )//negation is safe, since rhs is 64-bit + { + result = (T)( lhs + rhs ); + return true; + } + } + else + { + // now we know that rhs can be safely cast into an unsigned __int64 + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // special case - rhs cannot be larger than 0x7fffffffffffffff, lhs cannot be larger than 0xffffffff + // it is not possible for the operation above to overflow, so just check max + if( tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return true; + } + } + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + // lhs is unsigned and < 64-bit, rhs signed __int64 + if( rhs < 0 ) + { + if( lhs >= ~(unsigned __int64)( rhs ) + 1) //negation is safe, since rhs is 64-bit + { + result = (T)( lhs + rhs ); + return; + } + } + else + { + // now we know that rhs can be safely cast into an unsigned __int64 + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // special case - rhs cannot be larger than 0x7fffffffffffffff, lhs cannot be larger than 0xffffffff + // it is not possible for the operation above to overflow, so just check max + if( tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return; + } + } + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastInt64CheckOverflow> +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + // lhs is signed __int64, rhs signed + __int64 tmp = (__int64)((unsigned __int64)lhs + (unsigned __int64)rhs); + + if( lhs >= 0 ) + { + // mixed sign cannot overflow + if( rhs >= 0 && tmp < lhs ) + return false; + } + else + { + // lhs negative + if( rhs < 0 && tmp > lhs ) + return false; + } + + result = (T)tmp; + return true; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + // lhs is signed __int64, rhs signed + __int64 tmp = (__int64)((unsigned __int64)lhs + (unsigned __int64)rhs); + + if( lhs >= 0 ) + { + // mixed sign cannot overflow + if( rhs >= 0 && tmp < lhs ) + E::SafeIntOnOverflow(); + } + else + { + // lhs negative + if( rhs < 0 && tmp > lhs ) + E::SafeIntOnOverflow(); + } + + result = (T)tmp; + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastInt64CheckOverflowSafeIntMinMax> +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + //rhs is signed __int64, lhs signed + __int64 tmp; + + if( AdditionHelper< __int64, __int64, AdditionState_CastInt64CheckOverflow >::Addition( (__int64)lhs, (__int64)rhs, tmp ) && + tmp <= IntTraits< T >::maxInt && + tmp >= IntTraits< T >::minInt ) + { + result = (T)tmp; + return true; + } + + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + //rhs is signed __int64, lhs signed + __int64 tmp; + + AdditionHelper< __int64, __int64, AdditionState_CastInt64CheckOverflow >::AdditionThrow< E >( (__int64)lhs, (__int64)rhs, tmp ); + + if( tmp <= IntTraits< T >::maxInt && + tmp >= IntTraits< T >::minInt ) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastInt64CheckOverflowMax> +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + //lhs is signed __int64, rhs unsigned < 64-bit + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + if( (__int64)tmp >= lhs ) + { + result = (T)(__int64)tmp; + return true; + } + + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + // lhs is signed __int64, rhs unsigned < 64-bit + // Some compilers get optimization-happy, let's thwart them + + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + if( (__int64)tmp >= lhs ) + { + result = (T)(__int64)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_ManualCheckInt64Uint64 > +{ +public: + static bool Addition( const __int64& lhs, const unsigned __int64& rhs, __int64& result ) SAFEINT_NOTHROW + { + C_ASSERT( IntTraits< T >::isInt64 && IntTraits< U >::isUint64 ); + // rhs is unsigned __int64, lhs __int64 + // cast everything to unsigned, perform addition, then + // cast back for check - this is done to stop optimizers from removing the code + unsigned __int64 tmp = (unsigned __int64)lhs + rhs; + + if( (__int64)tmp >= lhs ) + { + result = (__int64)tmp; + return true; + } + + return false; + } + + template < typename E > + static void AdditionThrow( const __int64& lhs, const unsigned __int64& rhs, T& result ) SAFEINT_CPP_THROW + { + C_ASSERT( IntTraits< T >::isInt64 && IntTraits< U >::isUint64 ); + // rhs is unsigned __int64, lhs __int64 + unsigned __int64 tmp = (unsigned __int64)lhs + rhs; + + if( (__int64)tmp >= lhs ) + { + result = (__int64)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_ManualCheck> +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + // rhs is unsigned __int64, lhs signed, 32-bit or less + if( (unsigned __int32)( rhs >> 32 ) == 0 ) + { + // Now it just happens to work out that the standard behavior does what we want + // Adding explicit casts to show exactly what's happening here + // Note - this is tweaked to keep optimizers from tossing out the code. + unsigned __int32 tmp = (unsigned __int32)rhs + (unsigned __int32)lhs; + + if( (__int32)tmp >= lhs && SafeCastHelper< T, __int32, GetCastMethod< T, __int32 >::method >::Cast( (__int32)tmp, result ) ) + return true; + } + + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + // rhs is unsigned __int64, lhs signed, 32-bit or less + + if( (unsigned __int32)( rhs >> 32 ) == 0 ) + { + // Now it just happens to work out that the standard behavior does what we want + // Adding explicit casts to show exactly what's happening here + unsigned __int32 tmp = (unsigned __int32)rhs + (unsigned __int32)lhs; + + if( (__int32)tmp >= lhs ) + { + SafeCastHelper< T, __int32, GetCastMethod< T, __int32 >::method >::template CastThrow< E >( (__int32)tmp, result ); + return; + } + } + E::SafeIntOnOverflow(); + } +}; + +enum SubtractionState +{ + SubtractionState_BothUnsigned, + SubtractionState_CastIntCheckSafeIntMinMax, + SubtractionState_CastIntCheckMin, + SubtractionState_CastInt64CheckSafeIntMinMax, + SubtractionState_CastInt64CheckMin, + SubtractionState_Uint64Int, + SubtractionState_UintInt64, + SubtractionState_Int64Int, + SubtractionState_IntInt64, + SubtractionState_Int64Uint, + SubtractionState_IntUint64, + SubtractionState_Int64Uint64, + // states for SubtractionMethod2 + SubtractionState_BothUnsigned2, + SubtractionState_CastIntCheckSafeIntMinMax2, + SubtractionState_CastInt64CheckSafeIntMinMax2, + SubtractionState_Uint64Int2, + SubtractionState_UintInt642, + SubtractionState_Int64Int2, + SubtractionState_IntInt642, + SubtractionState_Int64Uint2, + SubtractionState_IntUint642, + SubtractionState_Int64Uint642, + SubtractionState_Error +}; + +template < typename T, typename U > class SubtractionMethod +{ +public: + enum + { + // unsigned-unsigned + method = ((IntRegion< T,U >::IntZone_UintLT32_UintLT32 || + (IntRegion< T,U >::IntZone_Uint32_UintLT64) || + (IntRegion< T,U >::IntZone_UintLT32_Uint32) || + (IntRegion< T,U >::IntZone_Uint64_Uint) || + (IntRegion< T,U >::IntZone_UintLT64_Uint64)) ? SubtractionState_BothUnsigned : + // unsigned-signed + (IntRegion< T,U >::IntZone_UintLT32_IntLT32) ? SubtractionState_CastIntCheckSafeIntMinMax : + (IntRegion< T,U >::IntZone_Uint32_IntLT64 || + IntRegion< T,U >::IntZone_UintLT32_Int32) ? SubtractionState_CastInt64CheckSafeIntMinMax : + (IntRegion< T,U >::IntZone_Uint64_Int || + IntRegion< T,U >::IntZone_Uint64_Int64) ? SubtractionState_Uint64Int : + (IntRegion< T,U >::IntZone_UintLT64_Int64) ? SubtractionState_UintInt64 : + // signed-signed + (IntRegion< T,U >::IntZone_IntLT32_IntLT32) ? SubtractionState_CastIntCheckSafeIntMinMax : + (IntRegion< T,U >::IntZone_Int32_IntLT64 || + IntRegion< T,U >::IntZone_IntLT32_Int32) ? SubtractionState_CastInt64CheckSafeIntMinMax : + (IntRegion< T,U >::IntZone_Int64_Int || + IntRegion< T,U >::IntZone_Int64_Int64) ? SubtractionState_Int64Int : + (IntRegion< T,U >::IntZone_IntLT64_Int64) ? SubtractionState_IntInt64 : + // signed-unsigned + (IntRegion< T,U >::IntZone_IntLT32_UintLT32) ? SubtractionState_CastIntCheckMin : + (IntRegion< T,U >::IntZone_Int32_UintLT32 || + IntRegion< T,U >::IntZone_IntLT64_Uint32) ? SubtractionState_CastInt64CheckMin : + (IntRegion< T,U >::IntZone_Int64_UintLT64) ? SubtractionState_Int64Uint : + (IntRegion< T,U >::IntZone_Int_Uint64) ? SubtractionState_IntUint64 : + (IntRegion< T,U >::IntZone_Int64_Uint64) ? SubtractionState_Int64Uint64 : + SubtractionState_Error) + }; +}; + +// this is for the case of U - SafeInt< T, E > +template < typename T, typename U > class SubtractionMethod2 +{ +public: + enum + { + // unsigned-unsigned + method = ((IntRegion< T,U >::IntZone_UintLT32_UintLT32 || + (IntRegion< T,U >::IntZone_Uint32_UintLT64) || + (IntRegion< T,U >::IntZone_UintLT32_Uint32) || + (IntRegion< T,U >::IntZone_Uint64_Uint) || + (IntRegion< T,U >::IntZone_UintLT64_Uint64)) ? SubtractionState_BothUnsigned2 : + // unsigned-signed + (IntRegion< T,U >::IntZone_UintLT32_IntLT32) ? SubtractionState_CastIntCheckSafeIntMinMax2 : + (IntRegion< T,U >::IntZone_Uint32_IntLT64 || + IntRegion< T,U >::IntZone_UintLT32_Int32) ? SubtractionState_CastInt64CheckSafeIntMinMax2 : + (IntRegion< T,U >::IntZone_Uint64_Int || + IntRegion< T,U >::IntZone_Uint64_Int64) ? SubtractionState_Uint64Int2 : + (IntRegion< T,U >::IntZone_UintLT64_Int64) ? SubtractionState_UintInt642 : + // signed-signed + (IntRegion< T,U >::IntZone_IntLT32_IntLT32) ? SubtractionState_CastIntCheckSafeIntMinMax2 : + (IntRegion< T,U >::IntZone_Int32_IntLT64 || + IntRegion< T,U >::IntZone_IntLT32_Int32) ? SubtractionState_CastInt64CheckSafeIntMinMax2 : + (IntRegion< T,U >::IntZone_Int64_Int || + IntRegion< T,U >::IntZone_Int64_Int64) ? SubtractionState_Int64Int2 : + (IntRegion< T,U >::IntZone_IntLT64_Int64) ? SubtractionState_IntInt642 : + // signed-unsigned + (IntRegion< T,U >::IntZone_IntLT32_UintLT32) ? SubtractionState_CastIntCheckSafeIntMinMax2 : + (IntRegion< T,U >::IntZone_Int32_UintLT32 || + IntRegion< T,U >::IntZone_IntLT64_Uint32) ? SubtractionState_CastInt64CheckSafeIntMinMax2 : + (IntRegion< T,U >::IntZone_Int64_UintLT64) ? SubtractionState_Int64Uint2 : + (IntRegion< T,U >::IntZone_Int_Uint64) ? SubtractionState_IntUint642 : + (IntRegion< T,U >::IntZone_Int64_Uint64) ? SubtractionState_Int64Uint642 : + SubtractionState_Error) + }; +}; + +template < typename T, typename U, int method > class SubtractionHelper; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_BothUnsigned > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + // both are unsigned - easy case + if( rhs <= lhs ) + { + result = (T)( lhs - rhs ); + return true; + } + + return false; + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + // both are unsigned - easy case + if( rhs <= lhs ) + { + result = (T)( lhs - rhs ); + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_BothUnsigned2 > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, U& result ) SAFEINT_NOTHROW + { + // both are unsigned - easy case + // Except we do have to check for overflow - lhs could be larger than result can hold + if( rhs <= lhs ) + { + T tmp = (T)(lhs - rhs); + return SafeCastHelper< U, T, GetCastMethod::method>::Cast( tmp, result); + } + + return false; + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, U& result ) SAFEINT_CPP_THROW + { + // both are unsigned - easy case + if( rhs <= lhs ) + { + T tmp = (T)(lhs - rhs); + SafeCastHelper< U, T, GetCastMethod::method >::template CastThrow( tmp, result); + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_CastIntCheckSafeIntMinMax > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + // both values are 16-bit or less + // rhs is signed, so could end up increasing or decreasing + __int32 tmp = lhs - rhs; + + if( SafeCastHelper< T, __int32, GetCastMethod< T, __int32 >::method >::Cast( tmp, result ) ) + { + result = (T)tmp; + return true; + } + + return false; + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + // both values are 16-bit or less + // rhs is signed, so could end up increasing or decreasing + __int32 tmp = lhs - rhs; + + SafeCastHelper< T, __int32, GetCastMethod< T, __int32 >::method >::template CastThrow< E >( tmp, result ); + } +}; + +template class SubtractionHelper< U, T, SubtractionState_CastIntCheckSafeIntMinMax2 > +{ +public: + static bool Subtract( const U& lhs, const T& rhs, T& result ) SAFEINT_NOTHROW + { + // both values are 16-bit or less + // rhs is signed, so could end up increasing or decreasing + __int32 tmp = lhs - rhs; + + return SafeCastHelper< T, __int32, GetCastMethod< T, __int32 >::method >::Cast( tmp, result ); + } + + template < typename E > + static void SubtractThrow( const U& lhs, const T& rhs, T& result ) SAFEINT_CPP_THROW + { + // both values are 16-bit or less + // rhs is signed, so could end up increasing or decreasing + __int32 tmp = lhs - rhs; + + SafeCastHelper< T, __int32, GetCastMethod< T, __int32 >::method >::template CastThrow< E >( tmp, result ); + } +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_CastIntCheckMin > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + // both values are 16-bit or less + // rhs is unsigned - check only minimum + __int32 tmp = lhs - rhs; + + if( tmp >= (__int32)IntTraits< T >::minInt ) + { + result = (T)tmp; + return true; + } + + return false; + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + // both values are 16-bit or less + // rhs is unsigned - check only minimum + __int32 tmp = lhs - rhs; + + if( tmp >= (__int32)IntTraits< T >::minInt ) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_CastInt64CheckSafeIntMinMax > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + // both values are 32-bit or less + // rhs is signed, so could end up increasing or decreasing + __int64 tmp = (__int64)lhs - (__int64)rhs; + + return SafeCastHelper< T, __int64, GetCastMethod< T, __int64 >::method >::Cast( tmp, result ); + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + // both values are 32-bit or less + // rhs is signed, so could end up increasing or decreasing + __int64 tmp = (__int64)lhs - (__int64)rhs; + + SafeCastHelper< T, __int64, GetCastMethod< T, __int64 >::method >::template CastThrow< E >( tmp, result ); + } +}; + +template class SubtractionHelper< U, T, SubtractionState_CastInt64CheckSafeIntMinMax2 > +{ +public: + static bool Subtract( const U& lhs, const T& rhs, T& result ) SAFEINT_NOTHROW + { + // both values are 32-bit or less + // rhs is signed, so could end up increasing or decreasing + __int64 tmp = (__int64)lhs - (__int64)rhs; + + return SafeCastHelper< T, __int64, GetCastMethod< T, __int64 >::method >::Cast( tmp, result ); + } + + template < typename E > + static void SubtractThrow( const U& lhs, const T& rhs, T& result ) SAFEINT_CPP_THROW + { + // both values are 32-bit or less + // rhs is signed, so could end up increasing or decreasing + __int64 tmp = (__int64)lhs - (__int64)rhs; + + SafeCastHelper< T, __int64, GetCastMethod< T, __int64 >::method >::template CastThrow< E >( tmp, result ); + } +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_CastInt64CheckMin > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + // both values are 32-bit or less + // rhs is unsigned - check only minimum + __int64 tmp = (__int64)lhs - (__int64)rhs; + + if( tmp >= (__int64)IntTraits< T >::minInt ) + { + result = (T)tmp; + return true; + } + + return false; + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + // both values are 32-bit or less + // rhs is unsigned - check only minimum + __int64 tmp = (__int64)lhs - (__int64)rhs; + + if( tmp >= (__int64)IntTraits< T >::minInt ) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_Uint64Int > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + // lhs is an unsigned __int64, rhs signed + // must first see if rhs is positive or negative + if( rhs >= 0 ) + { + if( (unsigned __int64)rhs <= lhs ) + { + result = (T)( lhs - (unsigned __int64)rhs ); + return true; + } + } + else + { + T tmp = lhs; + // we're now effectively adding + result = lhs + AbsValueHelper< U, GetAbsMethod< U >::method >::Abs( rhs ); + + if(result >= tmp) + return true; + } + + return false; + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + // lhs is an unsigned __int64, rhs signed + // must first see if rhs is positive or negative + if( rhs >= 0 ) + { + if( (unsigned __int64)rhs <= lhs ) + { + result = (T)( lhs - (unsigned __int64)rhs ); + return; + } + } + else + { + T tmp = lhs; + // we're now effectively adding + result = lhs + AbsValueHelper< U, GetAbsMethod< U >::method >::Abs( rhs ); + + if(result >= tmp) + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename U, typename T > class SubtractionHelper< U, T, SubtractionState_Uint64Int2 > +{ +public: + static bool Subtract( const U& lhs, const T& rhs, T& result ) SAFEINT_NOTHROW + { + // U is unsigned __int64, T is signed + if( rhs < 0 ) + { + // treat this as addition + unsigned __int64 tmp; + + tmp = lhs + (unsigned __int64)AbsValueHelper< T, GetAbsMethod< T >::method >::Abs( rhs ); + + // must check for addition overflow and max + if( tmp >= lhs && tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return true; + } + } + else if( (unsigned __int64)rhs > lhs ) // now both are positive, so comparison always works + { + // result is negative + // implies that lhs must fit into T, and result cannot overflow + // Also allows us to drop to 32-bit math, which is faster on a 32-bit system + result = (T)lhs - (T)rhs; + return true; + } + else + { + // result is positive + unsigned __int64 tmp = (unsigned __int64)lhs - (unsigned __int64)rhs; + + if( tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return true; + } + } + + return false; + } + + template < typename E > + static void SubtractThrow( const U& lhs, const T& rhs, T& result ) SAFEINT_CPP_THROW + { + // U is unsigned __int64, T is signed + if( rhs < 0 ) + { + // treat this as addition + unsigned __int64 tmp; + + tmp = lhs + (unsigned __int64)AbsValueHelper< T, GetAbsMethod< T >::method >::Abs( rhs ); + + // must check for addition overflow and max + if( tmp >= lhs && tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return; + } + } + else if( (unsigned __int64)rhs > lhs ) // now both are positive, so comparison always works + { + // result is negative + // implies that lhs must fit into T, and result cannot overflow + // Also allows us to drop to 32-bit math, which is faster on a 32-bit system + result = (T)lhs - (T)rhs; + return; + } + else + { + // result is positive + unsigned __int64 tmp = (unsigned __int64)lhs - (unsigned __int64)rhs; + + if( tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return; + } + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_UintInt64 > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + // lhs is an unsigned int32 or smaller, rhs signed __int64 + // must first see if rhs is positive or negative + if( rhs >= 0 ) + { + if( (unsigned __int64)rhs <= lhs ) + { + result = (T)( lhs - (T)rhs ); + return true; + } + } + else + { + // we're now effectively adding + // since lhs is 32-bit, and rhs cannot exceed 2^63 + // this addition cannot overflow + unsigned __int64 tmp = lhs + ~(unsigned __int64)( rhs ) + 1; // negation safe + + // but we could exceed MaxInt + if(tmp <= IntTraits< T >::maxInt) + { + result = (T)tmp; + return true; + } + } + + return false; + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + // lhs is an unsigned int32 or smaller, rhs signed __int64 + // must first see if rhs is positive or negative + if( rhs >= 0 ) + { + if( (unsigned __int64)rhs <= lhs ) + { + result = (T)( lhs - (T)rhs ); + return; + } + } + else + { + // we're now effectively adding + // since lhs is 32-bit, and rhs cannot exceed 2^63 + // this addition cannot overflow + unsigned __int64 tmp = lhs + ~(unsigned __int64)( rhs ) + 1; // negation safe + + // but we could exceed MaxInt + if(tmp <= IntTraits< T >::maxInt) + { + result = (T)tmp; + return; + } + } + + E::SafeIntOnOverflow(); + } +}; + +template class SubtractionHelper< U, T, SubtractionState_UintInt642 > +{ +public: + static bool Subtract( const U& lhs, const T& rhs, T& result ) SAFEINT_NOTHROW + { + // U unsigned 32-bit or less, T __int64 + if( rhs >= 0 ) + { + // overflow not possible + result = (T)( (__int64)lhs - rhs ); + return true; + } + else + { + // we effectively have an addition + // which cannot overflow internally + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)( -rhs ); + + if( tmp <= (unsigned __int64)IntTraits< T >::maxInt ) + { + result = (T)tmp; + return true; + } + } + + return false; + } + + template < typename E > + static void SubtractThrow( const U& lhs, const T& rhs, T& result ) SAFEINT_CPP_THROW + { + // U unsigned 32-bit or less, T __int64 + if( rhs >= 0 ) + { + // overflow not possible + result = (T)( (__int64)lhs - rhs ); + return; + } + else + { + // we effectively have an addition + // which cannot overflow internally + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)( -rhs ); + + if( tmp <= (unsigned __int64)IntTraits< T >::maxInt ) + { + result = (T)tmp; + return; + } + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_Int64Int > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + // lhs is an __int64, rhs signed (up to 64-bit) + // we have essentially 4 cases: + // + // 1) lhs positive, rhs positive - overflow not possible + // 2) lhs positive, rhs negative - equivalent to addition - result >= lhs or error + // 3) lhs negative, rhs positive - check result <= lhs + // 4) lhs negative, rhs negative - overflow not possible + + __int64 tmp = (__int64)((unsigned __int64)lhs - (unsigned __int64)rhs); + + // Note - ideally, we can order these so that true conditionals + // lead to success, which enables better pipelining + // It isn't practical here + if( ( lhs >= 0 && rhs < 0 && tmp < lhs ) || // condition 2 + ( rhs >= 0 && tmp > lhs ) ) // condition 3 + { + return false; + } + + result = (T)tmp; + return true; + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + // lhs is an __int64, rhs signed (up to 64-bit) + // we have essentially 4 cases: + // + // 1) lhs positive, rhs positive - overflow not possible + // 2) lhs positive, rhs negative - equivalent to addition - result >= lhs or error + // 3) lhs negative, rhs positive - check result <= lhs + // 4) lhs negative, rhs negative - overflow not possible + + __int64 tmp = (__int64)((unsigned __int64)lhs - (unsigned __int64)rhs); + + // Note - ideally, we can order these so that true conditionals + // lead to success, which enables better pipelining + // It isn't practical here + if( ( lhs >= 0 && rhs < 0 && tmp < lhs ) || // condition 2 + ( rhs >= 0 && tmp > lhs ) ) // condition 3 + { + E::SafeIntOnOverflow(); + } + + result = (T)tmp; + } +}; + +template < typename U, typename T > class SubtractionHelper< U, T, SubtractionState_Int64Int2 > +{ +public: + static bool Subtract( const U& lhs, const T& rhs, T& result ) SAFEINT_NOTHROW + { + // lhs __int64, rhs any signed int (including __int64) + __int64 tmp = lhs - rhs; + + // we have essentially 4 cases: + // + // 1) lhs positive, rhs positive - overflow not possible in tmp + // 2) lhs positive, rhs negative - equivalent to addition - result >= lhs or error + // 3) lhs negative, rhs positive - check result <= lhs + // 4) lhs negative, rhs negative - overflow not possible in tmp + + if( lhs >= 0 ) + { + // if both positive, overflow to negative not possible + // which is why we'll explicitly check maxInt, and not call SafeCast + if( ( IntTraits< T >::isLT64Bit && tmp > IntTraits< T >::maxInt ) || + ( rhs < 0 && tmp < lhs ) ) + { + return false; + } + } + else + { + // lhs negative + if( ( IntTraits< T >::isLT64Bit && tmp < IntTraits< T >::minInt) || + ( rhs >=0 && tmp > lhs ) ) + { + return false; + } + } + + result = (T)tmp; + return true; + } + + template < typename E > + static void SubtractThrow( const U& lhs, const T& rhs, T& result ) SAFEINT_CPP_THROW + { + // lhs __int64, rhs any signed int (including __int64) + __int64 tmp = lhs - rhs; + + // we have essentially 4 cases: + // + // 1) lhs positive, rhs positive - overflow not possible in tmp + // 2) lhs positive, rhs negative - equivalent to addition - result >= lhs or error + // 3) lhs negative, rhs positive - check result <= lhs + // 4) lhs negative, rhs negative - overflow not possible in tmp + + if( lhs >= 0 ) + { + // if both positive, overflow to negative not possible + // which is why we'll explicitly check maxInt, and not call SafeCast + if( ( CompileConst< IntTraits< T >::isLT64Bit >::Value() && tmp > IntTraits< T >::maxInt ) || + ( rhs < 0 && tmp < lhs ) ) + { + E::SafeIntOnOverflow(); + } + } + else + { + // lhs negative + if( ( CompileConst< IntTraits< T >::isLT64Bit >::Value() && tmp < IntTraits< T >::minInt) || + ( rhs >=0 && tmp > lhs ) ) + { + E::SafeIntOnOverflow(); + } + } + + result = (T)tmp; + } +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_IntInt64 > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + // lhs is a 32-bit int or less, rhs __int64 + // we have essentially 4 cases: + // + // lhs positive, rhs positive - rhs could be larger than lhs can represent + // lhs positive, rhs negative - additive case - check tmp >= lhs and tmp > max int + // lhs negative, rhs positive - check tmp <= lhs and tmp < min int + // lhs negative, rhs negative - addition cannot internally overflow, check against max + + __int64 tmp = (__int64)((unsigned __int64)lhs - (unsigned __int64)rhs); + + if( lhs >= 0 ) + { + // first case + if( rhs >= 0 ) + { + if( tmp >= IntTraits< T >::minInt ) + { + result = (T)tmp; + return true; + } + } + else + { + // second case + if( tmp >= lhs && tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return true; + } + } + } + else + { + // lhs < 0 + // third case + if( rhs >= 0 ) + { + if( tmp <= lhs && tmp >= IntTraits< T >::minInt ) + { + result = (T)tmp; + return true; + } + } + else + { + // fourth case + if( tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return true; + } + } + } + + return false; + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + // lhs is a 32-bit int or less, rhs __int64 + // we have essentially 4 cases: + // + // lhs positive, rhs positive - rhs could be larger than lhs can represent + // lhs positive, rhs negative - additive case - check tmp >= lhs and tmp > max int + // lhs negative, rhs positive - check tmp <= lhs and tmp < min int + // lhs negative, rhs negative - addition cannot internally overflow, check against max + + __int64 tmp = (__int64)((unsigned __int64)lhs - (unsigned __int64)rhs); + + if( lhs >= 0 ) + { + // first case + if( rhs >= 0 ) + { + if( tmp >= IntTraits< T >::minInt ) + { + result = (T)tmp; + return; + } + } + else + { + // second case + if( tmp >= lhs && tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return; + } + } + } + else + { + // lhs < 0 + // third case + if( rhs >= 0 ) + { + if( tmp <= lhs && tmp >= IntTraits< T >::minInt ) + { + result = (T)tmp; + return; + } + } + else + { + // fourth case + if( tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return; + } + } + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename U, typename T > class SubtractionHelper< U, T, SubtractionState_IntInt642 > +{ +public: + static bool Subtract( const U& lhs, const T& rhs, T& result ) SAFEINT_NOTHROW + { + // lhs is any signed int32 or smaller, rhs is int64 + __int64 tmp = (__int64)lhs - rhs; + + if( ( lhs >= 0 && rhs < 0 && tmp < lhs ) || + ( rhs > 0 && tmp > lhs ) ) + { + return false; + //else OK + } + + result = (T)tmp; + return true; + } + + template < typename E > + static void SubtractThrow( const U& lhs, const T& rhs, T& result ) SAFEINT_CPP_THROW + { + // lhs is any signed int32 or smaller, rhs is int64 + __int64 tmp = (__int64)lhs - rhs; + + if( ( lhs >= 0 && rhs < 0 && tmp < lhs ) || + ( rhs > 0 && tmp > lhs ) ) + { + E::SafeIntOnOverflow(); + //else OK + } + + result = (T)tmp; + } +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_Int64Uint > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + // lhs is a 64-bit int, rhs unsigned int32 or smaller + // perform test as unsigned to prevent unwanted optimizations + unsigned __int64 tmp = (unsigned __int64)lhs - (unsigned __int64)rhs; + + if( (__int64)tmp <= lhs ) + { + result = (T)(__int64)tmp; + return true; + } + + return false; + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + // lhs is a 64-bit int, rhs unsigned int32 or smaller + // perform test as unsigned to prevent unwanted optimizations + unsigned __int64 tmp = (unsigned __int64)lhs - (unsigned __int64)rhs; + + if( (__int64)tmp <= lhs ) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename U, typename T > class SubtractionHelper< U, T, SubtractionState_Int64Uint2 > +{ +public: + // lhs is __int64, rhs is unsigned 32-bit or smaller + static bool Subtract( const U& lhs, const T& rhs, T& result ) SAFEINT_NOTHROW + { + // Do this as unsigned to prevent unwanted optimizations + unsigned __int64 tmp = (unsigned __int64)lhs - (unsigned __int64)rhs; + + if( (__int64)tmp <= IntTraits< T >::maxInt && (__int64)tmp >= IntTraits< T >::minInt ) + { + result = (T)(__int64)tmp; + return true; + } + + return false; + } + + template < typename E > + static void SubtractThrow( const U& lhs, const T& rhs, T& result ) SAFEINT_CPP_THROW + { + // Do this as unsigned to prevent unwanted optimizations + unsigned __int64 tmp = (unsigned __int64)lhs - (unsigned __int64)rhs; + + if( (__int64)tmp <= IntTraits< T >::maxInt && (__int64)tmp >= IntTraits< T >::minInt ) + { + result = (T)(__int64)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_IntUint64 > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, T& result ) SAFEINT_NOTHROW + { + // lhs is any signed int, rhs unsigned int64 + // check against available range + + // We need the absolute value of IntTraits< T >::minInt + // This will give it to us without extraneous compiler warnings + const unsigned __int64 AbsMinIntT = (unsigned __int64)IntTraits< T >::maxInt + 1; + + if( lhs < 0 ) + { + if( rhs <= AbsMinIntT - AbsValueHelper< T, GetAbsMethod< T >::method >::Abs( lhs ) ) + { + result = (T)( lhs - rhs ); + return true; + } + } + else + { + if( rhs <= AbsMinIntT + (unsigned __int64)lhs ) + { + result = (T)( lhs - rhs ); + return true; + } + } + + return false; + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, T& result ) SAFEINT_CPP_THROW + { + // lhs is any signed int, rhs unsigned int64 + // check against available range + + // We need the absolute value of IntTraits< T >::minInt + // This will give it to us without extraneous compiler warnings + const unsigned __int64 AbsMinIntT = (unsigned __int64)IntTraits< T >::maxInt + 1; + + if( lhs < 0 ) + { + if( rhs <= AbsMinIntT - AbsValueHelper< T, GetAbsMethod< T >::method >::Abs( lhs ) ) + { + result = (T)( lhs - rhs ); + return; + } + } + else + { + if( rhs <= AbsMinIntT + (unsigned __int64)lhs ) + { + result = (T)( lhs - rhs ); + return; + } + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename U, typename T > class SubtractionHelper< U, T, SubtractionState_IntUint642 > +{ +public: + static bool Subtract( const U& lhs, const T& rhs, T& result ) SAFEINT_NOTHROW + { + // We run into upcasting problems on comparison - needs 2 checks + if( lhs >= 0 && (T)lhs >= rhs ) + { + result = (T)((U)lhs - (U)rhs); + return true; + } + + return false; + } + + template < typename E > + static void SubtractThrow( const U& lhs, const T& rhs, T& result ) SAFEINT_CPP_THROW + { + // We run into upcasting problems on comparison - needs 2 checks + if( lhs >= 0 && (T)lhs >= rhs ) + { + result = (T)((U)lhs - (U)rhs); + return; + } + + E::SafeIntOnOverflow(); + } + +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_Int64Uint64 > +{ +public: + static bool Subtract( const __int64& lhs, const unsigned __int64& rhs, __int64& result ) SAFEINT_NOTHROW + { + C_ASSERT( IntTraits< T >::isInt64 && IntTraits< U >::isUint64 ); + // if we subtract, and it gets larger, there's a problem + // Perform test as unsigned to prevent unwanted optimizations + unsigned __int64 tmp = (unsigned __int64)lhs - rhs; + + if( (__int64)tmp <= lhs ) + { + result = (__int64)tmp; + return true; + } + return false; + } + + template < typename E > + static void SubtractThrow( const __int64& lhs, const unsigned __int64& rhs, T& result ) SAFEINT_CPP_THROW + { + C_ASSERT( IntTraits< T >::isInt64 && IntTraits< U >::isUint64 ); + // if we subtract, and it gets larger, there's a problem + // Perform test as unsigned to prevent unwanted optimizations + unsigned __int64 tmp = (unsigned __int64)lhs - rhs; + + if( (__int64)tmp <= lhs ) + { + result = (__int64)tmp; + return; + } + + E::SafeIntOnOverflow(); + } + +}; + +template < typename U, typename T > class SubtractionHelper< U, T, SubtractionState_Int64Uint642 > +{ +public: + // If lhs is negative, immediate problem - return must be positive, and subtracting only makes it + // get smaller. If rhs > lhs, then it would also go negative, which is the other case + static bool Subtract( const __int64& lhs, const unsigned __int64& rhs, T& result ) SAFEINT_NOTHROW + { + C_ASSERT( IntTraits< T >::isUint64 && IntTraits< U >::isInt64 ); + if( lhs >= 0 && (unsigned __int64)lhs >= rhs ) + { + result = (unsigned __int64)lhs - rhs; + return true; + } + + return false; + } + + template < typename E > + static void SubtractThrow( const __int64& lhs, const unsigned __int64& rhs, T& result ) SAFEINT_CPP_THROW + { + C_ASSERT( IntTraits< T >::isUint64 && IntTraits< U >::isInt64 ); + if( lhs >= 0 && (unsigned __int64)lhs >= rhs ) + { + result = (unsigned __int64)lhs - rhs; + return; + } + + E::SafeIntOnOverflow(); + } + +}; + +enum BinaryState +{ + BinaryState_OK, + BinaryState_Int8, + BinaryState_Int16, + BinaryState_Int32 +}; + +template < typename T, typename U > class BinaryMethod +{ +public: + enum + { + // If both operands are unsigned OR + // return type is smaller than rhs OR + // return type is larger and rhs is unsigned + // Then binary operations won't produce unexpected results + method = ( sizeof( T ) <= sizeof( U ) || + SafeIntCompare< T, U >::isBothUnsigned || + !IntTraits< U >::isSigned ) ? BinaryState_OK : + IntTraits< U >::isInt8 ? BinaryState_Int8 : + IntTraits< U >::isInt16 ? BinaryState_Int16 + : BinaryState_Int32 + }; +}; + +#ifdef SAFEINT_DISABLE_BINARY_ASSERT +#define BinaryAssert(x) +#else +#define BinaryAssert(x) SAFEINT_ASSERT(x) +#endif + +template < typename T, typename U, int method > class BinaryAndHelper; + +template < typename T, typename U > class BinaryAndHelper< T, U, BinaryState_OK > +{ +public: + static T And( T lhs, U rhs ) SAFEINT_NOTHROW { return (T)( lhs & rhs ); } +}; + +template < typename T, typename U > class BinaryAndHelper< T, U, BinaryState_Int8 > +{ +public: + static T And( T lhs, U rhs ) SAFEINT_NOTHROW + { + // cast forces sign extension to be zeros + BinaryAssert( ( lhs & rhs ) == ( lhs & (unsigned __int8)rhs ) ); + return (T)( lhs & (unsigned __int8)rhs ); + } +}; + +template < typename T, typename U > class BinaryAndHelper< T, U, BinaryState_Int16 > +{ +public: + static T And( T lhs, U rhs ) SAFEINT_NOTHROW + { + //cast forces sign extension to be zeros + BinaryAssert( ( lhs & rhs ) == ( lhs & (unsigned __int16)rhs ) ); + return (T)( lhs & (unsigned __int16)rhs ); + } +}; + +template < typename T, typename U > class BinaryAndHelper< T, U, BinaryState_Int32 > +{ +public: + static T And( T lhs, U rhs ) SAFEINT_NOTHROW + { + //cast forces sign extension to be zeros + BinaryAssert( ( lhs & rhs ) == ( lhs & (unsigned __int32)rhs ) ); + return (T)( lhs & (unsigned __int32)rhs ); + } +}; + +template < typename T, typename U, int method > class BinaryOrHelper; + +template < typename T, typename U > class BinaryOrHelper< T, U, BinaryState_OK > +{ +public: + static T Or( T lhs, U rhs ) SAFEINT_NOTHROW { return (T)( lhs | rhs ); } +}; + +template < typename T, typename U > class BinaryOrHelper< T, U, BinaryState_Int8 > +{ +public: + static T Or( T lhs, U rhs ) SAFEINT_NOTHROW + { + //cast forces sign extension to be zeros + BinaryAssert( ( lhs | rhs ) == ( lhs | (unsigned __int8)rhs ) ); + return (T)( lhs | (unsigned __int8)rhs ); + } +}; + +template < typename T, typename U > class BinaryOrHelper< T, U, BinaryState_Int16 > +{ +public: + static T Or( T lhs, U rhs ) SAFEINT_NOTHROW + { + //cast forces sign extension to be zeros + BinaryAssert( ( lhs | rhs ) == ( lhs | (unsigned __int16)rhs ) ); + return (T)( lhs | (unsigned __int16)rhs ); + } +}; + +template < typename T, typename U > class BinaryOrHelper< T, U, BinaryState_Int32 > +{ +public: + static T Or( T lhs, U rhs ) SAFEINT_NOTHROW + { + //cast forces sign extension to be zeros + BinaryAssert( ( lhs | rhs ) == ( lhs | (unsigned __int32)rhs ) ); + return (T)( lhs | (unsigned __int32)rhs ); + } +}; + +template class BinaryXorHelper; + +template < typename T, typename U > class BinaryXorHelper< T, U, BinaryState_OK > +{ +public: + static T Xor( T lhs, U rhs ) SAFEINT_NOTHROW { return (T)( lhs ^ rhs ); } +}; + +template < typename T, typename U > class BinaryXorHelper< T, U, BinaryState_Int8 > +{ +public: + static T Xor( T lhs, U rhs ) SAFEINT_NOTHROW + { + // cast forces sign extension to be zeros + BinaryAssert( ( lhs ^ rhs ) == ( lhs ^ (unsigned __int8)rhs ) ); + return (T)( lhs ^ (unsigned __int8)rhs ); + } +}; + +template < typename T, typename U > class BinaryXorHelper< T, U, BinaryState_Int16 > +{ +public: + static T Xor( T lhs, U rhs ) SAFEINT_NOTHROW + { + // cast forces sign extension to be zeros + BinaryAssert( ( lhs ^ rhs ) == ( lhs ^ (unsigned __int16)rhs ) ); + return (T)( lhs ^ (unsigned __int16)rhs ); + } +}; + +template < typename T, typename U > class BinaryXorHelper< T, U, BinaryState_Int32 > +{ +public: + static T Xor( T lhs, U rhs ) SAFEINT_NOTHROW + { + // cast forces sign extension to be zeros + BinaryAssert( ( lhs ^ rhs ) == ( lhs ^ (unsigned __int32)rhs ) ); + return (T)( lhs ^ (unsigned __int32)rhs ); + } +}; + +/***************** External functions ****************************************/ + +// External functions that can be used where you only need to check one operation +// non-class helper function so that you can check for a cast's validity +// and handle errors how you like +template < typename T, typename U > +inline bool SafeCast( const T From, U& To ) SAFEINT_NOTHROW +{ + return SafeCastHelper< U, T, GetCastMethod< U, T >::method >::Cast( From, To ); +} + +template < typename T, typename U > +inline bool SafeEquals( const T t, const U u ) SAFEINT_NOTHROW +{ + return EqualityTest< T, U, ValidComparison< T, U >::method >::IsEquals( t, u ); +} + +template < typename T, typename U > +inline bool SafeNotEquals( const T t, const U u ) SAFEINT_NOTHROW +{ + return !EqualityTest< T, U, ValidComparison< T, U >::method >::IsEquals( t, u ); +} + +template < typename T, typename U > +inline bool SafeGreaterThan( const T t, const U u ) SAFEINT_NOTHROW +{ + return GreaterThanTest< T, U, ValidComparison< T, U >::method >::GreaterThan( t, u ); +} + +template < typename T, typename U > +inline bool SafeGreaterThanEquals( const T t, const U u ) SAFEINT_NOTHROW +{ + return !GreaterThanTest< U, T, ValidComparison< U, T >::method >::GreaterThan( u, t ); +} + +template < typename T, typename U > +inline bool SafeLessThan( const T t, const U u ) SAFEINT_NOTHROW +{ + return GreaterThanTest< U, T, ValidComparison< U, T >::method >::GreaterThan( u, t ); +} + +template < typename T, typename U > +inline bool SafeLessThanEquals( const T t, const U u ) SAFEINT_NOTHROW +{ + return !GreaterThanTest< T, U, ValidComparison< T, U >::method >::GreaterThan( t, u ); +} + +template < typename T, typename U > +inline bool SafeModulus( const T& t, const U& u, T& result ) SAFEINT_NOTHROW +{ + return ( ModulusHelper< T, U, ValidComparison< T, U >::method >::Modulus( t, u, result ) == SafeIntNoError ); +} + +template < typename T, typename U > +inline bool SafeMultiply( T t, U u, T& result ) SAFEINT_NOTHROW +{ + return MultiplicationHelper< T, U, MultiplicationMethod< T, U >::method >::Multiply( t, u, result ); +} + +template < typename T, typename U > +inline bool SafeDivide( T t, U u, T& result ) SAFEINT_NOTHROW +{ + return ( DivisionHelper< T, U, DivisionMethod< T, U >::method >::Divide( t, u, result ) == SafeIntNoError ); +} + +template < typename T, typename U > +inline bool SafeAdd( T t, U u, T& result ) SAFEINT_NOTHROW +{ + return AdditionHelper< T, U, AdditionMethod< T, U >::method >::Addition( t, u, result ); +} + +template < typename T, typename U > +inline bool SafeSubtract( T t, U u, T& result ) SAFEINT_NOTHROW +{ + return SubtractionHelper< T, U, SubtractionMethod< T, U >::method >::Subtract( t, u, result ); +} + +/***************** end external functions ************************************/ + +// Main SafeInt class +// Assumes exceptions can be thrown +template < typename T, typename E = SafeIntDefaultExceptionHandler > class SafeInt +{ +public: + SafeInt() SAFEINT_NOTHROW + { + C_ASSERT( NumericType< T >::isInt ); + m_int = 0; + } + + // Having a constructor for every type of int + // avoids having the compiler evade our checks when doing implicit casts - + // e.g., SafeInt s = 0x7fffffff; + SafeInt( const T& i ) SAFEINT_NOTHROW + { + C_ASSERT( NumericType< T >::isInt ); + //always safe + m_int = i; + } + + // provide explicit boolean converter + SafeInt( bool b ) SAFEINT_NOTHROW + { + C_ASSERT( NumericType< T >::isInt ); + m_int = (T)( b ? 1 : 0 ); + } + + template < typename U > + SafeInt(const SafeInt< U, E >& u) SAFEINT_CPP_THROW + { + C_ASSERT( NumericType< T >::isInt ); + *this = SafeInt< T, E >( (U)u ); + } + + template < typename U > + SafeInt( const U& i ) SAFEINT_CPP_THROW + { + C_ASSERT( NumericType< T >::isInt ); + // SafeCast will throw exceptions if i won't fit in type T + SafeCastHelper< T, U, GetCastMethod< T, U >::method >::template CastThrow< E >( i, m_int ); + } + + // The destructor is intentionally commented out - no destructor + // vs. a do-nothing destructor makes a huge difference in + // inlining characteristics. It wasn't doing anything anyway. + // ~SafeInt(){}; + + + // now start overloading operators + // assignment operator + // constructors exist for all int types and will ensure safety + + template < typename U > + SafeInt< T, E >& operator =( const U& rhs ) SAFEINT_CPP_THROW + { + // use constructor to test size + // constructor is optimized to do minimal checking based + // on whether T can contain U + // note - do not change this + *this = SafeInt< T, E >( rhs ); + return *this; + } + + SafeInt< T, E >& operator =( const T& rhs ) SAFEINT_NOTHROW + { + m_int = rhs; + return *this; + } + + template < typename U > + SafeInt< T, E >& operator =( const SafeInt< U, E >& rhs ) SAFEINT_CPP_THROW + { + SafeCastHelper< T, U, GetCastMethod< T, U >::method >::template CastThrow< E >( rhs.Ref(), m_int ); + return *this; + } + + SafeInt< T, E >& operator =( const SafeInt< T, E >& rhs ) SAFEINT_NOTHROW + { + m_int = rhs.m_int; + return *this; + } + + // Casting operators + + operator bool() const SAFEINT_NOTHROW + { + return !!m_int; + } + + operator char() const SAFEINT_CPP_THROW + { + char val; + SafeCastHelper< char, T, GetCastMethod< char, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + operator signed char() const SAFEINT_CPP_THROW + { + signed char val; + SafeCastHelper< signed char, T, GetCastMethod< signed char, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + operator unsigned char() const SAFEINT_CPP_THROW + { + unsigned char val; + SafeCastHelper< unsigned char, T, GetCastMethod< unsigned char, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + operator __int16() const SAFEINT_CPP_THROW + { + __int16 val; + SafeCastHelper< __int16, T, GetCastMethod< __int16, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + operator unsigned __int16() const SAFEINT_CPP_THROW + { + unsigned __int16 val; + SafeCastHelper< unsigned __int16, T, GetCastMethod< unsigned __int16, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + operator __int32() const SAFEINT_CPP_THROW + { + __int32 val; + SafeCastHelper< __int32, T, GetCastMethod< __int32, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + operator unsigned __int32() const SAFEINT_CPP_THROW + { + unsigned __int32 val; + SafeCastHelper< unsigned __int32, T, GetCastMethod< unsigned __int32, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + // The compiler knows that int == __int32 + // but not that long == __int32 + operator long() const SAFEINT_CPP_THROW + { + long val; + SafeCastHelper< long, T, GetCastMethod< long, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + operator unsigned long() const SAFEINT_CPP_THROW + { + unsigned long val; + SafeCastHelper< unsigned long, T, GetCastMethod< unsigned long, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + operator __int64() const SAFEINT_CPP_THROW + { + __int64 val; + SafeCastHelper< __int64, T, GetCastMethod< __int64, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + operator unsigned __int64() const SAFEINT_CPP_THROW + { + unsigned __int64 val; + SafeCastHelper< unsigned __int64, T, GetCastMethod< unsigned __int64, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + +#if defined SAFEINT_USE_WCHAR_T || defined _NATIVE_WCHAR_T_DEFINED + operator wchar_t() const SAFEINT_CPP_THROW + { + wchar_t val; + SafeCastHelper< wchar_t, T, GetCastMethod< wchar_t, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } +#endif + +#ifdef SIZE_T_CAST_NEEDED + // We also need an explicit cast to size_t, or the compiler will complain + // Apparently, only SOME compilers complain, and cl 14.00.50727.42 isn't one of them + // Leave here in case we decide to backport this to an earlier compiler + operator size_t() const SAFEINT_CPP_THROW + { + size_t val; + SafeCastHelper< size_t, T, GetCastMethod< size_t, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } +#endif + + // Also provide a cast operator for floating point types + operator float() const SAFEINT_CPP_THROW + { + float val; + SafeCastHelper< float, T, GetCastMethod< float, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + operator double() const SAFEINT_CPP_THROW + { + double val; + SafeCastHelper< double, T, GetCastMethod< double, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + operator long double() const SAFEINT_CPP_THROW + { + long double val; + SafeCastHelper< long double, T, GetCastMethod< long double, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + // If you need a pointer to the data + // this could be dangerous, but allows you to correctly pass + // instances of this class to APIs that take a pointer to an integer + // also see overloaded address-of operator below + T* Ptr() SAFEINT_NOTHROW { return &m_int; } + const T* Ptr() const SAFEINT_NOTHROW { return &m_int; } + const T& Ref() const SAFEINT_NOTHROW { return m_int; } + + // Or if SafeInt< T, E >::Ptr() is inconvenient, use the overload + // operator & + // This allows you to do unsafe things! + // It is meant to allow you to more easily + // pass a SafeInt into things like ReadFile + T* operator &() SAFEINT_NOTHROW { return &m_int; } + const T* operator &() const SAFEINT_NOTHROW { return &m_int; } + + // Unary operators + bool operator !() const SAFEINT_NOTHROW { return (!m_int) ? true : false; } + + // operator + (unary) + // note - normally, the '+' and '-' operators will upcast to a signed int + // for T < 32 bits. This class changes behavior to preserve type + const SafeInt< T, E >& operator +() const SAFEINT_NOTHROW { return *this; } + + //unary - + + SafeInt< T, E > operator -() const SAFEINT_CPP_THROW + { + // Note - unsigned still performs the bitwise manipulation + // will warn at level 2 or higher if the value is 32-bit or larger + return SafeInt(NegationHelper::isSigned>::template NegativeThrow(m_int)); + } + + // prefix increment operator + SafeInt< T, E >& operator ++() SAFEINT_CPP_THROW + { + if( m_int != IntTraits< T >::maxInt ) + { + ++m_int; + return *this; + } + E::SafeIntOnOverflow(); + } + + // prefix decrement operator + SafeInt< T, E >& operator --() SAFEINT_CPP_THROW + { + if( m_int != IntTraits< T >::minInt ) + { + --m_int; + return *this; + } + E::SafeIntOnOverflow(); + } + + // note that postfix operators have inherently worse perf + // characteristics + + // postfix increment operator + SafeInt< T, E > operator ++( int ) SAFEINT_CPP_THROW // dummy arg to comply with spec + { + if( m_int != IntTraits< T >::maxInt ) + { + SafeInt< T, E > tmp( m_int ); + + m_int++; + return tmp; + } + E::SafeIntOnOverflow(); + } + + // postfix decrement operator + SafeInt< T, E > operator --( int ) SAFEINT_CPP_THROW // dummy arg to comply with spec + { + if( m_int != IntTraits< T >::minInt ) + { + SafeInt< T, E > tmp( m_int ); + m_int--; + return tmp; + } + E::SafeIntOnOverflow(); + } + + // One's complement + // Note - this operator will normally change size to an int + // cast in return improves perf and maintains type + SafeInt< T, E > operator ~() const SAFEINT_NOTHROW { return SafeInt< T, E >( (T)~m_int ); } + + // Binary operators + // + // arithmetic binary operators + // % modulus + // * multiplication + // / division + // + addition + // - subtraction + // + // For each of the arithmetic operators, you will need to + // use them as follows: + // + // SafeInt c = 2; + // SafeInt i = 3; + // + // SafeInt i2 = i op (char)c; + // OR + // SafeInt i2 = (int)i op c; + // + // The base problem is that if the lhs and rhs inputs are different SafeInt types + // it is not possible in this implementation to determine what type of SafeInt + // should be returned. You have to let the class know which of the two inputs + // need to be the return type by forcing the other value to the base integer type. + // + // Note - as per feedback from Scott Meyers, I'm exploring how to get around this. + // 3.0 update - I'm still thinking about this. It can be done with template metaprogramming, + // but it is tricky, and there's a perf vs. correctness tradeoff where the right answer + // is situational. + // + // The case of: + // + // SafeInt< T, E > i, j, k; + // i = j op k; + // + // works just fine and no unboxing is needed because the return type is not ambiguous. + + // Modulus + // Modulus has some convenient properties - + // first, the magnitude of the return can never be + // larger than the lhs operand, and it must be the same sign + // as well. It does, however, suffer from the same promotion + // problems as comparisons, division and other operations + template < typename U > + SafeInt< T, E > operator %( U rhs ) const SAFEINT_CPP_THROW + { + T result; + ModulusHelper< T, U, ValidComparison< T, U >::method >::template ModulusThrow< E >( m_int, rhs, result ); + return SafeInt< T, E >( result ); + } + + SafeInt< T, E > operator %( SafeInt< T, E > rhs ) const SAFEINT_CPP_THROW + { + T result; + ModulusHelper< T, T, ValidComparison< T, T >::method >::template ModulusThrow< E >( m_int, rhs, result ); + return SafeInt< T, E >( result ); + } + + // Modulus assignment + template < typename U > + SafeInt< T, E >& operator %=( U rhs ) SAFEINT_CPP_THROW + { + ModulusHelper< T, U, ValidComparison< T, U >::method >::template ModulusThrow< E >( m_int, rhs, m_int ); + return *this; + } + + template < typename U > + SafeInt< T, E >& operator %=( SafeInt< U, E > rhs ) SAFEINT_CPP_THROW + { + ModulusHelper< T, U, ValidComparison< T, U >::method >::template ModulusThrow< E >( m_int, (U)rhs, m_int ); + return *this; + } + + // Multiplication + template < typename U > + SafeInt< T, E > operator *( U rhs ) const SAFEINT_CPP_THROW + { + T ret( 0 ); + MultiplicationHelper< T, U, MultiplicationMethod< T, U >::method >::template MultiplyThrow< E >( m_int, rhs, ret ); + return SafeInt< T, E >( ret ); + } + + SafeInt< T, E > operator *( SafeInt< T, E > rhs ) const SAFEINT_CPP_THROW + { + T ret( 0 ); + MultiplicationHelper< T, T, MultiplicationMethod< T, T >::method >::template MultiplyThrow< E >( m_int, (T)rhs, ret ); + return SafeInt< T, E >( ret ); + } + + // Multiplication assignment + SafeInt< T, E >& operator *=( SafeInt< T, E > rhs ) SAFEINT_CPP_THROW + { + MultiplicationHelper< T, T, MultiplicationMethod< T, T >::method >::template MultiplyThrow< E >( m_int, (T)rhs, m_int ); + return *this; + } + + template < typename U > + SafeInt< T, E >& operator *=( U rhs ) SAFEINT_CPP_THROW + { + MultiplicationHelper< T, U, MultiplicationMethod< T, U >::method >::template MultiplyThrow< E >( m_int, rhs, m_int ); + return *this; + } + + template < typename U > + SafeInt< T, E >& operator *=( SafeInt< U, E > rhs ) SAFEINT_CPP_THROW + { + MultiplicationHelper< T, U, MultiplicationMethod< T, U >::method >::template MultiplyThrow< E >( m_int, rhs.Ref(), m_int ); + return *this; + } + + // Division + template < typename U > + SafeInt< T, E > operator /( U rhs ) const SAFEINT_CPP_THROW + { + T ret( 0 ); + DivisionHelper< T, U, DivisionMethod< T, U >::method >::template DivideThrow< E >( m_int, rhs, ret ); + return SafeInt< T, E >( ret ); + } + + SafeInt< T, E > operator /( SafeInt< T, E > rhs ) const SAFEINT_CPP_THROW + { + T ret( 0 ); + DivisionHelper< T, T, DivisionMethod< T, T >::method >::template DivideThrow< E >( m_int, (T)rhs, ret ); + return SafeInt< T, E >( ret ); + } + + // Division assignment + SafeInt< T, E >& operator /=( SafeInt< T, E > i ) SAFEINT_CPP_THROW + { + DivisionHelper< T, T, DivisionMethod< T, T >::method >::template DivideThrow< E >( m_int, (T)i, m_int ); + return *this; + } + + template < typename U > SafeInt< T, E >& operator /=( U i ) SAFEINT_CPP_THROW + { + DivisionHelper< T, U, DivisionMethod< T, U >::method >::template DivideThrow< E >( m_int, i, m_int ); + return *this; + } + + template < typename U > SafeInt< T, E >& operator /=( SafeInt< U, E > i ) + { + DivisionHelper< T, U, DivisionMethod< T, U >::method >::template DivideThrow< E >( m_int, (U)i, m_int ); + return *this; + } + + // For addition and subtraction + + // Addition + SafeInt< T, E > operator +( SafeInt< T, E > rhs ) const SAFEINT_CPP_THROW + { + T ret( 0 ); + AdditionHelper< T, T, AdditionMethod< T, T >::method >::template AdditionThrow< E >( m_int, (T)rhs, ret ); + return SafeInt< T, E >( ret ); + } + + template < typename U > + SafeInt< T, E > operator +( U rhs ) const SAFEINT_CPP_THROW + { + T ret( 0 ); + AdditionHelper< T, U, AdditionMethod< T, U >::method >::template AdditionThrow< E >( m_int, rhs, ret ); + return SafeInt< T, E >( ret ); + } + + //addition assignment + SafeInt< T, E >& operator +=( SafeInt< T, E > rhs ) SAFEINT_CPP_THROW + { + AdditionHelper< T, T, AdditionMethod< T, T >::method >::template AdditionThrow< E >( m_int, (T)rhs, m_int ); + return *this; + } + + template < typename U > + SafeInt< T, E >& operator +=( U rhs ) SAFEINT_CPP_THROW + { + AdditionHelper< T, U, AdditionMethod< T, U >::method >::template AdditionThrow< E >( m_int, rhs, m_int ); + return *this; + } + + template < typename U > + SafeInt< T, E >& operator +=( SafeInt< U, E > rhs ) SAFEINT_CPP_THROW + { + AdditionHelper< T, U, AdditionMethod< T, U >::method >::template AdditionThrow< E >( m_int, (U)rhs, m_int ); + return *this; + } + + // Subtraction + template < typename U > + SafeInt< T, E > operator -( U rhs ) const SAFEINT_CPP_THROW + { + T ret( 0 ); + SubtractionHelper< T, U, SubtractionMethod< T, U >::method >::template SubtractThrow< E >( m_int, rhs, ret ); + return SafeInt< T, E >( ret ); + } + + SafeInt< T, E > operator -(SafeInt< T, E > rhs) const SAFEINT_CPP_THROW + { + T ret( 0 ); + SubtractionHelper< T, T, SubtractionMethod< T, T >::method >::template SubtractThrow< E >( m_int, (T)rhs, ret ); + return SafeInt< T, E >( ret ); + } + + // Subtraction assignment + SafeInt< T, E >& operator -=( SafeInt< T, E > rhs ) SAFEINT_CPP_THROW + { + SubtractionHelper< T, T, SubtractionMethod< T, T >::method >::template SubtractThrow< E >( m_int, (T)rhs, m_int ); + return *this; + } + + template < typename U > + SafeInt< T, E >& operator -=( U rhs ) SAFEINT_CPP_THROW + { + SubtractionHelper< T, U, SubtractionMethod< T, U >::method >::template SubtractThrow< E >( m_int, rhs, m_int ); + return *this; + } + + template < typename U > + SafeInt< T, E >& operator -=( SafeInt< U, E > rhs ) SAFEINT_CPP_THROW + { + SubtractionHelper< T, U, SubtractionMethod< T, U >::method >::template SubtractThrow< E >( m_int, (U)rhs, m_int ); + return *this; + } + + // Shift operators + // Note - shift operators ALWAYS return the same type as the lhs + // specific version for SafeInt< T, E > not needed - + // code path is exactly the same as for SafeInt< U, E > as rhs + + // Left shift + // Also, shifting > bitcount is undefined - trap in debug +#ifdef SAFEINT_DISABLE_SHIFT_ASSERT + #define ShiftAssert(x) +#else + #define ShiftAssert(x) SAFEINT_ASSERT(x) +#endif + + template < typename U > + SafeInt< T, E > operator <<( U bits ) const SAFEINT_NOTHROW + { + ShiftAssert( !IntTraits< U >::isSigned || bits >= 0 ); + ShiftAssert( bits < (int)IntTraits< T >::bitCount ); + + return SafeInt< T, E >( (T)( m_int << bits ) ); + } + + template < typename U > + SafeInt< T, E > operator <<( SafeInt< U, E > bits ) const SAFEINT_NOTHROW + { + ShiftAssert( !IntTraits< U >::isSigned || (U)bits >= 0 ); + ShiftAssert( (U)bits < (int)IntTraits< T >::bitCount ); + + return SafeInt< T, E >( (T)( m_int << (U)bits ) ); + } + + // Left shift assignment + + template < typename U > + SafeInt< T, E >& operator <<=( U bits ) SAFEINT_NOTHROW + { + ShiftAssert( !IntTraits< U >::isSigned || bits >= 0 ); + ShiftAssert( bits < (int)IntTraits< T >::bitCount ); + + m_int <<= bits; + return *this; + } + + template < typename U > + SafeInt< T, E >& operator <<=( SafeInt< U, E > bits ) SAFEINT_NOTHROW + { + ShiftAssert( !IntTraits< U >::isSigned || (U)bits >= 0 ); + ShiftAssert( (U)bits < (int)IntTraits< T >::bitCount ); + + m_int <<= (U)bits; + return *this; + } + + // Right shift + template < typename U > + SafeInt< T, E > operator >>( U bits ) const SAFEINT_NOTHROW + { + ShiftAssert( !IntTraits< U >::isSigned || bits >= 0 ); + ShiftAssert( bits < (int)IntTraits< T >::bitCount ); + + return SafeInt< T, E >( (T)( m_int >> bits ) ); + } + + template < typename U > + SafeInt< T, E > operator >>( SafeInt< U, E > bits ) const SAFEINT_NOTHROW + { + ShiftAssert( !IntTraits< U >::isSigned || (U)bits >= 0 ); + ShiftAssert( bits < (int)IntTraits< T >::bitCount ); + + return SafeInt< T, E >( (T)(m_int >> (U)bits) ); + } + + // Right shift assignment + template < typename U > + SafeInt< T, E >& operator >>=( U bits ) SAFEINT_NOTHROW + { + ShiftAssert( !IntTraits< U >::isSigned || bits >= 0 ); + ShiftAssert( bits < (int)IntTraits< T >::bitCount ); + + m_int >>= bits; + return *this; + } + + template < typename U > + SafeInt< T, E >& operator >>=( SafeInt< U, E > bits ) SAFEINT_NOTHROW + { + ShiftAssert( !IntTraits< U >::isSigned || (U)bits >= 0 ); + ShiftAssert( (U)bits < (int)IntTraits< T >::bitCount ); + + m_int >>= (U)bits; + return *this; + } + + // Bitwise operators + // This only makes sense if we're dealing with the same type and size + // demand a type T, or something that fits into a type T + + // Bitwise & + SafeInt< T, E > operator &( SafeInt< T, E > rhs ) const SAFEINT_NOTHROW + { + return SafeInt< T, E >( m_int & (T)rhs ); + } + + template < typename U > + SafeInt< T, E > operator &( U rhs ) const SAFEINT_NOTHROW + { + // we want to avoid setting bits by surprise + // consider the case of lhs = int, value = 0xffffffff + // rhs = char, value = 0xff + // + // programmer intent is to get only the lower 8 bits + // normal behavior is to upcast both sides to an int + // which then sign extends rhs, setting all the bits + + // If you land in the assert, this is because the bitwise operator + // was causing unexpected behavior. Fix is to properly cast your inputs + // so that it works like you meant, not unexpectedly + + return SafeInt< T, E >( BinaryAndHelper< T, U, BinaryMethod< T, U >::method >::And( m_int, rhs ) ); + } + + // Bitwise & assignment + SafeInt< T, E >& operator &=( SafeInt< T, E > rhs ) SAFEINT_NOTHROW + { + m_int &= (T)rhs; + return *this; + } + + template < typename U > + SafeInt< T, E >& operator &=( U rhs ) SAFEINT_NOTHROW + { + m_int = BinaryAndHelper< T, U, BinaryMethod< T, U >::method >::And( m_int, rhs ); + return *this; + } + + template < typename U > + SafeInt< T, E >& operator &=( SafeInt< U, E > rhs ) SAFEINT_NOTHROW + { + m_int = BinaryAndHelper< T, U, BinaryMethod< T, U >::method >::And( m_int, (U)rhs ); + return *this; + } + + // XOR + SafeInt< T, E > operator ^( SafeInt< T, E > rhs ) const SAFEINT_NOTHROW + { + return SafeInt< T, E >( (T)( m_int ^ (T)rhs ) ); + } + + template < typename U > + SafeInt< T, E > operator ^( U rhs ) const SAFEINT_NOTHROW + { + // If you land in the assert, this is because the bitwise operator + // was causing unexpected behavior. Fix is to properly cast your inputs + // so that it works like you meant, not unexpectedly + + return SafeInt< T, E >( BinaryXorHelper< T, U, BinaryMethod< T, U >::method >::Xor( m_int, rhs ) ); + } + + // XOR assignment + SafeInt< T, E >& operator ^=( SafeInt< T, E > rhs ) SAFEINT_NOTHROW + { + m_int ^= (T)rhs; + return *this; + } + + template < typename U > + SafeInt< T, E >& operator ^=( U rhs ) SAFEINT_NOTHROW + { + m_int = BinaryXorHelper< T, U, BinaryMethod< T, U >::method >::Xor( m_int, rhs ); + return *this; + } + + template < typename U > + SafeInt< T, E >& operator ^=( SafeInt< U, E > rhs ) SAFEINT_NOTHROW + { + m_int = BinaryXorHelper< T, U, BinaryMethod< T, U >::method >::Xor( m_int, (U)rhs ); + return *this; + } + + // bitwise OR + SafeInt< T, E > operator |( SafeInt< T, E > rhs ) const SAFEINT_NOTHROW + { + return SafeInt< T, E >( (T)( m_int | (T)rhs ) ); + } + + template < typename U > + SafeInt< T, E > operator |( U rhs ) const SAFEINT_NOTHROW + { + return SafeInt< T, E >( BinaryOrHelper< T, U, BinaryMethod< T, U >::method >::Or( m_int, rhs ) ); + } + + // bitwise OR assignment + SafeInt< T, E >& operator |=( SafeInt< T, E > rhs ) SAFEINT_NOTHROW + { + m_int |= (T)rhs; + return *this; + } + + template < typename U > + SafeInt< T, E >& operator |=( U rhs ) SAFEINT_NOTHROW + { + m_int = BinaryOrHelper< T, U, BinaryMethod< T, U >::method >::Or( m_int, rhs ); + return *this; + } + + template < typename U > + SafeInt< T, E >& operator |=( SafeInt< U, E > rhs ) SAFEINT_NOTHROW + { + m_int = BinaryOrHelper< T, U, BinaryMethod< T, U >::method >::Or( m_int, (U)rhs ); + return *this; + } + + // Miscellaneous helper functions + SafeInt< T, E > Min( SafeInt< T, E > test, const T floor = IntTraits< T >::minInt ) const SAFEINT_NOTHROW + { + T tmp = test < m_int ? (T)test : m_int; + return tmp < floor ? floor : tmp; + } + + SafeInt< T, E > Max( SafeInt< T, E > test, const T upper = IntTraits< T >::maxInt ) const SAFEINT_NOTHROW + { + T tmp = test > m_int ? (T)test : m_int; + return tmp > upper ? upper : tmp; + } + + void Swap( SafeInt< T, E >& with ) SAFEINT_NOTHROW + { + T temp( m_int ); + m_int = with.m_int; + with.m_int = temp; + } + + static SafeInt< T, E > SafeAtoI( const char* input ) SAFEINT_CPP_THROW + { + return SafeTtoI( input ); + } + + static SafeInt< T, E > SafeWtoI( const wchar_t* input ) + { + return SafeTtoI( input ); + } + + enum alignBits + { + align2 = 1, + align4 = 2, + align8 = 3, + align16 = 4, + align32 = 5, + align64 = 6, + align128 = 7, + align256 = 8 + }; + + template < alignBits bits > + const SafeInt< T, E >& Align() SAFEINT_CPP_THROW + { + // Zero is always aligned + if( m_int == 0 ) + return *this; + + // We don't support aligning negative numbers at this time + // Can't align unsigned numbers on bitCount (e.g., 8 bits = 256, unsigned char max = 255) + // or signed numbers on bitCount-1 (e.g., 7 bits = 128, signed char max = 127). + // Also makes no sense to try to align on negative or no bits. + + ShiftAssert( ( ( IntTraits::isSigned && bits < (int)IntTraits< T >::bitCount - 1 ) + || ( !IntTraits::isSigned && bits < (int)IntTraits< T >::bitCount ) ) && + bits >= 0 && ( !IntTraits::isSigned || m_int > 0 ) ); + + const T AlignValue = ( (T)1 << bits ) - 1; + + m_int = (T)( ( m_int + AlignValue ) & ~AlignValue ); + + if( m_int <= 0 ) + E::SafeIntOnOverflow(); + + return *this; + } + + // Commonly needed alignments: + const SafeInt< T, E >& Align2() { return Align< align2 >(); } + const SafeInt< T, E >& Align4() { return Align< align4 >(); } + const SafeInt< T, E >& Align8() { return Align< align8 >(); } + const SafeInt< T, E >& Align16() { return Align< align16 >(); } + const SafeInt< T, E >& Align32() { return Align< align32 >(); } + const SafeInt< T, E >& Align64() { return Align< align64 >(); } +private: + + // This is almost certainly not the best optimized version of atoi, + // but it does not display a typical bug where it isn't possible to set MinInt + // and it won't allow you to overflow your integer. + // This is here because it is useful, and it is an example of what + // can be done easily with SafeInt. + template < typename U > + static SafeInt< T, E > SafeTtoI( U* input ) SAFEINT_CPP_THROW + { + U* tmp = input; + SafeInt< T, E > s; + bool negative = false; + + // Bad input, or empty string + if( input == nullptr || input[0] == 0 ) + E::SafeIntOnOverflow(); + + switch( *tmp ) + { + case '-': + tmp++; + negative = true; + break; + case '+': + tmp++; + break; + } + + while( *tmp != 0 ) + { + if( *tmp < '0' || *tmp > '9' ) + break; + + if( (T)s != 0 ) + s *= (T)10; + + if( !negative ) + s += (T)( *tmp - '0' ); + else + s -= (T)( *tmp - '0' ); + + tmp++; + } + + return s; + } + + T m_int; +}; + +// Helper function used to subtract pointers. +// Used to squelch warnings +template +SafeInt SafePtrDiff(const P* p1, const P* p2) SAFEINT_CPP_THROW +{ + return SafeInt( p1 - p2 ); +} + +// Comparison operators + +//Less than +template < typename T, typename U, typename E > +bool operator <( U lhs, SafeInt< T, E > rhs ) SAFEINT_NOTHROW +{ + return GreaterThanTest< T, U, ValidComparison< T, U >::method >::GreaterThan( (T)rhs, lhs ); +} + +template < typename T, typename U, typename E > +bool operator <( SafeInt lhs, U rhs ) SAFEINT_NOTHROW +{ + return GreaterThanTest< U, T, ValidComparison< U, T >::method >::GreaterThan( rhs, (T)lhs ); +} + +template < typename T, typename U, typename E > +bool operator <( SafeInt< U, E > lhs, SafeInt< T, E > rhs ) SAFEINT_NOTHROW +{ + return GreaterThanTest< T, U, ValidComparison< T, U >::method >::GreaterThan( (T)rhs, (U)lhs ); +} + +// Greater than +template < typename T, typename U, typename E > +bool operator >( U lhs, SafeInt< T, E > rhs ) SAFEINT_NOTHROW +{ + return GreaterThanTest< U, T, ValidComparison< U, T >::method >::GreaterThan( lhs, (T)rhs ); +} + +template < typename T, typename U, typename E > +bool operator >( SafeInt lhs, U rhs ) SAFEINT_NOTHROW +{ + return GreaterThanTest< T, U, ValidComparison< T, U >::method >::GreaterThan( (T)lhs, rhs ); +} + +template < typename T, typename U, typename E > +bool operator >( SafeInt< T, E > lhs, SafeInt< U, E > rhs ) SAFEINT_NOTHROW +{ + return GreaterThanTest< T, U, ValidComparison< T, U >::method >::GreaterThan( (T)lhs, (U)rhs ); +} + +// Greater than or equal +template < typename T, typename U, typename E > +bool operator >=( U lhs, SafeInt< T, E > rhs ) SAFEINT_NOTHROW +{ + return !GreaterThanTest< T, U, ValidComparison< T, U >::method >::GreaterThan( (T)rhs, lhs ); +} + +template < typename T, typename U, typename E > +bool operator >=( SafeInt lhs, U rhs ) SAFEINT_NOTHROW +{ + return !GreaterThanTest< U, T, ValidComparison< U, T >::method >::GreaterThan( rhs, (T)lhs ); +} + +template < typename T, typename U, typename E > +bool operator >=( SafeInt< T, E > lhs, SafeInt< U, E > rhs ) SAFEINT_NOTHROW +{ + return !GreaterThanTest< U, T, ValidComparison< U, T >::method >::GreaterThan( (U)rhs, (T)lhs ); +} + +// Less than or equal +template < typename T, typename U, typename E > +bool operator <=( U lhs, SafeInt< T, E > rhs ) SAFEINT_NOTHROW +{ + return !GreaterThanTest< U, T, ValidComparison< U, T >::method >::GreaterThan( lhs, (T)rhs ); +} + +template < typename T, typename U, typename E > +bool operator <=( SafeInt< T, E > lhs, U rhs ) SAFEINT_NOTHROW +{ + return !GreaterThanTest< T, U, ValidComparison< T, U >::method >::GreaterThan( (T)lhs, rhs ); +} + +template < typename T, typename U, typename E > +bool operator <=( SafeInt< T, E > lhs, SafeInt< U, E > rhs ) SAFEINT_NOTHROW +{ + return !GreaterThanTest< T, U, ValidComparison< T, U >::method >::GreaterThan( (T)lhs, (U)rhs ); +} + +// equality +// explicit overload for bool +template < typename T, typename E > +bool operator ==( bool lhs, SafeInt< T, E > rhs ) SAFEINT_NOTHROW +{ + return lhs == ( (T)rhs == 0 ? false : true ); +} + +template < typename T, typename E > +bool operator ==( SafeInt< T, E > lhs, bool rhs ) SAFEINT_NOTHROW +{ + return rhs == ( (T)lhs == 0 ? false : true ); +} + +template < typename T, typename U, typename E > +bool operator ==( U lhs, SafeInt< T, E > rhs ) SAFEINT_NOTHROW +{ + return EqualityTest< T, U, ValidComparison< T, U >::method >::IsEquals((T)rhs, lhs); +} + +template < typename T, typename U, typename E > +bool operator ==( SafeInt< T, E > lhs, U rhs ) SAFEINT_NOTHROW +{ + return EqualityTest< T, U, ValidComparison< T, U >::method >::IsEquals( (T)lhs, rhs ); +} + +template < typename T, typename U, typename E > +bool operator ==( SafeInt< T, E > lhs, SafeInt< U, E > rhs ) SAFEINT_NOTHROW +{ + return EqualityTest< T, U, ValidComparison< T, U >::method >::IsEquals( (T)lhs, (U)rhs ); +} + +//not equals +template < typename T, typename U, typename E > +bool operator !=( U lhs, SafeInt< T, E > rhs ) SAFEINT_NOTHROW +{ + return !EqualityTest< T, U, ValidComparison< T, U >::method >::IsEquals( (T)rhs, lhs ); +} + +template < typename T, typename U, typename E > +bool operator !=( SafeInt< T, E > lhs, U rhs ) SAFEINT_NOTHROW +{ + return !EqualityTest< T, U, ValidComparison< T, U >::method >::IsEquals( (T)lhs, rhs ); +} + +template < typename T, typename U, typename E > +bool operator !=( SafeInt< T, E > lhs, SafeInt< U, E > rhs ) SAFEINT_NOTHROW +{ + return !EqualityTest< T, U, ValidComparison< T, U >::method >::IsEquals( lhs, rhs ); +} + + +template < typename T, typename E > +bool operator !=( bool lhs, SafeInt< T, E > rhs ) SAFEINT_NOTHROW +{ + return ( (T)rhs == 0 ? false : true ) != lhs; +} + +template < typename T, typename E > +bool operator !=( SafeInt< T, E > lhs, bool rhs ) SAFEINT_NOTHROW +{ + return ( (T)lhs == 0 ? false : true ) != rhs; +} + + +template < typename T, typename U, typename E, int method > class ModulusSimpleCaseHelper; + +template < typename T, typename E, int method > class ModulusSignedCaseHelper; + +template < typename T, typename E > class ModulusSignedCaseHelper < T, E, true > +{ +public: + static bool SignedCase( SafeInt< T, E > rhs, SafeInt< T, E >& result ) SAFEINT_NOTHROW + { + if( (T)rhs == (T)-1 ) + { + result = 0; + return true; + } + return false; + } +}; + +template < typename T, typename E > class ModulusSignedCaseHelper < T, E, false > +{ +public: + static bool SignedCase( SafeInt< T, E > /*rhs*/, SafeInt< T, E >& /*result*/ ) SAFEINT_NOTHROW + { + return false; + } +}; + +template < typename T, typename U, typename E > +class ModulusSimpleCaseHelper < T, U, E, true > +{ +public: + static bool ModulusSimpleCase( U lhs, SafeInt< T, E > rhs, SafeInt< T, E >& result ) SAFEINT_CPP_THROW + { + if( rhs != 0 ) + { + if( ModulusSignedCaseHelper< T, E, IntTraits< T >::isSigned >::SignedCase( rhs, result ) ) + return true; + + result = SafeInt< T, E >( (T)( lhs % (T)rhs ) ); + return true; + } + + E::SafeIntOnDivZero(); + } +}; + +template< typename T, typename U, typename E > +class ModulusSimpleCaseHelper < T, U, E, false > +{ +public: + static bool ModulusSimpleCase( U /*lhs*/, SafeInt< T, E > /*rhs*/, SafeInt< T, E >& /*result*/ ) SAFEINT_NOTHROW + { + return false; + } +}; + +// Modulus +template < typename T, typename U, typename E > +SafeInt< T, E > operator %( U lhs, SafeInt< T, E > rhs ) SAFEINT_CPP_THROW +{ + // Value of return depends on sign of lhs + // This one may not be safe - bounds check in constructor + // if lhs is negative and rhs is unsigned, this will throw an exception. + + // Fast-track the simple case + // same size and same sign + SafeInt< T, E > result; + + if( ModulusSimpleCaseHelper< T, U, E, + sizeof(T) == sizeof(U) && (bool)IntTraits< T >::isSigned == (bool)IntTraits< U >::isSigned >::ModulusSimpleCase( lhs, rhs, result ) ) + return result; + + return SafeInt< T, E >( ( SafeInt< U, E >( lhs ) % (T)rhs ) ); +} + +// Multiplication +template < typename T, typename U, typename E > +SafeInt< T, E > operator *( U lhs, SafeInt< T, E > rhs ) SAFEINT_CPP_THROW +{ + T ret( 0 ); + MultiplicationHelper< T, U, MultiplicationMethod< T, U >::method >::template MultiplyThrow< E >( (T)rhs, lhs, ret ); + return SafeInt< T, E >(ret); +} + +template < typename T, typename U, typename E, int method > class DivisionNegativeCornerCaseHelper; + +template < typename T, typename U, typename E > class DivisionNegativeCornerCaseHelper< T, U, E, true > +{ +public: + static bool NegativeCornerCase( U lhs, SafeInt< T, E > rhs, SafeInt& result ) SAFEINT_CPP_THROW + { + // Problem case - normal casting behavior changes meaning + // flip rhs to positive + // any operator casts now do the right thing + U tmp; + + if( CompileConst< sizeof(T) == 4 >::Value() ) + tmp = lhs/(U)( ~(unsigned __int32)(T)rhs + 1 ); + else + tmp = lhs/(U)( ~(unsigned __int64)(T)rhs + 1 ); + + if( tmp <= (U)IntTraits< T >::maxInt ) + { + result = SafeInt< T, E >( (T)(~(unsigned __int64)tmp + 1) ); + return true; + } + + // Corner case + T maxT = IntTraits< T >::maxInt; + if( tmp == (U)maxT + 1 ) + { + T minT = IntTraits< T >::minInt; + result = SafeInt< T, E >( minT ); + return true; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U, typename E > class DivisionNegativeCornerCaseHelper< T, U, E, false > +{ +public: + static bool NegativeCornerCase( U /*lhs*/, SafeInt< T, E > /*rhs*/, SafeInt& /*result*/ ) SAFEINT_NOTHROW + { + return false; + } +}; + +template < typename T, typename U, typename E, int method > class DivisionCornerCaseHelper; + +template < typename T, typename U, typename E > class DivisionCornerCaseHelper < T, U, E, true > +{ +public: + static bool DivisionCornerCase1( U lhs, SafeInt< T, E > rhs, SafeInt& result ) SAFEINT_CPP_THROW + { + if( (T)rhs > 0 ) + { + result = SafeInt< T, E >( lhs/(T)rhs ); + return true; + } + + // Now rhs is either negative, or zero + if( (T)rhs != 0 ) + { + if( DivisionNegativeCornerCaseHelper< T, U, E, sizeof( U ) >= 4 && sizeof( T ) <= sizeof( U ) >::NegativeCornerCase( lhs, rhs, result ) ) + return true; + + result = SafeInt< T, E >(lhs/(T)rhs); + return true; + } + + E::SafeIntOnDivZero(); + } +}; + +template < typename T, typename U, typename E > class DivisionCornerCaseHelper < T, U, E, false > +{ +public: + static bool DivisionCornerCase1( U /*lhs*/, SafeInt< T, E > /*rhs*/, SafeInt& /*result*/ ) SAFEINT_NOTHROW + { + return false; + } +}; + +template < typename T, typename U, typename E, int method > class DivisionCornerCaseHelper2; + +template < typename T, typename U, typename E > class DivisionCornerCaseHelper2 < T, U, E, true > +{ +public: + static bool DivisionCornerCase2( U lhs, SafeInt< T, E > rhs, SafeInt& result ) SAFEINT_CPP_THROW + { + if( lhs == IntTraits< U >::minInt && (T)rhs == -1 ) + { + // corner case of a corner case - lhs = min int, rhs = -1, + // but rhs is the return type, so in essence, we can return -lhs + // if rhs is a larger type than lhs + // If types are wrong, throws + +#if SAFEINT_COMPILER == VISUAL_STUDIO_COMPILER +#pragma warning(push) +//cast truncates constant value +#pragma warning(disable:4310) +#endif + + if( CompileConst::Value() ) + result = SafeInt< T, E >( (T)( -(T)IntTraits< U >::minInt ) ); + else + E::SafeIntOnOverflow(); + +#if SAFEINT_COMPILER == VISUAL_STUDIO_COMPILER +#pragma warning(pop) +#endif + + return true; + } + + return false; + } +}; + +template < typename T, typename U, typename E > class DivisionCornerCaseHelper2 < T, U, E, false > +{ +public: + static bool DivisionCornerCase2( U /*lhs*/, SafeInt< T, E > /*rhs*/, SafeInt& /*result*/ ) SAFEINT_NOTHROW + { + return false; + } +}; + +// Division +template < typename T, typename U, typename E > SafeInt< T, E > operator /( U lhs, SafeInt< T, E > rhs ) SAFEINT_CPP_THROW +{ + // Corner case - has to be handled seperately + SafeInt< T, E > result; + if( DivisionCornerCaseHelper< T, U, E, (int)DivisionMethod< U, T >::method == (int)DivisionState_UnsignedSigned >::DivisionCornerCase1( lhs, rhs, result ) ) + return result; + + if( DivisionCornerCaseHelper2< T, U, E, SafeIntCompare< T, U >::isBothSigned >::DivisionCornerCase2( lhs, rhs, result ) ) + return result; + + // Otherwise normal logic works with addition of bounds check when casting from U->T + U ret; + DivisionHelper< U, T, DivisionMethod< U, T >::method >::template DivideThrow< E >( lhs, (T)rhs, ret ); + return SafeInt< T, E >( ret ); +} + +// Addition +template < typename T, typename U, typename E > +SafeInt< T, E > operator +( U lhs, SafeInt< T, E > rhs ) SAFEINT_CPP_THROW +{ + T ret( 0 ); + AdditionHelper< T, U, AdditionMethod< T, U >::method >::template AdditionThrow< E >( (T)rhs, lhs, ret ); + return SafeInt< T, E >( ret ); +} + +// Subtraction +template < typename T, typename U, typename E > +SafeInt< T, E > operator -( U lhs, SafeInt< T, E > rhs ) SAFEINT_CPP_THROW +{ + T ret( 0 ); + SubtractionHelper< U, T, SubtractionMethod2< U, T >::method >::template SubtractThrow< E >( lhs, rhs.Ref(), ret ); + + return SafeInt< T, E >( ret ); +} + +// Overrides designed to deal with cases where a SafeInt is assigned out +// to a normal int - this at least makes the last operation safe +// += +template < typename T, typename U, typename E > +T& operator +=( T& lhs, SafeInt< U, E > rhs ) SAFEINT_CPP_THROW +{ + T ret( 0 ); + AdditionHelper< T, U, AdditionMethod< T, U >::method >::template AdditionThrow< E >( lhs, (U)rhs, ret ); + lhs = ret; + return lhs; +} + +template < typename T, typename U, typename E > +T& operator -=( T& lhs, SafeInt< U, E > rhs ) SAFEINT_CPP_THROW +{ + T ret( 0 ); + SubtractionHelper< T, U, SubtractionMethod< T, U >::method >::template SubtractThrow< E >( lhs, (U)rhs, ret ); + lhs = ret; + return lhs; +} + +template < typename T, typename U, typename E > +T& operator *=( T& lhs, SafeInt< U, E > rhs ) SAFEINT_CPP_THROW +{ + T ret( 0 ); + MultiplicationHelper< T, U, MultiplicationMethod< T, U >::method >::template MultiplyThrow< E >( lhs, (U)rhs, ret ); + lhs = ret; + return lhs; +} + +template < typename T, typename U, typename E > +T& operator /=( T& lhs, SafeInt< U, E > rhs ) SAFEINT_CPP_THROW +{ + T ret( 0 ); + DivisionHelper< T, U, DivisionMethod< T, U >::method >::template DivideThrow< E >( lhs, (U)rhs, ret ); + lhs = ret; + return lhs; +} + +template < typename T, typename U, typename E > +T& operator %=( T& lhs, SafeInt< U, E > rhs ) SAFEINT_CPP_THROW +{ + T ret( 0 ); + ModulusHelper< T, U, ValidComparison< T, U >::method >::template ModulusThrow< E >( lhs, (U)rhs, ret ); + lhs = ret; + return lhs; +} + +template < typename T, typename U, typename E > +T& operator &=( T& lhs, SafeInt< U, E > rhs ) SAFEINT_NOTHROW +{ + lhs = BinaryAndHelper< T, U, BinaryMethod< T, U >::method >::And( lhs, (U)rhs ); + return lhs; +} + +template < typename T, typename U, typename E > +T& operator ^=( T& lhs, SafeInt< U, E > rhs ) SAFEINT_NOTHROW +{ + lhs = BinaryXorHelper< T, U, BinaryMethod< T, U >::method >::Xor( lhs, (U)rhs ); + return lhs; +} + +template < typename T, typename U, typename E > +T& operator |=( T& lhs, SafeInt< U, E > rhs ) SAFEINT_NOTHROW +{ + lhs = BinaryOrHelper< T, U, BinaryMethod< T, U >::method >::Or( lhs, (U)rhs ); + return lhs; +} + +template < typename T, typename U, typename E > +T& operator <<=( T& lhs, SafeInt< U, E > rhs ) SAFEINT_NOTHROW +{ + lhs = (T)( SafeInt< T, E >( lhs ) << (U)rhs ); + return lhs; +} + +template < typename T, typename U, typename E > +T& operator >>=( T& lhs, SafeInt< U, E > rhs ) SAFEINT_NOTHROW +{ + lhs = (T)( SafeInt< T, E >( lhs ) >> (U)rhs ); + return lhs; +} + +// Specific pointer overrides +// Note - this function makes no attempt to ensure +// that the resulting pointer is still in the buffer, only +// that no int overflows happened on the way to getting the new pointer +template < typename T, typename U, typename E > +T*& operator +=( T*& lhs, SafeInt< U, E > rhs ) SAFEINT_CPP_THROW +{ + // Cast the pointer to a number so we can do arithmetic + SafeInt< size_t, E > ptr_val = reinterpret_cast< size_t >( lhs ); + // Check first that rhs is valid for the type of ptrdiff_t + // and that multiplying by sizeof( T ) doesn't overflow a ptrdiff_t + // Next, we need to add 2 SafeInts of different types, so unbox the ptr_diff + // Finally, cast the number back to a pointer of the correct type + lhs = reinterpret_cast< T* >( (size_t)( ptr_val + (ptrdiff_t)( SafeInt< ptrdiff_t, E >( rhs ) * sizeof( T ) ) ) ); + return lhs; +} + +template < typename T, typename U, typename E > +T*& operator -=( T*& lhs, SafeInt< U, E > rhs ) SAFEINT_CPP_THROW +{ + // Cast the pointer to a number so we can do arithmetic + SafeInt< size_t, E > ptr_val = reinterpret_cast< size_t >( lhs ); + // See above for comments + lhs = reinterpret_cast< T* >( (size_t)( ptr_val - (ptrdiff_t)( SafeInt< ptrdiff_t, E >( rhs ) * sizeof( T ) ) ) ); + return lhs; +} + +template < typename T, typename U, typename E > +T*& operator *=( T*& lhs, SafeInt< U, E > ) SAFEINT_NOTHROW +{ + // This operator explicitly not supported + C_ASSERT( sizeof(T) == 0 ); + return (lhs = NULL); +} + +template < typename T, typename U, typename E > +T*& operator /=( T*& lhs, SafeInt< U, E > ) SAFEINT_NOTHROW +{ + // This operator explicitly not supported + C_ASSERT( sizeof(T) == 0 ); + return (lhs = NULL); +} + +template < typename T, typename U, typename E > +T*& operator %=( T*& lhs, SafeInt< U, E > ) SAFEINT_NOTHROW +{ + // This operator explicitly not supported + C_ASSERT( sizeof(T) == 0 ); + return (lhs = NULL); +} + +template < typename T, typename U, typename E > +T*& operator &=( T*& lhs, SafeInt< U, E > ) SAFEINT_NOTHROW +{ + // This operator explicitly not supported + C_ASSERT( sizeof(T) == 0 ); + return (lhs = NULL); +} + +template < typename T, typename U, typename E > +T*& operator ^=( T*& lhs, SafeInt< U, E > ) SAFEINT_NOTHROW +{ + // This operator explicitly not supported + C_ASSERT( sizeof(T) == 0 ); + return (lhs = NULL); +} + +template < typename T, typename U, typename E > +T*& operator |=( T*& lhs, SafeInt< U, E > ) SAFEINT_NOTHROW +{ + // This operator explicitly not supported + C_ASSERT( sizeof(T) == 0 ); + return (lhs = NULL); +} + +template < typename T, typename U, typename E > +T*& operator <<=( T*& lhs, SafeInt< U, E > ) SAFEINT_NOTHROW +{ + // This operator explicitly not supported + C_ASSERT( sizeof(T) == 0 ); + return (lhs = NULL); +} + +template < typename T, typename U, typename E > +T*& operator >>=( T*& lhs, SafeInt< U, E > ) SAFEINT_NOTHROW +{ + // This operator explicitly not supported + C_ASSERT( sizeof(T) == 0 ); + return (lhs = NULL); +} + +// Shift operators +// NOTE - shift operators always return the type of the lhs argument + +// Left shift +template < typename T, typename U, typename E > +SafeInt< U, E > operator <<( U lhs, SafeInt< T, E > bits ) SAFEINT_NOTHROW +{ + ShiftAssert( !IntTraits< T >::isSigned || (T)bits >= 0 ); + ShiftAssert( (T)bits < (int)IntTraits< U >::bitCount ); + + return SafeInt< U, E >( (U)( lhs << (T)bits ) ); +} + +// Right shift +template < typename T, typename U, typename E > +SafeInt< U, E > operator >>( U lhs, SafeInt< T, E > bits ) SAFEINT_NOTHROW +{ + ShiftAssert( !IntTraits< T >::isSigned || (T)bits >= 0 ); + ShiftAssert( (T)bits < (int)IntTraits< U >::bitCount ); + + return SafeInt< U, E >( (U)( lhs >> (T)bits ) ); +} + +// Bitwise operators +// This only makes sense if we're dealing with the same type and size +// demand a type T, or something that fits into a type T. + +// Bitwise & +template < typename T, typename U, typename E > +SafeInt< T, E > operator &( U lhs, SafeInt< T, E > rhs ) SAFEINT_NOTHROW +{ + return SafeInt< T, E >( BinaryAndHelper< T, U, BinaryMethod< T, U >::method >::And( (T)rhs, lhs ) ); +} + +// Bitwise XOR +template < typename T, typename U, typename E > +SafeInt< T, E > operator ^( U lhs, SafeInt< T, E > rhs ) SAFEINT_NOTHROW +{ + return SafeInt< T, E >(BinaryXorHelper< T, U, BinaryMethod< T, U >::method >::Xor( (T)rhs, lhs ) ); +} + +// Bitwise OR +template < typename T, typename U, typename E > +SafeInt< T, E > operator |( U lhs, SafeInt< T, E > rhs ) SAFEINT_NOTHROW +{ + return SafeInt< T, E >( BinaryOrHelper< T, U, BinaryMethod< T, U >::method >::Or( (T)rhs, lhs ) ); +} + +#if SAFEINT_COMPILER == GCC_COMPILER +#pragma GCC diagnostic pop +#endif + +#if SAFEINT_COMPILER == CLANG_COMPILER +#pragma clang diagnostic pop +#endif + +} // utilities +} // safeint3 + diff --git a/src/corehost/cli/json/casablanca/include/cpprest/details/basic_types.h b/src/corehost/cli/json/casablanca/include/cpprest/details/basic_types.h new file mode 100755 index 000000000..ea28f8260 --- /dev/null +++ b/src/corehost/cli/json/casablanca/include/cpprest/details/basic_types.h @@ -0,0 +1,140 @@ +/*** +* ==++== +* +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* ==--== +* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +* +* Platform-dependent type definitions +* +* For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk +* +* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +****/ + +#pragma once + +#include +#include +#include +#include +#include "cpprest/details/cpprest_compat.h" + +#ifndef _WIN32 +# define __STDC_LIMIT_MACROS +# include +#else +#include +#endif + +#include "cpprest/details/SafeInt3.hpp" + +namespace utility +{ + +#ifdef _WIN32 +#define _UTF16_STRINGS +#endif + +// We should be using a 64-bit size type for most situations that do +// not involve specifying the size of a memory allocation or buffer. +typedef uint64_t size64_t; + +#ifndef _WIN32 +typedef uint32_t HRESULT; // Needed for PPLX +#endif + +#ifdef _UTF16_STRINGS +// +// On Windows, all strings are wide +// +typedef wchar_t char_t ; +typedef std::wstring string_t; +#define _XPLATSTR(x) L ## x +typedef std::wostringstream ostringstream_t; +typedef std::wofstream ofstream_t; +typedef std::wostream ostream_t; +typedef std::wistream istream_t; +typedef std::wifstream ifstream_t; +typedef std::wistringstream istringstream_t; +typedef std::wstringstream stringstream_t; +#define ucout std::wcout +#define ucin std::wcin +#define ucerr std::wcerr +#else +// +// On POSIX platforms, all strings are narrow +// +typedef char char_t; +typedef std::string string_t; +#define _XPLATSTR(x) x +typedef std::ostringstream ostringstream_t; +typedef std::ofstream ofstream_t; +typedef std::ostream ostream_t; +typedef std::istream istream_t; +typedef std::ifstream ifstream_t; +typedef std::istringstream istringstream_t; +typedef std::stringstream stringstream_t; +#define ucout std::cout +#define ucin std::cin +#define ucerr std::cerr +#endif // endif _UTF16_STRINGS + +#ifndef _TURN_OFF_PLATFORM_STRING +#define U(x) _XPLATSTR(x) +#endif // !_TURN_OFF_PLATFORM_STRING + +}// namespace utility + +typedef char utf8char; +typedef std::string utf8string; +typedef std::stringstream utf8stringstream; +typedef std::ostringstream utf8ostringstream; +typedef std::ostream utf8ostream; +typedef std::istream utf8istream; +typedef std::istringstream utf8istringstream; + +#ifdef _UTF16_STRINGS +typedef wchar_t utf16char; +typedef std::wstring utf16string; +typedef std::wstringstream utf16stringstream; +typedef std::wostringstream utf16ostringstream; +typedef std::wostream utf16ostream; +typedef std::wistream utf16istream; +typedef std::wistringstream utf16istringstream; +#else +typedef char16_t utf16char; +typedef std::u16string utf16string; +typedef std::basic_stringstream utf16stringstream; +typedef std::basic_ostringstream utf16ostringstream; +typedef std::basic_ostream utf16ostream; +typedef std::basic_istream utf16istream; +typedef std::basic_istringstream utf16istringstream; +#endif + + +#if defined(_WIN32) +// Include on everything except Windows Desktop ARM, unless explicitly excluded. +#if !defined(CPPREST_EXCLUDE_WEBSOCKETS) +#if defined(WINAPI_FAMILY) +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) && defined(_M_ARM) +#define CPPREST_EXCLUDE_WEBSOCKETS +#endif +#else +#if defined(_M_ARM) +#define CPPREST_EXCLUDE_WEBSOCKETS +#endif +#endif +#endif +#endif diff --git a/src/corehost/cli/json/casablanca/include/cpprest/details/cpprest_compat.h b/src/corehost/cli/json/casablanca/include/cpprest/details/cpprest_compat.h new file mode 100755 index 000000000..240a33899 --- /dev/null +++ b/src/corehost/cli/json/casablanca/include/cpprest/details/cpprest_compat.h @@ -0,0 +1,97 @@ +/*** +* ==++== +* +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* ==--== +* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +* +* Standard macros and definitions. +* This header has minimal dependency on windows headers and is safe for use in the public API +* +* For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk +* +* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +****/ + +#pragma once + +#if defined(_WIN32) // Settings specific to Windows + +#if _MSC_VER >= 1900 +#define CPPREST_NOEXCEPT noexcept +#else +#define CPPREST_NOEXCEPT +#endif + +#define CASABLANCA_UNREFERENCED_PARAMETER(x) (x) + +#include + +#else // End settings specific to Windows + +// Settings common to all but Windows + +#define __declspec(x) __attribute__ ((x)) +#define dllimport +#define novtable /* no novtable equivalent */ +#define __assume(x) do { if (!(x)) __builtin_unreachable(); } while (false) +#define CASABLANCA_UNREFERENCED_PARAMETER(x) (void)x +#define CPPREST_NOEXCEPT noexcept + +#include +#define _ASSERTE(x) assert(x) + +// No SAL on non Windows platforms +#include "cpprest/details/nosal.h" + +#if not defined __cdecl +#if defined cdecl +#define __cdecl __attribute__ ((cdecl)) +#else +#define __cdecl +#endif + +#if defined(__ANDROID__) +// This is needed to disable the use of __thread inside the boost library. +// Android does not support thread local storage -- if boost is included +// without this macro defined, it will create references to __tls_get_addr +// which (while able to link) will not be available at runtime and prevent +// the .so from loading. +#define BOOST_ASIO_DISABLE_THREAD_KEYWORD_EXTENSION +#endif + +#ifdef __clang__ +#include +#endif + +#endif // defined(__APPLE__) + +#endif + + +#ifdef _NO_ASYNCRTIMP +#define _ASYNCRTIMP +#else +#ifdef _ASYNCRT_EXPORT +#define _ASYNCRTIMP __declspec(dllexport) +#else +#define _ASYNCRTIMP __declspec(dllimport) +#endif +#endif + +#ifdef CASABLANCA_DEPRECATION_NO_WARNINGS +#define CASABLANCA_DEPRECATED(x) +#else +#define CASABLANCA_DEPRECATED(x) __declspec(deprecated(x)) +#endif diff --git a/src/corehost/cli/json/casablanca/include/cpprest/details/nosal.h b/src/corehost/cli/json/casablanca/include/cpprest/details/nosal.h new file mode 100755 index 000000000..4d7e583a7 --- /dev/null +++ b/src/corehost/cli/json/casablanca/include/cpprest/details/nosal.h @@ -0,0 +1,89 @@ +/*** +* ==++== +* +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* ==--== +* +* For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk +* +* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +***/ + +#pragma once +// selected MS SAL annotations + +#ifdef _In_ +#undef _In_ +#endif +#define _In_ + +#ifdef _Inout_ +#undef _Inout_ +#endif +#define _Inout_ + +#ifdef _Out_ +#undef _Out_ +#endif +#define _Out_ + +#ifdef _In_z_ +#undef _In_z_ +#endif +#define _In_z_ + +#ifdef _Out_z_ +#undef _Out_z_ +#endif +#define _Out_z_ + +#ifdef _Inout_z_ +#undef _Inout_z_ +#endif +#define _Inout_z_ + +#ifdef _In_opt_ +#undef _In_opt_ +#endif +#define _In_opt_ + +#ifdef _Out_opt_ +#undef _Out_opt_ +#endif +#define _Out_opt_ + +#ifdef _Inout_opt_ +#undef _Inout_opt_ +#endif +#define _Inout_opt_ + +#ifdef _Out_writes_ +#undef _Out_writes_ +#endif +#define _Out_writes_(x) + +#ifdef _Out_writes_opt_ +#undef _Out_writes_opt_ +#endif +#define _Out_writes_opt_(x) + +#ifdef _In_reads_ +#undef _In_reads_ +#endif +#define _In_reads_(x) + +#ifdef _Inout_updates_bytes_ +#undef _Inout_updates_bytes_ +#endif +#define _Inout_updates_bytes_(x) \ No newline at end of file diff --git a/src/corehost/cli/json/casablanca/include/cpprest/json.h b/src/corehost/cli/json/casablanca/include/cpprest/json.h new file mode 100755 index 000000000..704cbfad9 --- /dev/null +++ b/src/corehost/cli/json/casablanca/include/cpprest/json.h @@ -0,0 +1,1936 @@ +/*** +* ==++== +* +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* ==--== +* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +* +* HTTP Library: JSON parser and writer +* +* For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk +* +* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +****/ +#pragma once + +#ifndef _CASA_JSON_H +#define _CASA_JSON_H + +#include +#include +#include +#include +#include +#include +#include "cpprest/details/basic_types.h" +#include "cpprest/asyncrt_utils.h" + +namespace web +{ +/// Library for parsing and serializing JSON values to and from C++ types. +namespace json +{ + // Various forward declarations. + namespace details + { + class _Value; + class _Number; + class _Null; + class _Boolean; + class _String; + class _Object; + class _Array; + template class JSON_Parser; + } + + namespace details + { + extern bool g_keep_json_object_unsorted; + } + + /// + /// Preserve the order of the name/value pairs when parsing a JSON object. + /// The default is false, which can yield better performance. + /// + /// true if ordering should be preserved when parsing, false otherwise. + /// Note this is a global setting and affects all JSON parsing done. + void _ASYNCRTIMP __cdecl keep_object_element_order(bool keep_order); + +#ifdef _WIN32 +#ifdef _DEBUG +#define ENABLE_JSON_VALUE_VISUALIZER +#endif +#endif + + class number; + class array; + class object; + + /// + /// A JSON value represented as a C++ class. + /// + class value + { + public: + /// + /// This enumeration represents the various kinds of JSON values. + /// + enum value_type + { + /// Number value + Number, + /// Boolean value + Boolean, + /// String value + String, + /// Object value + Object, + /// Array value + Array, + /// Null value + Null + }; + + /// + /// Constructor creating a null value + /// + _ASYNCRTIMP value(); + + /// + /// Constructor creating a JSON number value + /// + /// The C++ value to create a JSON value from + _ASYNCRTIMP value(int32_t value); + + /// + /// Constructor creating a JSON number value + /// + /// The C++ value to create a JSON value from + _ASYNCRTIMP value(uint32_t value); + + /// + /// Constructor creating a JSON number value + /// + /// The C++ value to create a JSON value from + _ASYNCRTIMP value(int64_t value); + + /// + /// Constructor creating a JSON number value + /// + /// The C++ value to create a JSON value from + _ASYNCRTIMP value(uint64_t value); + + /// + /// Constructor creating a JSON number value + /// + /// The C++ value to create a JSON value from + _ASYNCRTIMP value(double value); + + /// + /// Constructor creating a JSON Boolean value + /// + /// The C++ value to create a JSON value from + _ASYNCRTIMP explicit value(bool value); + + /// + /// Constructor creating a JSON string value + /// + /// The C++ value to create a JSON value from, a C++ STL string of the platform-native character width + /// + /// This constructor has O(n) performance because it tries to determine if + /// specified string has characters that should be properly escaped in JSON. + /// + _ASYNCRTIMP explicit value(utility::string_t value); + + /// + /// Constructor creating a JSON string value specifying if the string contains characters to escape + /// + /// The C++ value to create a JSON value from, a C++ STL string of the platform-native character width + /// Whether contains characters + /// that should be escaped in JSON value + /// + /// This constructor has O(1) performance. + /// + _ASYNCRTIMP explicit value(utility::string_t value, bool has_escape_chars); + + /// + /// Constructor creating a JSON string value + /// + /// The C++ value to create a JSON value from, a C++ STL string of the platform-native character width + /// + /// + /// This constructor has O(n) performance because it tries to determine if + /// specified string has characters that should be properly escaped in JSON. + /// + /// + /// This constructor exists in order to avoid string literals matching another constructor, + /// as is very likely. For example, conversion to bool does not require a user-defined conversion, + /// and will therefore match first, which means that the JSON value turns up as a boolean. + /// + /// + _ASYNCRTIMP explicit value(const utility::char_t* value); + + /// + /// Constructor creating a JSON string value + /// + /// The C++ value to create a JSON value from, a C++ STL string of the platform-native character width + /// Whether contains characters + /// + /// + /// This overload has O(1) performance. + /// + /// + /// This constructor exists in order to avoid string literals matching another constructor, + /// as is very likely. For example, conversion to bool does not require a user-defined conversion, + /// and will therefore match first, which means that the JSON value turns up as a boolean. + /// + /// + _ASYNCRTIMP explicit value(const utility::char_t* value, bool has_escape_chars); + + /// + /// Copy constructor + /// + _ASYNCRTIMP value(const value &); + + /// + /// Move constructor + /// + _ASYNCRTIMP value(value &&) CPPREST_NOEXCEPT ; + + /// + /// Assignment operator. + /// + /// The JSON value object that contains the result of the assignment. + _ASYNCRTIMP value &operator=(const value &); + + /// + /// Move assignment operator. + /// + /// The JSON value object that contains the result of the assignment. + _ASYNCRTIMP value &operator=(value &&) CPPREST_NOEXCEPT ; + + // Static factories + + /// + /// Creates a null value + /// + /// A JSON null value + static _ASYNCRTIMP value __cdecl null(); + + /// + /// Creates a number value + /// + /// The C++ value to create a JSON value from + /// A JSON number value + static _ASYNCRTIMP value __cdecl number(double value); + + /// + /// Creates a number value + /// + /// The C++ value to create a JSON value from + /// A JSON number value + static _ASYNCRTIMP value __cdecl number(int32_t value); + + /// + /// Creates a number value + /// + /// The C++ value to create a JSON value from + /// A JSON number value + static _ASYNCRTIMP value __cdecl number(uint32_t value); + + /// + /// Creates a number value + /// + /// The C++ value to create a JSON value from + /// A JSON number value + static _ASYNCRTIMP value __cdecl number(int64_t value); + + /// + /// Creates a number value + /// + /// The C++ value to create a JSON value from + /// A JSON number value + static _ASYNCRTIMP value __cdecl number(uint64_t value); + + /// + /// Creates a Boolean value + /// + /// The C++ value to create a JSON value from + /// A JSON Boolean value + static _ASYNCRTIMP value __cdecl boolean(bool value); + + /// + /// Creates a string value + /// + /// The C++ value to create a JSON value from + /// A JSON string value + /// + /// This overload has O(n) performance because it tries to determine if + /// specified string has characters that should be properly escaped in JSON. + /// + static _ASYNCRTIMP value __cdecl string(utility::string_t value); + + /// + /// Creates a string value specifying if the string contains characters to escape + /// + /// The C++ value to create a JSON value from + /// Whether contains characters + /// that should be escaped in JSON value + /// A JSON string value + /// + /// This overload has O(1) performance. + /// + static _ASYNCRTIMP value __cdecl string(utility::string_t value, bool has_escape_chars); + +#ifdef _WIN32 +private: + // Only used internally by JSON parser. + static _ASYNCRTIMP value __cdecl string(const std::string &value); +public: +#endif + + /// + /// Creates an object value + /// + /// Whether to preserve the original order of the fields + /// An empty JSON object value + static _ASYNCRTIMP json::value __cdecl object(bool keep_order = false); + + /// + /// Creates an object value from a collection of field/values + /// + /// Field names associated with JSON values + /// Whether to preserve the original order of the fields + /// A non-empty JSON object value + static _ASYNCRTIMP json::value __cdecl object(std::vector> fields, bool keep_order = false); + + /// + /// Creates an empty JSON array + /// + /// An empty JSON array value + static _ASYNCRTIMP json::value __cdecl array(); + + /// + /// Creates a JSON array + /// + /// The initial number of elements of the JSON value + /// A JSON array value + static _ASYNCRTIMP json::value __cdecl array(size_t size); + + /// + /// Creates a JSON array + /// + /// A vector of JSON values + /// A JSON array value + static _ASYNCRTIMP json::value __cdecl array(std::vector elements); + + /// + /// Accesses the type of JSON value the current value instance is + /// + /// The value's type + _ASYNCRTIMP json::value::value_type type() const; + + /// + /// Is the current value a null value? + /// + /// true if the value is a null value, false otherwise + bool is_null() const { return type() == Null; }; + + /// + /// Is the current value a number value? + /// + /// true if the value is a number value, false otherwise + bool is_number() const { return type() == Number; } + + /// + /// Is the current value represented as an integer number value? + /// + /// + /// Note that if a json value is a number but represented as a double it can still + /// be retrieved as a integer using as_integer(), however the value will be truncated. + /// + /// true if the value is an integer value, false otherwise. + _ASYNCRTIMP bool is_integer() const; + + /// + /// Is the current value represented as an double number value? + /// + /// + /// Note that if a json value is a number but represented as a int it can still + /// be retrieved as a double using as_double(). + /// + /// true if the value is an double value, false otherwise. + _ASYNCRTIMP bool is_double() const; + + /// + /// Is the current value a Boolean value? + /// + /// true if the value is a Boolean value, false otherwise + bool is_boolean() const { return type() == Boolean; } + + /// + /// Is the current value a string value? + /// + /// true if the value is a string value, false otherwise + bool is_string() const { return type() == String; } + + /// + /// Is the current value an array? + /// + /// true if the value is an array, false otherwise + bool is_array() const { return type() == Array; } + + /// + /// Is the current value an object? + /// + /// true if the value is an object, false otherwise + bool is_object() const { return type() == Object; } + + /// + /// Gets the number of children of the value. + /// + /// The number of children. 0 for all non-composites. + size_t size() const; + + /// + /// Parses a string and construct a JSON value. + /// + /// The C++ value to create a JSON value from, a C++ STL double-byte string + _ASYNCRTIMP static value __cdecl parse(const utility::string_t &value); + + /// + /// Attempts to parse a string and construct a JSON value. + /// + /// The C++ value to create a JSON value from, a C++ STL double-byte string + /// If parsing fails, the error code is greater than 0 + /// The parsed object. Returns web::json::value::null if failed + _ASYNCRTIMP static value __cdecl parse(const utility::string_t &value, std::error_code &errorCode); + + /// + /// Serializes the current JSON value to a C++ string. + /// + /// A string representation of the value + _ASYNCRTIMP utility::string_t serialize() const; + + /// + /// Serializes the current JSON value to a C++ string. + /// + /// A string representation of the value + CASABLANCA_DEPRECATED("This API is deprecated and has been renamed to avoid confusion with as_string(), use ::web::json::value::serialize() instead.") + _ASYNCRTIMP utility::string_t to_string() const; + + /// + /// Parses a JSON value from the contents of an input stream using the native platform character width. + /// + /// The stream to read the JSON value from + /// The JSON value object created from the input stream. + _ASYNCRTIMP static value __cdecl parse(utility::istream_t &input); + + /// + /// Parses a JSON value from the contents of an input stream using the native platform character width. + /// + /// The stream to read the JSON value from + /// If parsing fails, the error code is greater than 0 + /// The parsed object. Returns web::json::value::null if failed + _ASYNCRTIMP static value __cdecl parse(utility::istream_t &input, std::error_code &errorCode); + + /// + /// Writes the current JSON value to a stream with the native platform character width. + /// + /// The stream that the JSON string representation should be written to. + _ASYNCRTIMP void serialize(utility::ostream_t &stream) const; + +#ifdef _WIN32 + /// + /// Parses a JSON value from the contents of a single-byte (UTF8) stream. + /// + /// The stream to read the JSON value from + _ASYNCRTIMP static value __cdecl parse(std::istream& stream); + + /// + /// Parses a JSON value from the contents of a single-byte (UTF8) stream. + /// + /// The stream to read the JSON value from + /// If parsing fails, the error code is greater than 0 + /// The parsed object. Returns web::json::value::null if failed + _ASYNCRTIMP static value __cdecl parse(std::istream& stream, std::error_code& error); + + /// + /// Serializes the content of the value into a single-byte (UTF8) stream. + /// + /// The stream that the JSON string representation should be written to. + _ASYNCRTIMP void serialize(std::ostream& stream) const; +#endif + + /// + /// Converts the JSON value to a C++ double, if and only if it is a number value. + /// Throws if the value is not a number + /// + /// A double representation of the value + _ASYNCRTIMP double as_double() const; + + /// + /// Converts the JSON value to a C++ integer, if and only if it is a number value. + /// Throws if the value is not a number + /// + /// An integer representation of the value + _ASYNCRTIMP int as_integer() const; + + /// + /// Converts the JSON value to a number class, if and only if it is a number value. + /// Throws if the value is not a number + /// + /// An instance of number class + _ASYNCRTIMP const json::number& as_number() const; + + /// + /// Converts the JSON value to a C++ bool, if and only if it is a Boolean value. + /// + /// A C++ bool representation of the value + _ASYNCRTIMP bool as_bool() const; + + /// + /// Converts the JSON value to a json array, if and only if it is an array value. + /// + /// The returned json::array should have the same or shorter lifetime as this + /// An array representation of the value + _ASYNCRTIMP json::array& as_array(); + + /// + /// Converts the JSON value to a json array, if and only if it is an array value. + /// + /// The returned json::array should have the same or shorter lifetime as this + /// An array representation of the value + _ASYNCRTIMP const json::array& as_array() const; + + /// + /// Converts the JSON value to a json object, if and only if it is an object value. + /// + /// An object representation of the value + _ASYNCRTIMP json::object& as_object(); + + /// + /// Converts the JSON value to a json object, if and only if it is an object value. + /// + /// An object representation of the value + _ASYNCRTIMP const json::object& as_object() const; + + /// + /// Converts the JSON value to a C++ STL string, if and only if it is a string value. + /// + /// A C++ STL string representation of the value + _ASYNCRTIMP const utility::string_t& as_string() const; + + /// + /// Compares two JSON values for equality. + /// + /// The JSON value to compare with. + /// True iff the values are equal. + _ASYNCRTIMP bool operator==(const value& other) const; + + /// + /// Compares two JSON values for inequality. + /// + /// The JSON value to compare with. + /// True iff the values are unequal. + bool operator!=(const value& other) const + { + return !((*this) == other); + } + + /// + /// Tests for the presence of a field. + /// + /// The name of the field + /// True if the field exists, false otherwise. + bool has_field(const utility::string_t &key) const; + + /// + /// Accesses a field of a JSON object. + /// + /// The name of the field + /// The value kept in the field; null if the field does not exist + CASABLANCA_DEPRECATED("This API is deprecated and will be removed in a future release, use json::value::at() instead.") + value get(const utility::string_t &key) const; + + /// + /// Erases an element of a JSON array. Throws if index is out of bounds. + /// + /// The index of the element to erase in the JSON array. + _ASYNCRTIMP void erase(size_t index); + + /// + /// Erases an element of a JSON object. Throws if the key doesn't exist. + /// + /// The key of the element to erase in the JSON object. + _ASYNCRTIMP void erase(const utility::string_t &key); + + /// + /// Accesses an element of a JSON array. Throws when index out of bounds. + /// + /// The index of an element in the JSON array. + /// A reference to the value. + _ASYNCRTIMP json::value& at(size_t index); + + /// + /// Accesses an element of a JSON array. Throws when index out of bounds. + /// + /// The index of an element in the JSON array. + /// A reference to the value. + _ASYNCRTIMP const json::value& at(size_t index) const; + + /// + /// Accesses an element of a JSON object. If the key doesn't exist, this method throws. + /// + /// The key of an element in the JSON object. + /// If the key exists, a reference to the value. + _ASYNCRTIMP json::value& at(const utility::string_t& key); + + /// + /// Accesses an element of a JSON object. If the key doesn't exist, this method throws. + /// + /// The key of an element in the JSON object. + /// If the key exists, a reference to the value. + _ASYNCRTIMP const json::value& at(const utility::string_t& key) const; + + /// + /// Accesses a field of a JSON object. + /// + /// The name of the field + /// A reference to the value kept in the field. + _ASYNCRTIMP value & operator [] (const utility::string_t &key); + +#ifdef _WIN32 +private: + // Only used internally by JSON parser + _ASYNCRTIMP value & operator [] (const std::string &key) + { + // JSON object stores its field map as a unordered_map of string_t, so this conversion is hard to avoid + return operator[](utility::conversions::to_string_t(key)); + } +public: +#endif + + /// + /// Accesses an element of a JSON array. + /// + /// The index of an element in the JSON array + /// The value kept at the array index; null if outside the boundaries of the array + CASABLANCA_DEPRECATED("This API is deprecated and will be removed in a future release, use json::value::at() instead.") + value get(size_t index) const; + + /// + /// Accesses an element of a JSON array. + /// + /// The index of an element in the JSON array. + /// A reference to the value kept in the field. + _ASYNCRTIMP value & operator [] (size_t index); + + private: + friend class web::json::details::_Object; + friend class web::json::details::_Array; + template friend class web::json::details::JSON_Parser; + +#ifdef _WIN32 + /// + /// Writes the current JSON value as a double-byte string to a string instance. + /// + /// The string that the JSON representation should be written to. + _ASYNCRTIMP void format(std::basic_string &string) const; +#endif + /// + /// Serializes the content of the value into a string instance in UTF8 format + /// + /// The string that the JSON representation should be written to + _ASYNCRTIMP void format(std::basic_string& string) const; + +#ifdef ENABLE_JSON_VALUE_VISUALIZER + explicit value(std::unique_ptr v, value_type kind) : m_value(std::move(v)), m_kind(kind) +#else + explicit value(std::unique_ptr v) : m_value(std::move(v)) +#endif + {} + + std::unique_ptr m_value; +#ifdef ENABLE_JSON_VALUE_VISUALIZER + value_type m_kind; +#endif + }; + + /// + /// A single exception type to represent errors in parsing, converting, and accessing + /// elements of JSON values. + /// + class json_exception : public std::exception + { + private: + std::string _message; + public: + json_exception(const utility::char_t * const &message) : _message(utility::conversions::to_utf8string(message)) { } + + // Must be narrow string because it derives from std::exception + const char* what() const CPPREST_NOEXCEPT + { + return _message.c_str(); + } + }; + + namespace details + { + enum json_error + { + left_over_character_in_stream = 1, + malformed_array_literal, + malformed_comment, + malformed_literal, + malformed_object_literal, + malformed_numeric_literal, + malformed_string_literal, + malformed_token, + mismatched_brances, + nesting, + unexpected_token + }; + + class json_error_category_impl : public std::error_category + { + public: + virtual const char* name() const CPPREST_NOEXCEPT override + { + return "json"; + } + + virtual std::string message(int ev) const override + { + switch (ev) + { + case json_error::left_over_character_in_stream: + return "Left-over characters in stream after parsing a JSON value"; + case json_error::malformed_array_literal: + return "Malformed array literal"; + case json_error::malformed_comment: + return "Malformed comment"; + case json_error::malformed_literal: + return "Malformed literal"; + case json_error::malformed_object_literal: + return "Malformed object literal"; + case json_error::malformed_numeric_literal: + return "Malformed numeric literal"; + case json_error::malformed_string_literal: + return "Malformed string literal"; + case json_error::malformed_token: + return "Malformed token"; + case json_error::mismatched_brances: + return "Mismatched braces"; + case json_error::nesting: + return "Nesting too deep"; + case json_error::unexpected_token: + return "Unexpected token"; + default: + return "Unknown json error"; + } + } + }; + + const json_error_category_impl& json_error_category(); + } + + /// + /// A JSON array represented as a C++ class. + /// + class array + { + typedef std::vector storage_type; + + public: + typedef storage_type::iterator iterator; + typedef storage_type::const_iterator const_iterator; + typedef storage_type::reverse_iterator reverse_iterator; + typedef storage_type::const_reverse_iterator const_reverse_iterator; + typedef storage_type::size_type size_type; + + private: + array() : m_elements() { } + array(size_type size) : m_elements(size) { } + array(storage_type elements) : m_elements(std::move(elements)) { } + + public: + /// + /// Gets the beginning iterator element of the array + /// + /// An iterator to the beginning of the JSON array. + iterator begin() + { + return m_elements.begin(); + } + + /// + /// Gets the beginning const iterator element of the array. + /// + /// A const_iterator to the beginning of the JSON array. + const_iterator begin() const + { + return m_elements.cbegin(); + } + + /// + /// Gets the end iterator element of the array + /// + /// An iterator to the end of the JSON array. + iterator end() + { + return m_elements.end(); + } + + /// + /// Gets the end const iterator element of the array. + /// + /// A const_iterator to the end of the JSON array. + const_iterator end() const + { + return m_elements.cend(); + } + + /// + /// Gets the beginning reverse iterator element of the array + /// + /// An reverse_iterator to the beginning of the JSON array. + reverse_iterator rbegin() + { + return m_elements.rbegin(); + } + + /// + /// Gets the beginning const reverse iterator element of the array + /// + /// An const_reverse_iterator to the beginning of the JSON array. + const_reverse_iterator rbegin() const + { + return m_elements.rbegin(); + } + + /// + /// Gets the end reverse iterator element of the array + /// + /// An reverse_iterator to the end of the JSON array. + reverse_iterator rend() + { + return m_elements.rend(); + } + + /// + /// Gets the end const reverse iterator element of the array + /// + /// An const_reverse_iterator to the end of the JSON array. + const_reverse_iterator rend() const + { + return m_elements.crend(); + } + + /// + /// Gets the beginning const iterator element of the array. + /// + /// A const_iterator to the beginning of the JSON array. + const_iterator cbegin() const + { + return m_elements.cbegin(); + } + + /// + /// Gets the end const iterator element of the array. + /// + /// A const_iterator to the end of the JSON array. + const_iterator cend() const + { + return m_elements.cend(); + } + + /// + /// Gets the beginning const reverse iterator element of the array. + /// + /// A const_reverse_iterator to the beginning of the JSON array. + const_reverse_iterator crbegin() const + { + return m_elements.crbegin(); + } + + /// + /// Gets the end const reverse iterator element of the array. + /// + /// A const_reverse_iterator to the end of the JSON array. + const_reverse_iterator crend() const + { + return m_elements.crend(); + } + + /// + /// Deletes an element of the JSON array. + /// + /// A const_iterator to the element to delete. + /// Iterator to the new location of the element following the erased element. + /// GCC doesn't support erase with const_iterator on vector yet. In the future this should be changed. + iterator erase(iterator position) + { + return m_elements.erase(position); + } + + /// + /// Deletes the element at an index of the JSON array. + /// + /// The index of the element to delete. + void erase(size_type index) + { + if (index >= m_elements.size()) + { + throw json_exception(_XPLATSTR("index out of bounds")); + } + m_elements.erase(m_elements.begin() + index); + } + + /// + /// Accesses an element of a JSON array. Throws when index out of bounds. + /// + /// The index of an element in the JSON array. + /// A reference to the value kept in the field. + json::value& at(size_type index) + { + if (index >= m_elements.size()) + throw json_exception(_XPLATSTR("index out of bounds")); + + return m_elements[index]; + } + + /// + /// Accesses an element of a JSON array. Throws when index out of bounds. + /// + /// The index of an element in the JSON array. + /// A reference to the value kept in the field. + const json::value& at(size_type index) const + { + if (index >= m_elements.size()) + throw json_exception(_XPLATSTR("index out of bounds")); + + return m_elements[index]; + } + + /// + /// Accesses an element of a JSON array. + /// + /// The index of an element in the JSON array. + /// A reference to the value kept in the field. + json::value& operator[](size_type index) + { + msl::safeint3::SafeInt nMinSize(index); + nMinSize += 1; + msl::safeint3::SafeInt nlastSize(m_elements.size()); + if (nlastSize < nMinSize) + m_elements.resize(nMinSize); + + return m_elements[index]; + } + + /// + /// Gets the number of elements of the array. + /// + /// The number of elements. + size_type size() const + { + return m_elements.size(); + } + + private: + storage_type m_elements; + + friend class details::_Array; + template friend class json::details::JSON_Parser; + }; + + /// + /// A JSON object represented as a C++ class. + /// + class object + { + typedef std::vector> storage_type; + + public: + typedef storage_type::iterator iterator; + typedef storage_type::const_iterator const_iterator; + typedef storage_type::reverse_iterator reverse_iterator; + typedef storage_type::const_reverse_iterator const_reverse_iterator; + typedef storage_type::size_type size_type; + + private: + object(bool keep_order = false) : m_elements(), m_keep_order(keep_order) { } + object(storage_type elements, bool keep_order = false) : m_elements(std::move(elements)), m_keep_order(keep_order) + { + if (!keep_order) { + sort(m_elements.begin(), m_elements.end(), compare_pairs); + } + } + + public: + /// + /// Gets the beginning iterator element of the object + /// + /// An iterator to the beginning of the JSON object. + iterator begin() + { + return m_elements.begin(); + } + + /// + /// Gets the beginning const iterator element of the object. + /// + /// A const_iterator to the beginning of the JSON object. + const_iterator begin() const + { + return m_elements.cbegin(); + } + + /// + /// Gets the end iterator element of the object + /// + /// An iterator to the end of the JSON object. + iterator end() + { + return m_elements.end(); + } + + /// + /// Gets the end const iterator element of the object. + /// + /// A const_iterator to the end of the JSON object. + const_iterator end() const + { + return m_elements.cend(); + } + + /// + /// Gets the beginning reverse iterator element of the object + /// + /// An reverse_iterator to the beginning of the JSON object. + reverse_iterator rbegin() + { + return m_elements.rbegin(); + } + + /// + /// Gets the beginning const reverse iterator element of the object + /// + /// An const_reverse_iterator to the beginning of the JSON object. + const_reverse_iterator rbegin() const + { + return m_elements.rbegin(); + } + + /// + /// Gets the end reverse iterator element of the object + /// + /// An reverse_iterator to the end of the JSON object. + reverse_iterator rend() + { + return m_elements.rend(); + } + + /// + /// Gets the end const reverse iterator element of the object + /// + /// An const_reverse_iterator to the end of the JSON object. + const_reverse_iterator rend() const + { + return m_elements.crend(); + } + + /// + /// Gets the beginning const iterator element of the object. + /// + /// A const_iterator to the beginning of the JSON object. + const_iterator cbegin() const + { + return m_elements.cbegin(); + } + + /// + /// Gets the end const iterator element of the object. + /// + /// A const_iterator to the end of the JSON object. + const_iterator cend() const + { + return m_elements.cend(); + } + + /// + /// Gets the beginning const reverse iterator element of the object. + /// + /// A const_reverse_iterator to the beginning of the JSON object. + const_reverse_iterator crbegin() const + { + return m_elements.crbegin(); + } + + /// + /// Gets the end const reverse iterator element of the object. + /// + /// A const_reverse_iterator to the end of the JSON object. + const_reverse_iterator crend() const + { + return m_elements.crend(); + } + + /// + /// Deletes an element of the JSON object. + /// + /// A const_iterator to the element to delete. + /// Iterator to the new location of the element following the erased element. + /// GCC doesn't support erase with const_iterator on vector yet. In the future this should be changed. + iterator erase(iterator position) + { + return m_elements.erase(position); + } + + /// + /// Deletes an element of the JSON object. If the key doesn't exist, this method throws. + /// + /// The key of an element in the JSON object. + void erase(const utility::string_t &key) + { + auto iter = find_by_key(key); + if (iter == m_elements.end()) + { + throw web::json::json_exception(_XPLATSTR("Key not found")); + } + + m_elements.erase(iter); + } + + /// + /// Accesses an element of a JSON object. If the key doesn't exist, this method throws. + /// + /// The key of an element in the JSON object. + /// If the key exists, a reference to the value kept in the field. + json::value& at(const utility::string_t& key) + { + auto iter = find_by_key(key); + if (iter == m_elements.end()) + { + throw web::json::json_exception(_XPLATSTR("Key not found")); + } + + return iter->second; + } + + /// + /// Accesses an element of a JSON object. If the key doesn't exist, this method throws. + /// + /// The key of an element in the JSON object. + /// If the key exists, a reference to the value kept in the field. + const json::value& at(const utility::string_t& key) const + { + auto iter = find_by_key(key); + if (iter == m_elements.end()) + { + throw web::json::json_exception(_XPLATSTR("Key not found")); + } + + return iter->second; + } + + /// + /// Accesses an element of a JSON object. + /// + /// The key of an element in the JSON object. + /// If the key exists, a reference to the value kept in the field, otherwise a newly created null value that will be stored for the given key. + json::value& operator[](const utility::string_t& key) + { + auto iter = find_insert_location(key); + + if (iter == m_elements.end() || key != iter->first) + { + return m_elements.insert(iter, std::pair(key, value()))->second; + } + + return iter->second; + } + + /// + /// Gets an iterator to an element of a JSON object. + /// + /// The key of an element in the JSON object. + /// A const iterator to the value kept in the field. + const_iterator find(const utility::string_t& key) const + { + return find_by_key(key); + } + + /// + /// Gets the number of elements of the object. + /// + /// The number of elements. + size_type size() const + { + return m_elements.size(); + } + + /// + /// Checks if there are any elements in the JSON object. + /// + /// True iff empty. + bool empty() const + { + return m_elements.empty(); + } + private: + + static bool compare_pairs(const std::pair& p1, const std::pair& p2) + { + return p1.first < p2.first; + } + static bool compare_with_key(const std::pair& p1, const utility::string_t& key) + { + return p1.first < key; + } + + storage_type::iterator find_insert_location(const utility::string_t &key) + { + if (m_keep_order) + { + return std::find_if(m_elements.begin(), m_elements.end(), + [&key](const std::pair& p) { + return p.first == key; + }); + } + else + { + return std::lower_bound(m_elements.begin(), m_elements.end(), key, compare_with_key); + } + } + + storage_type::const_iterator find_by_key(const utility::string_t& key) const + { + if (m_keep_order) + { + return std::find_if(m_elements.begin(), m_elements.end(), + [&key](const std::pair& p) { + return p.first == key; + }); + } + else + { + auto iter = std::lower_bound(m_elements.begin(), m_elements.end(), key, compare_with_key); + if (iter != m_elements.end() && key != iter->first) + { + return m_elements.end(); + } + return iter; + } + } + + storage_type::iterator find_by_key(const utility::string_t& key) + { + auto iter = find_insert_location(key); + if (iter != m_elements.end() && key != iter->first) + { + return m_elements.end(); + } + return iter; + } + + storage_type m_elements; + bool m_keep_order; + friend class details::_Object; + + template friend class json::details::JSON_Parser; + }; + + /// + /// A JSON number represented as a C++ class. + /// + class number + { + // Note that these constructors make sure that only negative integers are stored as signed int64 (while others convert to unsigned int64). + // This helps handling number objects e.g. comparing two numbers. + + number(double value) : m_value(value), m_type(double_type) { } + number(int32_t value) : m_intval(value), m_type(value < 0 ? signed_type : unsigned_type) { } + number(uint32_t value) : m_intval(value), m_type(unsigned_type) { } + number(int64_t value) : m_intval(value), m_type(value < 0 ? signed_type : unsigned_type) { } + number(uint64_t value) : m_uintval(value), m_type(unsigned_type) { } + + public: + + /// + /// Does the number fit into int32? + /// + /// true if the number fits into int32, false otherwise + _ASYNCRTIMP bool is_int32() const; + + /// + /// Does the number fit into unsigned int32? + /// + /// true if the number fits into unsigned int32, false otherwise + _ASYNCRTIMP bool is_uint32() const; + + /// + /// Does the number fit into int64? + /// + /// true if the number fits into int64, false otherwise + _ASYNCRTIMP bool is_int64() const; + + /// + /// Does the number fit into unsigned int64? + /// + /// true if the number fits into unsigned int64, false otherwise + bool is_uint64() const + { + switch (m_type) + { + case signed_type : return m_intval >= 0; + case unsigned_type : return true; + case double_type : + default : + return false; + } + } + + /// + /// Converts the JSON number to a C++ double. + /// + /// A double representation of the number + double to_double() const + { + switch (m_type) + { + case double_type : return m_value; + case signed_type : return static_cast(m_intval); + case unsigned_type : return static_cast(m_uintval); + default : return false; + } + } + + /// + /// Converts the JSON number to int32. + /// + /// An int32 representation of the number + int32_t to_int32() const + { + if (m_type == double_type) + return static_cast(m_value); + else + return static_cast(m_intval); + } + + /// + /// Converts the JSON number to unsigned int32. + /// + /// An usigned int32 representation of the number + uint32_t to_uint32() const + { + if (m_type == double_type) + return static_cast(m_value); + else + return static_cast(m_intval); + } + + /// + /// Converts the JSON number to int64. + /// + /// An int64 representation of the number + int64_t to_int64() const + { + if (m_type == double_type) + return static_cast(m_value); + else + return static_cast(m_intval); + } + + /// + /// Converts the JSON number to unsigned int64. + /// + /// An unsigned int64 representation of the number + uint64_t to_uint64() const + { + if (m_type == double_type) + return static_cast(m_value); + else + return static_cast(m_intval); + } + + /// + /// Is the number represented internally as an integral type? + /// + /// true if the number is represented as an integral type, false otherwise + bool is_integral() const + { + return m_type != double_type; + } + + /// + /// Compares two JSON numbers for equality. + /// + /// The JSON number to compare with. + /// True iff the numbers are equal. + bool operator==(const number &other) const + { + if (m_type != other.m_type) + return false; + + switch (m_type) + { + case json::number::type::signed_type : + return m_intval == other.m_intval; + case json::number::type::unsigned_type : + return m_uintval == other.m_uintval; + case json::number::type::double_type : + return m_value == other.m_value; + } + __assume(0); + } + + private: + union + { + int64_t m_intval; + uint64_t m_uintval; + double m_value; + }; + + enum type + { + signed_type=0, unsigned_type, double_type + } m_type; + + friend class details::_Number; + }; + + namespace details + { + class _Value + { + public: + virtual std::unique_ptr<_Value> _copy_value() = 0; + + virtual bool has_field(const utility::string_t &) const { return false; } + virtual value get_field(const utility::string_t &) const { throw json_exception(_XPLATSTR("not an object")); } + virtual value get_element(array::size_type) const { throw json_exception(_XPLATSTR("not an array")); } + + virtual value &index(const utility::string_t &) { throw json_exception(_XPLATSTR("not an object")); } + virtual value &index(array::size_type) { throw json_exception(_XPLATSTR("not an array")); } + + virtual const value &cnst_index(const utility::string_t &) const { throw json_exception(_XPLATSTR("not an object")); } + virtual const value &cnst_index(array::size_type) const { throw json_exception(_XPLATSTR("not an array")); } + + // Common function used for serialization to strings and streams. + virtual void serialize_impl(std::string& str) const + { + format(str); + } +#ifdef _WIN32 + virtual void serialize_impl(std::wstring& str) const + { + format(str); + } +#endif + + virtual utility::string_t to_string() const + { + utility::string_t str; + serialize_impl(str); + return str; + } + + virtual json::value::value_type type() const { return json::value::Null; } + + virtual bool is_integer() const { throw json_exception(_XPLATSTR("not a number")); } + virtual bool is_double() const { throw json_exception(_XPLATSTR("not a number")); } + + virtual const json::number& as_number() { throw json_exception(_XPLATSTR("not a number")); } + virtual double as_double() const { throw json_exception(_XPLATSTR("not a number")); } + virtual int as_integer() const { throw json_exception(_XPLATSTR("not a number")); } + virtual bool as_bool() const { throw json_exception(_XPLATSTR("not a boolean")); } + virtual json::array& as_array() { throw json_exception(_XPLATSTR("not an array")); } + virtual const json::array& as_array() const { throw json_exception(_XPLATSTR("not an array")); } + virtual json::object& as_object() { throw json_exception(_XPLATSTR("not an object")); } + virtual const json::object& as_object() const { throw json_exception(_XPLATSTR("not an object")); } + virtual const utility::string_t& as_string() const { throw json_exception(_XPLATSTR("not a string")); } + + virtual size_t size() const { return 0; } + + virtual ~_Value() {} + + protected: + _Value() {} + + virtual void format(std::basic_string& stream) const + { + stream.append("null"); + } +#ifdef _WIN32 + virtual void format(std::basic_string& stream) const + { + stream.append(L"null"); + } +#endif + private: + + friend class web::json::value; + }; + + class _Null : public _Value + { + public: + virtual std::unique_ptr<_Value> _copy_value() + { + return utility::details::make_unique<_Null>(); + } + virtual json::value::value_type type() const { return json::value::Null; } + }; + + class _Number : public _Value + { + public: + _Number(double value) : m_number(value) { } + _Number(int32_t value) : m_number(value) { } + _Number(uint32_t value) : m_number(value) { } + _Number(int64_t value) : m_number(value) { } + _Number(uint64_t value) : m_number(value) { } + + virtual std::unique_ptr<_Value> _copy_value() + { + return utility::details::make_unique<_Number>(*this); + } + + virtual json::value::value_type type() const { return json::value::Number; } + + virtual bool is_integer() const { return m_number.is_integral(); } + virtual bool is_double() const { return !m_number.is_integral(); } + + virtual double as_double() const + { + return m_number.to_double(); + } + + virtual int as_integer() const + { + return m_number.to_int32(); + } + + virtual const number& as_number() { return m_number; } + + protected: + virtual void format(std::basic_string& stream) const ; +#ifdef _WIN32 + virtual void format(std::basic_string& stream) const; +#endif + private: + template friend class json::details::JSON_Parser; + + json::number m_number; + }; + + class _Boolean : public _Value + { + public: + _Boolean(bool value) : m_value(value) { } + + virtual std::unique_ptr<_Value> _copy_value() + { + return utility::details::make_unique<_Boolean>(*this); + } + + virtual json::value::value_type type() const { return json::value::Boolean; } + + virtual bool as_bool() const { return m_value; } + + protected: + virtual void format(std::basic_string& stream) const + { + stream.append(m_value ? "true" : "false"); + } + +#ifdef _WIN32 + virtual void format(std::basic_string& stream) const + { + stream.append(m_value ? L"true" : L"false"); + } +#endif + private: + template friend class json::details::JSON_Parser; + bool m_value; + }; + + class _String : public _Value + { + public: + + _String(utility::string_t value) : m_string(std::move(value)) + { + m_has_escape_char = has_escape_chars(*this); + } + _String(utility::string_t value, bool escaped_chars) + : m_string(std::move(value)), + m_has_escape_char(escaped_chars) + { } + +#ifdef _WIN32 + _String(std::string &&value) : m_string(utility::conversions::to_utf16string(std::move(value))) + { + m_has_escape_char = has_escape_chars(*this); + } + _String(std::string &&value, bool escape_chars) + : m_string(utility::conversions::to_utf16string(std::move(value))), + m_has_escape_char(escape_chars) + { } +#endif + + virtual std::unique_ptr<_Value> _copy_value() + { + return utility::details::make_unique<_String>(*this); + } + + virtual json::value::value_type type() const { return json::value::String; } + + virtual const utility::string_t & as_string() const; + + virtual void serialize_impl(std::string& str) const + { + serialize_impl_char_type(str); + } +#ifdef _WIN32 + virtual void serialize_impl(std::wstring& str) const + { + serialize_impl_char_type(str); + } +#endif + + protected: + virtual void format(std::basic_string& str) const; +#ifdef _WIN32 + virtual void format(std::basic_string& str) const; +#endif + + private: + friend class _Object; + friend class _Array; + + size_t get_reserve_size() const + { + return m_string.size() + 2; + } + + template + void serialize_impl_char_type(std::basic_string& str) const + { + // To avoid repeated allocations reserve some space all up front. + // size of string + 2 for quotes + str.reserve(get_reserve_size()); + format(str); + } + + std::string as_utf8_string() const; + utf16string as_utf16_string() const; + + utility::string_t m_string; + + // There are significant performance gains that can be made by knowning whether + // or not a character that requires escaping is present. + bool m_has_escape_char; + static bool has_escape_chars(const _String &str); + }; + + template + _ASYNCRTIMP void append_escape_string(std::basic_string& str, const std::basic_string& escaped); + + void format_string(const utility::string_t& key, utility::string_t& str); + +#ifdef _WIN32 + void format_string(const utility::string_t& key, std::string& str); +#endif + + class _Object : public _Value + { + public: + + _Object(bool keep_order) : m_object(keep_order) { } + _Object(object::storage_type fields, bool keep_order) : m_object(std::move(fields), keep_order) { } + + virtual std::unique_ptr<_Value> _copy_value() + { + return utility::details::make_unique<_Object>(*this); + } + + virtual json::object& as_object() { return m_object; } + + virtual const json::object& as_object() const { return m_object; } + + virtual json::value::value_type type() const { return json::value::Object; } + + virtual bool has_field(const utility::string_t &) const; + + virtual json::value &index(const utility::string_t &key); + + bool is_equal(const _Object* other) const + { + if (m_object.size() != other->m_object.size()) + return false; + + return std::equal(std::begin(m_object), std::end(m_object), std::begin(other->m_object)); + } + + virtual void serialize_impl(std::string& str) const + { + // To avoid repeated allocations reserve some space all up front. + str.reserve(get_reserve_size()); + format(str); + } +#ifdef _WIN32 + virtual void serialize_impl(std::wstring& str) const + { + // To avoid repeated allocations reserve some space all up front. + str.reserve(get_reserve_size()); + format(str); + } +#endif + size_t size() const { return m_object.size(); } + + protected: + virtual void format(std::basic_string& str) const + { + format_impl(str); + } +#ifdef _WIN32 + virtual void format(std::basic_string& str) const + { + format_impl(str); + } +#endif + + private: + json::object m_object; + + template friend class json::details::JSON_Parser; + + template + void format_impl(std::basic_string& str) const + { + str.push_back('{'); + if(!m_object.empty()) + { + auto lastElement = m_object.end() - 1; + for (auto iter = m_object.begin(); iter != lastElement; ++iter) + { + format_string(iter->first, str); + str.push_back(':'); + iter->second.format(str); + str.push_back(','); + } + format_string(lastElement->first, str); + str.push_back(':'); + lastElement->second.format(str); + } + str.push_back('}'); + } + + size_t get_reserve_size() const + { + // This is a heuristic we can tune more in the future: + // Basically size of string plus + // sum size of value if an object, array, or string. + size_t reserveSize = 2; // For brackets {} + for(auto iter = m_object.begin(); iter != m_object.end(); ++iter) + { + reserveSize += iter->first.length() + 2; // 2 for quotes + size_t valueSize = iter->second.size() * 20; // Multipler by each object/array element + if(valueSize == 0) + { + if(iter->second.type() == json::value::String) + { + valueSize = static_cast<_String *>(iter->second.m_value.get())->get_reserve_size(); + } + else + { + valueSize = 5; // true, false, or null + } + } + reserveSize += valueSize; + } + return reserveSize; + } + }; + + class _Array : public _Value + { + public: + _Array() {} + _Array(array::size_type size) : m_array(size) {} + _Array(array::storage_type elements) : m_array(std::move(elements)) { } + + virtual std::unique_ptr<_Value> _copy_value() + { + return utility::details::make_unique<_Array>(*this); + } + + virtual json::value::value_type type() const { return json::value::Array; } + + virtual json::array& as_array() { return m_array; } + virtual const json::array& as_array() const { return m_array; } + + virtual json::value &index(json::array::size_type index) + { + return m_array[index]; + } + + bool is_equal(const _Array* other) const + { + if ( m_array.size() != other->m_array.size()) + return false; + + auto iterT = m_array.cbegin(); + auto iterO = other->m_array.cbegin(); + auto iterTe = m_array.cend(); + auto iterOe = other->m_array.cend(); + + for (; iterT != iterTe && iterO != iterOe; ++iterT, ++iterO) + { + if ( *iterT != *iterO ) + return false; + } + + return true; + } + + virtual void serialize_impl(std::string& str) const + { + // To avoid repeated allocations reserve some space all up front. + str.reserve(get_reserve_size()); + format(str); + } +#ifdef _WIN32 + virtual void serialize_impl(std::wstring& str) const + { + // To avoid repeated allocations reserve some space all up front. + str.reserve(get_reserve_size()); + format(str); + } +#endif + size_t size() const { return m_array.size(); } + + protected: + virtual void format(std::basic_string& str) const + { + format_impl(str); + } +#ifdef _WIN32 + virtual void format(std::basic_string& str) const + { + format_impl(str); + } +#endif + private: + json::array m_array; + + template friend class json::details::JSON_Parser; + + template + void format_impl(std::basic_string& str) const + { + str.push_back('['); + if(!m_array.m_elements.empty()) + { + auto lastElement = m_array.m_elements.end() - 1; + for (auto iter = m_array.m_elements.begin(); iter != lastElement; ++iter) + { + iter->format(str); + str.push_back(','); + } + lastElement->format(str); + } + str.push_back(']'); + } + + size_t get_reserve_size() const + { + // This is a heuristic we can tune more in the future: + // Basically sum size of each value if an object, array, or string by a multiplier. + size_t reserveSize = 2; // For brackets [] + for(auto iter = m_array.cbegin(); iter != m_array.cend(); ++iter) + { + size_t valueSize = iter->size() * 20; // Per each nested array/object + + if(valueSize == 0) + valueSize = 5; // true, false, or null + + reserveSize += valueSize; + } + return reserveSize; + } + }; + } // namespace details + + /// + /// Gets the number of children of the value. + /// + /// The number of children. 0 for all non-composites. + inline size_t json::value::size() const + { + return m_value->size(); + } + + /// + /// Test for the presence of a field. + /// + /// The name of the field + /// True if the field exists, false otherwise. + inline bool json::value::has_field(const utility::string_t& key) const + { + return m_value->has_field(key); + } + + /// + /// Access a field of a JSON object. + /// + /// The name of the field + /// The value kept in the field; null if the field does not exist + inline json::value json::value::get(const utility::string_t& key) const + { + return m_value->get_field(key); + } + + /// + /// Access an element of a JSON array. + /// + /// The index of an element in the JSON array + /// The value kept at the array index; null if outside the boundaries of the array + inline json::value json::value::get(size_t index) const + { + return m_value->get_element(index); + } + + /// + /// A standard std::ostream operator to facilitate writing JSON values to streams. + /// + /// The output stream to write the JSON value to. + /// The JSON value to be written to the stream. + /// The output stream object + _ASYNCRTIMP utility::ostream_t& __cdecl operator << (utility::ostream_t &os, const json::value &val); + + /// + /// A standard std::istream operator to facilitate reading JSON values from streams. + /// + /// The input stream to read the JSON value from. + /// The JSON value object read from the stream. + /// The input stream object. + _ASYNCRTIMP utility::istream_t& __cdecl operator >> (utility::istream_t &is, json::value &val); +}} + +#endif diff --git a/src/corehost/cli/json/casablanca/include/stdafx.h b/src/corehost/cli/json/casablanca/include/stdafx.h new file mode 100644 index 000000000..553d828a6 --- /dev/null +++ b/src/corehost/cli/json/casablanca/include/stdafx.h @@ -0,0 +1,109 @@ +/*** +* ==++== +* +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* ==--== +* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +* +* Pre-compiled headers +* +* For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk +* +* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +****/ + +#pragma once + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-local-typedef" +#endif + +#include +#include +#ifdef _WIN32 +#ifdef CPPREST_TARGET_XP +#include +#ifndef _WIN32_WINNT +#define _WIN32_WINNT _WIN32_WINNT_WS03 //Windows XP with SP2 +#endif +#endif +#include +// use the debug version of the CRT if _DEBUG is defined +#ifdef _DEBUG + #define _CRTDBG_MAP_ALLOC + #include + #include +#endif + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files: +#define NOMINMAX +#endif + +#include +#include + +// Windows Header Files: +#if !defined(__cplusplus_winrt) +#include + +#endif // #if !defined(__cplusplus_winrt) +#else // LINUX or APPLE +#define __STDC_LIMIT_MACROS +#include +#include +#include +#include +#include +#include +#include +#include "pthread.h" +#include +#include +#include +#include +#endif // _WIN32 + +// Macro indicating the C++ Rest SDK product itself is being built. +// This is to help track how many developers are directly building from source themselves. +#define _CASA_BUILD_FROM_SRC + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// json +#include "cpprest/json.h" + +#if defined(max) +#error: max macro defined -- make sure to #define NOMINMAX before including windows.h +#endif +#if defined(min) +#error: min macro defined -- make sure to #define NOMINMAX before including windows.h +#endif + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + diff --git a/src/corehost/cli/json/casablanca/src/json/json.cpp b/src/corehost/cli/json/casablanca/src/json/json.cpp new file mode 100644 index 000000000..77380460c --- /dev/null +++ b/src/corehost/cli/json/casablanca/src/json/json.cpp @@ -0,0 +1,495 @@ +/*** +* ==++== +* +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* ==--== +* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +* +* HTTP Library: JSON parser and writer +* +* For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk +* +* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +****/ + +#include "stdafx.h" + +using namespace web; + +bool json::details::g_keep_json_object_unsorted = false; +void json::keep_object_element_order(bool keep_order) +{ + json::details::g_keep_json_object_unsorted = keep_order; +} + +utility::ostream_t& web::json::operator << (utility::ostream_t &os, const web::json::value &val) +{ + val.serialize(os); + return os; +} + +utility::istream_t& web::json::operator >> (utility::istream_t &is, json::value &val) +{ + val = json::value::parse(is); + return is; +} + +web::json::value::value() : + m_value(utility::details::make_unique()) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,m_kind(value::Null) +#endif + { } + +web::json::value::value(int32_t value) : + m_value(utility::details::make_unique(value)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,m_kind(value::Number) +#endif + { } + +web::json::value::value(uint32_t value) : + m_value(utility::details::make_unique(value)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,m_kind(value::Number) +#endif + { } + +web::json::value::value(int64_t value) : + m_value(utility::details::make_unique(value)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,m_kind(value::Number) +#endif + { } + +web::json::value::value(uint64_t value) : + m_value(utility::details::make_unique(value)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,m_kind(value::Number) +#endif + { } + +web::json::value::value(double value) : + m_value(utility::details::make_unique(value)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,m_kind(value::Number) +#endif + { } + +web::json::value::value(bool value) : + m_value(utility::details::make_unique(value)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,m_kind(value::Boolean) +#endif + { } + +web::json::value::value(utility::string_t value) : + m_value(utility::details::make_unique(std::move(value))) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,m_kind(value::String) +#endif + { } + +web::json::value::value(utility::string_t value, bool has_escape_chars) : +m_value(utility::details::make_unique(std::move(value), has_escape_chars)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER +, m_kind(value::String) +#endif +{ } + +web::json::value::value(const utility::char_t* value) : + m_value(utility::details::make_unique(value)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,m_kind(value::String) +#endif + { } + +web::json::value::value(const utility::char_t* value, bool has_escape_chars) : +m_value(utility::details::make_unique(utility::string_t(value), has_escape_chars)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER +, m_kind(value::String) +#endif +{ } + +web::json::value::value(const value &other) : + m_value(other.m_value->_copy_value()) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,m_kind(other.m_kind) +#endif + { } + +web::json::value &web::json::value::operator=(const value &other) +{ + if(this != &other) + { + m_value = std::unique_ptr(other.m_value->_copy_value()); +#ifdef ENABLE_JSON_VALUE_VISUALIZER + m_kind = other.m_kind; +#endif + } + return *this; +} + +web::json::value::value(value &&other) CPPREST_NOEXCEPT : + m_value(std::move(other.m_value)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,m_kind(other.m_kind) +#endif +{} + +web::json::value &web::json::value::operator=(web::json::value &&other) CPPREST_NOEXCEPT +{ + if(this != &other) + { + m_value.swap(other.m_value); +#ifdef ENABLE_JSON_VALUE_VISUALIZER + m_kind = other.m_kind; +#endif + } + return *this; +} + +web::json::value web::json::value::null() +{ + return web::json::value(); +} + +web::json::value web::json::value::number(double value) +{ + return web::json::value(value); +} + +web::json::value web::json::value::number(int32_t value) +{ + return web::json::value(value); +} + +web::json::value web::json::value::number(uint32_t value) +{ + return web::json::value(value); +} + +web::json::value web::json::value::number(int64_t value) +{ + return web::json::value(value); +} + +web::json::value web::json::value::number(uint64_t value) +{ + return web::json::value(value); +} + +web::json::value web::json::value::boolean(bool value) +{ + return web::json::value(value); +} + +web::json::value web::json::value::string(utility::string_t value) +{ + std::unique_ptr ptr = utility::details::make_unique(std::move(value)); + return web::json::value(std::move(ptr) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,value::String +#endif + ); +} + +web::json::value web::json::value::string(utility::string_t value, bool has_escape_chars) +{ + std::unique_ptr ptr = utility::details::make_unique(std::move(value), has_escape_chars); + return web::json::value(std::move(ptr) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,value::String +#endif + ); +} + +#ifdef _WIN32 +web::json::value web::json::value::string(const std::string &value) +{ + std::unique_ptr ptr = utility::details::make_unique(utility::conversions::to_utf16string(value)); + return web::json::value(std::move(ptr) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,value::String +#endif + ); +} +#endif + +web::json::value web::json::value::object(bool keep_order) +{ + std::unique_ptr ptr = utility::details::make_unique(keep_order); + return web::json::value(std::move(ptr) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,value::Object +#endif + ); +} + +web::json::value web::json::value::object(std::vector> fields, bool keep_order) +{ + std::unique_ptr ptr = utility::details::make_unique(std::move(fields), keep_order); + return web::json::value(std::move(ptr) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,value::Object +#endif + ); +} + +web::json::value web::json::value::array() +{ + std::unique_ptr ptr = utility::details::make_unique(); + return web::json::value(std::move(ptr) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,value::Array +#endif + ); +} + +web::json::value web::json::value::array(size_t size) +{ + std::unique_ptr ptr = utility::details::make_unique(size); + return web::json::value(std::move(ptr) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,value::Array +#endif + ); +} + +web::json::value web::json::value::array(std::vector elements) +{ + std::unique_ptr ptr = utility::details::make_unique(std::move(elements)); + return web::json::value(std::move(ptr) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,value::Array +#endif + ); +} + +const web::json::number& web::json::value::as_number() const +{ + return m_value->as_number(); +} + +double web::json::value::as_double() const +{ + return m_value->as_double(); +} + +int web::json::value::as_integer() const +{ + return m_value->as_integer(); +} + +bool web::json::value::as_bool() const +{ + return m_value->as_bool(); +} + +json::array& web::json::value::as_array() +{ + return m_value->as_array(); +} + +const json::array& web::json::value::as_array() const +{ + return m_value->as_array(); +} + +json::object& web::json::value::as_object() +{ + return m_value->as_object(); +} + +const json::object& web::json::value::as_object() const +{ + return m_value->as_object(); +} + +bool web::json::number::is_int32() const +{ + switch (m_type) + { + case signed_type : return m_intval >= std::numeric_limits::min() && m_intval <= std::numeric_limits::max(); + case unsigned_type : return m_uintval <= std::numeric_limits::max(); + case double_type : + default : + return false; + } +} + +bool web::json::number::is_uint32() const +{ + switch (m_type) + { + case signed_type : return m_intval >= 0 && m_intval <= std::numeric_limits::max(); + case unsigned_type : return m_uintval <= std::numeric_limits::max(); + case double_type : + default : + return false; + } +} + +bool web::json::number::is_int64() const +{ + switch (m_type) + { + case signed_type : return true; + case unsigned_type : return m_uintval <= static_cast(std::numeric_limits::max()); + case double_type : + default : + return false; + } +} + +bool web::json::details::_String::has_escape_chars(const _String &str) +{ + return std::any_of(std::begin(str.m_string), std::end(str.m_string), [](utility::string_t::value_type const x) + { + if (x <= 31) { return true; } + if (x == '"') { return true; } + if (x == '\\') { return true; } + return false; + }); +} + +web::json::value::value_type json::value::type() const { return m_value->type(); } + +bool json::value::is_integer() const +{ + if(!is_number()) + { + return false; + } + return m_value->is_integer(); +} + +bool json::value::is_double() const +{ + if(!is_number()) + { + return false; + } + return m_value->is_double(); +} + +json::value& web::json::details::_Object::index(const utility::string_t &key) +{ + return m_object[key]; +} + +bool web::json::details::_Object::has_field(const utility::string_t &key) const +{ + return m_object.find(key) != m_object.end(); +} + +utility::string_t json::value::to_string() const +{ +#ifndef _WIN32 + utility::details::scoped_c_thread_locale locale; +#endif + return m_value->to_string(); +} + +bool json::value::operator==(const json::value &other) const +{ + if (this->m_value.get() == other.m_value.get()) + return true; + if (this->type() != other.type()) + return false; + + switch(this->type()) + { + case Null: + return true; + case Number: + return this->as_number() == other.as_number(); + case Boolean: + return this->as_bool() == other.as_bool(); + case String: + return this->as_string() == other.as_string(); + case Object: + return static_cast(this->m_value.get())->is_equal(static_cast(other.m_value.get())); + case Array: + return static_cast(this->m_value.get())->is_equal(static_cast(other.m_value.get())); + } + __assume(0); +} + +void web::json::value::erase(size_t index) +{ + return this->as_array().erase(index); +} + +void web::json::value::erase(const utility::string_t &key) +{ + return this->as_object().erase(key); +} + +// at() overloads +web::json::value& web::json::value::at(size_t index) +{ + return this->as_array().at(index); +} + +const web::json::value& web::json::value::at(size_t index) const +{ + return this->as_array().at(index); +} + +web::json::value& web::json::value::at(const utility::string_t& key) +{ + return this->as_object().at(key); +} + +const web::json::value& web::json::value::at(const utility::string_t& key) const +{ + return this->as_object().at(key); +} + +web::json::value& web::json::value::operator [] (const utility::string_t &key) +{ + if ( this->is_null() ) + { + m_value.reset(new web::json::details::_Object(details::g_keep_json_object_unsorted)); +#ifdef ENABLE_JSON_VALUE_VISUALIZER + m_kind = value::Object; +#endif + } + return m_value->index(key); +} + +web::json::value& web::json::value::operator[](size_t index) +{ + if ( this->is_null() ) + { + m_value.reset(new web::json::details::_Array()); +#ifdef ENABLE_JSON_VALUE_VISUALIZER + m_kind = value::Array; +#endif + } + return m_value->index(index); +} + +// Remove once VS 2013 is no longer supported. +#if defined(_WIN32) && _MSC_VER < 1900 +static web::json::details::json_error_category_impl instance; +#endif +const web::json::details::json_error_category_impl& web::json::details::json_error_category() +{ +#if !defined(_WIN32) || _MSC_VER >= 1900 + static web::json::details::json_error_category_impl instance; +#endif + return instance; +} diff --git a/src/corehost/cli/json/casablanca/src/json/json_parsing.cpp b/src/corehost/cli/json/casablanca/src/json/json_parsing.cpp new file mode 100644 index 000000000..1cf40dc67 --- /dev/null +++ b/src/corehost/cli/json/casablanca/src/json/json_parsing.cpp @@ -0,0 +1,1312 @@ +/*** +* ==++== +* +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* ==--== +* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +* +* HTTP Library: JSON parser +* +* For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk +* +* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +****/ + +#include "stdafx.h" +#include + +#if defined(_MSC_VER) +#pragma warning(disable : 4127) // allow expressions like while(true) pass +#endif +using namespace web; +using namespace web::json; +using namespace utility; +using namespace utility::conversions; + +std::array _hexval = {{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }}; + +namespace web { +namespace json +{ +namespace details +{ + +// +// JSON Parsing +// + +template +#if defined(_WIN32) + __declspec(noreturn) +#else + __attribute__((noreturn)) +#endif +void CreateException(const Token &tk, const utility::string_t &message) +{ + utility::ostringstream_t os; + os << _XPLATSTR("* Line ") << tk.start.m_line << _XPLATSTR(", Column ") << tk.start.m_column << _XPLATSTR(" Syntax error: ") << message; + utility::string_t osStr = os.str(); + throw web::json::json_exception(osStr.c_str()); +} + +template +void SetErrorCode(Token &tk, json_error jsonErrorCode) +{ + tk.m_error = std::error_code(jsonErrorCode, json_error_category()); +} + +template +class JSON_Parser +{ +public: + JSON_Parser() + : m_currentLine(1), + m_currentColumn(1), + m_currentParsingDepth(0) + { } + + struct Location + { + size_t m_line; + size_t m_column; + }; + + struct Token + { + enum Kind + { + TKN_EOF, + + TKN_OpenBrace, + TKN_CloseBrace, + TKN_OpenBracket, + TKN_CloseBracket, + TKN_Comma, + TKN_Colon, + TKN_StringLiteral, + TKN_NumberLiteral, + TKN_IntegerLiteral, + TKN_BooleanLiteral, + TKN_NullLiteral, + TKN_Comment + }; + + Token() : kind(TKN_EOF) {} + + Kind kind; + std::basic_string string_val; + + typename JSON_Parser::Location start; + + union + { + double double_val; + int64_t int64_val; + uint64_t uint64_val; + bool boolean_val; + bool has_unescape_symbol; + }; + + bool signed_number; + + std::error_code m_error; + }; + + void GetNextToken(Token &); + + web::json::value ParseValue(typename JSON_Parser::Token &first) + { +#ifndef _WIN32 + utility::details::scoped_c_thread_locale locale; +#endif + +#ifdef ENABLE_JSON_VALUE_VISUALIZER + auto _value = _ParseValue(first); + auto type = _value->type(); + return web::json::value(std::move(_value), type); +#else + return web::json::value(_ParseValue(first)); +#endif + } + +protected: + typedef typename std::char_traits::int_type int_type; + virtual int_type NextCharacter() = 0; + virtual int_type PeekCharacter() = 0; + + virtual bool CompleteComment(Token &token); + virtual bool CompleteStringLiteral(Token &token); + bool handle_unescape_char(Token &token); + +private: + + bool CompleteNumberLiteral(CharType first, Token &token); + bool ParseInt64(CharType first, uint64_t& value); + bool CompleteKeywordTrue(Token &token); + bool CompleteKeywordFalse(Token &token); + bool CompleteKeywordNull(Token &token); + std::unique_ptr _ParseValue(typename JSON_Parser::Token &first); + std::unique_ptr _ParseObject(typename JSON_Parser::Token &tkn); + std::unique_ptr _ParseArray(typename JSON_Parser::Token &tkn); + + JSON_Parser& operator=(const JSON_Parser&); + + int_type EatWhitespace(); + + void CreateToken(typename JSON_Parser::Token& tk, typename Token::Kind kind, Location &start) + { + tk.kind = kind; + tk.start = start; + tk.string_val.clear(); + } + + void CreateToken(typename JSON_Parser::Token& tk, typename Token::Kind kind) + { + tk.kind = kind; + tk.start.m_line = m_currentLine; + tk.start.m_column = m_currentColumn; + tk.string_val.clear(); + } + +protected: + + size_t m_currentLine; + size_t m_currentColumn; + size_t m_currentParsingDepth; + +// The DEBUG macro is defined in XCode but we don't in our CMakeList +// so for now we will keep the same on debug and release. In the future +// this can be increase on release if necessary. +#if defined(__APPLE__) + static const size_t maxParsingDepth = 32; +#else + static const size_t maxParsingDepth = 128; +#endif +}; + +// Replace with template alias once VS 2012 support is removed. +template +typename std::char_traits::int_type eof() +{ + return std::char_traits::eof(); +} + +template +class JSON_StreamParser : public JSON_Parser + { +public: + JSON_StreamParser(std::basic_istream &stream) + : m_streambuf(stream.rdbuf()) + { + } + +protected: + + virtual typename JSON_Parser::int_type NextCharacter(); + virtual typename JSON_Parser::int_type PeekCharacter(); + +private: + typename std::basic_streambuf>* m_streambuf; +}; + +template +class JSON_StringParser : public JSON_Parser +{ +public: + JSON_StringParser(const std::basic_string& string) + : m_position(&string[0]) + { + m_startpos = m_position; + m_endpos = m_position+string.size(); + } + +protected: + + virtual typename JSON_Parser::int_type NextCharacter(); + virtual typename JSON_Parser::int_type PeekCharacter(); + + virtual bool CompleteComment(typename JSON_Parser::Token &token); + virtual bool CompleteStringLiteral(typename JSON_Parser::Token &token); + +private: + bool finish_parsing_string_with_unescape_char(typename JSON_Parser::Token &token); + const CharType* m_position; + const CharType* m_startpos; + const CharType* m_endpos; +}; + + +template +typename JSON_Parser::int_type JSON_StreamParser::NextCharacter() +{ + auto ch = m_streambuf->sbumpc(); + + if (ch == '\n') + { + this->m_currentLine += 1; + this->m_currentColumn = 0; + } + else + { + this->m_currentColumn += 1; + } + + return ch; +} + +template +typename JSON_Parser::int_type JSON_StreamParser::PeekCharacter() +{ + return m_streambuf->sgetc(); +} + +template +typename JSON_Parser::int_type JSON_StringParser::NextCharacter() +{ + if (m_position == m_endpos) + return eof(); + + CharType ch = *m_position; + m_position += 1; + + if ( ch == '\n' ) + { + this->m_currentLine += 1; + this->m_currentColumn = 0; + } + else + { + this->m_currentColumn += 1; + } + + return ch; +} + +template +typename JSON_Parser::int_type JSON_StringParser::PeekCharacter() +{ + if ( m_position == m_endpos ) return eof(); + + return *m_position; +} + +// +// Consume whitespace characters and return the first non-space character or EOF +// +template +typename JSON_Parser::int_type JSON_Parser::EatWhitespace() +{ + auto ch = NextCharacter(); + + while ( ch != eof() && iswspace(static_cast(ch))) + { + ch = NextCharacter(); + } + + return ch; +} + +template +bool JSON_Parser::CompleteKeywordTrue(Token &token) +{ + if (NextCharacter() != 'r') + return false; + if (NextCharacter() != 'u') + return false; + if (NextCharacter() != 'e') + return false; + token.kind = Token::TKN_BooleanLiteral; + token.boolean_val = true; + return true; +} + +template +bool JSON_Parser::CompleteKeywordFalse(Token &token) +{ + if (NextCharacter() != 'a') + return false; + if (NextCharacter() != 'l') + return false; + if (NextCharacter() != 's') + return false; + if (NextCharacter() != 'e') + return false; + token.kind = Token::TKN_BooleanLiteral; + token.boolean_val = false; + return true; +} + +template +bool JSON_Parser::CompleteKeywordNull(Token &token) +{ + if (NextCharacter() != 'u') + return false; + if (NextCharacter() != 'l') + return false; + if (NextCharacter() != 'l') + return false; + token.kind = Token::TKN_NullLiteral; + return true; +} + +// Returns false only on overflow +template +inline bool JSON_Parser::ParseInt64(CharType first, uint64_t& value) +{ + value = first - '0'; + auto ch = PeekCharacter(); + while (ch >= '0' && ch <= '9') + { + unsigned int next_digit = (unsigned int)(ch - '0'); + if (value > (ULLONG_MAX / 10) || (value == ULLONG_MAX/10 && next_digit > ULLONG_MAX%10)) + return false; + + NextCharacter(); + + value *= 10; + value += next_digit; + ch = PeekCharacter(); + } + return true; +} + +// This namespace hides the x-plat helper functions +namespace +{ +#if defined(_WIN32) + static int print_llu(char* ptr, size_t n, uint64_t val64) + { + return _snprintf_s_l(ptr, n, _TRUNCATE, "%I64u", utility::details::scoped_c_thread_locale::c_locale(), val64); + } + + static int print_llu(wchar_t* ptr, size_t n, uint64_t val64) + { + return _snwprintf_s_l(ptr, n, _TRUNCATE, L"%I64u", utility::details::scoped_c_thread_locale::c_locale(), val64); + } + static double anystod(const char* str) + { + return _strtod_l(str, nullptr, utility::details::scoped_c_thread_locale::c_locale()); + } + static double anystod(const wchar_t* str) + { + return _wcstod_l(str, nullptr, utility::details::scoped_c_thread_locale::c_locale()); + } +#else + static int __attribute__((__unused__)) print_llu(char* ptr, size_t n, unsigned long long val64) + { + return snprintf(ptr, n, "%llu", val64); + } + static int __attribute__((__unused__)) print_llu(char* ptr, size_t n, unsigned long val64) + { + return snprintf(ptr, n, "%lu", val64); + } + static double __attribute__((__unused__)) anystod(const char* str) + { + return strtod(str, nullptr); + } + static double __attribute__((__unused__)) anystod(const wchar_t* str) + { + return wcstod(str, nullptr); + } +#endif +} + +template +bool JSON_Parser::CompleteNumberLiteral(CharType first, Token &token) +{ + bool minus_sign; + + if (first == '-') + { + minus_sign = true; + + // Safe to cast because the check after this if/else statement will cover EOF. + first = static_cast(NextCharacter()); + } + else + { + minus_sign = false; + } + + if (first < '0' || first > '9') + return false; + + auto ch = PeekCharacter(); + + //Check for two (or more) zeros at the beginning + if (first == '0' && ch == '0') + return false; + + // Parse the number assuming its integer + uint64_t val64; + bool complete = ParseInt64(first, val64); + + ch = PeekCharacter(); + if (complete && ch!='.' && ch!='E' && ch!='e') + { + if (minus_sign) + { + if (val64 > static_cast(1) << 63 ) + { + // It is negative and cannot be represented in int64, so we resort to double + token.double_val = 0 - static_cast(val64); + token.signed_number = true; + token.kind = JSON_Parser::Token::TKN_NumberLiteral; + return true; + } + + // It is negative, but fits into int64 + token.int64_val = 0 - static_cast(val64); + token.kind = JSON_Parser::Token::TKN_IntegerLiteral; + token.signed_number = true; + return true; + } + + // It is positive so we use unsigned int64 + token.uint64_val = val64; + token.kind = JSON_Parser::Token::TKN_IntegerLiteral; + token.signed_number = false; + return true; + } + + // Magic number 5 leaves room for decimal point, null terminator, etc (in most cases) + ::std::vector buf(::std::numeric_limits::digits10 + 5); + int count = print_llu(buf.data(), buf.size(), val64); + _ASSERTE(count >= 0); + _ASSERTE((size_t)count < buf.size()); + // Resize to cut off the null terminator + buf.resize(count); + + bool decimal = false; + + while (ch != eof()) + { + // Digit encountered? + if (ch >= '0' && ch <= '9') + { + buf.push_back(static_cast(ch)); + NextCharacter(); + ch = PeekCharacter(); + } + + // Decimal dot? + else if (ch == '.') + { + if (decimal) + return false; + + decimal = true; + buf.push_back(static_cast(ch)); + + NextCharacter(); + ch = PeekCharacter(); + + // Check that the following char is a digit + if (ch < '0' || ch > '9') + return false; + + buf.push_back(static_cast(ch)); + NextCharacter(); + ch = PeekCharacter(); + } + + // Exponent? + else if (ch == 'E' || ch == 'e') + { + buf.push_back(static_cast(ch)); + NextCharacter(); + ch = PeekCharacter(); + + // Check for the exponent sign + if (ch == '+') + { + buf.push_back(static_cast(ch)); + NextCharacter(); + ch = PeekCharacter(); + } + else if (ch == '-') + { + buf.push_back(static_cast(ch)); + NextCharacter(); + ch = PeekCharacter(); + } + + // First number of the exponent + if (ch >= '0' && ch <= '9') + { + buf.push_back(static_cast(ch)); + NextCharacter(); + ch = PeekCharacter(); + } + else return false; + + // The rest of the exponent + while (ch >= '0' && ch <= '9') + { + buf.push_back(static_cast(ch)); + NextCharacter(); + ch = PeekCharacter(); + } + + // The peeked character is not a number, so we can break from the loop and construct the number + break; + } + else + { + // Not expected number character? + break; + } + }; + + buf.push_back('\0'); + token.double_val = anystod(buf.data()); + if (minus_sign) + { + token.double_val = -token.double_val; + } + token.kind = (JSON_Parser::Token::TKN_NumberLiteral); + + return true; +} + +template +bool JSON_Parser::CompleteComment(Token &token) +{ + // We already found a '/' character as the first of a token -- what kind of comment is it? + + auto ch = NextCharacter(); + + if ( ch == eof() || (ch != '/' && ch != '*') ) + return false; + + if ( ch == '/' ) + { + // Line comment -- look for a newline or EOF to terminate. + + ch = NextCharacter(); + + while ( ch != eof() && ch != '\n') + { + ch = NextCharacter(); + } + } + else + { + // Block comment -- look for a terminating "*/" sequence. + + ch = NextCharacter(); + + while ( true ) + { + if ( ch == eof()) + return false; + + if ( ch == '*' ) + { + auto ch1 = PeekCharacter(); + + if ( ch1 == eof()) + return false; + + if ( ch1 == '/' ) + { + // Consume the character + NextCharacter(); + break; + } + + ch = ch1; + } + + ch = NextCharacter(); + } + } + + token.kind = Token::TKN_Comment; + + return true; +} + +template +bool JSON_StringParser::CompleteComment(typename JSON_Parser::Token &token) +{ + // This function is specialized for the string parser, since we can be slightly more + // efficient in copying data from the input to the token: do a memcpy() rather than + // one character at a time. + + auto ch = JSON_StringParser::NextCharacter(); + + if ( ch == eof() || (ch != '/' && ch != '*') ) + return false; + + if ( ch == '/' ) + { + // Line comment -- look for a newline or EOF to terminate. + + ch = JSON_StringParser::NextCharacter(); + + while ( ch != eof() && ch != '\n') + { + ch = JSON_StringParser::NextCharacter(); + } + } + else + { + // Block comment -- look for a terminating "*/" sequence. + + ch = JSON_StringParser::NextCharacter(); + + while ( true ) + { + if ( ch == eof()) + return false; + + if ( ch == '*' ) + { + ch = JSON_StringParser::PeekCharacter(); + + if ( ch == eof()) + return false; + + if ( ch == '/' ) + { + // Consume the character + JSON_StringParser::NextCharacter(); + break; + } + + } + + ch = JSON_StringParser::NextCharacter(); + } + } + + token.kind = JSON_Parser::Token::TKN_Comment; + + return true; +} + +void convert_append_unicode_code_unit(JSON_Parser::Token &token, utf16char value) +{ + token.string_val.push_back(value); +} +void convert_append_unicode_code_unit(JSON_Parser::Token &token, utf16char value) +{ + utf16string utf16(reinterpret_cast(&value), 1); + token.string_val.append(::utility::conversions::utf16_to_utf8(utf16)); +} + +template +inline bool JSON_Parser::handle_unescape_char(Token &token) +{ + token.has_unescape_symbol = true; + + // This function converts unescaped character pairs (e.g. "\t") into their ASCII or Unicode representations (e.g. tab sign) + // Also it handles \u + 4 hexadecimal digits + auto ch = NextCharacter(); + switch (ch) + { + case '\"': + token.string_val.push_back('\"'); + return true; + case '\\': + token.string_val.push_back('\\'); + return true; + case '/': + token.string_val.push_back('/'); + return true; + case 'b': + token.string_val.push_back('\b'); + return true; + case 'f': + token.string_val.push_back('\f'); + return true; + case 'r': + token.string_val.push_back('\r'); + return true; + case 'n': + token.string_val.push_back('\n'); + return true; + case 't': + token.string_val.push_back('\t'); + return true; + case 'u': + { + // A four-hexdigit Unicode character. + // Transform into a 16 bit code point. + int decoded = 0; + for (int i = 0; i < 4; ++i) + { + ch = NextCharacter(); + int ch_int = static_cast(ch); + if (ch_int < 0 || ch_int > 127) + return false; +#ifdef _WIN32 + const int isxdigitResult = _isxdigit_l(ch_int, utility::details::scoped_c_thread_locale::c_locale()); +#else + const int isxdigitResult = isxdigit(ch_int); +#endif + if (!isxdigitResult) + return false; + + int val = _hexval[static_cast(ch_int)]; + _ASSERTE(val != -1); + + // Add the input char to the decoded number + decoded |= (val << (4 * (3 - i))); + } + + // Construct the character based on the decoded number + convert_append_unicode_code_unit(token, static_cast(decoded)); + + return true; + } + default: + return false; + } +} + +template +bool JSON_Parser::CompleteStringLiteral(Token &token) +{ + token.has_unescape_symbol = false; + auto ch = NextCharacter(); + while ( ch != '"' ) + { + if ( ch == '\\' ) + { + handle_unescape_char(token); + } + else if (ch >= CharType(0x0) && ch < CharType(0x20)) + { + return false; + } + else + { + if (ch == eof()) + return false; + + token.string_val.push_back(static_cast(ch)); + } + ch = NextCharacter(); + } + + if ( ch == '"' ) + { + token.kind = Token::TKN_StringLiteral; + } + else + { + return false; + } + + return true; +} + +template +bool JSON_StringParser::CompleteStringLiteral(typename JSON_Parser::Token &token) +{ + // This function is specialized for the string parser, since we can be slightly more + // efficient in copying data from the input to the token: do a memcpy() rather than + // one character at a time. + + auto start = m_position; + token.has_unescape_symbol = false; + + auto ch = JSON_StringParser::NextCharacter(); + + while (ch != '"') + { + if (ch == eof()) + return false; + + if (ch == '\\') + { + const size_t numChars = m_position - start - 1; + const size_t prevSize = token.string_val.size(); + token.string_val.resize(prevSize + numChars); + memcpy(const_cast(token.string_val.c_str() + prevSize), start, numChars * sizeof(CharType)); + + if (!JSON_StringParser::handle_unescape_char(token)) + { + return false; + } + + // Reset start position and continue. + start = m_position; + } + else if (ch >= CharType(0x0) && ch < CharType(0x20)) + { + return false; + } + + ch = JSON_StringParser::NextCharacter(); + } + + const size_t numChars = m_position - start - 1; + const size_t prevSize = token.string_val.size(); + token.string_val.resize(prevSize + numChars); + memcpy(const_cast(token.string_val.c_str() + prevSize), start, numChars * sizeof(CharType)); + + token.kind = JSON_Parser::Token::TKN_StringLiteral; + + return true; +} + +template +void JSON_Parser::GetNextToken(typename JSON_Parser::Token& result) +{ +try_again: + auto ch = EatWhitespace(); + + CreateToken(result, Token::TKN_EOF); + + if (ch == eof()) return; + + switch (ch) + { + case '{': + case '[': + { + if(++m_currentParsingDepth > JSON_Parser::maxParsingDepth) + { + SetErrorCode(result, json_error::nesting); + break; + } + + typename JSON_Parser::Token::Kind tk = ch == '{' ? Token::TKN_OpenBrace : Token::TKN_OpenBracket; + CreateToken(result, tk, result.start); + break; + } + case '}': + case ']': + { + if((signed int)(--m_currentParsingDepth) < 0) + { + SetErrorCode(result, json_error::mismatched_brances); + break; + } + + typename JSON_Parser::Token::Kind tk = ch == '}' ? Token::TKN_CloseBrace : Token::TKN_CloseBracket; + CreateToken(result, tk, result.start); + break; + } + case ',': + CreateToken(result, Token::TKN_Comma, result.start); + break; + + case ':': + CreateToken(result, Token::TKN_Colon, result.start); + break; + + case 't': + if (!CompleteKeywordTrue(result)) + { + SetErrorCode(result, json_error::malformed_literal); + } + break; + case 'f': + if (!CompleteKeywordFalse(result)) + { + SetErrorCode(result, json_error::malformed_literal); + } + break; + case 'n': + if (!CompleteKeywordNull(result)) + { + SetErrorCode(result, json_error::malformed_literal); + } + break; + case '/': + if (!CompleteComment(result)) + { + SetErrorCode(result, json_error::malformed_comment); + break; + } + // For now, we're ignoring comments. + goto try_again; + case '"': + if (!CompleteStringLiteral(result)) + { + SetErrorCode(result, json_error::malformed_string_literal); + } + break; + + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (!CompleteNumberLiteral(static_cast(ch), result)) + { + SetErrorCode(result, json_error::malformed_numeric_literal); + } + break; + default: + SetErrorCode(result, json_error::malformed_token); + break; + } +} + +template +std::unique_ptr JSON_Parser::_ParseObject(typename JSON_Parser::Token &tkn) +{ + auto obj = utility::details::make_unique(g_keep_json_object_unsorted); + auto& elems = obj->m_object.m_elements; + + GetNextToken(tkn); + if (tkn.m_error) goto error; + + if (tkn.kind != JSON_Parser::Token::TKN_CloseBrace) + { + while (true) + { + // State 1: New field or end of object, looking for field name or closing brace + std::basic_string fieldName; + switch (tkn.kind) + { + case JSON_Parser::Token::TKN_StringLiteral: + fieldName = std::move(tkn.string_val); + break; + default: + goto error; + } + + GetNextToken(tkn); + if (tkn.m_error) goto error; + + // State 2: Looking for a colon. + if (tkn.kind != JSON_Parser::Token::TKN_Colon) goto done; + + GetNextToken(tkn); + if (tkn.m_error) goto error; + + // State 3: Looking for an expression. +#ifdef ENABLE_JSON_VALUE_VISUALIZER + auto fieldValue = _ParseValue(tkn); + auto type = fieldValue->type(); + elems.emplace_back(utility::conversions::to_string_t(std::move(fieldName)), json::value(std::move(fieldValue), type)); +#else + elems.emplace_back(utility::conversions::to_string_t(std::move(fieldName)), json::value(_ParseValue(tkn))); +#endif + if (tkn.m_error) goto error; + + // State 4: Looking for a comma or a closing brace + switch (tkn.kind) + { + case JSON_Parser::Token::TKN_Comma: + GetNextToken(tkn); + if (tkn.m_error) goto error; + break; + case JSON_Parser::Token::TKN_CloseBrace: + goto done; + default: + goto error; + } + } + } + +done: + GetNextToken(tkn); + if (tkn.m_error) return utility::details::make_unique(); + + if (!g_keep_json_object_unsorted) { + ::std::sort(elems.begin(), elems.end(), json::object::compare_pairs); + } + + return std::move(obj); + +error: + if (!tkn.m_error) + { + SetErrorCode(tkn, json_error::malformed_object_literal); + } + return utility::details::make_unique(); +} + +template +std::unique_ptr JSON_Parser::_ParseArray(typename JSON_Parser::Token &tkn) +{ + GetNextToken(tkn); + if (tkn.m_error) return utility::details::make_unique(); + + auto result = utility::details::make_unique(); + + if (tkn.kind != JSON_Parser::Token::TKN_CloseBracket) + { + while (true) + { + // State 1: Looking for an expression. + result->m_array.m_elements.emplace_back(ParseValue(tkn)); + if (tkn.m_error) return utility::details::make_unique(); + + // State 4: Looking for a comma or a closing bracket + switch (tkn.kind) + { + case JSON_Parser::Token::TKN_Comma: + GetNextToken(tkn); + if (tkn.m_error) return utility::details::make_unique(); + break; + case JSON_Parser::Token::TKN_CloseBracket: + GetNextToken(tkn); + if (tkn.m_error) return utility::details::make_unique(); + return std::move(result); + default: + SetErrorCode(tkn, json_error::malformed_array_literal); + return utility::details::make_unique(); + } + } + } + + GetNextToken(tkn); + if (tkn.m_error) return utility::details::make_unique(); + + return std::move(result); +} + +template +std::unique_ptr JSON_Parser::_ParseValue(typename JSON_Parser::Token &tkn) +{ + switch (tkn.kind) + { + case JSON_Parser::Token::TKN_OpenBrace: + { + return _ParseObject(tkn); + } + case JSON_Parser::Token::TKN_OpenBracket: + { + return _ParseArray(tkn); + } + case JSON_Parser::Token::TKN_StringLiteral: + { + auto value = utility::details::make_unique(std::move(tkn.string_val), tkn.has_unescape_symbol); + GetNextToken(tkn); + if (tkn.m_error) return utility::details::make_unique(); + return std::move(value); + } + case JSON_Parser::Token::TKN_IntegerLiteral: + { + std::unique_ptr value; + if (tkn.signed_number) + value = utility::details::make_unique(tkn.int64_val); + else + value = utility::details::make_unique(tkn.uint64_val); + + GetNextToken(tkn); + if (tkn.m_error) return utility::details::make_unique(); + return std::move(value); + } + case JSON_Parser::Token::TKN_NumberLiteral: + { + auto value = utility::details::make_unique(tkn.double_val); + GetNextToken(tkn); + if (tkn.m_error) return utility::details::make_unique(); + return std::move(value); + } + case JSON_Parser::Token::TKN_BooleanLiteral: + { + auto value = utility::details::make_unique(tkn.boolean_val); + GetNextToken(tkn); + if (tkn.m_error) return utility::details::make_unique(); + return std::move(value); + } + case JSON_Parser::Token::TKN_NullLiteral: + { + GetNextToken(tkn); + // Returning a null value whether or not an error occurred. + return utility::details::make_unique(); + } + default: + { + SetErrorCode(tkn, json_error::malformed_token); + return utility::details::make_unique(); + } + } +} + +}}} + +static web::json::value _parse_stream(utility::istream_t &stream) +{ + web::json::details::JSON_StreamParser parser(stream); + web::json::details::JSON_Parser::Token tkn; + + parser.GetNextToken(tkn); + if (tkn.m_error) + { + web::json::details::CreateException(tkn, utility::conversions::to_string_t(tkn.m_error.message())); + } + + auto value = parser.ParseValue(tkn); + if (tkn.m_error) + { + web::json::details::CreateException(tkn, utility::conversions::to_string_t(tkn.m_error.message())); + } + else if (tkn.kind != web::json::details::JSON_Parser::Token::TKN_EOF) + { + web::json::details::CreateException(tkn, _XPLATSTR("Left-over characters in stream after parsing a JSON value")); + } + return value; +} + +static web::json::value _parse_stream(utility::istream_t &stream, std::error_code& error) +{ + web::json::details::JSON_StreamParser parser(stream); + web::json::details::JSON_Parser::Token tkn; + + parser.GetNextToken(tkn); + if (tkn.m_error) + { + error = std::move(tkn.m_error); + return web::json::value(); + } + + auto returnObject = parser.ParseValue(tkn); + if (tkn.kind != web::json::details::JSON_Parser::Token::TKN_EOF) + { + web::json::details::SetErrorCode(tkn, web::json::details::json_error::left_over_character_in_stream); + } + + error = std::move(tkn.m_error); + return returnObject; +} + +#ifdef _WIN32 +static web::json::value _parse_narrow_stream(std::istream &stream) +{ + web::json::details::JSON_StreamParser parser(stream); + web::json::details::JSON_StreamParser::Token tkn; + + parser.GetNextToken(tkn); + if (tkn.m_error) + { + web::json::details::CreateException(tkn, utility::conversions::to_string_t(tkn.m_error.message())); + } + + auto value = parser.ParseValue(tkn); + if (tkn.m_error) + { + web::json::details::CreateException(tkn, utility::conversions::to_string_t(tkn.m_error.message())); + } + else if (tkn.kind != web::json::details::JSON_Parser::Token::TKN_EOF) + { + web::json::details::CreateException(tkn, _XPLATSTR("Left-over characters in stream after parsing a JSON value")); + } + return value; +} + +static web::json::value _parse_narrow_stream(std::istream &stream, std::error_code& error) +{ + web::json::details::JSON_StreamParser parser(stream); + web::json::details::JSON_StreamParser::Token tkn; + + parser.GetNextToken(tkn); + if (tkn.m_error) + { + error = std::move(tkn.m_error); + return web::json::value(); + } + + auto returnObject = parser.ParseValue(tkn); + if (tkn.kind != web::json::details::JSON_Parser::Token::TKN_EOF) + { + returnObject = web::json::value(); + web::json::details::SetErrorCode(tkn, web::json::details::json_error::left_over_character_in_stream); + } + + error = std::move(tkn.m_error); + return returnObject; +} +#endif + +web::json::value web::json::value::parse(const utility::string_t& str) +{ + web::json::details::JSON_StringParser parser(str); + web::json::details::JSON_Parser::Token tkn; + + parser.GetNextToken(tkn); + if (tkn.m_error) + { + web::json::details::CreateException(tkn, utility::conversions::to_string_t(tkn.m_error.message())); + } + + auto value = parser.ParseValue(tkn); + if (tkn.m_error) + { + web::json::details::CreateException(tkn, utility::conversions::to_string_t(tkn.m_error.message())); + } + else if (tkn.kind != web::json::details::JSON_Parser::Token::TKN_EOF) + { + web::json::details::CreateException(tkn, _XPLATSTR("Left-over characters in stream after parsing a JSON value")); + } + return value; +} + +web::json::value web::json::value::parse(const utility::string_t& str, std::error_code& error) +{ + web::json::details::JSON_StringParser parser(str); + web::json::details::JSON_Parser::Token tkn; + + parser.GetNextToken(tkn); + if (tkn.m_error) + { + error = std::move(tkn.m_error); + return web::json::value(); + } + + auto returnObject = parser.ParseValue(tkn); + if (tkn.kind != web::json::details::JSON_Parser::Token::TKN_EOF) + { + returnObject = web::json::value(); + web::json::details::SetErrorCode(tkn, web::json::details::json_error::left_over_character_in_stream); + } + + error = std::move(tkn.m_error); + return returnObject; +} + +web::json::value web::json::value::parse(utility::istream_t &stream) +{ + return _parse_stream(stream); +} + +web::json::value web::json::value::parse(utility::istream_t &stream, std::error_code& error) +{ + return _parse_stream(stream, error); +} + +#ifdef _WIN32 +web::json::value web::json::value::parse(std::istream& stream) +{ + return _parse_narrow_stream(stream); +} + +web::json::value web::json::value::parse(std::istream& stream, std::error_code& error) +{ + return _parse_narrow_stream(stream, error); +} +#endif diff --git a/src/corehost/cli/json/casablanca/src/json/json_serialization.cpp b/src/corehost/cli/json/casablanca/src/json/json_serialization.cpp new file mode 100644 index 000000000..e99e0bc5a --- /dev/null +++ b/src/corehost/cli/json/casablanca/src/json/json_serialization.cpp @@ -0,0 +1,274 @@ +/*** +* ==++== +* +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* ==--== +* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +* +* HTTP Library: JSON parser and writer +* +* For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk +* +* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +****/ + +#include "stdafx.h" +#include + +#ifndef _WIN32 +#define __STDC_FORMAT_MACROS +#include +#endif + +using namespace web; +using namespace web::json; +using namespace utility; +using namespace utility::conversions; + +// +// JSON Serialization +// + +#ifdef _WIN32 +void web::json::value::serialize(std::ostream& stream) const +{ + // This has better performance than writing directly to stream. + std::string str; + m_value->serialize_impl(str); + stream << str; +} +void web::json::value::format(std::basic_string &string) const +{ + m_value->format(string); +} +#endif + +void web::json::value::serialize(utility::ostream_t &stream) const +{ +#ifndef _WIN32 + utility::details::scoped_c_thread_locale locale; +#endif + + // This has better performance than writing directly to stream. + utility::string_t str; + m_value->serialize_impl(str); + stream << str; +} + +void web::json::value::format(std::basic_string& string) const +{ + m_value->format(string); +} + +template +void web::json::details::append_escape_string(std::basic_string& str, const std::basic_string& escaped) +{ + for (const auto &ch : escaped) + { + switch (ch) + { + case '\"': + str += '\\'; + str += '\"'; + break; + case '\\': + str += '\\'; + str += '\\'; + break; + case '\b': + str += '\\'; + str += 'b'; + break; + case '\f': + str += '\\'; + str += 'f'; + break; + case '\r': + str += '\\'; + str += 'r'; + break; + case '\n': + str += '\\'; + str += 'n'; + break; + case '\t': + str += '\\'; + str += 't'; + break; + default: + + // If a control character then must unicode escaped. + if (ch >= 0 && ch <= 0x1F) + { + static const std::array intToHex = { { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' } }; + str += '\\'; + str += 'u'; + str += '0'; + str += '0'; + str += intToHex[(ch & 0xF0) >> 4]; + str += intToHex[ch & 0x0F]; + } + else + { + str += ch; + } + } + } +} + +void web::json::details::format_string(const utility::string_t& key, utility::string_t& str) +{ + str.push_back('"'); + append_escape_string(str, key); + str.push_back('"'); +} + +#ifdef _WIN32 +void web::json::details::format_string(const utility::string_t& key, std::string& str) +{ + str.push_back('"'); + append_escape_string(str, utility::conversions::to_utf8string(key)); + str.push_back('"'); +} +#endif + +void web::json::details::_String::format(std::basic_string& str) const +{ + str.push_back('"'); + + if(m_has_escape_char) + { + append_escape_string(str, utility::conversions::to_utf8string(m_string)); + } + else + { + str.append(utility::conversions::to_utf8string(m_string)); + } + + str.push_back('"'); +} + +void web::json::details::_Number::format(std::basic_string& stream) const +{ + if(m_number.m_type != number::type::double_type) + { + // #digits + 1 to avoid loss + 1 for the sign + 1 for null terminator. + const size_t tempSize = std::numeric_limits::digits10 + 3; + char tempBuffer[tempSize]; + +#ifdef _WIN32 + // This can be improved performance-wise if we implement our own routine + if (m_number.m_type == number::type::signed_type) + _i64toa_s(m_number.m_intval, tempBuffer, tempSize, 10); + else + _ui64toa_s(m_number.m_uintval, tempBuffer, tempSize, 10); + + const auto numChars = strnlen_s(tempBuffer, tempSize); +#else + int numChars; + if (m_number.m_type == number::type::signed_type) + numChars = snprintf(tempBuffer, tempSize, "%" PRId64, m_number.m_intval); + else + numChars = snprintf(tempBuffer, tempSize, "%" PRIu64, m_number.m_uintval); +#endif + stream.append(tempBuffer, numChars); + } + else + { + // #digits + 2 to avoid loss + 1 for the sign + 1 for decimal point + 5 for exponent (e+xxx) + 1 for null terminator + const size_t tempSize = std::numeric_limits::digits10 + 10; + char tempBuffer[tempSize]; +#ifdef _WIN32 + const auto numChars = _sprintf_s_l( + tempBuffer, + tempSize, + "%.*g", + utility::details::scoped_c_thread_locale::c_locale(), + std::numeric_limits::digits10 + 2, + m_number.m_value); +#else + const auto numChars = snprintf(tempBuffer, tempSize, "%.*g", std::numeric_limits::digits10 + 2, m_number.m_value); +#endif + stream.append(tempBuffer, numChars); + } +} + +#ifdef _WIN32 + +void web::json::details::_String::format(std::basic_string& str) const +{ + str.push_back(L'"'); + + if(m_has_escape_char) + { + append_escape_string(str, m_string); + } + else + { + str.append(m_string); + } + + str.push_back(L'"'); +} + +void web::json::details::_Number::format(std::basic_string& stream) const +{ + if(m_number.m_type != number::type::double_type) + { + // #digits + 1 to avoid loss + 1 for the sign + 1 for null terminator. + const size_t tempSize = std::numeric_limits::digits10 + 3; + wchar_t tempBuffer[tempSize]; + + if (m_number.m_type == number::type::signed_type) + _i64tow_s(m_number.m_intval, tempBuffer, tempSize, 10); + else + _ui64tow_s(m_number.m_uintval, tempBuffer, tempSize, 10); + + stream.append(tempBuffer, wcsnlen_s(tempBuffer, tempSize)); + } + else + { + // #digits + 2 to avoid loss + 1 for the sign + 1 for decimal point + 5 for exponent (e+xxx) + 1 for null terminator + const size_t tempSize = std::numeric_limits::digits10 + 10; + wchar_t tempBuffer[tempSize]; + const int numChars = _swprintf_s_l( + tempBuffer, + tempSize, + L"%.*g", + utility::details::scoped_c_thread_locale::c_locale(), + std::numeric_limits::digits10 + 2, + m_number.m_value); + stream.append(tempBuffer, numChars); + } +} + +#endif + +const utility::string_t & web::json::details::_String::as_string() const +{ + return m_string; +} + +const utility::string_t & web::json::value::as_string() const +{ + return m_value->as_string(); +} + +utility::string_t json::value::serialize() const +{ +#ifndef _WIN32 + utility::details::scoped_c_thread_locale locale; +#endif + return m_value->to_string(); +} diff --git a/src/corehost/cli/json/casablanca/src/utilities/asyncrt_utils.cpp b/src/corehost/cli/json/casablanca/src/utilities/asyncrt_utils.cpp new file mode 100644 index 000000000..87a0d8957 --- /dev/null +++ b/src/corehost/cli/json/casablanca/src/utilities/asyncrt_utils.cpp @@ -0,0 +1,496 @@ +/*** +* ==++== +* +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* ==--== +* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +* +* Utilities +* +* For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk +* +* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +****/ + +#include "stdafx.h" + +#ifndef _WIN32 +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-local-typedef" +#endif +#if defined(__clang__) +#pragma clang diagnostic pop +#endif +#endif + +// Could use C++ standard library if not __GLIBCXX__, +// For testing purposes we just the handwritten on all platforms. +#if defined(CPPREST_STDLIB_UNICODE_CONVERSIONS) +#include +#endif + +using namespace web; +using namespace utility; +using namespace utility::conversions; + +namespace utility +{ +namespace details +{ + +#if !defined(ANDROID) && !defined(__ANDROID__) +std::once_flag g_c_localeFlag; +std::unique_ptr g_c_locale(nullptr, [](scoped_c_thread_locale::xplat_locale *){}); +scoped_c_thread_locale::xplat_locale scoped_c_thread_locale::c_locale() +{ + std::call_once(g_c_localeFlag, [&]() + { + scoped_c_thread_locale::xplat_locale *clocale = new scoped_c_thread_locale::xplat_locale(); +#ifdef _WIN32 + *clocale = _create_locale(LC_ALL, "C"); + if (*clocale == nullptr) + { + throw std::runtime_error("Unable to create 'C' locale."); + } + auto deleter = [](scoped_c_thread_locale::xplat_locale *clocale) + { + _free_locale(*clocale); + delete clocale; + }; +#else + *clocale = newlocale(LC_ALL, "C", nullptr); + if (*clocale == nullptr) + { + throw std::runtime_error("Unable to create 'C' locale."); + } + auto deleter = [](scoped_c_thread_locale::xplat_locale *clocale) + { + freelocale(*clocale); + delete clocale; + }; +#endif + g_c_locale = std::unique_ptr(clocale, deleter); + }); + return *g_c_locale; +} +#endif + +#ifdef _WIN32 +scoped_c_thread_locale::scoped_c_thread_locale() + : m_prevLocale(), m_prevThreadSetting(-1) +{ + char *prevLocale = setlocale(LC_ALL, nullptr); + if (prevLocale == nullptr) + { + throw std::runtime_error("Unable to retrieve current locale."); + } + + if (std::strcmp(prevLocale, "C") != 0) + { + m_prevLocale = prevLocale; + m_prevThreadSetting = _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); + if (m_prevThreadSetting == -1) + { + throw std::runtime_error("Unable to enable per thread locale."); + } + if (setlocale(LC_ALL, "C") == nullptr) + { + _configthreadlocale(m_prevThreadSetting); + throw std::runtime_error("Unable to set locale"); + } + } +} + +scoped_c_thread_locale::~scoped_c_thread_locale() +{ + if (m_prevThreadSetting != -1) + { + setlocale(LC_ALL, m_prevLocale.c_str()); + _configthreadlocale(m_prevThreadSetting); + } +} +#elif (defined(ANDROID) || defined(__ANDROID__)) +scoped_c_thread_locale::scoped_c_thread_locale() {} +scoped_c_thread_locale::~scoped_c_thread_locale() {} +#else +scoped_c_thread_locale::scoped_c_thread_locale() + : m_prevLocale(nullptr) +{ + char *prevLocale = setlocale(LC_ALL, nullptr); + if (prevLocale == nullptr) + { + throw std::runtime_error("Unable to retrieve current locale."); + } + + if (std::strcmp(prevLocale, "C") != 0) + { + m_prevLocale = uselocale(c_locale()); + if (m_prevLocale == nullptr) + { + throw std::runtime_error("Unable to set locale"); + } + } +} + +scoped_c_thread_locale::~scoped_c_thread_locale() +{ + if (m_prevLocale != nullptr) + { + uselocale(m_prevLocale); + } +} +#endif +} + +namespace details +{ + +const std::error_category & __cdecl platform_category() +{ +#ifdef _WIN32 + return windows_category(); +#else + return linux_category(); +#endif +} + +#ifdef _WIN32 + +// Remove once VS 2013 is no longer supported. +#if _MSC_VER < 1900 +static details::windows_category_impl instance; +#endif +const std::error_category & __cdecl windows_category() +{ +#if _MSC_VER >= 1900 + static details::windows_category_impl instance; +#endif + return instance; +} + +std::string windows_category_impl::message(int errorCode) const CPPREST_NOEXCEPT +{ + const size_t buffer_size = 4096; + DWORD dwFlags = FORMAT_MESSAGE_FROM_SYSTEM; + LPCVOID lpSource = NULL; + +#if !defined(__cplusplus_winrt) + if (errorCode >= 12000) + { + dwFlags = FORMAT_MESSAGE_FROM_HMODULE; + lpSource = GetModuleHandleA("winhttp.dll"); // this handle DOES NOT need to be freed + } +#endif + + std::wstring buffer; + buffer.resize(buffer_size); + + const auto result = ::FormatMessageW( + dwFlags, + lpSource, + errorCode, + 0, + &buffer[0], + buffer_size, + NULL); + if (result == 0) + { + std::ostringstream os; + os << "Unable to get an error message for error code: " << errorCode << "."; + return os.str(); + } + + return utility::conversions::to_utf8string(buffer); +} + +std::error_condition windows_category_impl::default_error_condition(int errorCode) const CPPREST_NOEXCEPT +{ + // First see if the STL implementation can handle the mapping for common cases. + const std::error_condition errCondition = std::system_category().default_error_condition(errorCode); + const std::string errConditionMsg = errCondition.message(); + if(_stricmp(errConditionMsg.c_str(), "unknown error") != 0) + { + return errCondition; + } + + switch(errorCode) + { +#ifndef __cplusplus_winrt + case ERROR_WINHTTP_TIMEOUT: + return std::errc::timed_out; + case ERROR_WINHTTP_CANNOT_CONNECT: + return std::errc::host_unreachable; + case ERROR_WINHTTP_CONNECTION_ERROR: + return std::errc::connection_aborted; +#endif + case INET_E_RESOURCE_NOT_FOUND: + case INET_E_CANNOT_CONNECT: + return std::errc::host_unreachable; + case INET_E_CONNECTION_TIMEOUT: + return std::errc::timed_out; + case INET_E_DOWNLOAD_FAILURE: + return std::errc::connection_aborted; + default: + break; + } + + return std::error_condition(errorCode, *this); +} + +#else + +const std::error_category & __cdecl linux_category() +{ + // On Linux we are using boost error codes which have the exact same + // mapping and are equivalent with std::generic_category error codes. + return std::generic_category(); +} + +#endif + +} + +#define LOW_3BITS 0x7 +#define LOW_4BITS 0xF +#define LOW_5BITS 0x1F +#define LOW_6BITS 0x3F +#define BIT4 0x8 +#define BIT5 0x10 +#define BIT6 0x20 +#define BIT7 0x40 +#define BIT8 0x80 +#define L_SURROGATE_START 0xDC00 +#define L_SURROGATE_END 0xDFFF +#define H_SURROGATE_START 0xD800 +#define H_SURROGATE_END 0xDBFF +#define SURROGATE_PAIR_START 0x10000 + +utf16string __cdecl conversions::utf8_to_utf16(const std::string &s) +{ +#if defined(CPPREST_STDLIB_UNICODE_CONVERSIONS) + std::wstring_convert, utf16char> conversion; + return conversion.from_bytes(src); +#else + utf16string dest; + // Save repeated heap allocations, use less than source string size assuming some + // of the characters are not just ASCII and collapse. + dest.reserve(static_cast(static_cast(s.size()) * .70)); + + for (auto src = s.begin(); src != s.end(); ++src) + { + if ((*src & BIT8) == 0) // single byte character, 0x0 to 0x7F + { + dest.push_back(utf16string::value_type(*src)); + } + else + { + unsigned char numContBytes = 0; + uint32_t codePoint; + if ((*src & BIT7) == 0) + { + throw std::range_error("UTF-8 string character can never start with 10xxxxxx"); + } + else if ((*src & BIT6) == 0) // 2 byte character, 0x80 to 0x7FF + { + codePoint = *src & LOW_5BITS; + numContBytes = 1; + } + else if ((*src & BIT5) == 0) // 3 byte character, 0x800 to 0xFFFF + { + codePoint = *src & LOW_4BITS; + numContBytes = 2; + } + else if ((*src & BIT4) == 0) // 4 byte character, 0x10000 to 0x10FFFF + { + codePoint = *src & LOW_3BITS; + numContBytes = 3; + } + else + { + throw std::range_error("UTF-8 string has invalid Unicode code point"); + } + + for (unsigned char i = 0; i < numContBytes; ++i) + { + if (++src == s.end()) + { + throw std::range_error("UTF-8 string is missing bytes in character"); + } + if ((*src & BIT8) == 0 || (*src & BIT7) != 0) + { + throw std::range_error("UTF-8 continuation byte is missing leading byte"); + } + codePoint <<= 6; + codePoint |= *src & LOW_6BITS; + } + + if (codePoint >= SURROGATE_PAIR_START) + { + // In UTF-16 U+10000 to U+10FFFF are represented as two 16-bit code units, surrogate pairs. + // - 0x10000 is subtracted from the code point + // - high surrogate is 0xD800 added to the top ten bits + // - low surrogate is 0xDC00 added to the low ten bits + codePoint -= SURROGATE_PAIR_START; + dest.push_back(utf16string::value_type((codePoint >> 10) | H_SURROGATE_START)); + dest.push_back(utf16string::value_type((codePoint & 0x3FF) | L_SURROGATE_START)); + } + else + { + // In UTF-16 U+0000 to U+D7FF and U+E000 to U+FFFF are represented exactly as the Unicode code point value. + // U+D800 to U+DFFF are not valid characters, for simplicity we assume they are not present but will encode + // them if encountered. + dest.push_back(utf16string::value_type(codePoint)); + } + } + } + return dest; +#endif +} + +std::string __cdecl conversions::utf16_to_utf8(const utf16string &w) +{ + #if defined(CPPREST_STDLIB_UNICODE_CONVERSIONS) + std::wstring_convert, utf16char> conversion; + return conversion.to_bytes(w); + #else + std::string dest; + dest.reserve(w.size()); + for (auto src = w.begin(); src != w.end(); ++src) + { + // Check for high surrogate. + if (*src >= H_SURROGATE_START && *src <= H_SURROGATE_END) + { + const auto highSurrogate = *src++; + if (src == w.end()) + { + throw std::range_error("UTF-16 string is missing low surrogate"); + } + const auto lowSurrogate = *src; + if (lowSurrogate < L_SURROGATE_START || lowSurrogate > L_SURROGATE_END) + { + throw std::range_error("UTF-16 string has invalid low surrogate"); + } + + // To get from surrogate pair to Unicode code point: + // - subract 0xD800 from high surrogate, this forms top ten bits + // - subract 0xDC00 from low surrogate, this forms low ten bits + // - add 0x10000 + // Leaves a code point in U+10000 to U+10FFFF range. + uint32_t codePoint = highSurrogate - H_SURROGATE_START; + codePoint <<= 10; + codePoint |= lowSurrogate - L_SURROGATE_START; + codePoint += SURROGATE_PAIR_START; + + // 4 bytes need using 21 bits + dest.push_back(char((codePoint >> 18) | 0xF0)); // leading 3 bits + dest.push_back(char(((codePoint >> 12) & LOW_6BITS) | BIT8)); // next 6 bits + dest.push_back(char(((codePoint >> 6) & LOW_6BITS) | BIT8)); // next 6 bits + dest.push_back(char((codePoint & LOW_6BITS) | BIT8)); // trailing 6 bits + } + else + { + if (*src <= 0x7F) // single byte character + { + dest.push_back(static_cast(*src)); + } + else if (*src <= 0x7FF) // 2 bytes needed (11 bits used) + { + dest.push_back(char((*src >> 6) | 0xC0)); // leading 5 bits + dest.push_back(char((*src & LOW_6BITS) | BIT8)); // trailing 6 bits + } + else // 3 bytes needed (16 bits used) + { + dest.push_back(char((*src >> 12) | 0xE0)); // leading 4 bits + dest.push_back(char(((*src >> 6) & LOW_6BITS) | BIT8)); // middle 6 bits + dest.push_back(char((*src & LOW_6BITS) | BIT8)); // trailing 6 bits + } + } + } + + return dest; + #endif +} + +utf16string __cdecl conversions::usascii_to_utf16(const std::string &s) +{ + // Ascii is a subset of UTF-8 so just convert to UTF-16 + return utf8_to_utf16(s); +} + +utf16string __cdecl conversions::latin1_to_utf16(const std::string &s) +{ + // Latin1 is the first 256 code points in Unicode. + // In UTF-16 encoding each of these is represented as exactly the numeric code point. + utf16string dest; + dest.resize(s.size()); + for (size_t i = 0; i < s.size(); ++i) + { + dest[i] = utf16char(s[i]); + } + return dest; +} + +utf8string __cdecl conversions::latin1_to_utf8(const std::string &s) +{ + return utf16_to_utf8(latin1_to_utf16(s)); +} + +utility::string_t __cdecl conversions::to_string_t(utf16string &&s) +{ +#ifdef _UTF16_STRINGS + return std::move(s); +#else + return utf16_to_utf8(std::move(s)); +#endif +} + +utility::string_t __cdecl conversions::to_string_t(std::string &&s) +{ +#ifdef _UTF16_STRINGS + return utf8_to_utf16(std::move(s)); +#else + return std::move(s); +#endif +} + +utility::string_t __cdecl conversions::to_string_t(const utf16string &s) +{ +#ifdef _UTF16_STRINGS + return s; +#else + return utf16_to_utf8(s); +#endif +} + +utility::string_t __cdecl conversions::to_string_t(const std::string &s) +{ +#ifdef _UTF16_STRINGS + return utf8_to_utf16(s); +#else + return s; +#endif +} + +std::string __cdecl conversions::to_utf8string(std::string value) { return std::move(value); } + +std::string __cdecl conversions::to_utf8string(const utf16string &value) { return utf16_to_utf8(value); } + +utf16string __cdecl conversions::to_utf16string(const std::string &value) { return utf8_to_utf16(value); } + +utf16string __cdecl conversions::to_utf16string(utf16string value) { return std::move(value); } + +static bool is_digit(utility::char_t c) { return c >= _XPLATSTR('0') && c <= _XPLATSTR('9'); } + +} diff --git a/src/corehost/cli/libhost.cpp b/src/corehost/cli/libhost.cpp new file mode 100644 index 000000000..4537554da --- /dev/null +++ b/src/corehost/cli/libhost.cpp @@ -0,0 +1,62 @@ +// 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 "pal.h" +#include "utils.h" +#include "trace.h" +#include "libhost.h" + +pal::string_t get_runtime_config_from_file(const pal::string_t& file) +{ + auto name = get_filename_without_ext(file); + name = name.substr(0, name.find(_X('.'))); + auto json_name = name + _X(".runtimeconfig.json"); + auto json_path = get_directory(file); + + append_path(&json_path, json_name.c_str()); + if (pal::file_exists(json_path)) + { + return json_path; + } + return pal::string_t(); +} + +host_mode_t detect_operating_mode(const int argc, const pal::char_t* argv[], pal::string_t* p_own_dir) +{ + pal::string_t own_path; + if (!pal::get_own_executable_path(&own_path) || !pal::realpath(&own_path)) + { + trace::error(_X("Failed to locate current executable")); + return host_mode_t::invalid; + } + + pal::string_t own_name = get_filename(own_path); + pal::string_t own_dir = get_directory(own_path); + if (p_own_dir) + { + p_own_dir->assign(own_dir); + } + + pal::string_t own_dll_filename = strip_file_ext(own_name) + _X(".dll"); + pal::string_t own_dll = own_dir; + append_path(&own_dll, own_dll_filename.c_str()); + trace::info(_X("Exists %s"), own_dll.c_str()); + if (coreclr_exists_in_dir(own_dir) || pal::file_exists(own_dll)) + { + pal::string_t own_deps_json = own_dir; + pal::string_t own_deps_filename = strip_file_ext(own_name) + _X(".deps.json"); + pal::string_t own_config_filename = strip_file_ext(own_name) + _X(".runtimeconfig.json"); + append_path(&own_deps_json, own_deps_filename.c_str()); + if (trace::is_enabled()) + { + trace::info(_X("Detecting mode... CoreCLR present in own dir [%s] and checking if [%s] file present=[%d]"), + own_dir.c_str(), own_deps_filename.c_str(), pal::file_exists(own_deps_json)); + } + return ((pal::file_exists(own_deps_json) || !pal::file_exists(own_config_filename)) && pal::file_exists(own_dll)) ? host_mode_t::standalone : host_mode_t::split_fx; + } + else + { + return host_mode_t::muxer; + } +} + diff --git a/src/corehost/cli/libhost.h b/src/corehost/cli/libhost.h index 0b8ea823a..d4e587e62 100644 --- a/src/corehost/cli/libhost.h +++ b/src/corehost/cli/libhost.h @@ -1,4 +1,70 @@ // 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 __LIBHOST_H__ +#define __LIBHOST_H__ + #define LIBHOST_NAME MAKE_LIBNAME("hostpolicy") + +enum host_mode_t +{ + invalid = 0, + muxer, + standalone, + split_fx +}; + +class runtime_config_t; + +class corehost_init_t +{ + const pal::string_t m_probe_path; + const pal::string_t m_deps_file; + const pal::string_t m_fx_dir; + host_mode_t m_host_mode; + const runtime_config_t* m_runtime_config; +public: + corehost_init_t( + const pal::string_t& deps_file, + const pal::string_t& probe_path, + const pal::string_t& fx_dir, + const host_mode_t mode, + const runtime_config_t* runtime_config) + : m_fx_dir(fx_dir) + , m_runtime_config(runtime_config) + , m_deps_file(deps_file) + , m_probe_path(probe_path) + , m_host_mode(mode) + { + } + + const host_mode_t host_mode() const + { + return m_host_mode; + } + + const pal::string_t& deps_file() const + { + return m_deps_file; + } + + const pal::string_t& probe_dir() const + { + return m_probe_path; + } + + const pal::string_t& fx_dir() const + { + return m_fx_dir; + } + + const runtime_config_t* runtime_config() const + { + return m_runtime_config; + } +}; + +pal::string_t get_runtime_config_from_file(const pal::string_t& file); +host_mode_t detect_operating_mode(const int argc, const pal::char_t* argv[], pal::string_t* own_dir = nullptr); + +#endif // __LIBHOST_H__ diff --git a/src/corehost/cli/runtime_config.cpp b/src/corehost/cli/runtime_config.cpp new file mode 100644 index 000000000..e27c5a3a0 --- /dev/null +++ b/src/corehost/cli/runtime_config.cpp @@ -0,0 +1,114 @@ +// 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 "pal.h" +#include "utils.h" +#include "cpprest/json.h" +#include "runtime_config.h" +#include + +typedef web::json::value json_value; + +runtime_config_t::runtime_config_t(const pal::string_t& path) + : m_fx_roll_fwd(true) + , m_path(path) + , m_portable(false) + , m_gc_server(_X("0")) +{ + m_valid = ensure_parsed(); +} + +void parse_fx(const json_value& opts, pal::string_t* name, pal::string_t* version, bool* roll_fwd, bool* portable) +{ + name->clear(); + version->clear(); + *roll_fwd = true; + *portable = false; + + if (opts.is_null()) + { + return; + } + + const auto& opts_obj = opts.as_object(); + auto framework = opts_obj.find(_X("framework")); + if (framework == opts_obj.end()) + { + return; + } + + *portable = true; + + const auto& fx_obj = framework->second.as_object(); + *name = fx_obj.at(_X("name")).as_string(); + *version = fx_obj.at(_X("version")).as_string(); + + auto value = fx_obj.find(_X("rollForward")); + if (value == fx_obj.end()) + { + return; + } + + *roll_fwd = value->second.as_bool(); +} + +bool runtime_config_t::ensure_parsed() +{ + pal::string_t retval; + if (!pal::file_exists(m_path)) + { + // Not existing is not an error. + return true; + } + + pal::ifstream_t file(m_path); + if (!file.good()) + { + return false; + } + + try + { + const auto root = json_value::parse(file); + const auto& json = root.as_object(); + const auto iter = json.find(_X("runtimeOptions")); + if (iter != json.end()) + { + parse_fx(iter->second, &m_fx_name, &m_fx_ver, &m_fx_roll_fwd, &m_portable); + } + } + catch (...) + { + return false; + } + return true; +} + +const pal::string_t& runtime_config_t::get_gc_server() const +{ + assert(m_valid); + return m_gc_server; +} + +const pal::string_t& runtime_config_t::get_fx_name() const +{ + assert(m_valid); + return m_fx_name; +} + +const pal::string_t& runtime_config_t::get_fx_version() const +{ + assert(m_valid); + return m_fx_ver; +} + +bool runtime_config_t::get_fx_roll_fwd() const +{ + assert(m_valid); + return m_fx_roll_fwd; +} + +bool runtime_config_t::get_portable() const +{ + return m_portable; +} diff --git a/src/corehost/cli/runtime_config.h b/src/corehost/cli/runtime_config.h new file mode 100644 index 000000000..bdb14fda1 --- /dev/null +++ b/src/corehost/cli/runtime_config.h @@ -0,0 +1,29 @@ +// 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 "pal.h" + +class runtime_config_t +{ +public: + runtime_config_t(const pal::string_t& path); + bool is_valid() { return m_valid; } + const pal::string_t& get_path() { return m_path; } + const pal::string_t& get_gc_server() const; + const pal::string_t& get_fx_version() const; + const pal::string_t& get_fx_name() const; + bool get_fx_roll_fwd() const; + bool get_portable() const; + +private: + bool ensure_parsed(); + + pal::string_t m_gc_server; + pal::string_t m_fx_name; + pal::string_t m_fx_ver; + bool m_fx_roll_fwd; + + pal::string_t m_path; + bool m_portable; + bool m_valid; +}; diff --git a/src/corehost/cli/servicing_index.cpp b/src/corehost/cli/servicing_index.cpp index 687006ff4..8dd9ebd76 100644 --- a/src/corehost/cli/servicing_index.cpp +++ b/src/corehost/cli/servicing_index.cpp @@ -38,18 +38,33 @@ bool servicing_index_t::find_redirection( 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)) + pal::string_t ni_root = m_patch_root; + append_path(&ni_root, get_arch()); + + // First prefer the architecture specific NI image. + pal::string_t paths[2] = { ni_root, m_patch_root }; + for (pal::string_t& full_path : paths) { - *redirection = full_path; - trace::verbose(_X("Servicing %s with %s"), stream.str().c_str(), redirection->c_str()); - return true; + append_path(&full_path, iter->second.c_str()); + if (pal::file_exists(full_path)) + { + *redirection = full_path; + if (trace::is_enabled()) + { + pal::string_t stream_str = stream.str(); + 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("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()); + if (trace::is_enabled()) + { + auto stream_str = stream.str(); + trace::verbose(_X("Entry %s not serviced or file doesn't exist"), stream_str.c_str()); + } return false; } @@ -112,7 +127,8 @@ void servicing_index_t::ensure_redirections() if (trace::is_enabled()) { - trace::verbose(_X("Adding servicing entry %s => %s"), sstream.str().c_str(), str.substr(from).c_str()); + auto stream_str = sstream.str(); + trace::verbose(_X("Adding servicing entry %s => %s"), stream_str.c_str(), str.substr(from).c_str()); } // Store just the filename. diff --git a/src/corehost/cli/setup.cmake b/src/corehost/cli/setup.cmake index ef2028035..d3e65f55d 100644 --- a/src/corehost/cli/setup.cmake +++ b/src/corehost/cli/setup.cmake @@ -33,4 +33,20 @@ if(WIN32) set(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO} /DEBUG /OPT:REF /OPT:ICF") set(CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO} /DEBUG /OPT:REF /OPT:ICF") set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO} /DEBUG /OPT:REF /OPT:ICF") +else() + add_compile_options(-Wno-unused-local-typedef) +endif() + +if(CLI_CMAKE_PLATFORM_ARCH_I386) + add_definitions(-D_TARGET_X86_=1) +elseif(CLI_CMAKE_PLATFORM_ARCH_AMD64) + add_definitions(-D_TARGET_AMD64_=1) +else() + message(FATAL_ERROR "Unknown target architecture") +endif() + +if(${CLI_CMAKE_RUNTIME_ID} STREQUAL "") + message(FATAL_ERROR "Runtime ID not specified") +else() + add_definitions(-DTARGET_RUNTIME_ID="${CLI_CMAKE_RUNTIME_ID}") endif() diff --git a/src/corehost/common/pal.h b/src/corehost/common/pal.h index 930104d34..dfd76b3a6 100644 --- a/src/corehost/common/pal.h +++ b/src/corehost/common/pal.h @@ -15,10 +15,12 @@ #include #include #include +#include #if defined(_WIN32) -#include +#define NOMINMAX +#include #define HOST_EXE_NAME L"corehost.exe" #define xerr std::wcerr @@ -93,11 +95,17 @@ namespace pal typedef HMODULE dll_t; typedef FARPROC proc_t; + pal::string_t to_string(int value); + + bool getcwd(pal::string_t* recv); + inline int strcmp(const char_t* str1, const char_t* str2) { return ::wcscmp(str1, str2); } inline int strcasecmp(const char_t* str1, const char_t* str2) { return ::_wcsicmp(str1, str2); } inline int strncmp(const char_t* str1, const char_t* str2, int len) { return ::wcsncmp(str1, str2, len); } inline int strncasecmp(const char_t* str1, const char_t* str2, int len) { return ::_wcsnicmp(str1, str2, len); } + pal::string_t to_lower(const pal::string_t& in); + inline size_t strlen(const char_t* str) { return ::wcslen(str); } inline void err_vprintf(const char_t* format, va_list vl) { ::vfwprintf(stderr, format, vl); ::fputws(_X("\r\n"), stderr); } @@ -126,10 +134,17 @@ namespace pal typedef void* dll_t; typedef void* proc_t; + pal::string_t to_string(int value); + + bool getcwd(pal::string_t* recv); + inline int strcmp(const char_t* str1, const char_t* str2) { return ::strcmp(str1, str2); } inline int strcasecmp(const char_t* str1, const char_t* str2) { return ::strcasecmp(str1, str2); } inline int strncmp(const char_t* str1, const char_t* str2, int len) { return ::strncmp(str1, str2, len); } inline int strncasecmp(const char_t* str1, const char_t* str2, int len) { return ::strncasecmp(str1, str2, len); } + + pal::string_t to_lower(const pal::string_t& in); + inline size_t strlen(const char_t* str) { return ::strlen(str); } 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; } diff --git a/src/corehost/common/pal.unix.cpp b/src/corehost/common/pal.unix.cpp index b060afcc0..192ac3a87 100644 --- a/src/corehost/common/pal.unix.cpp +++ b/src/corehost/common/pal.unix.cpp @@ -13,6 +13,8 @@ #include #include +#include + #if defined(__APPLE__) #include #endif @@ -23,6 +25,33 @@ #define symlinkEntrypointExecutable "/proc/curproc/exe" #endif +pal::string_t pal::to_string(int value) { return std::to_string(value); } + +pal::string_t pal::to_lower(const pal::string_t& in) +{ + pal::string_t ret = in; + std::transform(ret.begin(), ret.end(), ret.begin(), ::tolower); + return ret; +} + +bool pal::getcwd(pal::string_t* recv) +{ + recv->clear(); + pal::char_t* buf = ::getcwd(nullptr, PATH_MAX + 1); + if (buf == nullptr) + { + if (errno == ENOENT) + { + return false; + } + perror("getcwd()"); + return false; + } + recv->assign(buf); + ::free(buf); + return true; +} + bool pal::find_coreclr(pal::string_t* recv) { pal::string_t candidate; @@ -146,8 +175,7 @@ bool pal::getenv(const pal::char_t* name, pal::string_t* recv) 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(), nullptr); if (resolved == nullptr) { if (errno == ENOENT) @@ -158,6 +186,7 @@ bool pal::realpath(pal::string_t* path) return false; } path->assign(resolved); + ::free(resolved); return true; } @@ -186,6 +215,7 @@ void pal::readdir(const pal::string_t& path, std::vector* list) // We are interested in files only switch (entry->d_type) { + case DT_DIR: case DT_REG: break; diff --git a/src/corehost/common/pal.windows.cpp b/src/corehost/common/pal.windows.cpp index 8a7a9a960..fc03c40ed 100644 --- a/src/corehost/common/pal.windows.cpp +++ b/src/corehost/common/pal.windows.cpp @@ -11,6 +11,18 @@ static std::wstring_convert, wchar_t> g_converter; +pal::string_t pal::to_lower(const pal::string_t& in) +{ + pal::string_t ret = in; + std::transform(ret.begin(), ret.end(), ret.begin(), ::towlower); + return ret; +} + +pal::string_t pal::to_string(int value) +{ + return std::to_wstring(value); +} + bool pal::find_coreclr(pal::string_t* recv) { pal::string_t candidate; @@ -42,6 +54,35 @@ bool pal::find_coreclr(pal::string_t* recv) return false; } + +bool pal::getcwd(pal::string_t* recv) +{ + recv->clear(); + + pal::char_t buf[MAX_PATH]; + DWORD result = GetCurrentDirectoryW(MAX_PATH, buf); + if (result < MAX_PATH) + { + recv->assign(buf); + return true; + } + else if (result != 0) + { + std::vector str; + str.resize(result); + result = GetCurrentDirectoryW(str.size(), str.data()); + assert(result <= str.size()); + if (result != 0) + { + recv->assign(str.data()); + return true; + } + } + assert(result == 0); + trace::error(_X("Failed to obtain working directory, HRESULT: 0x%X"), HRESULT_FROM_WIN32(GetLastError())); + return false; +} + bool pal::load_library(const char_t* path, dll_t* dll) { *dll = ::LoadLibraryW(path); diff --git a/src/corehost/common/utils.cpp b/src/corehost/common/utils.cpp index 2515b2155..1587e7df5 100644 --- a/src/corehost/common/utils.cpp +++ b/src/corehost/common/utils.cpp @@ -55,16 +55,43 @@ pal::string_t get_executable(const pal::string_t& filename) return result; } -pal::string_t get_filename(const pal::string_t& path) +pal::string_t strip_file_ext(const pal::string_t& path) { - // Find the last dir separator - auto path_sep = path.find_last_of(DIR_SEPARATOR); - if (path_sep == pal::string_t::npos) + if (path.empty()) { - return pal::string_t(path); + return path; + } + return path.substr(0, path.rfind(_X('.'))); +} + +pal::string_t get_filename_without_ext(const pal::string_t& path) +{ + if (path.empty()) + { + return path; } - return path.substr(path_sep + 1); + size_t name_pos = path.find_last_of(_X("/\\")); + size_t dot_pos = path.rfind(_X('.')); + size_t start_pos = (name_pos == pal::string_t::npos) ? 0 : (name_pos + 1); + size_t count = (dot_pos == pal::string_t::npos || dot_pos < start_pos) ? pal::string_t::npos : (dot_pos - start_pos); + return path.substr(start_pos, count); +} + +pal::string_t get_filename(const pal::string_t& path) +{ + if (path.empty()) + { + return path; + } + + auto name_pos = path.find_last_of(DIR_SEPARATOR); + if (name_pos == pal::string_t::npos) + { + return path; + } + + return path.substr(name_pos + 1); } pal::string_t get_directory(const pal::string_t& path) @@ -87,3 +114,49 @@ void replace_char(pal::string_t* path, pal::char_t match, pal::char_t repl) (*path)[pos] = repl; } } + +const pal::char_t* get_arch() +{ +#if _TARGET_AMD64_ + return _X("x64"); +#elif _TARGET_X86_ + return _X("x86"); +#else +#error "Unknown target" +#endif +} + +bool parse_known_args( + const int argc, + const pal::char_t* argv[], + const std::vector& known_opts, + std::unordered_map* opts, + int* num_args) +{ + int arg_i = *num_args; + while (arg_i < argc) + { + pal::string_t arg = argv[arg_i]; + if (std::find(known_opts.begin(), known_opts.end(), pal::to_lower(arg)) == known_opts.end()) + { + // Unknown argument. + break; + } + + // Known argument, so expect one more arg (value) to be present. + if (arg_i + 1 >= argc) + { + return false; + } + + (*opts)[arg] = argv[arg_i + 1]; + + // Increment for both the option and its value. + arg_i += 2; + } + + *num_args = arg_i; + + return true; +} + diff --git a/src/corehost/common/utils.h b/src/corehost/common/utils.h index 9d2dc2098..bbcdae4ad 100644 --- a/src/corehost/common/utils.h +++ b/src/corehost/common/utils.h @@ -10,8 +10,17 @@ bool ends_with(const pal::string_t& value, const pal::string_t& suffix, bool mat bool starts_with(const pal::string_t& value, const pal::string_t& prefix, bool match_case); pal::string_t get_executable(const pal::string_t& filename); pal::string_t get_directory(const pal::string_t& path); +pal::string_t strip_file_ext(const pal::string_t& path); pal::string_t get_filename(const pal::string_t& path); +pal::string_t get_filename_without_ext(const pal::string_t& path); 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); +const pal::char_t* get_arch(); +bool parse_known_args( + const int argc, + const pal::char_t* argv[], + const std::vector& known_opts, + std::unordered_map* opts, + int* num_args); #endif diff --git a/src/corehost/corehost.cpp b/src/corehost/corehost.cpp index 70e4d7d36..bf1904faa 100644 --- a/src/corehost/corehost.cpp +++ b/src/corehost/corehost.cpp @@ -2,64 +2,140 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. #include "trace.h" -#include "pal.h" #include "utils.h" -#include "libhost.h" +#include "corehost.h" +#include "fx_ver.h" +#include "error_codes.h" +#include "policy_load.h" +#include -extern int corehost_main(const int argc, const pal::char_t* argv[]); +#define LIBFXR_NAME MAKE_LIBNAME("hostfxr") -namespace +bool corehost_t::hostpolicy_exists_in_svc(pal::string_t* resolved_dir) { -enum StatusCode -{ - Success = 0, - CoreHostLibLoadFailure = 0x41, - CoreHostLibMissingFailure = 0x42, - CoreHostEntryPointFailure = 0x43, - CoreHostCurExeFindFailure = 0x44, -}; - -typedef int (*corehost_main_fn) (const int argc, const pal::char_t* argv[]); - -// ----------------------------------------------------------------------------- -// Load the corehost library from the path specified -// -// Parameters: -// lib_dir - dir path to the corehost library -// h_host - handle to the library which will be kept live -// main_fn - Contains the entrypoint "corehost_main" when returns success. -// -// Returns: -// Non-zero exit code on failure. "main_fn" contains "corehost_main" -// entrypoint on success. -// -StatusCode load_host_lib(const pal::string_t& lib_dir, pal::dll_t* h_host, corehost_main_fn* main_fn) -{ - pal::string_t host_path = lib_dir; - append_path(&host_path, LIBHOST_NAME); - - // Missing library - if (!pal::file_exists(host_path)) +#ifdef COREHOST_PACKAGE_SERVICING + pal::string_t svc_dir; + if (!pal::getenv(_X("DOTNET_SERVICING"), &svc_dir)) { - return StatusCode::CoreHostLibMissingFailure; + return false; } - // Load library - if (!pal::load_library(host_path.c_str(), h_host)) + pal::string_t path = svc_dir; + append_path(&path, COREHOST_PACKAGE_NAME); + append_path(&path, COREHOST_PACKAGE_VERSION); + append_path(&path, COREHOST_PACKAGE_COREHOST_RELATIVE_DIR); + if (library_exists_in_dir(path, LIBHOST_NAME)) { - trace::info(_X("Load library of %s failed"), host_path.c_str()); + resolved_dir->assign(path); + } + return true; +#else + return false; +#endif +} + +pal::string_t corehost_t::resolve_fxr_path(const pal::string_t& own_dir) +{ + pal::string_t fxr_path; + + pal::string_t fxr_dir = own_dir; + append_path(&fxr_dir, _X("dotnethost")); + append_path(&fxr_dir, _X("fxr")); + if (pal::directory_exists(fxr_dir)) + { + trace::info(_X("Reading fx resolver directory=[%s]"), fxr_dir.c_str()); + + std::vector list; + pal::readdir(fxr_dir, &list); + + fx_ver_t max_ver(-1, -1, -1); + for (const auto& dir : list) + { + trace::info(_X("Considering fxr version=[%s]..."), dir.c_str()); + + pal::string_t ver = get_filename(dir); + + fx_ver_t fx_ver(-1, -1, -1); + if (fx_ver_t::parse(ver, &fx_ver, false)) + { + max_ver = std::max(max_ver, fx_ver); + } + } + + pal::string_t max_ver_str = max_ver.as_str(); + append_path(&fxr_dir, max_ver_str.c_str()); + trace::info(_X("Detected latest fxr version=[%s]..."), fxr_dir.c_str()); + } + + const pal::string_t* dirs[] = { &fxr_dir, &own_dir }; + for (const auto& dir : dirs) + { + trace::info(_X("Considering fxr dir=[%s]..."), fxr_dir.c_str()); + if (policy_load_t::library_exists_in_dir(*dir, LIBFXR_NAME, &fxr_path)) + { + trace::info(_X("Resolved fxr [%s]..."), fxr_path.c_str()); + return fxr_path; + } + } + return pal::string_t(); +} + +int corehost_t::resolve_fx_and_execute_app(const pal::string_t& own_dir, const int argc, const pal::char_t* argv[]) +{ + pal::dll_t fxr; + + pal::string_t fxr_path = resolve_fxr_path(own_dir); + + // Load library + if (!pal::load_library(fxr_path.c_str(), &fxr)) + { + trace::info(_X("Load library of %s failed"), fxr_path.c_str()); return StatusCode::CoreHostLibLoadFailure; } - // Obtain entrypoint symbol - *main_fn = (corehost_main_fn) pal::get_symbol(*h_host, "corehost_main"); - - return (*main_fn != nullptr) - ? StatusCode::Success - : StatusCode::CoreHostEntryPointFailure; + // Obtain entrypoint symbols + hostfxr_main_fn main_fn = (hostfxr_main_fn) pal::get_symbol(fxr, "hostfxr_main"); + return main_fn(argc, argv); } -}; // end of anonymous namespace +int corehost_t::run(const int argc, const pal::char_t* argv[]) +{ + pal::string_t own_dir; + auto mode = detect_operating_mode(argc, argv, &own_dir); + + switch (mode) + { + case muxer: + trace::info(_X("Host operating in Muxer mode")); + return resolve_fx_and_execute_app(own_dir, argc, argv); + + case split_fx: + { + trace::info(_X("Host operating in split mode; own dir=[%s]"), own_dir.c_str()); + corehost_init_t init(_X(""), _X(""), own_dir, host_mode_t::split_fx, nullptr); + return policy_load_t::execute_app(own_dir, &init, argc, argv); + } + + case standalone: + { + trace::info(_X("Host operating from standalone app dir %s"), own_dir.c_str()); + + pal::string_t svc_dir; + corehost_init_t init(_X(""), _X(""), _X(""), host_mode_t::standalone, nullptr); + return policy_load_t::execute_app( + hostpolicy_exists_in_svc(&svc_dir) ? svc_dir : own_dir, &init, argc, argv); + } + return StatusCode::CoreHostLibMissingFailure; + + default: + trace::error(_X("Unknown mode detected or could not resolve the mode.")); + return StatusCode::CoreHostResolveModeFailure; + } +} + +#include + +#include "deps_format.h" #if defined(_WIN32) int __cdecl wmain(const int argc, const pal::char_t* argv[]) @@ -69,56 +145,19 @@ int main(const int argc, const pal::char_t* argv[]) { trace::setup(); - pal::dll_t corehost; + //deps_json_t deps(true, _X("H:\\code\\sharedfx\\PortableApp\\PortableAppWithNative.deps.json")); + //deps_json_t deps2(false, _X("H:\\code\\sharedfx\\StandaloneApp\\StandaloneApp.deps.json")); -#ifdef COREHOST_PACKAGE_SERVICING - // No custom host asked, so load the corehost if serviced first. - pal::string_t svc_dir; - if (pal::getenv(_X("DOTNET_SERVICING"), &svc_dir)) + if (trace::is_enabled()) { - pal::string_t path = svc_dir; - append_path(&path, COREHOST_PACKAGE_NAME); - append_path(&path, COREHOST_PACKAGE_VERSION); - append_path(&path, COREHOST_PACKAGE_COREHOST_RELATIVE_DIR); - - corehost_main_fn host_main; - StatusCode code = load_host_lib(path, &corehost, &host_main); - if (code != StatusCode::Success) + trace::info(_X("--- Invoked host main = {")); + for (int i = 0; i < argc; ++i) { - trace::info(_X("Failed to load host library from servicing dir: %s; Status=%08X"), path.c_str(), code); - // Ignore all errors for the servicing case, and proceed to the next step. - } - else - { - trace::info(_X("Calling host entrypoint from library at servicing dir %s"), path.c_str()); - return host_main(argc, argv); + trace::info(_X("%s"), argv[i]); } + trace::info(_X("}")); } -#endif - - // Get current path to look for the library app locally. - pal::string_t own_path; - if (!pal::get_own_executable_path(&own_path) || !pal::realpath(&own_path)) - { - trace::error(_X("Failed to locate current executable")); - return StatusCode::CoreHostCurExeFindFailure; - } - - // Local load of the corehost library. - auto own_dir = get_directory(own_path); - - corehost_main_fn host_main; - StatusCode code = load_host_lib(own_dir, &corehost, &host_main); - switch (code) - { - // Success, call the entrypoint. - case StatusCode::Success: - trace::info(_X("Calling host entrypoint from library at own dir %s"), own_dir.c_str()); - return host_main(argc, argv); - - // Some other fatal error including StatusCode::CoreHostLibMissingFailure. - default: - trace::error(_X("Error loading the host library from own dir: %s; Status=%08X"), own_dir.c_str(), code); - return code; - } + corehost_t corehost; + return corehost.run(argc, argv); } + diff --git a/src/corehost/corehost.h b/src/corehost/corehost.h new file mode 100644 index 000000000..92063e673 --- /dev/null +++ b/src/corehost/corehost.h @@ -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. + +#include "pal.h" +#include "libhost.h" +#include "policy_load.h" + +typedef int(*hostfxr_main_fn) (const int argc, const pal::char_t* argv[]); + +class corehost_t +{ +public: + + int run(const int argc, const pal::char_t* argv[]); + static int execute_app( + const pal::string_t& policy_dir, + const pal::string_t& fx_dir, + const runtime_config_t* config, + const int argc, + const pal::char_t* argv[]); + +private: + + static int load_host_library( + const pal::string_t& lib_dir, + pal::dll_t* h_host, + corehost_load_fn* load_fn, + corehost_main_fn* main_fn, + corehost_unload_fn* unload_fn); + + pal::string_t resolve_fxr_path(const pal::string_t& own_dir); + int resolve_fx_and_execute_app(const pal::string_t& own_dir, const int argc, const pal::char_t* argv[]); + + static bool hostpolicy_exists_in_svc(pal::string_t* resolved_path); + static bool library_exists_in_dir(const pal::string_t& lib_dir, const pal::string_t& lib_name, pal::string_t* p_host_path); +}; diff --git a/src/corehost/error_codes.h b/src/corehost/error_codes.h new file mode 100644 index 000000000..d5a0bc141 --- /dev/null +++ b/src/corehost/error_codes.h @@ -0,0 +1,29 @@ +// 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 __ERROR_CODES_H__ +#define __ERROR_CODES_H__ +enum StatusCode +{ + Success = 0, + InvalidArgFailure = 0x81, + CoreHostLibLoadFailure = 0x82, + CoreHostLibMissingFailure = 0x83, + CoreHostEntryPointFailure = 0x84, + CoreHostCurExeFindFailure = 0x85, + CoreHostResolveModeFailure = 0x86, + CoreClrResolveFailure = 0x87, + CoreClrBindFailure = 0x88, + CoreClrInitFailure = 0x89, + CoreClrExeFailure = 0x90, + ResolverInitFailure = 0x91, + ResolverResolveFailure = 0x92, + LibHostCurExeFindFailure = 0x93, + LibHostInitFailure = 0x94, + LibHostMuxFailure = 0x95, + LibHostExecModeFailure = 0x96, + LibHostSdkFindFailure = 0x97, + LibHostInvalidArgs = 0x98, + InvalidConfigFile = 0x99, +}; +#endif // __ERROR_CODES_H__ diff --git a/src/corehost/policy_load.cpp b/src/corehost/policy_load.cpp new file mode 100644 index 000000000..514f8a086 --- /dev/null +++ b/src/corehost/policy_load.cpp @@ -0,0 +1,79 @@ +// 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 "policy_load.h" +#include "corehost.h" + +bool policy_load_t::library_exists_in_dir(const pal::string_t& lib_dir, const pal::string_t& lib_name, pal::string_t* p_host_path) +{ + pal::string_t host_path = lib_dir; + append_path(&host_path, lib_name.c_str()); + + if (!pal::file_exists(host_path)) + { + return false; + } + if (p_host_path) + { + *p_host_path = host_path; + } + return true; +} + +int policy_load_t::load_host_library( + const pal::string_t& lib_dir, + pal::dll_t* h_host, + corehost_load_fn* load_fn, + corehost_main_fn* main_fn, + corehost_unload_fn* unload_fn) +{ + pal::string_t host_path; + if (!library_exists_in_dir(lib_dir, LIBHOST_NAME, &host_path)) + { + return StatusCode::CoreHostLibMissingFailure; + } + + // Load library + if (!pal::load_library(host_path.c_str(), h_host)) + { + trace::info(_X("Load library of %s failed"), host_path.c_str()); + return StatusCode::CoreHostLibLoadFailure; + } + + // Obtain entrypoint symbols + *load_fn = (corehost_load_fn)pal::get_symbol(*h_host, "corehost_load"); + *main_fn = (corehost_main_fn)pal::get_symbol(*h_host, "corehost_main"); + *unload_fn = (corehost_unload_fn)pal::get_symbol(*h_host, "corehost_unload"); + + return (*main_fn) && (*load_fn) && (*unload_fn) + ? StatusCode::Success + : StatusCode::CoreHostEntryPointFailure; +} + +int policy_load_t::execute_app( + const pal::string_t& impl_dll_dir, + const corehost_init_t* init, + const int argc, + const pal::char_t* argv[]) +{ + pal::dll_t corehost; + corehost_main_fn host_main = nullptr; + corehost_load_fn host_load = nullptr; + corehost_unload_fn host_unload = nullptr; + + int code = load_host_library(impl_dll_dir, &corehost, &host_load, &host_main, &host_unload); + + if (code != StatusCode::Success) + { + trace::error(_X("Could not load host policy library [%s]"), impl_dll_dir.c_str()); + return code; + } + + if ((code = host_load(init)) == 0) + { + code = host_main(argc, argv); + (void) host_unload(); + } + return code; +} + diff --git a/src/corehost/policy_load.h b/src/corehost/policy_load.h new file mode 100644 index 000000000..8d3cc7411 --- /dev/null +++ b/src/corehost/policy_load.h @@ -0,0 +1,38 @@ +// 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 __POLICY_LOAD_H__ +#define __POLICY_LOAD_H__ + +#include "pal.h" +#include "utils.h" +#include "trace.h" +#include "error_codes.h" + +class corehost_init_t; +class runtime_config_t; + +typedef int(*corehost_load_fn) (const corehost_init_t* init); +typedef int(*corehost_main_fn) (const int argc, const pal::char_t* argv[]); +typedef int(*corehost_unload_fn) (); + +class policy_load_t +{ +public: + static int execute_app( + const pal::string_t& impl_dll_dir, + const corehost_init_t* init, + const int argc, + const pal::char_t* argv[]); + + static bool library_exists_in_dir(const pal::string_t& lib_dir, const pal::string_t& lib_name, pal::string_t* p_host_path = nullptr); + + static int load_host_library( + const pal::string_t& lib_dir, + pal::dll_t* h_host, + corehost_load_fn* load_fn, + corehost_main_fn* main_fn, + corehost_unload_fn* unload_fn); +}; + +#endif // __POLICY_LOAD_H__ diff --git a/src/dotnet/Program.cs b/src/dotnet/Program.cs index b858fc955..0f4e67d45 100644 --- a/src/dotnet/Program.cs +++ b/src/dotnet/Program.cs @@ -112,7 +112,6 @@ namespace Microsoft.DotNet.Cli ["publish"] = PublishCommand.Run, ["repl"] = ReplCommand.Run, ["restore"] = RestoreCommand.Run, - ["resgen"] = ResgenCommand.Run, ["run"] = RunCommand.Run, ["test"] = TestCommand.Run }; diff --git a/src/dotnet/commands/dotnet-build/CompileContext.cs b/src/dotnet/commands/dotnet-build/CompileContext.cs index 237fb744c..229c5af15 100644 --- a/src/dotnet/commands/dotnet-build/CompileContext.cs +++ b/src/dotnet/commands/dotnet-build/CompileContext.cs @@ -33,7 +33,7 @@ namespace Microsoft.DotNet.Tools.Build { _rootProject = rootProject; - // Cleaner to clone the args and mutate the clone than have separate CompileContext fields for mutated args + // Cleaner to clone the args and mutate the clone than have separate CompileContext fields for mutated args // and then reasoning which ones to get from args and which ones from fields. _args = (BuilderCommandApp)args.ShallowCopy(); @@ -255,27 +255,33 @@ namespace Microsoft.DotNet.Tools.Build private void CollectCompilerNamePreconditions(ProjectContext project, IncrementalPreconditions preconditions) { - var projectCompiler = project.ProjectFile.CompilerName; - - if (!KnownCompilers.Any(knownCompiler => knownCompiler.Equals(projectCompiler, StringComparison.Ordinal))) + if (project.ProjectFile != null) { - preconditions.AddUnknownCompilerPrecondition(project.ProjectName(), projectCompiler); + var projectCompiler = project.ProjectFile.CompilerName; + + if (!KnownCompilers.Any(knownCompiler => knownCompiler.Equals(projectCompiler, StringComparison.Ordinal))) + { + preconditions.AddUnknownCompilerPrecondition(project.ProjectName(), projectCompiler); + } } } private void CollectScriptPreconditions(ProjectContext project, IncrementalPreconditions preconditions) { - var preCompileScripts = project.ProjectFile.Scripts.GetOrEmpty(ScriptNames.PreCompile); - var postCompileScripts = project.ProjectFile.Scripts.GetOrEmpty(ScriptNames.PostCompile); - - if (preCompileScripts.Any()) + if (project.ProjectFile != null) { - preconditions.AddPrePostScriptPrecondition(project.ProjectName(), ScriptNames.PreCompile); - } + var preCompileScripts = project.ProjectFile.Scripts.GetOrEmpty(ScriptNames.PreCompile); + var postCompileScripts = project.ProjectFile.Scripts.GetOrEmpty(ScriptNames.PostCompile); - if (postCompileScripts.Any()) - { - preconditions.AddPrePostScriptPrecondition(project.ProjectName(), ScriptNames.PostCompile); + if (preCompileScripts.Any()) + { + preconditions.AddPrePostScriptPrecondition(project.ProjectName(), ScriptNames.PreCompile); + } + + if (postCompileScripts.Any()) + { + preconditions.AddPrePostScriptPrecondition(project.ProjectName(), ScriptNames.PostCompile); + } } } @@ -405,6 +411,13 @@ namespace Microsoft.DotNet.Tools.Build { var dest = outputPaths.RuntimeOutputPath; var source = outputPaths.CompilationOutputPath; + + // No need to copy if dest and source are the same + if(string.Equals(dest, source, StringComparison.OrdinalIgnoreCase)) + { + return; + } + foreach (var file in outputPaths.CompilationFiles.All()) { var destFileName = file.Replace(source, dest); @@ -420,22 +433,12 @@ namespace Microsoft.DotNet.Tools.Build private void MakeRunnable() { var runtimeContext = _rootProject.CreateRuntimeContext(_args.GetRuntimes()); - if(_args.PortableMode) - { - // HACK: Force the use of the portable target - runtimeContext = _rootProject; - } - var outputPaths = runtimeContext.GetOutputPaths(_args.ConfigValue, _args.BuildBasePathValue, _args.OutputValue); var libraryExporter = runtimeContext.CreateExporter(_args.ConfigValue, _args.BuildBasePathValue); - // If we're building for a specific RID, we need to copy the RID-less compilation output into - // the RID-specific output dir - if (!string.IsNullOrEmpty(runtimeContext.RuntimeIdentifier)) - { - CopyCompilationOutput(outputPaths); - } + CopyCompilationOutput(outputPaths); + var options = runtimeContext.ProjectFile.GetCompilerOptions(runtimeContext.TargetFramework, _args.ConfigValue); var executable = new Executable(runtimeContext, outputPaths, libraryExporter, _args.ConfigValue); executable.MakeCompilationOutputRunnable(); @@ -446,24 +449,22 @@ namespace Microsoft.DotNet.Tools.Build // time. See: https://github.com/dotnet/cli/issues/1374 private static void PatchMscorlibNextToCoreClr(ProjectContext context, string config) { + foreach (var exp in context.CreateExporter(config).GetAllExports()) { - foreach (var exp in context.CreateExporter(config).GetAllExports()) + var coreclrLib = exp.NativeLibraries.FirstOrDefault(nLib => + string.Equals(Constants.LibCoreClrFileName, nLib.Name)); + if (string.IsNullOrEmpty(coreclrLib.ResolvedPath)) { - var coreclrLib = exp.NativeLibraries.FirstOrDefault(nLib => - string.Equals(Constants.LibCoreClrFileName, nLib.Name)); - if (string.IsNullOrEmpty(coreclrLib.ResolvedPath)) - { - continue; - } - var coreclrDir = Path.GetDirectoryName(coreclrLib.ResolvedPath); - if (File.Exists(Path.Combine(coreclrDir, "mscorlib.dll")) || - File.Exists(Path.Combine(coreclrDir, "mscorlib.ni.dll"))) - { - continue; - } - var mscorlibFile = exp.RuntimeAssemblies.FirstOrDefault(r => r.Name.Equals("mscorlib") || r.Name.Equals("mscorlib.ni")).ResolvedPath; - File.Copy(mscorlibFile, Path.Combine(coreclrDir, Path.GetFileName(mscorlibFile)), overwrite: true); + continue; } + var coreclrDir = Path.GetDirectoryName(coreclrLib.ResolvedPath); + if (File.Exists(Path.Combine(coreclrDir, "mscorlib.dll")) || + File.Exists(Path.Combine(coreclrDir, "mscorlib.ni.dll"))) + { + continue; + } + var mscorlibFile = exp.RuntimeAssemblies.FirstOrDefault(r => r.Name.Equals("mscorlib") || r.Name.Equals("mscorlib.ni")).ResolvedPath; + File.Copy(mscorlibFile, Path.Combine(coreclrDir, Path.GetFileName(mscorlibFile)), overwrite: true); } } @@ -533,12 +534,16 @@ namespace Microsoft.DotNet.Tools.Build // input: dependencies AddDependencies(dependencies, compilerIO); - var allOutputPath = new List(calculator.CompilationFiles.All()); + var allOutputPath = new HashSet(calculator.CompilationFiles.All()); if (isRootProject && project.ProjectFile.HasRuntimeOutput(buildConfiguration)) { var runtimeContext = project.CreateRuntimeContext(_args.GetRuntimes()); - allOutputPath.AddRange(runtimeContext.GetOutputPaths(buildConfiguration, buildBasePath, outputPath).RuntimeFiles.All()); + foreach (var path in runtimeContext.GetOutputPaths(buildConfiguration, buildBasePath, outputPath).RuntimeFiles.All()) + { + allOutputPath.Add(path); + } } + // output: compiler outputs foreach (var path in allOutputPath) { diff --git a/src/dotnet/commands/dotnet-compile-native/IntermediateCompilation/Linux/LinuxCppCompileStep.cs b/src/dotnet/commands/dotnet-compile-native/IntermediateCompilation/Linux/LinuxCppCompileStep.cs index 28e543de4..6197c0adc 100644 --- a/src/dotnet/commands/dotnet-compile-native/IntermediateCompilation/Linux/LinuxCppCompileStep.cs +++ b/src/dotnet/commands/dotnet-compile-native/IntermediateCompilation/Linux/LinuxCppCompileStep.cs @@ -12,7 +12,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native // TODO: debug/release support private readonly string [] _cLibsFlags = { "-lm", "-ldl"}; - private readonly string [] _cflags = { "-g", "-lstdc++", "-lrt", "-Wno-invalid-offsetof", "-pthread"}; + private readonly string [] _cflags = { "-g", "-lstdc++", "-lrt", "-Wno-invalid-offsetof", "-lpthread"}; public IEnumerable CompilerArgs { get; set; } diff --git a/src/dotnet/commands/dotnet-compile-native/IntermediateCompilation/Mac/MacCppCompileStep.cs b/src/dotnet/commands/dotnet-compile-native/IntermediateCompilation/Mac/MacCppCompileStep.cs index 64fec18ef..a628535b6 100644 --- a/src/dotnet/commands/dotnet-compile-native/IntermediateCompilation/Mac/MacCppCompileStep.cs +++ b/src/dotnet/commands/dotnet-compile-native/IntermediateCompilation/Mac/MacCppCompileStep.cs @@ -16,7 +16,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native public const string InputExtension = ".cpp"; // TODO: debug/release support - private readonly string [] _cflags = { "-g", "-lstdc++", "-Wno-invalid-offsetof", "-pthread"}; + private readonly string [] _cflags = { "-g", "-lstdc++", "-Wno-invalid-offsetof", "-lpthread"}; // Link to iconv APIs private const string LibFlags = "-liconv"; diff --git a/src/dotnet/commands/dotnet-compile-native/IntermediateCompilation/Mac/MacRyuJitCompileStep.cs b/src/dotnet/commands/dotnet-compile-native/IntermediateCompilation/Mac/MacRyuJitCompileStep.cs index 6a7ecb13c..4a44bd459 100644 --- a/src/dotnet/commands/dotnet-compile-native/IntermediateCompilation/Mac/MacRyuJitCompileStep.cs +++ b/src/dotnet/commands/dotnet-compile-native/IntermediateCompilation/Mac/MacRyuJitCompileStep.cs @@ -15,7 +15,7 @@ namespace Microsoft.DotNet.Tools.Compiler.Native private IEnumerable CompilerArgs; // TODO: debug/release support - private readonly string [] _cflags = { "-g", "-lstdc++", "-Wno-invalid-offsetof", "-pthread", "-ldl", "-lm", "-liconv" }; + private readonly string [] _cflags = { "-g", "-lstdc++", "-Wno-invalid-offsetof", "-lpthread", "-ldl", "-lm", "-liconv" }; private readonly string[] _ilcSdkLibs = { diff --git a/src/dotnet/commands/dotnet-compile-native/appdep/project.json b/src/dotnet/commands/dotnet-compile-native/appdep/project.json index 689ef478a..eaac3555d 100644 --- a/src/dotnet/commands/dotnet-compile-native/appdep/project.json +++ b/src/dotnet/commands/dotnet-compile-native/appdep/project.json @@ -1,15 +1,15 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true - }, - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "Microsoft.DotNet.AppDep":"1.0.6-prerelease-00003" - }, - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "Microsoft.DotNet.AppDep": "1.0.6-prerelease-00003" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" } + } } diff --git a/src/dotnet/commands/dotnet-compile/CompilerCommandApp.cs b/src/dotnet/commands/dotnet-compile/CompilerCommandApp.cs index 9a66f1777..8da23d62e 100644 --- a/src/dotnet/commands/dotnet-compile/CompilerCommandApp.cs +++ b/src/dotnet/commands/dotnet-compile/CompilerCommandApp.cs @@ -29,7 +29,6 @@ namespace Microsoft.DotNet.Tools.Compiler private CommandOption _runtimeOption; private CommandOption _versionSuffixOption; private CommandOption _configurationOption; - private CommandOption _portableOption; private CommandArgument _projectArgument; private CommandOption _nativeOption; private CommandOption _archOption; @@ -55,7 +54,6 @@ namespace Microsoft.DotNet.Tools.Compiler public bool IsCppModeValue { get; set; } public string AppDepSdkPathValue { get; set; } public string CppCompilerFlagsValue { get; set; } - public bool PortableMode { get; set; } // workaround: CommandLineApplication is internal therefore I cannot make _app protected so baseclasses can add their own params private readonly Dictionary baseClassOptions; @@ -86,9 +84,6 @@ namespace Microsoft.DotNet.Tools.Compiler _versionSuffixOption = _app.Option("--version-suffix ", "Defines what `*` should be replaced with in version field in project.json", CommandOptionType.SingleValue); _projectArgument = _app.Argument("", "The project to compile, defaults to the current directory. Can be a path to a project.json or a project directory"); - // HACK: Allow us to treat a project as though it was portable by ignoring the runtime-specific targets. This is temporary until RID inference is removed from NuGet - _portableOption = _app.Option("--portable", "TEMPORARY: Enforces portable build/publish mode", CommandOptionType.NoValue); - // Native Args _nativeOption = _app.Option("-n|--native", "Compiles source to native machine code.", CommandOptionType.NoValue); _archOption = _app.Option("-a|--arch ", "The architecture for which to compile. x64 only currently supported.", CommandOptionType.SingleValue); @@ -122,7 +117,6 @@ namespace Microsoft.DotNet.Tools.Compiler ConfigValue = _configurationOption.Value() ?? Constants.DefaultConfiguration; RuntimeValue = _runtimeOption.Value(); VersionSuffixValue = _versionSuffixOption.Value(); - PortableMode = _portableOption.HasValue(); IsNativeValue = _nativeOption.HasValue(); ArchValue = _archOption.Value(); diff --git a/src/dotnet/commands/dotnet-compile/CompilerUtil.cs b/src/dotnet/commands/dotnet-compile/CompilerUtil.cs index 739e46dfe..f258dc43c 100644 --- a/src/dotnet/commands/dotnet-compile/CompilerUtil.cs +++ b/src/dotnet/commands/dotnet-compile/CompilerUtil.cs @@ -122,7 +122,7 @@ namespace Microsoft.DotNet.Tools.Compiler //used in incremental precondition checks public static IEnumerable GetCommandsInvokedByCompile(ProjectContext project) { - return new List {project.ProjectFile.CompilerName, "compile"}; + return new List {project.ProjectFile?.CompilerName, "compile"}; } } } \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs b/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs index d4f3dc4c3..46b7d7cb1 100644 --- a/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs +++ b/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs @@ -114,7 +114,7 @@ namespace Microsoft.DotNet.Tools.Compiler var dependencyContext = new DependencyContextBuilder().Build(compilationOptions, allExports, allExports, - args.PortableMode, + false, // For now, just assume non-portable mode in the legacy deps file (this is going away soon anyway) context.TargetFramework, context.RuntimeIdentifier ?? string.Empty); diff --git a/src/dotnet/commands/dotnet-new/CSharp_Console/project.json.template b/src/dotnet/commands/dotnet-new/CSharp_Console/project.json.template index c02d0dffe..5f0805e14 100644 --- a/src/dotnet/commands/dotnet-new/CSharp_Console/project.json.template +++ b/src/dotnet/commands/dotnet-new/CSharp_Console/project.json.template @@ -1,16 +1,14 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true - }, - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "Microsoft.NETCore.App": "1.0.0-rc2-23911" + }, + "frameworks": { + "netstandard1.5": { + "imports": [ "portable-net45+win8", "dnxcore50" ] } + } } diff --git a/src/dotnet/commands/dotnet-new/FSharp_Console/project.json.template b/src/dotnet/commands/dotnet-new/FSharp_Console/project.json.template index 60ba0af6e..501e3dd6c 100644 --- a/src/dotnet/commands/dotnet-new/FSharp_Console/project.json.template +++ b/src/dotnet/commands/dotnet-new/FSharp_Console/project.json.template @@ -1,22 +1,19 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true - }, - - "compilerName": "fsc", - "compileFiles": [ - "Program.fs" - ], - - "dependencies": { - "Microsoft.FSharp.Core.netcore": "1.0.0-alpha-151221", - "NETStandard.Library": "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "compilerName": "fsc", + "compileFiles": [ + "Program.fs" + ], + "dependencies": { + "Microsoft.FSharp.Core.netcore": "1.0.0-alpha-151221", + "Microsoft.NETCore.App": "1.0.0-rc2-23911" + }, + "frameworks": { + "netstandard1.5": { + "imports": [ "portable-net45+win8", "dnxcore50" ] } + } } diff --git a/src/dotnet/commands/dotnet-pack/PackageGenerator.cs b/src/dotnet/commands/dotnet-pack/PackageGenerator.cs index 8995e2025..44a793b6f 100644 --- a/src/dotnet/commands/dotnet-pack/PackageGenerator.cs +++ b/src/dotnet/commands/dotnet-pack/PackageGenerator.cs @@ -94,6 +94,7 @@ namespace Microsoft.DotNet.Tools.Compiler TryAddOutputFile(context, inputFolder, outputName); TryAddOutputFile(context, inputFolder, $"{Project.Name}.xml"); + TryAddOutputFile(context, inputFolder, $"{Project.Name}.runtimeconfig.json"); } protected virtual bool GeneratePackage(string nupkg, List packDiagnostics) diff --git a/src/dotnet/commands/dotnet-publish/PublishCommand.cs b/src/dotnet/commands/dotnet-publish/PublishCommand.cs index 802a1c963..2c5f3ef29 100644 --- a/src/dotnet/commands/dotnet-publish/PublishCommand.cs +++ b/src/dotnet/commands/dotnet-publish/PublishCommand.cs @@ -29,7 +29,7 @@ namespace Microsoft.DotNet.Tools.Publish public string Runtime { get; set; } public bool NativeSubdirectories { get; set; } public NuGetFramework NugetFramework { get; set; } - public IEnumerable ProjectContexts { get; set; } + public IList ProjectContexts { get; set; } public string VersionSuffix { get; set; } public int NumberOfProjects { get; private set; } public int NumberOfPublishedProjects { get; private set; } @@ -47,10 +47,10 @@ namespace Microsoft.DotNet.Tools.Publish } } - ProjectContexts = SelectContexts(ProjectPath, NugetFramework, Runtime); + ProjectContexts = SelectContexts(ProjectPath, NugetFramework, Runtime).ToList(); if (!ProjectContexts.Any()) { - string errMsg = $"'{ProjectPath}' cannot be published for '{Framework ?? ""}' '{Runtime ?? ""}'"; + string errMsg = $"'{ProjectPath}' cannot be published for '{Framework ?? ""}' '{Runtime ?? ""}'"; Reporter.Output.WriteLine(errMsg.Red()); return false; } @@ -84,7 +84,12 @@ namespace Microsoft.DotNet.Tools.Publish /// Return 0 if successful else return non-zero private bool PublishProjectContext(ProjectContext context, string buildBasePath, string outputPath, string configuration, bool nativeSubdirectories) { - Reporter.Output.WriteLine($"Publishing {context.RootProject.Identity.Name.Yellow()} for {context.TargetFramework.DotNetFrameworkName.Yellow()}/{context.RuntimeIdentifier.Yellow()}"); + var target = context.TargetFramework.DotNetFrameworkName; + if (!string.IsNullOrEmpty(context.RuntimeIdentifier)) + { + target = $"{target}/{context.RuntimeIdentifier}"; + } + Reporter.Output.WriteLine($"Publishing {context.RootProject.Identity.Name.Yellow()} for {target.Yellow()}"); var options = context.ProjectFile.GetCompilerOptions(context.TargetFramework, configuration); var outputPaths = context.GetOutputPaths(configuration, buildBasePath, outputPath); @@ -115,13 +120,17 @@ namespace Microsoft.DotNet.Tools.Publish var args = new List() { "--framework", $"{context.TargetFramework.DotNetFrameworkName}", - "--runtime", - context.RuntimeIdentifier, "--configuration", configuration, context.ProjectFile.ProjectDirectory }; + if (!string.IsNullOrEmpty(context.RuntimeIdentifier)) + { + args.Insert(0, context.RuntimeIdentifier); + args.Insert(0, "--runtime"); + } + if (!string.IsNullOrEmpty(VersionSuffix)) { args.Add("--version-suffix"); @@ -147,23 +156,43 @@ namespace Microsoft.DotNet.Tools.Publish { Reporter.Verbose.WriteLine($"Publishing {export.Library.Identity.ToString().Green().Bold()} ..."); - PublishFiles(export.RuntimeAssemblies, outputPath, nativeSubdirectories: false); - PublishFiles(export.NativeLibraries, outputPath, nativeSubdirectories); + PublishFiles(export.RuntimeAssemblies, outputPath, nativeSubdirectories: false, preserveRelativePath: false); + PublishFiles(export.NativeLibraries, outputPath, nativeSubdirectories, preserveRelativePath: false); export.RuntimeAssets.StructuredCopyTo(outputPath, outputPaths.IntermediateOutputDirectoryPath); + if (string.IsNullOrEmpty(context.RuntimeIdentifier)) + { + var assets = export.RuntimeTargets.SelectMany(t => Enumerable.Concat(t.NativeLibraries, t.RuntimeAssemblies)); + PublishFiles(assets, outputPath, nativeSubdirectories: false, preserveRelativePath: true); + } + if (options.PreserveCompilationContext.GetValueOrDefault()) { PublishRefs(export, outputPath); } } + if (context.ProjectFile.HasRuntimeOutput(configuration) && !context.TargetFramework.IsDesktop()) + { + // Get the output paths used by the call to `dotnet build` above (since we didn't pass `--output`, they will be different from + // our current output paths) + var buildOutputPaths = context.GetOutputPaths(configuration, buildBasePath); + PublishFiles( + new[] { + buildOutputPaths.RuntimeFiles.Deps, + buildOutputPaths.RuntimeFiles.DepsJson, + buildOutputPaths.RuntimeFiles.RuntimeConfigJson + }, + outputPath); + } + var contentFiles = new ContentFiles(context); contentFiles.StructuredCopyTo(outputPath); // Publish a host if this is an application - if (options.EmitEntryPoint.GetValueOrDefault()) + if (options.EmitEntryPoint.GetValueOrDefault() && !string.IsNullOrEmpty(context.RuntimeIdentifier)) { - Reporter.Verbose.WriteLine($"Making {context.ProjectFile.Name.Cyan()} runnable ..."); + Reporter.Verbose.WriteLine($"Copying native host to output to create fully standalone output."); PublishHost(context, outputPath); } @@ -228,18 +257,23 @@ namespace Microsoft.DotNet.Tools.Publish } } - private static void PublishFiles(IEnumerable files, string outputPath, bool nativeSubdirectories) + private static void PublishFiles(IEnumerable files, string outputPath, bool nativeSubdirectories, bool preserveRelativePath) { foreach (var file in files) { var destinationDirectory = DetermineFileDestinationDirectory(file, outputPath, nativeSubdirectories); + if (preserveRelativePath) + { + destinationDirectory = Path.Combine(destinationDirectory, Path.GetDirectoryName(file.RelativePath)); + } + if (!Directory.Exists(destinationDirectory)) { Directory.CreateDirectory(destinationDirectory); } - File.Copy(file.ResolvedPath, Path.Combine(destinationDirectory, Path.GetFileName(file.ResolvedPath)), overwrite: true); + File.Copy(file.ResolvedPath, Path.Combine(destinationDirectory, file.FileName), overwrite: true); } } @@ -272,29 +306,46 @@ namespace Microsoft.DotNet.Tools.Publish return candidate; } - private static IEnumerable SelectContexts(string projectPath, NuGetFramework framework, string runtime) + private IEnumerable SelectContexts(string projectPath, NuGetFramework framework, string runtime) { - var allContexts = ProjectContext.CreateContextForEachTarget(projectPath); + var allContexts = ProjectContext.CreateContextForEachTarget(projectPath).ToList(); + var frameworks = framework == null ? + allContexts.Select(c => c.TargetFramework).Distinct().ToArray() : + new[] { framework }; + if (string.IsNullOrEmpty(runtime)) { - // Nothing was specified, so figure out what the candidate runtime identifiers are and try each of them - // Temporary until #619 is resolved - foreach (var candidate in PlatformServices.Default.Runtime.GetAllCandidateRuntimeIdentifiers()) - { - var contexts = GetMatchingProjectContexts(allContexts, framework, candidate); - if (contexts.Any()) - { - return contexts; - } - } - return Enumerable.Empty(); + // For each framework, find the best matching RID item + var candidates = PlatformServices.Default.Runtime.GetAllCandidateRuntimeIdentifiers(); + return frameworks.Select(f => FindBestTarget(f, allContexts, candidates)); } else { - return GetMatchingProjectContexts(allContexts, framework, runtime); + return frameworks.SelectMany(f => allContexts.Where(c => + Equals(c.TargetFramework, f) && + string.Equals(c.RuntimeIdentifier, runtime, StringComparison.Ordinal))); } } + private ProjectContext FindBestTarget(NuGetFramework f, List allContexts, IEnumerable candidates) + { + foreach (var candidate in candidates) + { + var target = allContexts.FirstOrDefault(c => + Equals(c.TargetFramework, f) && + string.Equals(c.RuntimeIdentifier, candidate, StringComparison.Ordinal)); + if (target != null) + { + return target; + } + } + + // No RID-specific target found, use the RID-less target and publish portable + return allContexts.FirstOrDefault(c => + Equals(c.TargetFramework, f) && + string.IsNullOrEmpty(c.RuntimeIdentifier)); + } + /// /// Return the matching framework/runtime ProjectContext. /// If 'framework' or 'runtimeIdentifier' is null or empty then it matches with any. diff --git a/src/dotnet/commands/dotnet-restore/Program.cs b/src/dotnet/commands/dotnet-restore/Program.cs index 1b530edb0..7cf615663 100644 --- a/src/dotnet/commands/dotnet-restore/Program.cs +++ b/src/dotnet/commands/dotnet-restore/Program.cs @@ -40,18 +40,7 @@ namespace Microsoft.DotNet.Tools.Restore { try { - var projectRestoreResult = NuGet3.Restore(args, quiet); - - var restoreTasks = GetRestoreTasks(args); - - foreach (var restoreTask in restoreTasks) - { - var project = ProjectReader.GetProject(restoreTask.ProjectPath); - - RestoreTools(project, restoreTask, quiet); - } - - return projectRestoreResult; + return NuGet3.Restore(args, quiet); } catch (InvalidOperationException e) { @@ -69,140 +58,5 @@ namespace Microsoft.DotNet.Tools.Restore return app.Execute(args); } - - private static IEnumerable GetRestoreTasks(IEnumerable args) - { - var directory = Directory.GetCurrentDirectory(); - - if (args.Any()) - { - var firstArg = args.First(); - - if (IsProjectFile(firstArg)) - { - return new [] {new RestoreTask { ProjectPath = firstArg, Arguments = args.Skip(1)} }; - } - - if (Directory.Exists(firstArg)) - { - directory = firstArg; - - args = args.Skip(1); - } - } - - return GetAllProjectFiles(directory) - .Select(p => new RestoreTask {ProjectPath = p, Arguments = args}); - } - - private static string[] GetAllProjectFiles(string directory) - { - return Directory.GetFiles(directory, Project.FileName, SearchOption.AllDirectories); - } - - private static bool IsProjectFile(string firstArg) - { - return firstArg.EndsWith(Project.FileName) && File.Exists(firstArg); - } - - private static void RestoreTools(Project project, RestoreTask restoreTask, bool quiet) - { - foreach (var tooldep in project.Tools) - { - RestoreTool(tooldep, restoreTask, quiet); - } - } - - private static void RestoreTool(LibraryRange tooldep, RestoreTask restoreTask, bool quiet) - { - var tempRoot = Path.Combine(restoreTask.ProjectDirectory, "obj"); - try - { - var tempPath = Path.Combine(tempRoot, Guid.NewGuid().ToString(), "bin"); - - var restoreSucceded = RestoreToolToPath(tooldep, restoreTask.Arguments, tempPath, quiet); - if (restoreSucceded) - { - CreateDepsInPackageCache(tooldep, tempPath); - PersistLockFile(tooldep, tempPath, restoreTask.ProjectDirectory); - } - } - finally - { - Directory.Delete(tempRoot, true); - } - } - - private static void PersistLockFile(LibraryRange tooldep, string tempPath, string projectPath) - { - var sourcePath = Path.Combine(tempPath, "project.lock.json"); - var targetDir = Path.Combine(projectPath, "artifacts", "Tools", tooldep.Name); - var targetPath = Path.Combine(targetDir, "project.lock.json"); - - if (Directory.Exists(targetDir)) Directory.Delete(targetDir, true); - Directory.CreateDirectory(targetDir); - - Console.WriteLine($"Writing '{sourcePath}' to '{targetPath}'"); - - File.Move(sourcePath, targetPath); - } - - private static void CreateDepsInPackageCache(LibraryRange toolLibrary, string projectPath) - { - var context = ProjectContext.Create(projectPath, - FrameworkConstants.CommonFrameworks.NetStandardApp15, new[] { DefaultRid }); - - var toolDescription = context.LibraryManager.GetLibraries() - .Select(l => l as PackageDescription) - .Where(l => l != null) - .FirstOrDefault(l => l.Identity.Name == toolLibrary.Name); - - var depsPath = Path.Combine( - toolDescription.Path, - Path.GetDirectoryName(toolDescription.RuntimeAssemblies.First().Path), - toolDescription.Identity.Name + FileNameSuffixes.Deps); - - var calculator = context.GetOutputPaths(Constants.DefaultConfiguration, buidBasePath: null, outputPath: context.ProjectDirectory); - var executable = new Executable(context, calculator, context.CreateExporter(Constants.DefaultConfiguration), null); - - executable.MakeCompilationOutputRunnable(); - - if (File.Exists(depsPath)) File.Delete(depsPath); - - File.Move(Path.Combine(calculator.RuntimeOutputPath, "bin" + FileNameSuffixes.Deps), depsPath); - } - - private static bool RestoreToolToPath(LibraryRange tooldep, IEnumerable args, string tempPath, bool quiet) - { - Directory.CreateDirectory(tempPath); - var projectPath = Path.Combine(tempPath, Project.FileName); - - Console.WriteLine($"Restoring Tool '{tooldep.Name}' for '{projectPath}' in '{tempPath}'"); - - File.WriteAllText(projectPath, GenerateProjectJsonContents(new[] {"netstandardapp1.5"}, tooldep)); - return NuGet3.Restore(new[] { $"{projectPath}" }.Concat(args), quiet) == 0; - } - - private static string GenerateProjectJsonContents(IEnumerable frameworks, LibraryRange tooldep) - { - var sb = new StringBuilder(); - sb.AppendLine("{"); - sb.AppendLine(" \"dependencies\": {"); - sb.AppendLine($" \"{tooldep.Name}\": \"{tooldep.VersionRange.OriginalString}\""); - sb.AppendLine(" },"); - sb.AppendLine(" \"frameworks\": {"); - foreach (var framework in frameworks) - { - var importsStatement = "\"imports\": [ \"dnxcore50\", \"portable-net452+win81\" ]"; - sb.AppendLine($" \"{framework}\": {{ {importsStatement} }}"); - } - sb.AppendLine(" },"); - sb.AppendLine(" \"runtimes\": { "); - sb.AppendLine($" \"{DefaultRid}\": {{}}"); - sb.AppendLine(" }"); - sb.AppendLine("}"); - var pjContents = sb.ToString(); - return pjContents; - } } } diff --git a/src/dotnet/commands/dotnet-restore/RestoreTask.cs b/src/dotnet/commands/dotnet-restore/RestoreTask.cs deleted file mode 100644 index 04de31d79..000000000 --- a/src/dotnet/commands/dotnet-restore/RestoreTask.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using Microsoft.DotNet.ProjectModel; - -namespace Microsoft.DotNet.Tools.Restore -{ - public struct RestoreTask - { - public string ProjectPath { get; set; } - - public IEnumerable Arguments { get; set; } - - public string ProjectDirectory => ProjectPath.EndsWith(Project.FileName, StringComparison.OrdinalIgnoreCase) - ? Path.GetDirectoryName(ProjectPath) - : ProjectPath; - } -} \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-run/RunCommand.cs b/src/dotnet/commands/dotnet-run/RunCommand.cs index cc8c65e32..db67a4394 100644 --- a/src/dotnet/commands/dotnet-run/RunCommand.cs +++ b/src/dotnet/commands/dotnet-run/RunCommand.cs @@ -84,7 +84,7 @@ namespace Microsoft.DotNet.Tools.Run { throw new InvalidOperationException($"Couldn't find target to run. Possible causes:" + Environment.NewLine + "1. No project.lock.json file or restore failed - run `dotnet restore`" + Environment.NewLine + - $"2. project.lock.json has multiple targets none of which is in default list ({string.Join(", " , defaultFrameworks)})"); + $"2. project.lock.json has multiple targets none of which is in default list ({string.Join(", ", defaultFrameworks)})"); } } @@ -125,7 +125,8 @@ namespace Microsoft.DotNet.Tools.Run } // Now launch the output and give it the results - var outputName = _context.GetOutputPaths(Configuration).RuntimeFiles.Executable; + var outputPaths = _context.GetOutputPaths(Configuration); + var outputName = outputPaths.RuntimeFiles.Executable; if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { @@ -145,7 +146,18 @@ namespace Microsoft.DotNet.Tools.Run } } - result = Command.Create(outputName, _args) + Command command; + if (outputName.EndsWith(FileNameSuffixes.DotNet.DynamicLib, StringComparison.OrdinalIgnoreCase)) + { + // The executable is a ".dll", we need to call it through dotnet.exe + command = Command.Create("corehost", Enumerable.Concat(new[] { outputName }, _args)); + } + else + { + command = Command.Create(outputName, _args); + } + + result = command .ForwardStdOut() .ForwardStdErr() .Execute() @@ -156,7 +168,7 @@ namespace Microsoft.DotNet.Tools.Run private static int RunInteractive(string scriptName) { - var command = Command.CreateDotNet($"repl-csi", new [] {scriptName}) + var command = Command.CreateDotNet($"repl-csi", new[] { scriptName }) .ForwardStdOut() .ForwardStdErr(); var result = command.Execute(); diff --git a/src/dotnet/commands/dotnet-test/MessageHandlers/GetTestRunnerProcessStartInfoMessageHandler.cs b/src/dotnet/commands/dotnet-test/MessageHandlers/GetTestRunnerProcessStartInfoMessageHandler.cs index 08898a8f4..95931d9cd 100644 --- a/src/dotnet/commands/dotnet-test/MessageHandlers/GetTestRunnerProcessStartInfoMessageHandler.cs +++ b/src/dotnet/commands/dotnet-test/MessageHandlers/GetTestRunnerProcessStartInfoMessageHandler.cs @@ -41,7 +41,7 @@ namespace Microsoft.DotNet.Tools.Test dotnetTest.StartListeningTo(testRunnerChannel); - testRunnerChannel.Accept(); + testRunnerChannel.Connect(); var testRunner = _testRunnerFactory.CreateTestRunner( new RunTestsArgumentsBuilder(dotnetTest.PathToAssemblyUnderTest, testRunnerChannel.Port, message)); diff --git a/src/dotnet/commands/dotnet-test/MessageHandlers/TestDiscoveryStartMessageHandler.cs b/src/dotnet/commands/dotnet-test/MessageHandlers/TestDiscoveryStartMessageHandler.cs index 941c48c13..bf3976a27 100644 --- a/src/dotnet/commands/dotnet-test/MessageHandlers/TestDiscoveryStartMessageHandler.cs +++ b/src/dotnet/commands/dotnet-test/MessageHandlers/TestDiscoveryStartMessageHandler.cs @@ -55,7 +55,7 @@ namespace Microsoft.DotNet.Cli.Tools.Test dotnetTest.StartListeningTo(testRunnerChannel); - testRunnerChannel.Accept(); + testRunnerChannel.Connect(); var testRunner = _testRunnerFactory.CreateTestRunner( new DiscoverTestsArgumentsBuilder(dotnetTest.PathToAssemblyUnderTest, testRunnerChannel.Port)); diff --git a/src/dotnet/commands/dotnet-test/MessageHandlers/TestMessageTypes.cs b/src/dotnet/commands/dotnet-test/MessageHandlers/TestMessageTypes.cs index a12a62e4f..ce76ccef4 100644 --- a/src/dotnet/commands/dotnet-test/MessageHandlers/TestMessageTypes.cs +++ b/src/dotnet/commands/dotnet-test/MessageHandlers/TestMessageTypes.cs @@ -11,6 +11,7 @@ namespace Microsoft.DotNet.Tools.Test public const string TestRunnerTestStarted = "TestExecution.TestStarted"; public const string TestRunnerTestCompleted = "TestRunner.TestCompleted"; public const string TestRunnerTestFound = "TestDiscovery.TestFound"; + public const string TestSessionConnected = "TestSession.Connected"; public const string TestSessionTerminate = "TestSession.Terminate"; public const string VersionCheck = "ProtocolVersion"; public const string TestDiscoveryStart = "TestDiscovery.Start"; diff --git a/src/dotnet/commands/dotnet-test/Program.cs b/src/dotnet/commands/dotnet-test/Program.cs index 24d12c11b..1f327b2c3 100644 --- a/src/dotnet/commands/dotnet-test/Program.cs +++ b/src/dotnet/commands/dotnet-test/Program.cs @@ -183,7 +183,7 @@ namespace Microsoft.DotNet.Tools.Test dotnetTest.StartListeningTo(adapterChannel); - adapterChannel.Accept(); + adapterChannel.Connect(); dotnetTest.StartHandlingMessages(); } diff --git a/src/dotnet/commands/dotnet-test/ReportingChannels/AdapterReportingChannel.cs b/src/dotnet/commands/dotnet-test/ReportingChannels/AdapterReportingChannel.cs new file mode 100644 index 000000000..1bf9ffb21 --- /dev/null +++ b/src/dotnet/commands/dotnet-test/ReportingChannels/AdapterReportingChannel.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 Microsoft.Extensions.Testing.Abstractions; +using System.Net; +using System.Net.Sockets; + +namespace Microsoft.DotNet.Tools.Test +{ + public class AdapterReportingChannel : ReportingChannel + { + private readonly IPEndPoint _ipEndPoint; + + public static AdapterReportingChannel ConnectTo(int port) + { + var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + var ipEndPoint = new IPEndPoint(IPAddress.Loopback, port); + + return new AdapterReportingChannel(socket, ipEndPoint); + } + + private AdapterReportingChannel(Socket connectSocket, IPEndPoint ipEndPoint) + : base(connectSocket, ipEndPoint.Port) + { + _ipEndPoint = ipEndPoint; + } + + public override void Connect() + { + Socket = ConnectSocket; + + Socket.Connect(_ipEndPoint); + + StartReadingMessages(); + + Send(new Message + { + MessageType = TestMessageTypes.TestSessionConnected + }); + } + } +} diff --git a/src/dotnet/commands/dotnet-test/IReportingChannel.cs b/src/dotnet/commands/dotnet-test/ReportingChannels/IReportingChannel.cs similarity index 95% rename from src/dotnet/commands/dotnet-test/IReportingChannel.cs rename to src/dotnet/commands/dotnet-test/ReportingChannels/IReportingChannel.cs index f103c61e2..0729b4032 100644 --- a/src/dotnet/commands/dotnet-test/IReportingChannel.cs +++ b/src/dotnet/commands/dotnet-test/ReportingChannels/IReportingChannel.cs @@ -13,7 +13,7 @@ namespace Microsoft.DotNet.Tools.Test int Port { get; } - void Accept(); + void Connect(); void Send(Message message); diff --git a/src/dotnet/commands/dotnet-test/IReportingChannelFactory.cs b/src/dotnet/commands/dotnet-test/ReportingChannels/IReportingChannelFactory.cs similarity index 100% rename from src/dotnet/commands/dotnet-test/IReportingChannelFactory.cs rename to src/dotnet/commands/dotnet-test/ReportingChannels/IReportingChannelFactory.cs diff --git a/src/dotnet/commands/dotnet-test/ReportingChannel.cs b/src/dotnet/commands/dotnet-test/ReportingChannels/ReportingChannel.cs similarity index 68% rename from src/dotnet/commands/dotnet-test/ReportingChannel.cs rename to src/dotnet/commands/dotnet-test/ReportingChannels/ReportingChannel.cs index f6a919ace..624e75c32 100644 --- a/src/dotnet/commands/dotnet-test/ReportingChannel.cs +++ b/src/dotnet/commands/dotnet-test/ReportingChannels/ReportingChannel.cs @@ -14,52 +14,26 @@ using Newtonsoft.Json.Linq; namespace Microsoft.DotNet.Tools.Test { - public class ReportingChannel : IReportingChannel + public abstract class ReportingChannel : IReportingChannel { - public static ReportingChannel ListenOn(int port) - { - // This fixes the mono incompatibility but ties it to ipv4 connections - var listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - - listenSocket.Bind(new IPEndPoint(IPAddress.Loopback, port)); - listenSocket.Listen(10); - - return new ReportingChannel(listenSocket); - } - private BinaryWriter _writer; private BinaryReader _reader; - private Socket _listenSocket; - private ReportingChannel(Socket listenSocket) + protected ReportingChannel(Socket connectSocket, int port) { - _listenSocket = listenSocket; - Port = ((IPEndPoint)listenSocket.LocalEndPoint).Port; + ConnectSocket = connectSocket; + Port = port; } + protected Socket Socket { get; set; } + public event EventHandler MessageReceived; - public Socket Socket { get; private set; } + public Socket ConnectSocket { get; } public int Port { get; } - public void Accept() - { - new Thread(() => - { - using (_listenSocket) - { - Socket = _listenSocket.Accept(); - - var stream = new NetworkStream(Socket); - _writer = new BinaryWriter(stream); - _reader = new BinaryReader(stream); - - // Read incoming messages on the background thread - new Thread(ReadMessages) { IsBackground = true }.Start(); - } - }) { IsBackground = true }.Start(); - } + public abstract void Connect(); public void Send(Message message) { @@ -104,6 +78,16 @@ namespace Microsoft.DotNet.Tools.Test SendError(ex.ToString()); } + protected void StartReadingMessages() + { + var stream = new NetworkStream(Socket); + _writer = new BinaryWriter(stream); + _reader = new BinaryReader(stream); + + // Read incoming messages on the background thread + new Thread(ReadMessages) { IsBackground = true }.Start(); + } + private void ReadMessages() { while (true) @@ -140,10 +124,7 @@ namespace Microsoft.DotNet.Tools.Test public void Dispose() { - if (Socket != null) - { - Socket.Dispose(); - } + Socket?.Dispose(); } } } \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-test/ReportingChannelFactory.cs b/src/dotnet/commands/dotnet-test/ReportingChannels/ReportingChannelFactory.cs similarity index 83% rename from src/dotnet/commands/dotnet-test/ReportingChannelFactory.cs rename to src/dotnet/commands/dotnet-test/ReportingChannels/ReportingChannelFactory.cs index f8a5940ea..0ecec58c6 100644 --- a/src/dotnet/commands/dotnet-test/ReportingChannelFactory.cs +++ b/src/dotnet/commands/dotnet-test/ReportingChannels/ReportingChannelFactory.cs @@ -11,7 +11,7 @@ namespace Microsoft.DotNet.Tools.Test public IReportingChannel CreateTestRunnerChannel() { - var testRunnerChannel = ReportingChannel.ListenOn(0); + var testRunnerChannel = TestRunnerReportingChannel.ListenOn(0); TestRunnerChannelCreated?.Invoke(this, testRunnerChannel); @@ -20,7 +20,7 @@ namespace Microsoft.DotNet.Tools.Test public IReportingChannel CreateAdapterChannel(int port) { - return ReportingChannel.ListenOn(port); + return AdapterReportingChannel.ConnectTo(port); } } } diff --git a/src/dotnet/commands/dotnet-test/ReportingChannels/TestRunnerReportingChannel.cs b/src/dotnet/commands/dotnet-test/ReportingChannels/TestRunnerReportingChannel.cs new file mode 100644 index 000000000..204988441 --- /dev/null +++ b/src/dotnet/commands/dotnet-test/ReportingChannels/TestRunnerReportingChannel.cs @@ -0,0 +1,42 @@ +// 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.Net; +using System.Net.Sockets; +using System.Threading; + +namespace Microsoft.DotNet.Tools.Test +{ + public class TestRunnerReportingChannel : ReportingChannel + { + public static ReportingChannel ListenOn(int port) + { + // This fixes the mono incompatibility but ties it to ipv4 connections + var listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + + listenSocket.Bind(new IPEndPoint(IPAddress.Loopback, port)); + listenSocket.Listen(10); + + return new TestRunnerReportingChannel(listenSocket, ((IPEndPoint)listenSocket.LocalEndPoint)); + } + + private TestRunnerReportingChannel(Socket connectSocket, IPEndPoint ipEndPoint) + : base(connectSocket, ipEndPoint.Port) + { + } + + public override void Connect() + { + new Thread(() => + { + using (ConnectSocket) + { + Socket = ConnectSocket.Accept(); + + StartReadingMessages(); + } + }) + { IsBackground = true }.Start(); + } + } +} diff --git a/src/dotnet/project.json b/src/dotnet/project.json index b4a5354f1..ed86374d8 100644 --- a/src/dotnet/project.json +++ b/src/dotnet/project.json @@ -1,77 +1,73 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "compileExclude": [ + "commands/dotnet-new/CSharp_Console/**", + "commands/dotnet-new/FSharp_Console/**" + ], + "resource": [ + "commands/dotnet-new/CSharp_Console/NuGet.Config", + "commands/dotnet-new/CSharp_Console/Program.cs", + "commands/dotnet-new/CSharp_Console/project.json.template", + "commands/dotnet-new/FSharp_Console/NuGet.config", + "commands/dotnet-new/FSharp_Console/Program.fs", + "commands/dotnet-new/FSharp_Console/project.json.template" + ], + "dependencies": { + "Newtonsoft.Json": "7.0.1", + "Microsoft.Net.Compilers.netcore": "1.3.0-beta1-20160225-02", + "Microsoft.FSharp.Compiler.netcore": "1.0.0-alpha-151218", + "Microsoft.Net.CSharp.Interactive.netcore": "1.3.0-beta1-20160225-02", + "Microsoft.CodeAnalysis.CSharp": "1.3.0-beta1-20160225-02", + "Microsoft.DiaSymReader.Native": "1.3.3", + "NuGet.CommandLine.XPlat": "3.4.0-rtm-0763", + "System.CommandLine": "0.1.0-e160119-1", + "Microsoft.DotNet.ProjectModel": "1.0.0-*", + "Microsoft.DotNet.Compiler.Common": "1.0.0-*", + "Microsoft.DotNet.Cli.Utils": "1.0.0-*", + "Microsoft.DotNet.ILCompiler.SDK": "1.0.6-prerelease-00003", + "Microsoft.Extensions.Logging": "1.0.0-rc2-16040", + "Microsoft.Extensions.Logging.Console": "1.0.0-rc2-16040", + "Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-16537", + "Microsoft.Extensions.CommandLineUtils.Sources": { + "type": "build", + "version": "1.0.0-rc2-16453" }, - "compileExclude": [ - "commands/dotnet-new/CSharp_Console/**", - "commands/dotnet-new/FSharp_Console/**" - ], - "resource": [ - "commands/dotnet-new/CSharp_Console/NuGet.Config", - "commands/dotnet-new/CSharp_Console/Program.cs", - "commands/dotnet-new/CSharp_Console/project.json.template", - "commands/dotnet-new/FSharp_Console/NuGet.config", - "commands/dotnet-new/FSharp_Console/Program.fs", - "commands/dotnet-new/FSharp_Console/project.json.template" - ], - "dependencies": { - "Newtonsoft.Json": "7.0.1", - - "Microsoft.Net.Compilers.netcore": "1.3.0-beta1-20160225-02", - "Microsoft.FSharp.Compiler.netcore": "1.0.0-alpha-151218", - "Microsoft.Net.CSharp.Interactive.netcore": "1.3.0-beta1-20160225-02", - "Microsoft.CodeAnalysis.CSharp": "1.3.0-beta1-20160225-02", - "Microsoft.DiaSymReader.Native": "1.3.3", - - "NuGet.CommandLine.XPlat": "3.4.0-rtm-0733", - "System.CommandLine": "0.1.0-e160119-1", - - "Microsoft.DotNet.ProjectModel": "1.0.0-*", - "Microsoft.DotNet.Compiler.Common": "1.0.0-*", - "Microsoft.DotNet.Cli.Utils": "1.0.0-*", - "Microsoft.DotNet.ILCompiler.SDK": "1.0.6-prerelease-00003", - - "Microsoft.Extensions.Logging": "1.0.0-rc2-16040", - "Microsoft.Extensions.Logging.Console": "1.0.0-rc2-16040", - "Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-16537", - "Microsoft.Extensions.CommandLineUtils.Sources": { - "type": "build", - "version": "1.0.0-rc2-16453" - }, - "Microsoft.Dnx.Runtime.CommandParsing.Sources": { - "version": "1.0.0-rc2-16453", - "type": "build" - }, - "Microsoft.Dnx.Runtime.Sources": { - "version": "1.0.0-rc2-16453", - "type": "build" - }, - "Microsoft.Extensions.Testing.Abstractions": "1.0.0-*", - "Microsoft.NETCore.ConsoleHost": "1.0.0-rc2-23901", - "Microsoft.NETCore.TestHost": "1.0.0-rc2-23901", - "NETStandard.Library": "1.0.0-rc2-23901", - "System.Reflection.Metadata": "1.3.0-beta-23901", - "System.Diagnostics.TextWriterTraceListener": "4.0.0-rc2-23901", - "System.Diagnostics.TraceSource": "4.0.0-rc2-23901", - "System.Linq.Expressions": "4.0.11-rc2-23901", - "System.Xml.XDocument": "4.0.11-rc2-23901", - "System.Resources.ReaderWriter": "4.0.0-rc2-23901", - "System.Net.Sockets": "4.1.0-rc2-23901", - "System.IO.Compression.ZipFile": "4.0.1-rc2-23901", - "System.Threading.ThreadPool": "4.0.10-rc2-23901", - "System.Runtime.Serialization.Primitives": "4.1.0-rc2-23901", - - "System.Private.DataContractSerialization": "4.1.0-rc2-23901" + "Microsoft.Dnx.Runtime.CommandParsing.Sources": { + "version": "1.0.0-rc2-16453", + "type": "build" }, - "frameworks": { - "netstandardapp1.5": { - "imports": [ - "dnxcore50", - "portable-net45+win8" - ] - } + "Microsoft.Dnx.Runtime.Sources": { + "version": "1.0.0-rc2-16453", + "type": "build" }, - "scripts": { + "Microsoft.Extensions.Testing.Abstractions": "1.0.0-*", + "Microsoft.NETCore.ConsoleHost": "1.0.0-rc2-23911", + "Microsoft.NETCore.TestHost": "1.0.0-rc2-23911", + "NETStandard.Library": "1.5.0-rc2-23911", + "System.Reflection.Metadata": "1.3.0-rc2-23911", + "System.Diagnostics.TextWriterTraceListener": "4.0.0-rc2-23911", + "System.Diagnostics.TraceSource": "4.0.0-rc2-23911", + "System.Linq.Expressions": "4.0.11-rc2-23911", + "System.Xml.XDocument": "4.0.11-rc2-23911", + "System.Resources.ReaderWriter": "4.0.0-rc2-23911", + "System.Net.Sockets": "4.1.0-rc2-23911", + "System.Threading.ThreadPool": "4.0.10-rc2-23911", + "Microsoft.Win32.Registry": { + "version": "4.0.0-rc2-23911", + "exclude": "Compile" + }, + "System.Net.NameResolution": "4.0.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ] } + }, + "scripts": {} } diff --git a/src/sharedframework/framework/Program.cs b/src/sharedframework/framework/Program.cs new file mode 100644 index 000000000..4aa370b90 --- /dev/null +++ b/src/sharedframework/framework/Program.cs @@ -0,0 +1,11 @@ +using System; + +namespace ConsoleApplication +{ + public class Program + { + public static void Main(string[] args) + { + } + } +} \ No newline at end of file diff --git a/TestAssets/TestProjects/BuildTestStandaloneProject/project.json b/src/sharedframework/framework/project.json similarity index 51% rename from TestAssets/TestProjects/BuildTestStandaloneProject/project.json rename to src/sharedframework/framework/project.json index 5aff9ad77..b10880254 100644 --- a/TestAssets/TestProjects/BuildTestStandaloneProject/project.json +++ b/src/sharedframework/framework/project.json @@ -1,16 +1,13 @@ { - "dependencies": { }, - "frameworks": { - "netstandardapp1.5": { - "imports": [ - "dnxcore50", - "portable-net45+win8" - ], - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901" - } - } + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true }, + + "dependencies": { + "Microsoft.NETCore.App": "1.0.0-rc2-23911" + }, + "runtimes": { "win7-x64": {}, "win7-x86": {}, @@ -20,5 +17,11 @@ "centos.7-x64": {}, "rhel.7.2-x64": {}, "debian.8.2-x64": {} + }, + + "frameworks": { + "dnxcore50": { + "imports": [ "portable-net45+win8"] + } } -} +} \ No newline at end of file diff --git a/src/sharedframework/rid-fallbacks/windows.json b/src/sharedframework/rid-fallbacks/windows.json new file mode 100644 index 000000000..aff335e39 --- /dev/null +++ b/src/sharedframework/rid-fallbacks/windows.json @@ -0,0 +1,12 @@ +{ + "runtimes": { + "win10-x64": [ "win10", "win81-x64", "win81", "win8-x64", "win8", "win7-x64", "win7", "win-x64", "win", "any", "base" ], + "win10-x86": [ "win10", "win81-x86", "win81", "win8-x86", "win8", "win7-x86", "win7", "win-x86", "win", "any", "base" ], + "win81-x64": [ "win81", "win8-x64", "win8", "win7-x64", "win7", "win-x64", "win", "any", "base" ], + "win81-x86": [ "win81", "win8-x86", "win8", "win7-x86", "win7", "win-x86", "win", "any", "base" ], + "win8-x64": [ "win8", "win7-x64", "win7", "win-x64", "win", "any", "base" ], + "win8-x86": [ "win8", "win7-x86", "win7", "win-x86", "win", "any", "base" ], + "win7-x64": [ "win7", "win-x64", "win", "any", "base" ], + "win7-x86": [ "win7", "win-x86", "win", "any", "base" ] + } +} diff --git a/test/ArgumentForwardingTests/project.json b/test/ArgumentForwardingTests/project.json index 8b99a663f..70c21b3f1 100644 --- a/test/ArgumentForwardingTests/project.json +++ b/test/ArgumentForwardingTests/project.json @@ -1,31 +1,32 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "Microsoft.DotNet.ProjectModel": { + "target": "project" }, - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "System.IO.Compression": "4.1.0-rc2-23901", - - "Microsoft.DotNet.ProjectModel": { "target": "project" }, - "Microsoft.DotNet.Cli.Utils": { "target": "project" }, - "Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" }, - - "xunit": "2.1.0", - "dotnet-test-xunit": "1.0.0-dev-79755-47" + "Microsoft.DotNet.Cli.Utils": { + "target": "project" }, - - "frameworks": { - "netstandardapp1.5": { - "imports": [ - "dnxcore50", - "portable-net45+win8" - ] - } + "Microsoft.DotNet.Tools.Tests.Utilities": { + "target": "project" }, - - "testRunner": "xunit", - - "scripts": { "precompile": "dotnet build ../ArgumentsReflector/project.json --framework netstandardapp1.5 --runtime %compile:RuntimeIdentifier% --output %compile:RuntimeOutputDir%" } + "xunit": "2.1.0", + "dotnet-test-xunit": "1.0.0-dev-91790-12" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ] + } + }, + "testRunner": "xunit", + "scripts": { + "precompile": "dotnet build ../ArgumentsReflector/project.json --framework netstandardapp1.5 --runtime %compile:RuntimeIdentifier% --output %compile:RuntimeOutputDir%" + } } diff --git a/test/ArgumentsReflector/project.json b/test/ArgumentsReflector/project.json index b78dfacbe..6869effa2 100644 --- a/test/ArgumentsReflector/project.json +++ b/test/ArgumentsReflector/project.json @@ -1,18 +1,17 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true - }, - - "dependencies": { - "NETStandard.Library" : "1.0.0-rc2-23901" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": "dnxcore50" - } - }, - - "content": ["reflector_cmd.cmd"] + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": "dnxcore50" + } + }, + "content": [ + "reflector_cmd.cmd" + ] } diff --git a/test/EndToEnd/EndToEndTest.cs b/test/EndToEnd/EndToEndTest.cs index be21d46ca..a07507602 100644 --- a/test/EndToEnd/EndToEndTest.cs +++ b/test/EndToEnd/EndToEndTest.cs @@ -8,11 +8,13 @@ using System.Runtime.InteropServices; using Microsoft.DotNet.Tools.Test.Utilities; using Microsoft.Extensions.PlatformAbstractions; using Xunit; +using System.Diagnostics; namespace Microsoft.DotNet.Tests.EndToEnd { public class EndToEndTest : TestBase { + private static readonly string NetStandardTfm = "netstandard1.5"; private static readonly string s_expectedOutput = "Hello World!" + Environment.NewLine; private static readonly string s_testdirName = "e2etestroot"; private static readonly string s_outputdirName = "test space/bin"; @@ -42,20 +44,20 @@ namespace Microsoft.DotNet.Tests.EndToEnd [Fact] public void TestDotnetBuild() { - var buildCommand = new BuildCommand(TestProject, output: OutputDirectory, framework: DefaultFramework); + var buildCommand = new BuildCommand(TestProject, output: OutputDirectory, framework: NetStandardTfm); buildCommand.Execute().Should().Pass(); - TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName(), s_expectedOutput); + TestOutputExecutable(OutputDirectory, buildCommand.GetPortableOutputName(), s_expectedOutput); } [Fact] public void TestDotnetIncrementalBuild() { // first build - var buildCommand = new BuildCommand(TestProject, output: OutputDirectory, framework: DefaultFramework); + var buildCommand = new BuildCommand(TestProject, output: OutputDirectory, framework: NetStandardTfm); buildCommand.Execute().Should().Pass(); - TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName(), s_expectedOutput); + TestOutputExecutable(OutputDirectory, buildCommand.GetPortableOutputName(), s_expectedOutput); var binariesOutputDirectory = GetCompilationOutputPath(OutputDirectory, false); var latestWriteTimeFirstBuild = GetLastWriteTimeUtcOfDirectoryFiles( @@ -63,7 +65,7 @@ namespace Microsoft.DotNet.Tests.EndToEnd // second build; should get skipped (incremental because no inputs changed) buildCommand.Execute().Should().Pass(); - TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName(), s_expectedOutput); + TestOutputExecutable(OutputDirectory, buildCommand.GetPortableOutputName(), s_expectedOutput); var latestWriteTimeUtcSecondBuild = GetLastWriteTimeUtcOfDirectoryFiles( binariesOutputDirectory); @@ -73,14 +75,14 @@ namespace Microsoft.DotNet.Tests.EndToEnd // third build; should get compiled because the source file got touched buildCommand.Execute().Should().Pass(); - TestOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName(), s_expectedOutput); + TestOutputExecutable(OutputDirectory, buildCommand.GetPortableOutputName(), s_expectedOutput); var latestWriteTimeUtcThirdBuild = GetLastWriteTimeUtcOfDirectoryFiles( binariesOutputDirectory); Assert.NotEqual(latestWriteTimeUtcSecondBuild, latestWriteTimeUtcThirdBuild); } - [Fact] + [Fact(Skip = "Native compilation isn't shipping in 1.0 and we're moving it out anyway")] public void TestDotnetBuildNativeRyuJit() { if (!IsNativeCompilationSupported()) @@ -88,14 +90,14 @@ namespace Microsoft.DotNet.Tests.EndToEnd return; } - var buildCommand = new BuildCommand(TestProject, output: OutputDirectory, native: true, framework: DefaultFramework); + var buildCommand = new BuildCommand(TestProject, output: OutputDirectory, native: true, framework: NetStandardTfm); buildCommand.Execute().Should().Pass(); TestNativeOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName(), s_expectedOutput); } - [Fact] + [Fact(Skip = "Native compilation isn't shipping in 1.0 and we're moving it out anyway")] public void TestDotnetBuildNativeCpp() { if (!IsNativeCompilationSupported()) @@ -103,14 +105,14 @@ namespace Microsoft.DotNet.Tests.EndToEnd return; } - var buildCommand = new BuildCommand(TestProject, output: OutputDirectory, native: true, nativeCppMode: true, framework: DefaultFramework); + var buildCommand = new BuildCommand(TestProject, output: OutputDirectory, native: true, nativeCppMode: true, framework: NetStandardTfm); buildCommand.Execute().Should().Pass(); TestNativeOutputExecutable(OutputDirectory, buildCommand.GetOutputExecutableName(), s_expectedOutput); } - [Fact] + [Fact(Skip = "Native compilation isn't shipping in 1.0 and we're moving it out anyway")] public void TestDotnetCompileNativeCppIncremental() { if (!IsNativeCompilationSupported()) @@ -119,7 +121,7 @@ namespace Microsoft.DotNet.Tests.EndToEnd } // first build - var buildCommand = new BuildCommand(TestProject, output: OutputDirectory, native: true, nativeCppMode: true, framework: DefaultFramework); + var buildCommand = new BuildCommand(TestProject, output: OutputDirectory, native: true, nativeCppMode: true, framework: NetStandardTfm); var binariesOutputDirectory = GetCompilationOutputPath(OutputDirectory, false); buildCommand.Execute().Should().Pass(); @@ -163,7 +165,7 @@ namespace Microsoft.DotNet.Tests.EndToEnd var publishCommand = new PublishCommand(TestProject, output: OutputDirectory); publishCommand.Execute().Should().Pass(); - TestExecutable(OutputDirectory, publishCommand.GetOutputExecutable(), s_expectedOutput); + TestExecutable(OutputDirectory, publishCommand.GetPortableOutputName(), s_expectedOutput); } [Fact] diff --git a/test/EndToEnd/project.json b/test/EndToEnd/project.json index 4bcbd9290..ef90e4e9e 100644 --- a/test/EndToEnd/project.json +++ b/test/EndToEnd/project.json @@ -3,20 +3,21 @@ "compilationOptions": { "emitEntryPoint": true }, - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "System.IO.Compression": "4.1.0-rc2-23901", - - "Microsoft.DotNet.ProjectModel": { "target": "project" }, - "Microsoft.DotNet.Cli.Utils": { "target": "project" }, - "Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" }, - + "NETStandard.Library": "1.5.0-rc2-23911", + "Microsoft.DotNet.ProjectModel": { + "target": "project" + }, + "Microsoft.DotNet.Cli.Utils": { + "target": "project" + }, + "Microsoft.DotNet.Tools.Tests.Utilities": { + "target": "project" + }, "xunit": "2.1.0", "xunit.netcore.extensions": "1.0.0-prerelease-*", "dotnet-test-xunit": "1.0.0-dev-91790-12" }, - "frameworks": { "netstandardapp1.5": { "imports": [ @@ -25,6 +26,5 @@ ] } }, - "testRunner": "xunit" } diff --git a/test/Installer/testmsi.ps1 b/test/Installer/testmsi.ps1 index 3f4f0d3cc..d1b82f67b 100644 --- a/test/Installer/testmsi.ps1 +++ b/test/Installer/testmsi.ps1 @@ -38,7 +38,7 @@ $testName = "Microsoft.DotNet.Cli.Msi.Tests" $testDir="$PSScriptRoot\$testName" $testBin="$RepoRoot\artifacts\tests\$testName" -pushd "$Stage2Dir\bin" +pushd "$Stage2Dir" try { .\dotnet restore ` diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectToolsCommandResolver.cs b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectToolsCommandResolver.cs index c414b1345..63a85bfc7 100644 --- a/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectToolsCommandResolver.cs +++ b/test/Microsoft.DotNet.Cli.Utils.Tests/GivenAProjectToolsCommandResolver.cs @@ -16,11 +16,14 @@ using Microsoft.Extensions.PlatformAbstractions; using System.Threading; using FluentAssertions; using NuGet.Frameworks; +using NuGet.Versioning; +using NuGet.ProjectModel; namespace Microsoft.DotNet.Cli.Utils.Tests { public class GivenAProjectToolsCommandResolver { + private static readonly NuGetFramework s_toolPackageFramework = FrameworkConstants.CommonFrameworks.NetStandardApp15; private static readonly string s_liveProjectDirectory = Path.Combine(AppContext.BaseDirectory, "TestAssets/TestProjects/AppWithToolDependency"); @@ -77,13 +80,13 @@ namespace Microsoft.DotNet.Cli.Utils.Tests } [Fact] - public void It_returns_a_CommandSpec_with_CoreHost_as_FileName_and_CommandName_in_Args_when_CommandName_exists_in_ProjectTools() + public void It_returns_a_CommandSpec_with_DOTNET_as_FileName_and_CommandName_in_Args_when_CommandName_exists_in_ProjectTools() { var projectToolsCommandResolver = SetupProjectToolsCommandResolver(); var commandResolverArguments = new CommandResolverArguments() { - CommandName = "dotnet-hello", + CommandName = "dotnet-portable", CommandArguments = null, ProjectDirectory = s_liveProjectDirectory }; @@ -94,7 +97,7 @@ namespace Microsoft.DotNet.Cli.Utils.Tests var commandFile = Path.GetFileNameWithoutExtension(result.Path); - commandFile.Should().Be("corehost"); + commandFile.Should().Be("dotnet"); result.Args.Should().Contain(commandResolverArguments.CommandName); } @@ -106,7 +109,7 @@ namespace Microsoft.DotNet.Cli.Utils.Tests var commandResolverArguments = new CommandResolverArguments() { - CommandName = "dotnet-hello", + CommandName = "dotnet-portable", CommandArguments = new [] { "arg with space"}, ProjectDirectory = s_liveProjectDirectory }; @@ -118,13 +121,13 @@ namespace Microsoft.DotNet.Cli.Utils.Tests } [Fact] - public void It_returns_a_CommandSpec_with_Args_as_CommandPath_when_returning_a_CommandSpec_and_CommandArguments_are_null() + public void It_returns_a_CommandSpec_with_Args_containing_CommandPath_when_returning_a_CommandSpec_and_CommandArguments_are_null() { var projectToolsCommandResolver = SetupProjectToolsCommandResolver(); var commandResolverArguments = new CommandResolverArguments() { - CommandName = "dotnet-hello", + CommandName = "dotnet-portable", CommandArguments = null, ProjectDirectory = s_liveProjectDirectory }; @@ -134,9 +137,51 @@ namespace Microsoft.DotNet.Cli.Utils.Tests result.Should().NotBeNull(); var commandPath = result.Args.Trim('"'); - commandPath.Should().Contain("dotnet-hello"); + commandPath.Should().Contain("dotnet-portable.dll"); + } - File.Exists(commandPath).Should().BeTrue(); + [Fact] + public void It_writes_a_deps_json_file_next_to_the_lockfile() + { + var projectToolsCommandResolver = SetupProjectToolsCommandResolver(); + + var commandResolverArguments = new CommandResolverArguments() + { + CommandName = "dotnet-portable", + CommandArguments = null, + ProjectDirectory = s_liveProjectDirectory + }; + + var context = ProjectContext.Create(Path.Combine(s_liveProjectDirectory, "project.json"), s_toolPackageFramework); + + var nugetPackagesRoot = context.PackagesDirectory; + var toolPathCalculator = new ToolPathCalculator(nugetPackagesRoot); + + var lockFilePath = toolPathCalculator.GetLockFilePath( + "dotnet-portable", + new NuGetVersion("1.0.0"), + s_toolPackageFramework); + + var directory = Path.GetDirectoryName(lockFilePath); + + var depsJsonFile = Directory + .EnumerateFiles(directory) + .FirstOrDefault(p => Path.GetFileName(p).EndsWith(FileNameSuffixes.DepsJson)); + + if (depsJsonFile != null) + { + File.Delete(depsJsonFile); + } + + var result = projectToolsCommandResolver.Resolve(commandResolverArguments); + result.Should().NotBeNull(); + + + depsJsonFile = Directory + .EnumerateFiles(directory) + .FirstOrDefault(p => Path.GetFileName(p).EndsWith(FileNameSuffixes.DepsJson)); + + depsJsonFile.Should().NotBeNull(); } private ProjectToolsCommandResolver SetupProjectToolsCommandResolver( diff --git a/test/Microsoft.DotNet.Cli.Utils.Tests/project.json b/test/Microsoft.DotNet.Cli.Utils.Tests/project.json index 97536b3c7..7be15c9ff 100644 --- a/test/Microsoft.DotNet.Cli.Utils.Tests/project.json +++ b/test/Microsoft.DotNet.Cli.Utils.Tests/project.json @@ -1,39 +1,42 @@ { - "version": "1.0.0-*", - "compilationOptions": { - "emitEntryPoint": true + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "NuGet.Versioning": "3.4.0-rtm-0763", + "NuGet.Packaging": "3.4.0-rtm-0763", + "NuGet.Frameworks": "3.4.0-rtm-0763", + "NuGet.ProjectModel": "3.4.0-rtm-0763", + + "Microsoft.DotNet.ProjectModel": { + "target": "project" }, - - "dependencies": { - "NETStandard.Library" : "1.0.0-rc2-23901", - "System.IO.Compression": "4.1.0-rc2-23901", - - "Microsoft.DotNet.ProjectModel": { "target": "project" }, - "Microsoft.DotNet.Cli.Utils": { "target": "project" }, - - "Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" }, - - "moq.netcore": "4.4.0-beta8", - "xunit": "2.1.0", - "dotnet-test-xunit": "1.0.0-dev-79755-47" + "Microsoft.DotNet.Cli.Utils": { + "target": "project" }, - - "frameworks": { - "netstandardapp1.5": { - "imports": [ - "dnxcore50", - "portable-net45+win8" - ] - } + "Microsoft.DotNet.Tools.Tests.Utilities": { + "target": "project" }, - - "content": [ - "../../TestAssets/TestProjects/OutputStandardOutputAndError/*", - "../../TestAssets/TestProjects/TestAppWithArgs/*", - "../../TestAssets/TestProjects/AppWithDirectAndToolDependency/**/*", - "../../TestAssets/TestProjects/AppWithDirectDependency/**/*", - "../../TestAssets/TestProjects/AppWithToolDependency/**/*" - ], - - "testRunner": "xunit" + "moq.netcore": "4.4.0-beta8", + "xunit": "2.1.0", + "dotnet-test-xunit": "1.0.0-dev-91790-12" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ] + } + }, + "content": [ + "../../TestAssets/TestProjects/OutputStandardOutputAndError/*", + "../../TestAssets/TestProjects/TestAppWithArgs/*", + "../../TestAssets/TestProjects/AppWithDirectAndToolDependency/**/*", + "../../TestAssets/TestProjects/AppWithDirectDependency/**/*", + "../../TestAssets/TestProjects/AppWithToolDependency/**/*" + ], + "testRunner": "xunit" } diff --git a/test/Microsoft.DotNet.Compiler.Common.Tests/project.json b/test/Microsoft.DotNet.Compiler.Common.Tests/project.json index 6b7d51697..e95dacee2 100644 --- a/test/Microsoft.DotNet.Compiler.Common.Tests/project.json +++ b/test/Microsoft.DotNet.Compiler.Common.Tests/project.json @@ -1,26 +1,26 @@ { - "version": "1.0.0-*", - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "System.IO.Compression": "4.1.0-rc2-23901", - - "Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" }, - "Microsoft.DotNet.ProjectModel": { "target": "project" }, - "Microsoft.DotNet.Compiler.Common": { "target": "project" }, - - "xunit": "2.1.0", - "dotnet-test-xunit": "1.0.0-dev-79755-47" + "version": "1.0.0-*", + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "Microsoft.DotNet.Tools.Tests.Utilities": { + "target": "project" }, - - "frameworks": { - "netstandardapp1.5": { - "imports": [ - "dnxcore50", - "portable-net45+win8" - ] - } + "Microsoft.DotNet.ProjectModel": { + "target": "project" }, - - "testRunner": "xunit" + "Microsoft.DotNet.Compiler.Common": { + "target": "project" + }, + "xunit": "2.1.0", + "dotnet-test-xunit": "1.0.0-dev-91790-12" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ] + } + }, + "testRunner": "xunit" } diff --git a/test/Microsoft.DotNet.ProjectModel.Tests/project.json b/test/Microsoft.DotNet.ProjectModel.Tests/project.json index edaaef649..25cf46739 100644 --- a/test/Microsoft.DotNet.ProjectModel.Tests/project.json +++ b/test/Microsoft.DotNet.ProjectModel.Tests/project.json @@ -1,10 +1,13 @@ { "version": "1.0.0-*", "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "System.IO.Compression": "4.1.0-rc2-23901", - "Microsoft.DotNet.ProjectModel": { "target": "project" }, - "Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" }, + "NETStandard.Library": "1.5.0-rc2-23911", + "Microsoft.DotNet.ProjectModel": { + "target": "project" + }, + "Microsoft.DotNet.Tools.Tests.Utilities": { + "target": "project" + }, "xunit": "2.1.0", "dotnet-test-xunit": "1.0.0-dev-91790-12" }, diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/DirectoryInfoAssertions.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/DirectoryInfoAssertions.cs index 6d4aa575a..ad83606b7 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/DirectoryInfoAssertions.cs +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Assertions/DirectoryInfoAssertions.cs @@ -47,7 +47,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities } public AndConstraint HaveFiles(IEnumerable expectedFiles) - { + { foreach (var expectedFile in expectedFiles) { HaveFile(expectedFile); @@ -61,7 +61,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities 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/Commands/BuildCommand.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/BuildCommand.cs index da059a1fe..693b32586 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/BuildCommand.cs +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/BuildCommand.cs @@ -29,7 +29,6 @@ namespace Microsoft.DotNet.Tools.Test.Utilities private readonly bool _noIncremental; private readonly bool _noDependencies; private readonly string _runtime; - private readonly bool _forcePortable; private string OutputOption { @@ -41,16 +40,6 @@ namespace Microsoft.DotNet.Tools.Test.Utilities } } - private string ForcePortableOption - { - get - { - return _forcePortable ? - "--portable" : - string.Empty; - } - } - private string BuildBasePathOption { get @@ -228,9 +217,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities string cppCompilerFlags="", bool buildProfile=true, bool noIncremental=false, - bool noDependencies=false, - bool forcePortable=false - ) + bool noDependencies=false) : base("dotnet") { _projectPath = projectPath; @@ -253,7 +240,6 @@ namespace Microsoft.DotNet.Tools.Test.Utilities _buildProfile = buildProfile; _noIncremental = noIncremental; _noDependencies = noDependencies; - _forcePortable = forcePortable; } public override CommandResult Execute(string args = "") @@ -268,6 +254,11 @@ namespace Microsoft.DotNet.Tools.Test.Utilities return base.ExecuteWithCapturedOutput(args); } + public string GetPortableOutputName() + { + return $"{_project.Name}.dll"; + } + public string GetOutputExecutableName() { var result = _project.Name; @@ -277,7 +268,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities private string BuildArgs() { - return $"{BuildProfile} {ForcePortableOption} {NoDependencies} {NoIncremental} \"{_projectPath}\" {OutputOption} {BuildBasePathOption} {ConfigurationOption} {FrameworkOption} {RuntimeOption} {VersionSuffixOption} {NoHostOption} {NativeOption} {ArchitectureOption} {IlcArgsOption} {IlcPathOption} {AppDepSDKPathOption} {NativeCppModeOption} {CppCompilerFlagsOption}"; + return $"{BuildProfile} {NoDependencies} {NoIncremental} \"{_projectPath}\" {OutputOption} {BuildBasePathOption} {ConfigurationOption} {FrameworkOption} {RuntimeOption} {VersionSuffixOption} {NoHostOption} {NativeOption} {ArchitectureOption} {IlcArgsOption} {IlcPathOption} {AppDepSDKPathOption} {NativeCppModeOption} {CppCompilerFlagsOption}"; } } } diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/DotnetTestCommand.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/DotnetTestCommand.cs new file mode 100644 index 000000000..181bdd97b --- /dev/null +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/DotnetTestCommand.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; + +namespace Microsoft.DotNet.Tools.Test.Utilities +{ + public class DotnetTestCommand : TestCommand + { + public DotnetTestCommand() : base("dotnet") + { + } + + public override CommandResult Execute(string args = "") + { + args = $"test {args}"; + return base.Execute(args); + } + } +} diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/PublishCommand.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/PublishCommand.cs index 154b1d6cd..785a1d386 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/PublishCommand.cs +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/PublishCommand.cs @@ -1,16 +1,12 @@ // 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 Microsoft.DotNet.Cli.Utils; +using Microsoft.DotNet.ProjectModel; +using Microsoft.Extensions.PlatformAbstractions; 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; -using Microsoft.Extensions.PlatformAbstractions; namespace Microsoft.DotNet.Tools.Test.Utilities { @@ -18,14 +14,14 @@ namespace Microsoft.DotNet.Tools.Test.Utilities { private const string PublishSubfolderName = "publish"; - private Project _project; - private string _path; - private string _framework; - private string _runtime; - private string _config; - private string _output; + private readonly Project _project; + private readonly string _path; + private readonly string _framework; + private readonly string _runtime; + private readonly string _config; + private readonly string _output; - public PublishCommand(string projectPath, string framework="", string runtime="", string output="", string config="") + public PublishCommand(string projectPath, string framework = "", string runtime = "", string output = "", string config = "", bool forcePortable = false) : base("dotnet") { _path = projectPath; @@ -36,7 +32,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities _config = config; } - public override CommandResult Execute(string args="") + public override CommandResult Execute(string args = "") { args = $"publish {BuildArgs()} {args}"; return base.Execute(args); @@ -48,37 +44,41 @@ namespace Microsoft.DotNet.Tools.Test.Utilities return base.ExecuteWithCapturedOutput(args); } - public string ProjectName - { - get - { - return _project.Name; - } - } + public string ProjectName => _project.Name; - private string BuildRelativeOutputPath() + private string BuildRelativeOutputPath(bool portable) { // 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) ? PlatformServices.Default.Runtime.GetLegacyRestoreRuntimeIdentifier() : _runtime; - string output = Path.Combine(config, framework, runtime, PublishSubfolderName); - - return output; + if (!portable) + { + var runtime = string.IsNullOrEmpty(_runtime) ? PlatformServices.Default.Runtime.GetLegacyRestoreRuntimeIdentifier() : _runtime; + return Path.Combine(config, framework, runtime, PublishSubfolderName); + } + else + { + return Path.Combine(config, framework, PublishSubfolderName); + } } - public DirectoryInfo GetOutputDirectory() + public DirectoryInfo GetOutputDirectory(bool portable = false) { if (!string.IsNullOrEmpty(_output)) { return new DirectoryInfo(_output); } - string output = Path.Combine(_project.ProjectDirectory, "bin", BuildRelativeOutputPath()); + string output = Path.Combine(_project.ProjectDirectory, "bin", BuildRelativeOutputPath(portable)); return new DirectoryInfo(output); } + public string GetPortableOutputName() + { + return $"{_project.Name}.dll"; + } + public string GetOutputExecutable() { var result = _project.Name; @@ -88,27 +88,12 @@ namespace Microsoft.DotNet.Tools.Test.Utilities private string BuildArgs() { - return $"{_path} {GetFrameworkOption()} {GetRuntimeOption()} {GetOutputOption()} {GetConfigOption()}"; + return $"{_path} {FrameworkOption} {RuntimeOption} {OutputOption} {ConfigOption}"; } - 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}"; - } + private string FrameworkOption => string.IsNullOrEmpty(_framework) ? "" : $"-f {_framework}"; + private string RuntimeOption => string.IsNullOrEmpty(_runtime) ? "" : $"-r {_runtime}"; + private string OutputOption => string.IsNullOrEmpty(_output) ? "" : $"-o \"{_output}\""; + private string ConfigOption => string.IsNullOrEmpty(_config) ? "" : $"-c {_output}"; } } diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/TestCommand.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/TestCommand.cs index 16a677add..ef0f086e2 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/TestCommand.cs +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Commands/TestCommand.cs @@ -23,11 +23,7 @@ namespace Microsoft.DotNet.Tools.Test.Utilities public virtual CommandResult Execute(string args = "") { var commandPath = _command; - if (!Path.IsPathRooted(_command)) - { - _command = Env.GetCommandPath(_command) ?? - Env.GetCommandPathFromRootPath(AppContext.BaseDirectory, _command); - } + ResolveCommand(ref commandPath, ref args); Console.WriteLine($"Executing - {_command} {args}"); @@ -44,9 +40,11 @@ namespace Microsoft.DotNet.Tools.Test.Utilities { Console.WriteLine($"Executing (Captured Output) - {_command} {args}"); - var commandPath = Env.GetCommandPath(_command, ".exe", ".cmd", "") ?? - Env.GetCommandPathFromRootPath(AppContext.BaseDirectory, _command, ".exe", ".cmd", ""); - + var command = _command; + ResolveCommand(ref command, ref args); + var commandPath = Env.GetCommandPath(command, ".exe", ".cmd", "") ?? + Env.GetCommandPathFromRootPath(AppContext.BaseDirectory, command, ".exe", ".cmd", ""); + var stdOut = new StreamForwarder(); var stdErr = new StreamForwarder(); @@ -56,6 +54,26 @@ namespace Microsoft.DotNet.Tools.Test.Utilities return RunProcess(commandPath, args, stdOut, stdErr); } + private void ResolveCommand(ref string executable, ref string args) + { + if (executable.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) + { + var newArgs = ArgumentEscaper.EscapeSingleArg(executable); + if (!string.IsNullOrEmpty(args)) + { + newArgs += " " + args; + } + args = newArgs; + executable = "corehost"; + } + + if (!Path.IsPathRooted(executable)) + { + executable = Env.GetCommandPath(executable) ?? + Env.GetCommandPathFromRootPath(AppContext.BaseDirectory, executable); + } + } + private CommandResult RunProcess(string executable, string args, StreamForwarder stdOut, StreamForwarder stdErr) { var psi = new ProcessStartInfo @@ -88,8 +106,8 @@ namespace Microsoft.DotNet.Tools.Test.Utilities var result = new CommandResult( process.StartInfo, - process.ExitCode, - stdOut.CapturedOutput, + process.ExitCode, + stdOut.CapturedOutput, stdErr.CapturedOutput); return result; diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/Mock/FileSystemMockBuilder.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/Mock/FileSystemMockBuilder.cs index 40ac2aa37..b5bfd5a56 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/Mock/FileSystemMockBuilder.cs +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/Mock/FileSystemMockBuilder.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using Microsoft.Extensions.EnvironmentAbstractions; namespace Microsoft.Extensions.DependencyModel.Tests @@ -74,6 +75,11 @@ namespace Microsoft.Extensions.DependencyModel.Tests } return text; } + + public Stream OpenRead(string path) + { + return new MemoryStream(Encoding.UTF8.GetBytes(ReadAllText(path))); + } } private class DirectoryMock : IDirectory diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/TestBase.cs b/test/Microsoft.DotNet.Tools.Tests.Utilities/TestBase.cs index 2db36724b..0d5f4a0a0 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/TestBase.cs +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/TestBase.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.TestFramework; +using Microsoft.DotNet.ProjectModel; namespace Microsoft.DotNet.Tools.Test.Utilities { @@ -107,10 +108,20 @@ namespace Microsoft.DotNet.Tools.Test.Utilities string expectedOutput) { var executablePath = Path.Combine(outputDir, executableName); + var args = new List(); + + if (IsPortable(executablePath)) + { + args.Add("exec"); + args.Add(ArgumentEscaper.EscapeSingleArg(executablePath)); + + var muxer = new Muxer(); + executablePath = muxer.MuxerPath; + } var executableCommand = new TestCommand(executablePath); - var result = executableCommand.ExecuteWithCapturedOutput(""); + var result = executableCommand.ExecuteWithCapturedOutput(string.Join(" ", args)); result.Should().HaveStdOut(expectedOutput); result.Should().NotHaveStdErr(); @@ -141,5 +152,22 @@ namespace Microsoft.DotNet.Tools.Test.Utilities return executablePath; } + + private bool IsPortable(string executablePath) + { + var commandDir = Path.GetDirectoryName(executablePath); + + var runtimeConfigPath = Directory.EnumerateFiles(commandDir) + .FirstOrDefault(x => x.EndsWith("runtimeconfig.json")); + + if (runtimeConfigPath == null) + { + return false; + } + + var runtimeConfig = new RuntimeConfig(runtimeConfigPath); + Console.WriteLine(runtimeConfig.Framework); + return runtimeConfig.IsPortable; + } } } diff --git a/test/Microsoft.DotNet.Tools.Tests.Utilities/project.json b/test/Microsoft.DotNet.Tools.Tests.Utilities/project.json index cf42e619c..2463caa8f 100644 --- a/test/Microsoft.DotNet.Tools.Tests.Utilities/project.json +++ b/test/Microsoft.DotNet.Tools.Tests.Utilities/project.json @@ -1,32 +1,30 @@ { - "version": "1.0.0-*", - "description": "Microsoft.DotNet.Tools.Tests.Utilities Class Library", - "compilationOptions": { - "keyFile": "../../tools/Key.snk" - }, - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "System.Collections.Immutable": "1.2.0-rc2-23901", - "System.IO.Compression": "4.1.0-rc2-23901", - "FluentAssertions": "4.0.0", - "xunit": "2.1.0", - "dotnet-test-xunit": "1.0.0-dev-91790-12", - - "Microsoft.DotNet.TestFramework": "1.0.0-*", - "Microsoft.DotNet.Cli.Utils": "1.0.0-*", - "Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-16537", - "Microsoft.DotNet.InternalAbstractions": { - "target": "project", - "version": "1.0.0-*" - } - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": [ - "dnxcore50", - "portable-net45+win8" - ] - } - }, + "version": "1.0.0-*", + "description": "Microsoft.DotNet.Tools.Tests.Utilities Class Library", + "compilationOptions": { + "keyFile": "../../tools/Key.snk" + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "System.Collections.Immutable": "1.2.0-rc2-23911", + "FluentAssertions": "4.0.0", + "xunit": "2.1.0", + "dotnet-test-xunit": "1.0.0-dev-91790-12", + "Microsoft.DotNet.TestFramework": "1.0.0-*", + "Microsoft.DotNet.Cli.Utils": "1.0.0-*", + "Microsoft.DotNet.ProjectModel": "1.0.0-*", + "Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-16537", + "Microsoft.DotNet.InternalAbstractions": { + "target": "project", + "version": "1.0.0-*" + } + }, + "frameworks": { + "netstandardapp1.5": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ] + } + } } diff --git a/test/Microsoft.Extensions.DependencyModel.Tests/CompositeResolverTests.cs b/test/Microsoft.Extensions.DependencyModel.Tests/CompositeResolverTests.cs index c78bf7d4a..eb407f548 100644 --- a/test/Microsoft.Extensions.DependencyModel.Tests/CompositeResolverTests.cs +++ b/test/Microsoft.Extensions.DependencyModel.Tests/CompositeResolverTests.cs @@ -10,7 +10,7 @@ using Moq; using Xunit; using FluentAssertions; -namespace StreamForwarderTests +namespace Microsoft.Extensions.DependencyModel.Tests { public class CompositeResolverTests { @@ -55,14 +55,7 @@ namespace StreamForwarderTests failTwo.Object }; - var library = new CompilationLibrary( - string.Empty, - string.Empty, - string.Empty, - string.Empty, - Enumerable.Empty(), - Enumerable.Empty(), - false); + var library = TestLibraryFactory.Create(); var resolver = new CompositeCompilationAssemblyResolver(resolvers); var result = resolver.TryResolveAssemblyPaths(library, null); @@ -90,14 +83,7 @@ namespace StreamForwarderTests }; var assemblies = new List(); - var library = new CompilationLibrary( - string.Empty, - string.Empty, - string.Empty, - string.Empty, - Enumerable.Empty(), - Enumerable.Empty(), - false); + var library = TestLibraryFactory.Create(); var resolver = new CompositeCompilationAssemblyResolver(resolvers); var result = resolver.TryResolveAssemblyPaths(library, assemblies); diff --git a/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextCsvReaderTests.cs b/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextCsvReaderTests.cs deleted file mode 100644 index 7c19c1c69..000000000 --- a/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextCsvReaderTests.cs +++ /dev/null @@ -1,94 +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. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using System.Text; -using Microsoft.Extensions.DependencyModel; -using FluentAssertions; -using Xunit; - -namespace Microsoft.Extensions.DependencyModel.Tests -{ - public class DependencyContextCsvReaderTests - { - private DependencyContext Read(string text) - { - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(text))) - { - return new DependencyContextCsvReader().Read(stream); - } - } - - [Fact] - public void GroupsAssetsCorrectlyIntoLibraries() - { - var context = Read(@" -""Package"",""runtime.any.System.AppContext"",""4.1.0-rc2-23811"",""sha512-1"",""runtime"",""System.AppContext"",""lib\\dnxcore50\\System.AppContext.dll"" -""Package"",""runtime.any.System.AppContext"",""4.1.0-rc2-23811"",""sha512-1"",""runtime"",""System.AppContext"",""lib\\dnxcore50\\System.Runtime.dll"" -"); - context.RuntimeLibraries.Should().HaveCount(1); - var library = context.RuntimeLibraries.Single(); - library.Type.Should().Be("Package"); - library.Name.Should().Be("runtime.any.System.AppContext"); - library.Version.Should().Be("4.1.0-rc2-23811"); - library.Hash.Should().Be("sha512-1"); - library.Assemblies.Should().HaveCount(2).And - .Contain(a => a.Path == "lib\\dnxcore50\\System.AppContext.dll").And - .Contain(a => a.Path == "lib\\dnxcore50\\System.Runtime.dll"); - } - - [Fact] - public void IgnoresAllButRuntimeAssets() - { - var context = Read(@" -""Package"",""runtime.any.System.AppContext"",""4.1.0-rc2-23811"",""sha512-1"",""runtime"",""System.AppContext"",""lib\\dnxcore50\\System.AppContext.dll"" -""Package"",""runtime.any.System.AppContext"",""4.1.0-rc2-23811"",""sha512-1"",""native"",""System.AppContext"",""lib\\dnxcore50\\System.AppContext2.so"" -"); - context.RuntimeLibraries.Should().HaveCount(1); - var library = context.RuntimeLibraries.Single(); - library.Assemblies.Should().HaveCount(1).And - .Contain(a => a.Path == "lib\\dnxcore50\\System.AppContext.dll"); - } - - [Fact] - public void IgnoresNiDllAssemblies() - { - var context = Read(@" -""Package"",""runtime.any.System.AppContext"",""4.1.0-rc2-23811"",""sha512-1"",""runtime"",""System.AppContext"",""lib\\dnxcore50\\System.AppContext.dll"" -""Package"",""runtime.any.System.AppContext"",""4.1.0-rc2-23811"",""sha512-1"",""runtime"",""System.AppContext"",""lib\\dnxcore50\\System.AppContext.ni.dll"" -"); - context.RuntimeLibraries.Should().HaveCount(1); - var library = context.RuntimeLibraries.Single(); - library.Assemblies.Should().HaveCount(1).And - .Contain(a => a.Path == "lib\\dnxcore50\\System.AppContext.dll"); - } - - [Fact] - public void UsesTypeNameVersionAndHashToGroup() - { - var context = Read(@" -""Package"",""runtime.any.System.AppContext"",""4.1.0-rc2-23811"",""sha512-1"",""runtime"",""System.AppContext"",""lib\\dnxcore50\\System.AppContext.dll"" -""Package"",""runtime.any.System.AppContext"",""4.1.0-rc2-23812"",""sha512-1"",""runtime"",""System.AppContext"",""lib\\dnxcore50\\System.AppContext.dll"" -""Package"",""runtime.any.System.AppContext"",""4.1.0-rc2-23811"",""sha512-2"",""runtime"",""System.AppContext"",""lib\\dnxcore50\\System.AppContext.dll"" -""Package"",""runtime.any.System.AppContext2"",""4.1.0-rc2-23811"",""sha512-1"",""runtime"",""System.AppContext"",""lib\\dnxcore50\\System.AppContext.dll"" -""Project"",""runtime.any.System.AppContext"",""4.1.0-rc2-23811"",""sha512-1"",""runtime"",""System.AppContext"",""lib\\dnxcore50\\System.AppContext.dll"" -"); - context.RuntimeLibraries.Should().HaveCount(5); - } - - [Theory] - [InlineData("text")] - [InlineData(" ")] - [InlineData("\"")] - [InlineData(@""",""")] - [InlineData(@"\\")] - public void ThrowsFormatException(string intput) - { - Assert.Throws(() => Read(intput)); - } - } -} diff --git a/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextJsonReaderTest.cs b/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextJsonReaderTest.cs index b48d812e5..b611ebd69 100644 --- a/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextJsonReaderTest.cs +++ b/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextJsonReaderTest.cs @@ -26,7 +26,6 @@ namespace Microsoft.Extensions.DependencyModel.Tests @"{ ""runtimeTarget"": "".NETStandardApp,Version=v1.5/osx.10.10-x64"", ""targets"": { - "".NETStandardApp,Version=v1.5"": {}, "".NETStandardApp,Version=v1.5/osx.10.10-x64"": {}, } }"); @@ -35,21 +34,11 @@ namespace Microsoft.Extensions.DependencyModel.Tests context.Runtime.Should().Be("osx.10.10-x64"); } - [Fact] - public void DefaultsToPortable() - { - var context = Read( -@"{ -}"); - context.IsPortable.Should().BeTrue(); - } - [Fact] public void SetsPortableIfRuntimeTargetHasNoRid() { var context = Read( @"{ - ""runtimeTarget"": "".NETStandardApp,Version=v1.5"", ""targets"": { "".NETStandardApp,Version=v1.5"": {} } @@ -64,7 +53,6 @@ namespace Microsoft.Extensions.DependencyModel.Tests @"{ ""runtimeTarget"": "".NETStandardApp,Version=v1.5/osx.10.10-x64"", ""targets"": { - "".NETStandardApp,Version=v1.5"": {}, "".NETStandardApp,Version=v1.5/osx.10.10-x64"": {} } }"); @@ -88,22 +76,23 @@ namespace Microsoft.Extensions.DependencyModel.Tests { var context = Read( @"{ + ""targets"": { + "".NETStandardApp,Version=v1.5/osx.10.10-x64"": {}, + }, ""runtimes"": { - "".NETStandardApp,Version=v1.5"": { - ""osx.10.10-x64"": [ ], - ""osx.10.11-x64"": [ ""osx"" ], - ""rhel.7-x64"": [ ""linux-x64"", ""unix"" ] - } + ""osx.10.10-x64"": [ ], + ""osx.10.11-x64"": [ ""osx"" ], + ""rhel.7-x64"": [ ""linux-x64"", ""unix"" ] } }"); - context.RuntimeGraph.Should().Contain(p => p.Key == "osx.10.10-x64").Which - .Value.Should().BeEquivalentTo(); + context.RuntimeGraph.Should().Contain(p => p.Runtime == "osx.10.10-x64").Which + .Fallbacks.Should().BeEquivalentTo(); - context.RuntimeGraph.Should().Contain(p => p.Key == "osx.10.11-x64").Which - .Value.Should().BeEquivalentTo("osx"); + context.RuntimeGraph.Should().Contain(p => p.Runtime == "osx.10.11-x64").Which + .Fallbacks.Should().BeEquivalentTo("osx"); - context.RuntimeGraph.Should().Contain(p => p.Key == "rhel.7-x64").Which - .Value.Should().BeEquivalentTo("linux-x64", "unix"); + context.RuntimeGraph.Should().Contain(p => p.Runtime == "rhel.7-x64").Which + .Fallbacks.Should().BeEquivalentTo("linux-x64", "unix"); } [Fact] @@ -239,6 +228,9 @@ namespace Microsoft.Extensions.DependencyModel.Tests ""publicSign"": true, ""warningsAsErrors"": true, ""optimize"": true + }, + ""targets"": { + "".NETStandardApp,Version=v1.5/osx.10.10-x64"": {}, } }"); context.CompilationOptions.AllowUnsafe.Should().Be(true); diff --git a/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextJsonWriterTests.cs b/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextJsonWriterTests.cs index cafc90bf2..ec7ca1a8c 100644 --- a/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextJsonWriterTests.cs +++ b/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextJsonWriterTests.cs @@ -37,16 +37,16 @@ namespace Microsoft.Extensions.DependencyModel.Tests CompilationOptions compilationOptions = null, CompilationLibrary[] compileLibraries = null, RuntimeLibrary[] runtimeLibraries = null, - IReadOnlyList> runtimeGraph = null) + IReadOnlyList runtimeGraph = null) { return new DependencyContext( - target ?? string.Empty, + target ?? "DefaultTarget", runtime ?? string.Empty, isPortable ?? false, compilationOptions ?? CompilationOptions.Default, compileLibraries ?? new CompilationLibrary[0], runtimeLibraries ?? new RuntimeLibrary[0], - runtimeGraph ?? new KeyValuePair[0] + runtimeGraph ?? new RuntimeFallbacks[0] ); } @@ -58,8 +58,8 @@ namespace Microsoft.Extensions.DependencyModel.Tests "Target/runtime", runtimeGraph: new[] { - new KeyValuePair("win7-x64", new [] { "win6", "win5"}), - new KeyValuePair("win8-x64", new [] { "win7-x64"}), + new RuntimeFallbacks("win7-x64", new [] { "win6", "win5"}), + new RuntimeFallbacks("win8-x64", new [] { "win7-x64"}), })); var rids = result.Should().HaveProperty("runtimes") @@ -151,6 +151,7 @@ namespace Microsoft.Extensions.DependencyModel.Tests "1.2.3", "HASH", new [] { RuntimeAssembly.Create("Banana.dll")}, + new [] { "runtimes\\linux\\native\\native.so" }, new [] { new ResourceAssembly("en-US\\Banana.Resource.dll", "en-US")}, new [] { @@ -172,8 +173,11 @@ namespace Microsoft.Extensions.DependencyModel.Tests var library = target.Should().HavePropertyAsObject("PackageName/1.2.3").Subject; var dependencies = library.Should().HavePropertyAsObject("dependencies").Subject; dependencies.Should().HavePropertyValue("Fruits.Abstract.dll", "2.0.0"); + library.Should().HavePropertyAsObject("runtime") .Subject.Should().HaveProperty("Banana.dll"); + library.Should().HavePropertyAsObject("native") + .Subject.Should().HaveProperty("runtimes/linux/native/native.so"); var runtimeTargets = library.Should().HavePropertyAsObject("runtimeTargets").Subject; @@ -226,6 +230,7 @@ namespace Microsoft.Extensions.DependencyModel.Tests "1.2.3", "HASH", new [] { RuntimeAssembly.Create("Banana.dll")}, + new [] { "native.dll" }, new ResourceAssembly[] {}, new [] { @@ -247,8 +252,11 @@ namespace Microsoft.Extensions.DependencyModel.Tests var library = target.Should().HavePropertyAsObject("PackageName/1.2.3").Subject; var dependencies = library.Should().HavePropertyAsObject("dependencies").Subject; dependencies.Should().HavePropertyValue("Fruits.Abstract.dll", "2.0.0"); + library.Should().HavePropertyAsObject("runtime") .Subject.Should().HaveProperty("Banana.dll"); + library.Should().HavePropertyAsObject("native") + .Subject.Should().HaveProperty("native.dll"); library.Should().HavePropertyAsObject("compile") .Subject.Should().HaveProperty("ref/Banana.dll"); @@ -286,6 +294,7 @@ namespace Microsoft.Extensions.DependencyModel.Tests "1.2.3", "HASH", new [] { RuntimeAssembly.Create("Banana.dll")}, + new [] { "runtimes\\osx\\native\\native.dylib" }, new ResourceAssembly[] {}, new RuntimeTarget[] {}, new [] { @@ -303,6 +312,8 @@ namespace Microsoft.Extensions.DependencyModel.Tests dependencies.Should().HavePropertyValue("Fruits.Abstract.dll", "2.0.0"); library.Should().HavePropertyAsObject("runtime") .Subject.Should().HaveProperty("Banana.dll"); + library.Should().HavePropertyAsObject("native") + .Subject.Should().HaveProperty("runtimes/osx/native/native.dylib"); //libraries var libraries = result.Should().HavePropertyAsObject("libraries").Subject; @@ -327,6 +338,7 @@ namespace Microsoft.Extensions.DependencyModel.Tests "1.2.3", "HASH", new RuntimeAssembly[] { }, + new string[] { }, new [] { new ResourceAssembly("en-US/Fruits.resources.dll", "en-US") @@ -361,6 +373,7 @@ namespace Microsoft.Extensions.DependencyModel.Tests "1.2.3", "HASH", new RuntimeAssembly[] { }, + new string[] { }, new [] { new ResourceAssembly("en-US/Fruits.resources.dll", "en-US") diff --git a/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextLoaderTests.cs b/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextLoaderTests.cs new file mode 100644 index 000000000..3b6e88fea --- /dev/null +++ b/test/Microsoft.Extensions.DependencyModel.Tests/DependencyContextLoaderTests.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyModel; +using FluentAssertions; +using Xunit; + +namespace Microsoft.Extensions.DependencyModel.Tests +{ + public class DependencyContextTests + { + [Fact] + public void MergeMergesLibraries() + { + var compilationLibraries = new[] + { + CreateCompilation("PackageA"), + CreateCompilation("PackageB"), + }; + + var runtimeLibraries = new[] + { + CreateRuntime("PackageA"), + CreateRuntime("PackageB"), + }; + + var compilationLibrariesRedist = new[] + { + CreateCompilation("PackageB"), + CreateCompilation("PackageC"), + }; + + var runtimeLibrariesRedist = new[] + { + CreateRuntime("PackageB"), + CreateRuntime("PackageC"), + }; + + var context = new DependencyContext( + "Framework", + "runtime", + true, + CompilationOptions.Default, + compilationLibraries, + runtimeLibraries, + new RuntimeFallbacks[] { }); + + var contextRedist = new DependencyContext( + "Framework", + "runtime", + true, + CompilationOptions.Default, + compilationLibrariesRedist, + runtimeLibrariesRedist, + new RuntimeFallbacks[] { }); + + var result = context.Merge(contextRedist); + + result.CompileLibraries.Should().BeEquivalentTo(new[] + { + compilationLibraries[0], + compilationLibraries[1], + compilationLibrariesRedist[1], + }); + + result.RuntimeLibraries.Should().BeEquivalentTo(new[] + { + runtimeLibraries[0], + runtimeLibraries[1], + runtimeLibrariesRedist[1], + }); + } + + public void MergeMergesRuntimeGraph() + { + var context = new DependencyContext( + "Framework", + "runtime", + true, + CompilationOptions.Default, + Enumerable.Empty(), + Enumerable.Empty(), + new RuntimeFallbacks[] + { + new RuntimeFallbacks("win8-x64", new [] { "win8" }), + }); + + var contextRedist = new DependencyContext( + "Framework", + "runtime", + true, + CompilationOptions.Default, + Enumerable.Empty(), + Enumerable.Empty(), + new RuntimeFallbacks[] + { + new RuntimeFallbacks("win8", new [] { "win7-x64", "win7-x86" }), + }); + + var result = context.Merge(contextRedist); + result.RuntimeGraph.Should().Contain(g => g.Runtime == "win8-x64"). + Subject.Fallbacks.Should().BeEquivalentTo("win8"); + result.RuntimeGraph.Should().Contain(g => g.Runtime == "win8"). + Subject.Fallbacks.Should().BeEquivalentTo("win7-x64", "win7-x86"); + } + + private CompilationLibrary CreateCompilation(string name) + { + return new CompilationLibrary( + "project", + name, + "1.1.1", + "HASH", + new string[] { }, + new Dependency[] { }, + false); + } + + private RuntimeLibrary CreateRuntime(string name) + { + return new RuntimeLibrary( + "project", + name, + "1.1.1", + "HASH", + new RuntimeAssembly[] { }, + new string[] { }, + new ResourceAssembly[] { }, + new RuntimeTarget[] { }, + new Dependency[] {}, + false); + } + } +} diff --git a/test/Microsoft.Extensions.DependencyModel.Tests/ReferenceAssemblyResolverTests.cs b/test/Microsoft.Extensions.DependencyModel.Tests/ReferenceAssemblyResolverTests.cs index 7561c77dc..dbaf6589d 100644 --- a/test/Microsoft.Extensions.DependencyModel.Tests/ReferenceAssemblyResolverTests.cs +++ b/test/Microsoft.Extensions.DependencyModel.Tests/ReferenceAssemblyResolverTests.cs @@ -36,12 +36,12 @@ namespace Microsoft.Extensions.DependencyModel.Tests { var runtime = new Mock(); runtime.SetupGet(r => r.OperatingSystemPlatform).Returns(Platform.Windows); - + var environment = EnvironmentMockBuilder.Create() .AddVariable("DOTNET_REFERENCE_ASSEMBLIES_PATH", ReferencePath) .Build(); - var result = ReferenceAssemblyPathResolver.GetDefaultReferenceAssembliesPath(runtime.Object, environment); + var result = ReferenceAssemblyPathResolver.GetDefaultReferenceAssembliesPath(runtime.Object, FileSystemMockBuilder.Empty, environment); result.Should().Be(ReferencePath); } @@ -51,7 +51,7 @@ namespace Microsoft.Extensions.DependencyModel.Tests var runtime = new Mock(); runtime.SetupGet(r => r.OperatingSystemPlatform).Returns(Platform.Linux); - var result = ReferenceAssemblyPathResolver.GetDefaultReferenceAssembliesPath(runtime.Object, EnvironmentMockBuilder.Empty); + var result = ReferenceAssemblyPathResolver.GetDefaultReferenceAssembliesPath(runtime.Object, FileSystemMockBuilder.Empty, EnvironmentMockBuilder.Empty); result.Should().BeNull(); } @@ -66,7 +66,7 @@ namespace Microsoft.Extensions.DependencyModel.Tests .AddVariable("ProgramFiles", "Program Files") .Build(); - var result = ReferenceAssemblyPathResolver.GetDefaultReferenceAssembliesPath(runtime.Object, environment); + var result = ReferenceAssemblyPathResolver.GetDefaultReferenceAssembliesPath(runtime.Object, FileSystemMockBuilder.Empty, environment); result.Should().Be(Path.Combine("Program Files (x86)", "Reference Assemblies", "Microsoft", "Framework")); } @@ -80,7 +80,7 @@ namespace Microsoft.Extensions.DependencyModel.Tests .AddVariable("ProgramFiles", "Program Files") .Build(); - var result = ReferenceAssemblyPathResolver.GetDefaultReferenceAssembliesPath(runtime.Object, environment); + var result = ReferenceAssemblyPathResolver.GetDefaultReferenceAssembliesPath(runtime.Object, FileSystemMockBuilder.Empty, environment); result.Should().Be(Path.Combine("Program Files", "Reference Assemblies", "Microsoft", "Framework")); } diff --git a/test/Microsoft.Extensions.DependencyModel.Tests/project.json b/test/Microsoft.Extensions.DependencyModel.Tests/project.json index 18a43cf55..f5f578e2e 100644 --- a/test/Microsoft.Extensions.DependencyModel.Tests/project.json +++ b/test/Microsoft.Extensions.DependencyModel.Tests/project.json @@ -5,16 +5,15 @@ "keyFile": "../../tools/Key.snk" }, "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "System.IO.Compression": "4.1.0-rc2-23901", - - "Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" }, + "NETStandard.Library": "1.5.0-rc2-23911", + "Microsoft.DotNet.Tools.Tests.Utilities": { + "target": "project" + }, "FluentAssertions": "4.0.0", "moq.netcore": "4.4.0-beta8", "xunit": "2.1.0", "dotnet-test-xunit": "1.0.0-dev-91790-12" }, - "frameworks": { "netstandardapp1.5": { "imports": [ @@ -23,6 +22,5 @@ ] } }, - "testRunner": "xunit" -} \ No newline at end of file +} diff --git a/test/ScriptExecutorTests/project.json b/test/ScriptExecutorTests/project.json index 0317d0a3b..a7848a2c4 100644 --- a/test/ScriptExecutorTests/project.json +++ b/test/ScriptExecutorTests/project.json @@ -1,17 +1,19 @@ { "version": "1.0.0-*", - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - - "Microsoft.DotNet.ProjectModel": { "target": "project" }, - "Microsoft.DotNet.Cli.Utils": { "target": "project" }, - "Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" }, - + "NETStandard.Library": "1.5.0-rc2-23911", + "Microsoft.DotNet.ProjectModel": { + "target": "project" + }, + "Microsoft.DotNet.Cli.Utils": { + "target": "project" + }, + "Microsoft.DotNet.Tools.Tests.Utilities": { + "target": "project" + }, "xunit": "2.1.0", - "dotnet-test-xunit": "1.0.0-dev-79755-47" + "dotnet-test-xunit": "1.0.0-dev-91790-12" }, - "frameworks": { "netstandardapp1.5": { "imports": [ @@ -20,10 +22,8 @@ ] } }, - "content": [ "../../TestAssets/TestProjects/TestApp/**/*" ], - "testRunner": "xunit" } diff --git a/test/dotnet-build.Tests/BuildInvalidArgumentsTests.cs b/test/dotnet-build.Tests/BuildInvalidArgumentsTests.cs index 15a38b24b..a90f634b1 100644 --- a/test/dotnet-build.Tests/BuildInvalidArgumentsTests.cs +++ b/test/dotnet-build.Tests/BuildInvalidArgumentsTests.cs @@ -9,11 +9,11 @@ namespace Microsoft.DotNet.Tools.Builder.Tests [Fact] public void ErrorOccursWhenBuildingPortableProjectToSpecificOutputPathWithoutSpecifyingFramework() { - var testInstance = TestAssetsManager.CreateTestInstance("BuildTestPortableProject") + var testInstance = TestAssetsManager.CreateTestInstance("PortableTests") .WithLockFiles(); var result = new BuildCommand( - projectPath: testInstance.TestRoot, + projectPath: Path.Combine(testInstance.TestRoot, "PortableApp"), output: Path.Combine(testInstance.TestRoot, "out")) .ExecuteWithCapturedOutput(); @@ -24,11 +24,11 @@ namespace Microsoft.DotNet.Tools.Builder.Tests [Fact] public void ErrorOccursWhenBuildingPortableProjectAndSpecifyingFrameworkThatProjectDoesNotSupport() { - var testInstance = TestAssetsManager.CreateTestInstance("BuildTestPortableProject") + var testInstance = TestAssetsManager.CreateTestInstance("PortableTests") .WithLockFiles(); var result = new BuildCommand( - projectPath: testInstance.TestRoot, + projectPath: Path.Combine(testInstance.TestRoot, "PortableApp"), output: Path.Combine(testInstance.TestRoot, "out"), framework: "sl40") .ExecuteWithCapturedOutput(); @@ -40,11 +40,11 @@ namespace Microsoft.DotNet.Tools.Builder.Tests [Fact] public void ErrorOccursWhenBuildingStandaloneProjectToSpecificOutputPathWithoutSpecifyingFramework() { - var testInstance = TestAssetsManager.CreateTestInstance("BuildTestStandaloneProject") + var testInstance = TestAssetsManager.CreateTestInstance("PortableTests") .WithLockFiles(); var result = new BuildCommand( - projectPath: testInstance.TestRoot, + projectPath: Path.Combine(testInstance.TestRoot, "StandaloneApp"), output: Path.Combine(testInstance.TestRoot, "out")) .ExecuteWithCapturedOutput(); diff --git a/test/dotnet-build.Tests/BuildOutputTests.cs b/test/dotnet-build.Tests/BuildOutputTests.cs index 8d8072456..dae4de553 100644 --- a/test/dotnet-build.Tests/BuildOutputTests.cs +++ b/test/dotnet-build.Tests/BuildOutputTests.cs @@ -39,7 +39,7 @@ namespace Microsoft.DotNet.Tools.Builder.Tests private readonly string[] _libCompileFiles = { "TestLibrary" + FileNameSuffixes.DotNet.DynamicLib, - "TestLibrary" + FileNameSuffixes.DotNet.ProgramDatabase, + "TestLibrary" + FileNameSuffixes.DotNet.ProgramDatabase }; private void GetProjectInfo(string testRoot) @@ -114,7 +114,7 @@ namespace Microsoft.DotNet.Tools.Builder.Tests fileVersion.Should().NotBeNull(); fileVersion.Should().BeEquivalentTo("1.0.0.345"); } - + [Fact] public void SettingVersionSuffixFlag_ShouldStampAssemblyInfoInOutputAssembly() { diff --git a/test/dotnet-build.Tests/BuildPortableTests.cs b/test/dotnet-build.Tests/BuildPortableTests.cs index c9a0eebbd..5ea72db09 100644 --- a/test/dotnet-build.Tests/BuildPortableTests.cs +++ b/test/dotnet-build.Tests/BuildPortableTests.cs @@ -9,28 +9,27 @@ namespace Microsoft.DotNet.Tools.Builder.Tests [Fact] public void BuildingAPortableProjectProducesDepsFile() { - var testInstance = TestAssetsManager.CreateTestInstance("BuildTestPortableProject") + var testInstance = TestAssetsManager.CreateTestInstance("PortableTests") .WithLockFiles(); var result = new BuildCommand( - projectPath: testInstance.TestRoot, - forcePortable: true) + projectPath: Path.Combine(testInstance.TestRoot, "PortableApp")) .ExecuteWithCapturedOutput(); result.Should().Pass(); - var outputBase = new DirectoryInfo(Path.Combine(testInstance.TestRoot, "bin", "Debug")); + var outputBase = new DirectoryInfo(Path.Combine(testInstance.TestRoot, "PortableApp", "bin", "Debug")); - var netstandardappOutput = outputBase.Sub("netstandardapp1.5"); + var netstandardappOutput = outputBase.Sub("netstandard1.5"); netstandardappOutput.Should() .Exist().And .HaveFiles(new[] { - "BuildTestPortableProject.deps", - "BuildTestPortableProject.deps.json", - "BuildTestPortableProject.dll", - "BuildTestPortableProject.pdb" + "PortableApp.deps", + "PortableApp.deps.json", + "PortableApp.dll", + "PortableApp.pdb" }); } } diff --git a/test/dotnet-build.Tests/project.json b/test/dotnet-build.Tests/project.json index c158034ef..23dce9bc7 100644 --- a/test/dotnet-build.Tests/project.json +++ b/test/dotnet-build.Tests/project.json @@ -1,19 +1,16 @@ { "version": "1.0.0-*", - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "System.IO.Compression": "4.1.0-rc2-23901", - - "Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" }, + "NETStandard.Library": "1.5.0-rc2-23911", + "Microsoft.DotNet.Tools.Tests.Utilities": { + "target": "project" + }, "Microsoft.DotNet.Cli.Utils": { "target": "project" }, - "xunit": "2.1.0", "dotnet-test-xunit": "1.0.0-dev-91790-12" }, - "frameworks": { "netstandardapp1.5": { "imports": [ @@ -22,6 +19,5 @@ ] } }, - "testRunner": "xunit" } diff --git a/test/dotnet-compile.Tests/project.json b/test/dotnet-compile.Tests/project.json index dd02d8697..c1c79bacd 100644 --- a/test/dotnet-compile.Tests/project.json +++ b/test/dotnet-compile.Tests/project.json @@ -1,19 +1,16 @@ { "version": "1.0.0-*", - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "System.IO.Compression": "4.1.0-rc2-23901", - - "Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" }, + "NETStandard.Library": "1.5.0-rc2-23911", + "Microsoft.DotNet.Tools.Tests.Utilities": { + "target": "project" + }, "Microsoft.DotNet.Cli.Utils": { "target": "project" }, - "xunit": "2.1.0", "dotnet-test-xunit": "1.0.0-dev-91790-12" }, - "frameworks": { "netstandardapp1.5": { "imports": [ @@ -22,7 +19,6 @@ ] } }, - "content": [ "../../TestAssets/TestProjects/DependencyContextValidator/**/*", "../../TestAssets/TestProjects/TestLibraryWithAnalyzer/*", @@ -31,6 +27,5 @@ "../../TestAssets/TestProjects/TestAppCompilationContext/**/*", "../../TestAssets/TestProjects/global.json" ], - "testRunner": "xunit" } diff --git a/test/dotnet-compile.UnitTests/project.json b/test/dotnet-compile.UnitTests/project.json index 9813a2d85..e6cd5027b 100644 --- a/test/dotnet-compile.UnitTests/project.json +++ b/test/dotnet-compile.UnitTests/project.json @@ -1,23 +1,25 @@ { "version": "1.0.0-*", - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "System.IO.Compression": "4.1.0-rc2-23901", - + "NETStandard.Library": "1.5.0-rc2-23911", "Microsoft.DotNet.Cli.Utils": { "target": "project" }, - - "dotnet": { "target": "project" }, - "Microsoft.DotNet.ProjectModel": { "target": "project" }, - + "dotnet": { + "target": "project" + }, + "Microsoft.Win32.Registry": { + "version": "4.0.0-rc2-23911", + "exclude": "Compile" + }, + "Microsoft.DotNet.ProjectModel": { + "target": "project" + }, "xunit": "2.1.0", "dotnet-test-xunit": "1.0.0-dev-91790-12", "moq.netcore": "4.4.0-beta8", "FluentAssertions": "4.2.2" }, - "frameworks": { "netstandardapp1.5": { "imports": [ @@ -26,10 +28,8 @@ ] } }, - "content": [ "../../TestAssets/TestProjects/TestAppWithLibrary/**/*" ], - "testRunner": "xunit" } diff --git a/test/dotnet-pack.Tests/project.json b/test/dotnet-pack.Tests/project.json index 178d883d3..18b034060 100644 --- a/test/dotnet-pack.Tests/project.json +++ b/test/dotnet-pack.Tests/project.json @@ -1,19 +1,17 @@ { "version": "1.0.0-*", - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "System.IO.Compression.ZipFile": "4.0.1-rc2-23901", - - "Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" }, + "NETStandard.Library": "1.5.0-rc2-23911", + "System.IO.Compression.ZipFile": "4.0.1-rc2-23911", + "Microsoft.DotNet.Tools.Tests.Utilities": { + "target": "project" + }, "Microsoft.DotNet.Cli.Utils": { "target": "project" }, - "xunit": "2.1.0", - "dotnet-test-xunit": "1.0.0-dev-79755-47" + "dotnet-test-xunit": "1.0.0-dev-91790-12" }, - "frameworks": { "netstandardapp1.5": { "imports": [ @@ -22,10 +20,8 @@ ] } }, - "content": [ "../../TestAssets/TestProjects/TestLibraryWithConfiguration/*" ], - "testRunner": "xunit" } diff --git a/test/dotnet-projectmodel-server.Tests/project.json b/test/dotnet-projectmodel-server.Tests/project.json index 42d8fd6f6..2c6dd69cb 100644 --- a/test/dotnet-projectmodel-server.Tests/project.json +++ b/test/dotnet-projectmodel-server.Tests/project.json @@ -1,20 +1,29 @@ { "dependencies": { - "dotnet": { "target": "project" }, - "Microsoft.DotNet.ProjectModel": { "target": "project" }, - "Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" }, + "dotnet": { + "target": "project" + }, + "Microsoft.Win32.Registry": { + "version": "4.0.0-rc2-23911", + "exclude": "Compile" + }, + "Microsoft.DotNet.ProjectModel": { + "target": "project" + }, + "Microsoft.DotNet.Tools.Tests.Utilities": { + "target": "project" + }, "xunit": "2.1.0", "dotnet-test-xunit": "1.0.0-dev-91790-12", - - "System.Net.NameResolution": "4.0.0-rc2-23901" + "System.Net.NameResolution": "4.0.0-rc2-23911" }, - "frameworks": { - "netstandardapp1.5": { - "imports": [ - "dnxcore50", - "portable-net45+win8" - ] - } - }, - "testRunner": "xunit" + "frameworks": { + "netstandardapp1.5": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ] + } + }, + "testRunner": "xunit" } diff --git a/test/dotnet-publish.Tests/PublishPortableTests.cs b/test/dotnet-publish.Tests/PublishPortableTests.cs new file mode 100644 index 000000000..bb2e82c80 --- /dev/null +++ b/test/dotnet-publish.Tests/PublishPortableTests.cs @@ -0,0 +1,54 @@ +using Microsoft.DotNet.Tools.Test.Utilities; +using System; +using System.Collections.Generic; +using System.IO; +using Xunit; + +namespace Microsoft.DotNet.Tools.Publish.Tests +{ + public class PublishPortableTests : TestBase + { + private static readonly IEnumerable> ExpectedRuntimeOutputs = new[] { + Tuple.Create("debian-x64", "libuv.so"), + Tuple.Create("rhel-x64", "libuv.so"), + Tuple.Create("osx", "libuv.dylib"), + Tuple.Create("win7-arm", "libuv.dll"), + Tuple.Create("win7-x86", "libuv.dll"), + Tuple.Create("win7-x64", "libuv.dll") + }; + + [Fact] + public void PortableAppWithRuntimeTargetsIsPublishedCorrectly() + { + var testInstance = TestAssetsManager.CreateTestInstance("PortableTests") + .WithLockFiles(); + + var publishCommand = new PublishCommand(Path.Combine(testInstance.TestRoot, "PortableAppWithNative")); + var publishResult = publishCommand.Execute(); + + publishResult.Should().Pass(); + + var publishDir = publishCommand.GetOutputDirectory(portable: true); + publishDir.Should().HaveFiles(new[] + { + "PortableAppWithNative.dll", + "PortableAppWithNative.deps", + "PortableAppWithNative.deps.json" + }); + + var runtimesOutput = publishDir.Sub("runtimes"); + + runtimesOutput.Should().Exist(); + + foreach (var output in ExpectedRuntimeOutputs) + { + var ridDir = runtimesOutput.Sub(output.Item1); + ridDir.Should().Exist(); + + var nativeDir = ridDir.Sub("native"); + nativeDir.Should().Exist(); + nativeDir.Should().HaveFile(output.Item2); + } + } + } +} diff --git a/test/dotnet-publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs b/test/dotnet-publish.Tests/PublishTests.cs similarity index 100% rename from test/dotnet-publish.Tests/Microsoft.DotNet.Tools.Publish.Tests.cs rename to test/dotnet-publish.Tests/PublishTests.cs diff --git a/test/dotnet-publish.Tests/project.json b/test/dotnet-publish.Tests/project.json index 0064f9349..5a746e56c 100644 --- a/test/dotnet-publish.Tests/project.json +++ b/test/dotnet-publish.Tests/project.json @@ -1,21 +1,18 @@ { "version": "1.0.0-*", - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "System.IO.Compression": "4.1.0-rc2-23901", - + "NETStandard.Library": "1.5.0-rc2-23911", "Microsoft.DotNet.TestFramework": "1.0.0-*", - "Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" }, + "Microsoft.DotNet.Tools.Tests.Utilities": { + "target": "project" + }, "Microsoft.DotNet.Cli.Utils": { "target": "project" }, - "xunit": "2.1.0", "xunit.netcore.extensions": "1.0.0-prerelease-*", "dotnet-test-xunit": "1.0.0-dev-91790-12" }, - "frameworks": { "netstandardapp1.5": { "imports": [ @@ -24,6 +21,5 @@ ] } }, - "testRunner": "xunit" } diff --git a/test/dotnet-resgen.Tests/project.json b/test/dotnet-resgen.Tests/project.json index fd3d26275..346c14af5 100644 --- a/test/dotnet-resgen.Tests/project.json +++ b/test/dotnet-resgen.Tests/project.json @@ -1,20 +1,17 @@ { "version": "1.0.0-*", - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "System.IO.Compression": "4.1.0-rc2-23901", - - "Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" }, + "NETStandard.Library": "1.5.0-rc2-23911", + "Microsoft.DotNet.Tools.Tests.Utilities": { + "target": "project" + }, "Microsoft.DotNet.Cli.Utils": { "target": "project" }, - "xunit": "2.1.0", "xunit.netcore.extensions": "1.0.0-prerelease-*", - "dotnet-test-xunit": "1.0.0-dev-79755-47" + "dotnet-test-xunit": "1.0.0-dev-91790-12" }, - "frameworks": { "netstandardapp1.5": { "imports": [ @@ -23,10 +20,8 @@ ] } }, - "content": [ "../../TestAssets/TestProjects/TestProjectWithResource/**/*" ], - "testRunner": "xunit" } diff --git a/test/dotnet-run.Tests/project.json b/test/dotnet-run.Tests/project.json index ecbf0c5fc..6731f311f 100644 --- a/test/dotnet-run.Tests/project.json +++ b/test/dotnet-run.Tests/project.json @@ -1,19 +1,16 @@ { "version": "1.0.0-*", - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "System.IO.Compression": "4.1.0-rc2-23901", - - "Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" }, + "NETStandard.Library": "1.5.0-rc2-23911", + "Microsoft.DotNet.Tools.Tests.Utilities": { + "target": "project" + }, "Microsoft.DotNet.Cli.Utils": { "target": "project" }, - "xunit": "2.1.0", - "dotnet-test-xunit": "1.0.0-dev-79755-47" + "dotnet-test-xunit": "1.0.0-dev-91790-12" }, - "frameworks": { "netstandardapp1.5": { "imports": [ @@ -22,6 +19,5 @@ ] } }, - "testRunner": "xunit" } diff --git a/test/dotnet-test.Tests/Adapter.cs b/test/dotnet-test.Tests/Adapter.cs new file mode 100644 index 000000000..f4c66fa60 --- /dev/null +++ b/test/dotnet-test.Tests/Adapter.cs @@ -0,0 +1,177 @@ +// 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.Net; +using System.Net.Sockets; +using System.Threading; +using Microsoft.Extensions.Testing.Abstractions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System.Diagnostics; + +namespace Microsoft.Dotnet.Tools.Test.Tests +{ + public class Adapter : IDisposable + { + private readonly string _startMessage; + private BinaryWriter _writer; + private BinaryReader _reader; + private Socket _socket; + private Socket _listenSocket; + + public IDictionary> Messages { get; } + + public int Port { get; private set; } + + public Adapter(string startMessage) + { + _startMessage = startMessage; + Messages = new Dictionary>(); + + _listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + var endpoint = new IPEndPoint(IPAddress.Loopback, 0); + _listenSocket.Bind(endpoint); + + Port = ((IPEndPoint)_listenSocket.LocalEndPoint).Port; + } + + public void Listen() + { + var listenThread = new Thread(() => + { + using (_listenSocket) + { + _listenSocket.Listen(1); + _socket = _listenSocket.Accept(); + } + + var stream = new NetworkStream(_socket); + _writer = new BinaryWriter(stream); + _reader = new BinaryReader(stream); + + ReadMessages(); + }) + { + IsBackground = true + }; + + listenThread.Start(); + } + + public void Send(string messageType) + { + lock (_writer) + { + _writer.Write(JsonConvert.SerializeObject(new + { + MessageType = messageType, + PayLoad = JToken.FromObject(new + { + }) + })); + } + } + + private void ReadMessages() + { + while (true) + { + try + { + var message = GetMessage(); + + StoreMessage(message); + + if (HandleMessage(message)) + { + break; + } + } + catch (Exception) + { + throw; + } + } + } + + private void StoreMessage(Message message) + { + if (!Messages.ContainsKey(message.MessageType)) + { + Messages.Add(message.MessageType, new List()); + } + + Messages[message.MessageType].Add(message); + } + + private bool HandleMessage(Message message) + { + if (message.MessageType == "TestSession.Connected") + { + Send(_startMessage); + } + + if (message.MessageType == "TestExecution.TestRunnerProcessStartInfo") + { + StartTestRunner(message.Payload.ToObject()); + } + + if (message.MessageType == "TestDiscovery.Completed") + { + Send("TestSession.Terminate"); + return true; + } + + if (message.MessageType == "TestExecution.Completed") + { + Send("TestSession.Terminate"); + return true; + } + + return false; + } + + private static void StartTestRunner(TestStartInfo testPsiInfo) + { + var testPsi = new ProcessStartInfo(testPsiInfo.FileName, testPsiInfo.Arguments); + testPsi.RedirectStandardOutput = true; + testPsi.UseShellExecute = false; + var testProcess = new Process + { + StartInfo = testPsi + }; + + var testProcessThread = new Thread(() => { testProcess.Start(); }) + { + IsBackground = true + }; + testProcessThread.Start(); + } + + private Message GetMessage() + { + var rawMessage = _reader.ReadString(); + Console.WriteLine("\nRECEIVING MESSAGE:"); + Console.WriteLine($"{rawMessage}"); + Console.WriteLine($"==============================\n"); + + var message = JsonConvert.DeserializeObject(rawMessage); + return message; + } + + public void Dispose() + { + _socket?.Dispose(); + } + + private class TestStartInfo + { + public string FileName { get; set; } + + public string Arguments { get; set; } + } + } +} \ No newline at end of file diff --git a/test/dotnet-test.Tests/GivenThatWeWantToUseDotnetTestE2EInDesignTime.cs b/test/dotnet-test.Tests/GivenThatWeWantToUseDotnetTestE2EInDesignTime.cs new file mode 100644 index 000000000..5c874ac30 --- /dev/null +++ b/test/dotnet-test.Tests/GivenThatWeWantToUseDotnetTestE2EInDesignTime.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 Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.Tools.Test.Utilities; +using System.IO; +using FluentAssertions; +using Xunit; +using Microsoft.Extensions.PlatformAbstractions; +using System.Linq; + +namespace Microsoft.Dotnet.Tools.Test.Tests +{ + public class GivenThatWeWantToUseDotnetTestE2EInDesignTime : TestBase + { + private string _projectFilePath; + private string _outputPath; + + public GivenThatWeWantToUseDotnetTestE2EInDesignTime() + { + var testInstance = TestAssetsManager.CreateTestInstance("ProjectWithTests").WithLockFiles(); + + _projectFilePath = Path.Combine(testInstance.TestRoot, "project.json"); + var contexts = ProjectContext.CreateContextForEachFramework( + _projectFilePath, + null, + PlatformServices.Default.Runtime.GetAllCandidateRuntimeIdentifiers()); + var runtime = contexts.FirstOrDefault(c => !string.IsNullOrEmpty(c.RuntimeIdentifier))?.RuntimeIdentifier; + _outputPath = Path.Combine(testInstance.TestRoot, "bin", "Debug", DefaultFramework, runtime); + var buildCommand = new BuildCommand(_projectFilePath); + var result = buildCommand.Execute(); + + result.Should().Pass(); + } + + [WindowsOnlyFact] + public void It_discovers_two_tests_for_the_ProjectWithTests() + { + using (var adapter = new Adapter("TestDiscovery.Start")) + { + adapter.Listen(); + + var testCommand = new DotnetTestCommand(); + var result = testCommand.Execute($"{_projectFilePath} -o {_outputPath} --port {adapter.Port}"); + result.Should().Pass(); + + adapter.Messages["TestSession.Connected"].Count.Should().Be(1); + adapter.Messages["TestDiscovery.TestFound"].Count.Should().Be(2); + adapter.Messages["TestDiscovery.Completed"].Count.Should().Be(1); + } + } + + [Fact] + public void It_runs_two_tests_for_the_ProjectWithTests() + { + using (var adapter = new Adapter("TestExecution.GetTestRunnerProcessStartInfo")) + { + adapter.Listen(); + + var testCommand = new DotnetTestCommand(); + var result = testCommand.Execute($"{_projectFilePath} -o {_outputPath} --port {adapter.Port}"); + result.Should().Pass(); + + adapter.Messages["TestSession.Connected"].Count.Should().Be(1); + adapter.Messages["TestExecution.TestRunnerProcessStartInfo"].Count.Should().Be(1); + adapter.Messages["TestExecution.TestStarted"].Count.Should().Be(2); + adapter.Messages["TestExecution.TestResult"].Count.Should().Be(2); + adapter.Messages["TestExecution.Completed"].Count.Should().Be(1); + } + } + } +} diff --git a/test/dotnet-test.Tests/dotnet-test.Tests.xproj b/test/dotnet-test.Tests/dotnet-test.Tests.xproj new file mode 100644 index 000000000..19adc5ad5 --- /dev/null +++ b/test/dotnet-test.Tests/dotnet-test.Tests.xproj @@ -0,0 +1,18 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 60c33d0a-a5d8-4ab0-9956-1f804654df05 + Microsoft.Dotnet.Tools.Test.Tests + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin + + + 2.0 + + + \ No newline at end of file diff --git a/test/dotnet-test.Tests/project.json b/test/dotnet-test.Tests/project.json new file mode 100644 index 000000000..07eb34c33 --- /dev/null +++ b/test/dotnet-test.Tests/project.json @@ -0,0 +1,33 @@ +{ + "version": "1.0.0-*", + + "dependencies": { + "Newtonsoft.Json": "7.0.1", + "NETStandard.Library": "1.5.0-rc2-23911", + "Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" }, + "Microsoft.DotNet.TestFramework": { "target": "project" }, + "Microsoft.DotNet.ProjectModel": { "target": "project" }, + "Microsoft.Extensions.Testing.Abstractions": { "target": "project" }, + "System.Net.NameResolution": "4.0.0-rc2-23911", + "System.Net.Sockets": "4.1.0-rc2-23911", + "System.Runtime.Serialization.Primitives": "4.1.1-rc2-23911", + + "xunit": "2.1.0", + "dotnet-test-xunit": "1.0.0-dev-91790-12" + }, + + "frameworks": { + "netstandardapp1.5": { + "imports": [ + "dnxcore50", + "portable-net45+win8" + ] + } + }, + "content": [ + "../../TestAssets/TestProjects/ProjectWithTests/**/*", + "../../TestAssets/TestProjects/global.json" + ], + + "testRunner": "xunit" +} diff --git a/test/dotnet-test.UnitTests/GivenATestDiscoveryStartMessageHandler.cs b/test/dotnet-test.UnitTests/GivenATestDiscoveryStartMessageHandler.cs index 971ceeb3b..1c6ea4bbf 100644 --- a/test/dotnet-test.UnitTests/GivenATestDiscoveryStartMessageHandler.cs +++ b/test/dotnet-test.UnitTests/GivenATestDiscoveryStartMessageHandler.cs @@ -141,7 +141,7 @@ namespace Microsoft.Dotnet.Tools.Test.Tests _dotnetTestMock.Object, _validMessage); - _testRunnerChannelMock.Verify(t => t.Accept(), Times.Once); + _testRunnerChannelMock.Verify(t => t.Connect(), Times.Once); } [Fact] diff --git a/test/dotnet-test.UnitTests/GivenATestExecutionGetTestRunnerProcessStartInfoMessageHandler.cs b/test/dotnet-test.UnitTests/GivenATestExecutionGetTestRunnerProcessStartInfoMessageHandler.cs index 24a9942b7..e99271634 100644 --- a/test/dotnet-test.UnitTests/GivenATestExecutionGetTestRunnerProcessStartInfoMessageHandler.cs +++ b/test/dotnet-test.UnitTests/GivenATestExecutionGetTestRunnerProcessStartInfoMessageHandler.cs @@ -161,7 +161,7 @@ namespace Microsoft.Dotnet.Tools.Test.Tests _dotnetTestMock.Object, _validMessage); - _testRunnerChannelMock.Verify(t => t.Accept(), Times.Once); + _testRunnerChannelMock.Verify(t => t.Connect(), Times.Once); } [Fact] diff --git a/test/dotnet-test.UnitTests/project.json b/test/dotnet-test.UnitTests/project.json index eb23b1792..81e469a45 100644 --- a/test/dotnet-test.UnitTests/project.json +++ b/test/dotnet-test.UnitTests/project.json @@ -1,18 +1,20 @@ { "version": "1.0.0-*", - "dependencies": { "Newtonsoft.Json": "7.0.1", - "NETStandard.Library": "1.0.0-rc2-23901", - - "dotnet": { "target": "project" }, - + "NETStandard.Library": "1.5.0-rc2-23911", + "dotnet": { + "target": "project" + }, + "Microsoft.Win32.Registry": { + "version": "4.0.0-rc2-23911", + "exclude": "Compile" + }, "xunit": "2.1.0", "dotnet-test-xunit": "1.0.0-dev-91790-12", "moq.netcore": "4.4.0-beta8", "FluentAssertions": "4.2.2" }, - "frameworks": { "netstandardapp1.5": { "imports": [ @@ -21,6 +23,5 @@ ] } }, - "testRunner": "xunit" } diff --git a/test/dotnet.Tests/PackagedCommandTests.cs b/test/dotnet.Tests/PackagedCommandTests.cs index 941d3f258..7b4a91b28 100644 --- a/test/dotnet.Tests/PackagedCommandTests.cs +++ b/test/dotnet.Tests/PackagedCommandTests.cs @@ -36,9 +36,9 @@ namespace Microsoft.DotNet.Tests try { - CommandResult result = new HelloCommand().ExecuteWithCapturedOutput(); + CommandResult result = new PortableCommand().ExecuteWithCapturedOutput(); - result.Should().HaveStdOut("Hello World!" + Environment.NewLine); + result.Should().HaveStdOut("Hello Portable World!" + Environment.NewLine); result.Should().NotHaveStdErr(); result.Should().Pass(); } @@ -94,5 +94,25 @@ namespace Microsoft.DotNet.Tests return base.ExecuteWithCapturedOutput(args); } } + + class PortableCommand : TestCommand + { + public PortableCommand() + : base("dotnet") + { + } + + public override CommandResult Execute(string args = "") + { + args = $"portable {args}"; + return base.Execute(args); + } + + public override CommandResult ExecuteWithCapturedOutput(string args = "") + { + args = $"portable {args}"; + return base.ExecuteWithCapturedOutput(args); + } + } } } diff --git a/test/dotnet.Tests/project.json b/test/dotnet.Tests/project.json index 46da798d1..4443e85ea 100644 --- a/test/dotnet.Tests/project.json +++ b/test/dotnet.Tests/project.json @@ -1,20 +1,17 @@ { "version": "1.0.0-*", - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "System.IO.Compression": "4.1.0-rc2-23901", - - "Microsoft.DotNet.Tools.Tests.Utilities": { "target": "project" }, + "NETStandard.Library": "1.5.0-rc2-23911", + "Microsoft.DotNet.Tools.Tests.Utilities": { + "target": "project" + }, "Microsoft.DotNet.Cli.Utils": { "target": "project", "type": "build" }, - "xunit": "2.1.0", "dotnet-test-xunit": "1.0.0-dev-91790-12" }, - "frameworks": { "netstandardapp1.5": { "imports": [ @@ -23,12 +20,10 @@ ] } }, - "content": [ - "../../TestAssets/TestProjects/AppWithDirectAndToolDependency/**/*", - "../../TestAssets/TestProjects/AppWithDirectDependency/**/*", - "../../TestAssets/TestProjects/AppWithToolDependency/**/*" + "../../TestAssets/TestProjects/AppWithDirectAndToolDependency/**/*", + "../../TestAssets/TestProjects/AppWithDirectDependency/**/*", + "../../TestAssets/TestProjects/AppWithToolDependency/**/*" ], - "testRunner": "xunit" } diff --git a/tools/DebianPackageTool/package_tool b/tools/DebianPackageTool/package_tool index c48da1755..3cc139878 100755 --- a/tools/DebianPackageTool/package_tool +++ b/tools/DebianPackageTool/package_tool @@ -132,7 +132,10 @@ parse_config_and_set_env_vars(){ DOCS_JSON_PATH="$INPUT_DIR/docs.json" PACKAGE_SOURCE_DIR="${OUTPUT_DIR}/${PACKAGE_NAME}-${PACKAGE_VERSION}" - INSTALL_ROOT="/usr/share/${PACKAGE_NAME}" + + if ! INSTALL_ROOT="$($extract_base_cmd $CONFIG "install_root")"; then + INSTALL_ROOT="/usr/share/$PACKAGE_NAME" + fi DEBIAN_DIR="${PACKAGE_SOURCE_DIR}/debian" DOCS_DIR="${PACKAGE_SOURCE_DIR}/docs" diff --git a/tools/DebianPackageTool/scripts/debian_build_lib.sh b/tools/DebianPackageTool/scripts/debian_build_lib.sh index 0b3f4141d..97c4c0ead 100755 --- a/tools/DebianPackageTool/scripts/debian_build_lib.sh +++ b/tools/DebianPackageTool/scripts/debian_build_lib.sh @@ -142,6 +142,7 @@ _get_files_in_dir_tree(){ # Use Globstar expansion to enumerate all directories and files in the tree shopt -s globstar + shopt -s dotglob dir_tree_list=( "${root_dir}/"** ) # Build a new array with only the Files contained in $dir_tree_list diff --git a/tools/MultiProjectValidator/project.json b/tools/MultiProjectValidator/project.json index 59d9c5fb9..130929817 100644 --- a/tools/MultiProjectValidator/project.json +++ b/tools/MultiProjectValidator/project.json @@ -1,21 +1,19 @@ { - "version": "1.0.0-*", - "name": "pjvalidate", - "compilationOptions": { - "emitEntryPoint": true - }, - - "dependencies": { - "NETStandard.Library": "1.0.0-rc2-23901", - "Microsoft.DotNet.ProjectModel": "1.0.0-*", - "Microsoft.DotNet.Cli.Utils": "1.0.0-*" - }, - - "frameworks": { - "netstandardapp1.5": { - "imports": [ - "dnxcore50" - ] - } + "version": "1.0.0-*", + "name": "pjvalidate", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "NETStandard.Library": "1.5.0-rc2-23911", + "Microsoft.DotNet.ProjectModel": "1.0.0-*", + "Microsoft.DotNet.Cli.Utils": "1.0.0-*" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": [ + "dnxcore50" + ] } + } } diff --git a/tools/RuntimeGraphGenerator/Program.cs b/tools/RuntimeGraphGenerator/Program.cs new file mode 100644 index 000000000..09253af49 --- /dev/null +++ b/tools/RuntimeGraphGenerator/Program.cs @@ -0,0 +1,110 @@ +// 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.CommandLine; +using Microsoft.DotNet.Cli.Utils; +using System.IO; +using System.Runtime.Versioning; +using Microsoft.DotNet.ProjectModel; +using Microsoft.Extensions.DependencyModel; +using NuGet.Frameworks; +using NuGet.Packaging; +using NuGet.Versioning; +using Microsoft.DotNet.ProjectModel.Graph; + +namespace RuntimeGraphGenerator +{ + public class Program + { + public static int Main(string[] args) + { + DebugHelper.HandleDebugSwitch(ref args); + + string projectDirectory = null; + string depsFile = null; + IReadOnlyList runtimes = null; + try + { + ArgumentSyntax.Parse(args, syntax => + { + syntax.ApplicationName = "Runtime GraphGenerator"; + + syntax.HandleHelp = false; + syntax.HandleErrors = false; + + syntax.DefineOption("p|project", ref projectDirectory, "Project location"); + syntax.DefineOption("d|deps", ref depsFile, "Deps file path"); + + syntax.DefineParameterList("runtimes", ref runtimes, "Runtimes"); + }); + } + catch (ArgumentSyntaxException exception) + { + Console.Error.WriteLine(exception.Message); + return 1; + } + + if (runtimes == null || runtimes.Count == 0) + { + Reporter.Error.WriteLine("No runtimes specified"); + return 1; + } + if (!File.Exists(depsFile)) + { + Reporter.Error.WriteLine($"Deps file not found: {depsFile}"); + return 1; + } + if (!Directory.Exists(projectDirectory)) + { + Reporter.Error.WriteLine($"Project directory not found: {projectDirectory}"); + return 1; + } + + try + { + DependencyContext context; + using (var depsStream = File.OpenRead(depsFile)) + { + context = new DependencyContextJsonReader().Read(depsStream); + } + var framework = NuGetFramework.Parse(context.TargetFramework); + var projectContext = ProjectContext.Create(projectDirectory, framework); + + // Configuration is used only for P2P dependencies so were don't care + var exporter = projectContext.CreateExporter("Debug"); + var manager = new RuntimeGraphManager(); + var graph = manager.Collect(exporter.GetDependencies(LibraryType.Package)); + var expandedGraph = manager.Expand(graph, runtimes); + + context = new DependencyContext( + context.TargetFramework, + context.Runtime, + context.IsPortable, + context.CompilationOptions, + context.CompileLibraries, + context.RuntimeLibraries, + expandedGraph + ); + + using (var depsStream = File.Create(depsFile)) + { + new DependencyContextWriter().Write(context, depsStream); + } + + return 0; + } + catch (Exception ex) + { +#if DEBUG + Reporter.Error.WriteLine(ex.ToString()); +#else + Reporter.Error.WriteLine(ex.Message); +#endif + return 1; + } + } + + } +} diff --git a/tools/RuntimeGraphGenerator/RuntimeGraphGenerator.xproj b/tools/RuntimeGraphGenerator/RuntimeGraphGenerator.xproj new file mode 100644 index 000000000..1126cb59b --- /dev/null +++ b/tools/RuntimeGraphGenerator/RuntimeGraphGenerator.xproj @@ -0,0 +1,19 @@ + + + + 14.0.25029 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + efc4fe68-83eb-40e4-bfa8-61d0b4626f25 + RuntimeGraphGenerator + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + + 2.0 + + + \ No newline at end of file diff --git a/tools/RuntimeGraphGenerator/RuntimeGraphManager.cs b/tools/RuntimeGraphGenerator/RuntimeGraphManager.cs new file mode 100644 index 000000000..f1c0629c1 --- /dev/null +++ b/tools/RuntimeGraphGenerator/RuntimeGraphManager.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.DotNet.ProjectModel.Compilation; +using Microsoft.DotNet.ProjectModel.Graph; +using NuGet.RuntimeModel; +using System.IO; +using Microsoft.Extensions.DependencyModel; + +namespace Microsoft.DotNet.ProjectModel +{ + public class RuntimeGraphManager + { + private const string RuntimeJsonFileName = "runtime.json"; + + public NuGet.RuntimeModel.RuntimeGraph Collect(IEnumerable exports) + { + var graph = RuntimeGraph.Empty; + foreach (var export in exports) + { + if (export.Library.Identity.Type == LibraryType.Package) + { + var runtimeJson = ((PackageDescription) export.Library).Library.Files.FirstOrDefault(f => f == RuntimeJsonFileName); + if (runtimeJson != null) + { + var runtimeJsonFullName = Path.Combine(export.Library.Path, runtimeJson); + graph = RuntimeGraph.Merge(graph, JsonRuntimeFormat.ReadRuntimeGraph(runtimeJsonFullName)); + } + } + } + return graph; + } + + public IEnumerable Expand(RuntimeGraph runtimeGraph, IEnumerable runtimes) + { + foreach (var runtime in runtimes) + { + var importers = FindImporters(runtimeGraph, runtime); + foreach (var importer in importers) + { + // ExpandRuntime return runtime itself as first item so we are skiping it + yield return new RuntimeFallbacks(importer, runtimeGraph.ExpandRuntime(importer).Skip(1)); + } + } + } + + private IEnumerable FindImporters(RuntimeGraph runtimeGraph, string runtime) + { + foreach (var runtimePair in runtimeGraph.Runtimes) + { + var expanded = runtimeGraph.ExpandRuntime(runtimePair.Key); + if (expanded.Contains(runtime)) + { + yield return runtimePair.Key; + } + } + } + } +} diff --git a/tools/RuntimeGraphGenerator/project.json b/tools/RuntimeGraphGenerator/project.json new file mode 100644 index 000000000..9719346de --- /dev/null +++ b/tools/RuntimeGraphGenerator/project.json @@ -0,0 +1,25 @@ +{ + "version": "1.0.0-*", + "compilationOptions": { + "emitEntryPoint": true + }, + "dependencies": { + "NuGet.RuntimeModel": "3.4.0-rtm-0763", + "NuGet.Versioning": "3.4.0-rtm-0763", + "System.CommandLine": "0.1.0-e160119-1", + "System.Runtime.Serialization.Json": "1.0.0-rc2-23911", + "Microsoft.DotNet.ProjectModel": "1.0.0-*", + "Microsoft.DotNet.Cli.Utils": "1.0.0-*", + "Microsoft.Extensions.PlatformAbstractions": "1.0.0-rc2-16537", + "Microsoft.NETCore.ConsoleHost": "1.0.0-rc2-23911", + "NETStandard.Library": "1.5.0-rc2-23911" + }, + "frameworks": { + "netstandardapp1.5": { + "imports": [ + "dnxcore50", + "portable-net45+wp80+win8+wpa81+dnxcore50" + ] + } + }, +}