From 5cca5803269e80d3bb517aa009e5c7c3b130d659 Mon Sep 17 00:00:00 2001 From: Matthew Asplund Date: Sun, 8 Sep 2024 18:41:16 -0500 Subject: [PATCH] Masplund/path load (#256) * Load paths directly * Add benchmark tests * Use parse for monitor paths * update locks * Fix up C# paths --- Docs/Benchmarks.md | 14 + RootRecipe.sml | 2 +- Source/BenchTests/Main.cpp | 652 +++ Source/BenchTests/Nanobench.cpp | 2 + Source/BenchTests/PackageLock.sml | 34 + Source/BenchTests/Recipe.sml | 13 + Source/BenchTests/nanobench.h | 3484 +++++++++++++++++ Source/Client/CLI/PackageLock.sml | 2 +- .../Client/CLI/Source/Commands/BuildCommand.h | 5 +- .../CLI/Source/Commands/InitializeCommand.h | 3 +- .../CLI/Source/Commands/InstallCommand.h | 3 +- .../CLI/Source/Commands/PublishCommand.h | 3 +- .../CLI/Source/Commands/RestoreCommand.h | 3 +- .../Client/CLI/Source/Commands/RunCommand.h | 3 +- .../CLI/Source/Commands/TargetCommand.h | 3 +- .../Client/CLI/Source/Commands/ViewCommand.h | 9 +- Source/Client/Core/Recipe.sml | 1 - .../Client/Core/Source/Build/BuildConstants.h | 24 +- Source/Client/Core/Source/Build/BuildEngine.h | 8 +- .../Core/Source/Build/BuildEvaluateEngine.h | 8 +- .../Core/Source/Build/BuildLoadEngine.h | 12 +- Source/Client/Core/Source/Build/BuildRunner.h | 6 +- .../Source/Build/RecipeBuildLocationManager.h | 12 +- .../OperationGraph/OperationGraphReader.h | 129 +- .../OperationGraph/OperationGraphWriter.h | 2 +- .../OperationGraph/OperationResultsReader.h | 110 +- .../Core/Source/Package/PackageManager.h | 6 +- Source/Client/Core/Source/Recipe/RecipeSML.h | 10 +- .../Core/Source/Recipe/RootRecipeExtensions.h | 2 +- Source/Client/Core/Source/SML/SML.h | 1 + Source/Client/Core/Source/SML/SMLParser.cpp | 21 + Source/Client/Core/Source/SML/SMLParser.l | 21 + .../Core/Source/ValueTable/ValueTableReader.h | 97 +- .../Core/UnitTests/Build/BuildEngineTests.h | 37 +- .../Build/BuildEvaluateEngineTests.h | 18 +- .../UnitTests/Build/BuildLoadEngineTests.h | 10 +- .../UnitTests/Build/FileSystemStateTests.h | 4 +- .../Build/RecipeBuildLocationManagerTests.h | 7 +- .../LocalUserConfigExtensionsTests.h | 10 +- .../OperationGraphManagerTests.h | 20 +- .../OperationGraphReaderTests.h | 16 +- .../OperationGraph/OperationGraphTests.h | 14 +- .../OperationGraphWriterTests.h | 8 +- .../OperationResultsManagerTests.h | 12 +- .../UnitTests/Recipe/RecipeExtensionsTests.h | 14 +- .../Core/UnitTests/Recipe/RecipeSMLTests.h | 12 +- .../ValueTable/ValueTableManagerTests.h | 12 +- .../ValueTable/ValueTableReaderTests.h | 71 + .../ValueTable/ValueTableWriterTests.h | 72 + .../ValueTable/ValueTableReaderTests.gen.h | 1 + .../ValueTable/ValueTableWriterTests.gen.h | 1 + Source/Client/Tools/PackageLock.sml | 2 +- Source/Generate/PackageLock.sml | 2 +- .../Opal.UnitTests/Utilities/PathUnitTests.cs | 119 +- .../Opal/System/RuntimeFileSystem.cs | 4 +- Source/GenerateSharp/Opal/Utilities/Path.cs | 300 +- .../PackageManager.Core/ClosureManager.cs | 18 +- .../PackageManager.Core/InitializeCommand.cs | 2 +- .../PackageManager.Core/PackageManager.cs | 16 +- .../ClosureManagerUnitTests.cs | 40 +- .../ViewModels/OperationGraphViewModel.cs | 2 +- .../SoupView/ViewModels/TaskGraphViewModel.cs | 2 +- .../Utilities/DotNetSDKUtilitiesUnitTests.cs | 160 +- .../Utilities/NugetSDKUtilitiesUnitTests.cs | 42 +- .../Utilities/SwhereManagerUnitTests.cs | 504 +-- .../Utilities/VSWhereUtilitiesUnitTests.cs | 4 +- .../Swhere.Core/DotNet/DotNetSDKUtilities.cs | 24 +- .../Swhere.Core/Nuget/NugetSDKUtilities.cs | 10 +- .../Swhere.Core/SwhereManager.cs | 6 +- .../Swhere.Core/VSWhereUtilities.cs | 4 +- .../Swhere.Core/WhereIsUtilities.cs | 4 +- .../Swhere.Core/WindowsSDKUtilities.cs | 2 +- .../GenerateSharp/Utilities/BuildConstants.cs | 18 +- Source/GenerateTest/PackageLock.sml | 2 +- Source/Monitor/Client/ConnectionManagerBase.h | 29 +- .../Monitor/Client/FileSystemAccessSandbox.h | 4 +- .../Monitor/Client/Linux/ConnectionManager.h | 10 +- Source/Monitor/Client/MessageBuilder.h | 125 + Source/Monitor/Client/MessageSender.h | 85 +- Source/Monitor/Client/PackageLock.sml | 2 +- .../Client/Windows/ConnectionManager.h | 13 +- .../Monitor/Host/Linux/DetourCallbackLogger.h | 4 +- .../Monitor/Host/Linux/DetourForkCallback.h | 6 +- .../Host/Linux/DetourMonitorCallback.h | 14 +- Source/Monitor/Host/Linux/EventListener.h | 3 +- Source/Monitor/Host/Linux/IDetourCallback.h | 2 +- .../Monitor/Host/Linux/LinuxMonitorProcess.h | 2 +- .../Host/Windows/DetourCallbackLogger.h | 4 +- .../Monitor/Host/Windows/DetourForkCallback.h | 6 +- .../Host/Windows/DetourMonitorCallback.h | 12 +- Source/Monitor/Host/Windows/EventListener.h | 31 +- Source/Monitor/Host/Windows/IDetourCallback.h | 2 +- .../Host/Windows/WindowsMonitorProcess.h | 4 +- Source/Tools/Mkdir/Main.cpp | 2 +- Source/Tools/Mkdir/PackageLock.sml | 2 +- Source/Tools/PrintGraph/PackageLock.sml | 2 +- Source/Tools/PrintResults/PackageLock.sml | 2 +- Source/Tools/PrintValueTable/PackageLock.sml | 2 +- 98 files changed, 5651 insertions(+), 1048 deletions(-) create mode 100644 Docs/Benchmarks.md create mode 100644 Source/BenchTests/Main.cpp create mode 100644 Source/BenchTests/Nanobench.cpp create mode 100644 Source/BenchTests/PackageLock.sml create mode 100644 Source/BenchTests/Recipe.sml create mode 100644 Source/BenchTests/nanobench.h create mode 100644 Source/Monitor/Client/MessageBuilder.h diff --git a/Docs/Benchmarks.md b/Docs/Benchmarks.md new file mode 100644 index 000000000..42b1afa49 --- /dev/null +++ b/Docs/Benchmarks.md @@ -0,0 +1,14 @@ +# Benchmarks +Current benchmark numbers on my old XPS 15 9500 with Win11 + +| ns/op | op/s | err% | total | benchmark +|--------------------:|--------------------:|--------:|----------:|:---------- +| 22,654.56 | 44,141.22 | 2.0% | 2.76 | `PackageReference Parse Name Only` +| 33,680.68 | 29,690.61 | 1.8% | 4.07 | `PackageReference Parse Language, User, Name and Version` +| 667.07 | 1,499,095.26 | 1.6% | 0.08 | `ValueTableReader Deserialize Empty` +| 22,420.17 | 44,602.69 | 3.9% | 2.78 | `ValueTableReader Deserialize Complex` +| 1,370.46 | 729,681.06 | 2.7% | 0.17 | `OperationResultsReader Deserialize Empty` +| 9,236.14 | 108,270.35 | 2.8% | 1.15 | `OperationResultsReader Deserialize Complex` +| 12,989.81 | 76,983.45 | 4.6% | 1.58 | `RecipeSML Deserialize Simple` +| 63,355.38 | 15,783.98 | 2.6% | 7.67 | `RecipeSML Deserialize Complex` +| 1,217,940.63 | 821.06 | 1.7% | 44.15 | `BuildEngine Execute NoDependencies UpToDate` \ No newline at end of file diff --git a/RootRecipe.sml b/RootRecipe.sml index 9b31eaba6..d57d0ca12 100644 --- a/RootRecipe.sml +++ b/RootRecipe.sml @@ -1 +1 @@ -OutputRoot: 'out/' +OutputRoot: './out/' diff --git a/Source/BenchTests/Main.cpp b/Source/BenchTests/Main.cpp new file mode 100644 index 000000000..31d0cf6b4 --- /dev/null +++ b/Source/BenchTests/Main.cpp @@ -0,0 +1,652 @@ +#include "nanobench.h" +#include + +import Monitor.Host; +import Opal; +import Soup.Core; + +using namespace Opal; +using namespace Opal::System; +using namespace Soup::Core; + +int main() +{ + { + ankerl::nanobench::Bench().minEpochIterations(10000).run("PackageReference Parse Name Only", [&] + { + auto actual = PackageReference::Parse("Package1"); + ankerl::nanobench::doNotOptimizeAway(actual); + }); + } + + { + ankerl::nanobench::Bench().minEpochIterations(10000).run("PackageReference Parse Language, User, Name and Version", [&] + { + auto actual = PackageReference::Parse("[C#]User1|Package1@1.2.3"); + ankerl::nanobench::doNotOptimizeAway(actual); + }); + } + + { + auto binaryFileContent = std::vector( + { + 'B', 'V', 'T', '\0', 0x02, 0x00, 0x00, 0x00, + 'T', 'B', 'L', '\0', 0x00, 0x00, 0x00, 0x00, + }); + auto content = std::stringstream(std::string(reinterpret_cast(binaryFileContent.data()), binaryFileContent.size())); + + ankerl::nanobench::Bench().minEpochIterations(10000).run("ValueTableReader Deserialize Empty", [&] + { + auto actual = ValueTableReader::Deserialize(content); + ankerl::nanobench::doNotOptimizeAway(actual); + }); + } + + { + auto binaryFileContent = std::vector( + { + 'B', 'V', 'T', '\0', 0x02, 0x00, 0x00, 0x00, + 'T', 'B', 'L', '\0', 0x08, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'B', 'o', 'o', 'l', 'e', 'a', 'n', 0x6, 0x0, 0x0, 0x0, 0x1, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'D', 'e', 'e', 'p', 'T', 'a', 'b', 'l', 'e', 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 'V', 'a', 'l', 'u', 'e', '1', 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 'V', 'a', 'l', 'u', 'e', '2', 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 'V', 'a', 'l', 'u', 'e', '3', 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 'V', 'a', 'l', 'u', 'e', '4', 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0d, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'E', 'm', 'p', 't', 'y', 'L', 'i', 's', 't', 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0e, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'E', 'm', 'p', 't', 'y', 'T', 'a', 'b', 'l', 'e', 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x09, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'F', 'l', 'o', 'a', 't', 0x5, 0x0, 0x0, 0x0, 0xae, 0x47, 0xe1, 0x7a, 0x14, 0xae, 0xf3, 0x3f, + 0x0b, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'I', 'n', 't', 'e', 'g', 'e', 'r', 0x4, 0x0, 0x0, 0x0, 0x85, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0f, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'I', 'n', 't', 'e', 'g', 'e', 'r', 'L', 'i', 's', 't', 0x2, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0a, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'S', 't', 'r', 'i', 'n', 'g', 0x3, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 'V', 'a', 'l', 'u', 'e', + }); + auto content = std::stringstream(std::string(reinterpret_cast(binaryFileContent.data()), binaryFileContent.size())); + + ankerl::nanobench::Bench().minEpochIterations(10000).run("ValueTableReader Deserialize Complex", [&] + { + auto actual = Soup::Core::ValueTableReader::Deserialize(content); + ankerl::nanobench::doNotOptimizeAway(actual); + }); + } + + { + auto fileSystemState = FileSystemState(); + auto binaryFileContent = std::vector( + { + 'B', 'O', 'R', '\0', 0x02, 0x00, 0x00, 0x00, + 'F', 'I', 'S', '\0', 0x00, 0x00, 0x00, 0x00, + 'R', 'T', 'S', '\0', 0x00, 0x00, 0x00, 0x00, + }); + auto content = std::stringstream(std::string(binaryFileContent.data(), binaryFileContent.size())); + + ankerl::nanobench::Bench().minEpochIterations(10000).run("OperationResultsReader Deserialize Empty", [&] + { + auto actual = OperationResultsReader::Deserialize(content, fileSystemState); + ankerl::nanobench::doNotOptimizeAway(actual); + }); + } + + { + auto fileSystemState = FileSystemState( + 20, + { + { 11, Path("C:/File1") }, + { 12, Path("C:/File2") }, + { 13, Path("C:/File3") }, + { 14, Path("C:/File4") }, + { 15, Path("C:/File5") }, + { 16, Path("C:/File6") }, + { 17, Path("C:/File7") }, + { 18, Path("C:/File8") }, + }); + auto binaryFileContent = std::vector( + { + 'B', 'O', 'R', '\0', 0x02, 0x00, 0x00, 0x00, + 'F', 'I', 'S', '\0', 0x08, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 'C', ':', '/', 'F', 'i', 'l', 'e', '1', + 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 'C', ':', '/', 'F', 'i', 'l', 'e', '2', + 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 'C', ':', '/', 'F', 'i', 'l', 'e', '3', + 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 'C', ':', '/', 'F', 'i', 'l', 'e', '4', + 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 'C', ':', '/', 'F', 'i', 'l', 'e', '5', + 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 'C', ':', '/', 'F', 'i', 'l', 'e', '6', + 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 'C', ':', '/', 'F', 'i', 'l', 'e', '7', + 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 'C', ':', '/', 'F', 'i', 'l', 'e', '8', + 'R', 'T', 'S', '\0', 0x02, 0x00, 0x00, 0x00, + 0x05, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x10, 0x16, 0x62, 0xbb, 0x0b, 0x41, 0x38, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x80, 0x8d, 0xa9, 0xeb, 0x0b, 0x41, 0x38, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + }); + auto content = std::stringstream(std::string((char*)binaryFileContent.data(), binaryFileContent.size())); + + ankerl::nanobench::Bench().minEpochIterations(10000).run("OperationResultsReader Deserialize Complex", [&] + { + auto actual = OperationResultsReader::Deserialize(content, fileSystemState); + ankerl::nanobench::doNotOptimizeAway(actual); + }); + } + + { + auto recipeFile = Path("./Recipe.sml"); + auto recipe = std::stringstream( + R"( + Name: 'MyPackage' + Language: 'C++|1' + )"); + ankerl::nanobench::Bench().minEpochIterations(10000).run("RecipeSML Deserialize Simple", [&] + { + auto actual = Recipe(RecipeSML::Deserialize(recipeFile, recipe)); + ankerl::nanobench::doNotOptimizeAway(actual); + }); + } + + { + auto recipeFile = Path("./Recipe.sml"); + auto recipe = std::stringstream( + R"( + Name: 'MyPackage' + Language: 'C++|1' + Version: '1.2.3' + Source: [ + 'File1.cpp' + 'File2.cpp' + 'File3.cpp' + 'File4.cpp' + 'File5.cpp' + 'File6.cpp' + 'File7.cpp' + 'File8.cpp' + 'File9.cpp' + 'File10.cpp' + ] + Dependencies: { + Runtime: [ + 'Package1' + ] + Build: [] + Test: [] + } + )"); + ankerl::nanobench::Bench().minEpochIterations(10000).run("RecipeSML Deserialize Complex", [&] + { + auto actual = Recipe(RecipeSML::Deserialize(recipeFile, recipe)); + ankerl::nanobench::doNotOptimizeAway(actual); + }); + } + + { + // Register the test listener + auto testListener = std::make_shared(); + auto scopedTraceListener = ScopedTraceListenerRegister(testListener); + + // Register the test system + auto system = std::make_shared(); + auto scopedSystem = ScopedSystemRegister(system); + + // Register the test file system + auto fileSystem = std::make_shared(); + auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); + + fileSystem->CreateMockFile( + Path("C:/testlocation/Soup.Generate.exe"), + std::make_shared()); + + fileSystem->CreateMockDirectory( + Path("C:/WorkingDirectory/MyPackage/"), + std::make_shared(std::vector({ + Path("./Recipe.sml"), + }))); + + fileSystem->CreateMockDirectory( + Path("C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/"), + std::make_shared(std::vector({}))); + + fileSystem->CreateMockDirectory( + Path("C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/.soup/"), + std::make_shared(std::vector({}))); + + fileSystem->CreateMockDirectory( + Path("C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/temp/"), + std::make_shared(std::vector({}))); + + fileSystem->CreateMockDirectory( + Path("C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/"), + std::make_shared(std::vector({ + Path("./Recipe.sml"), + }))); + + fileSystem->CreateMockDirectory( + Path("C:/BuiltIn/Packages/mwasplund/Soup.Wren/0.4.1/"), + std::make_shared(std::vector({ + Path("./Recipe.sml"), + }))); + + fileSystem->CreateMockDirectory( + Path("C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/"), + std::make_shared(std::vector({}))); + + fileSystem->CreateMockDirectory( + Path("C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/.soup/"), + std::make_shared(std::vector({}))); + + fileSystem->CreateMockDirectory( + Path("C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/temp/"), + std::make_shared(std::vector({}))); + + // Create the Recipe to build + fileSystem->CreateMockFile( + Path("C:/WorkingDirectory/MyPackage/Recipe.sml"), + std::make_shared(std::stringstream(R"( + Name: 'MyPackage' + Language: 'C++|0.8.2' + )"))); + + fileSystem->CreateMockFile( + Path("C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/Recipe.sml"), + std::make_shared(std::stringstream(R"( + Name: 'Soup.Cpp' + Language: 'Wren|1' + )"))); + + fileSystem->CreateMockFile( + Path("C:/BuiltIn/Packages/mwasplund/Soup.Wren/0.4.1/Recipe.sml"), + std::make_shared(std::stringstream(R"( + Name: 'Soup.Wren' + Language: 'Wren|1' + )"))); + + // Create the package lock + fileSystem->CreateMockFile( + Path("C:/WorkingDirectory/MyPackage/PackageLock.sml"), + std::make_shared(std::stringstream(R"( + Version: 5 + Closures: { + Root: { + 'C++': { + MyPackage: { Version: '../MyPackage/', Build: 'Build0', Tool: 'Tool0' } + } + } + Build0: { + Wren: { + 'mwasplund|Soup.Cpp': { Version: '0.8.2' } + } + } + Tool0: {} + } + )"))); + + fileSystem->CreateMockFile( + Path("C:/Users/Me/.soup/locks/Wren/mwasplund/Soup.Cpp/0.8.2/PackageLock.sml"), + std::make_shared(std::stringstream(R"( + Version: 5 + Closures: { + Root: { + Wren: { + 'mwasplund|Soup.Cpp': { Version: './', Build: 'Build0', Tool: 'Tool0' } + } + } + Build0: { + Wren: { + 'mwasplund|Soup.Wren': { Version: '0.4.1' } + } + } + Tool0: {} + } + )"))); + + auto fileSystemState = FileSystemState(); + + auto myPackageOperationGraph = OperationGraph( + std::vector(), + std::vector()); + auto myPackageFiles = std::set(); + auto myPackageOperationGraphContent = std::stringstream(); + OperationGraphWriter::Serialize(myPackageOperationGraph, myPackageFiles, fileSystemState, myPackageOperationGraphContent); + fileSystem->CreateMockFile( + Path("C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/.soup/Evaluate.bog"), + std::make_shared(std::move(myPackageOperationGraphContent))); + + auto soupCppOperationGraph = OperationGraph( + std::vector(), + std::vector()); + auto soupCppFiles = std::set(); + auto soupCppOperationGraphContent = std::stringstream(); + OperationGraphWriter::Serialize(soupCppOperationGraph, soupCppFiles, fileSystemState, soupCppOperationGraphContent); + fileSystem->CreateMockFile( + Path("C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/.soup/Evaluate.bog"), + std::make_shared(std::move(soupCppOperationGraphContent))); + + auto soupCppGenerateInput = ValueTable({ + { + "Dependencies", + ValueTable( + { + { + "Build", + ValueTable( + { + { + "mwasplund|Soup.Wren", + ValueTable( + { + { "SoupTargetDirectory", std::string("C:/BuiltIn/Packages/mwasplund/Soup.Wren/0.4.1/out/.soup/") }, + }) + }, + }) + }, + }) + }, + { + "EvaluateMacros", + ValueTable( + { + { "/(PACKAGE_mwasplund|Soup.Cpp)/", std::string("C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/") }, + { "/(TARGET_mwasplund|Soup.Cpp)/", std::string("C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/") }, + }) + }, + { + "EvaluateReadAccess", + ValueList( + { + std::string("/(PACKAGE_mwasplund|Soup.Cpp)/"), + std::string("/(TARGET_mwasplund|Soup.Cpp)/"), + }) + }, + { + "EvaluateWriteAccess", + ValueList( + { + std::string("/(TARGET_mwasplund|Soup.Cpp)/"), + }) + }, + { + "GenerateMacros", + ValueTable( + { + { "/(BUILD_TARGET_mwasplund|Soup.Wren)/", std::string("C:/BuiltIn/Packages/mwasplund/Soup.Wren/0.4.1/out/") }, + }) + }, + { + "GenerateSubGraphMacros", + ValueTable( + { + { "/(TARGET_mwasplund|Soup.Wren)/", std::string("/(BUILD_TARGET_mwasplund|Soup.Wren)/") }, + }) + }, + { + "GlobalState", + ValueTable( + { + { + "Context", + ValueTable( + { + { "HostPlatform", std::string("TestPlatform") }, + { "PackageDirectory", std::string("/(PACKAGE_mwasplund|Soup.Cpp)/") }, + { "TargetDirectory", std::string("/(TARGET_mwasplund|Soup.Cpp)/") }, + }) + }, + { + "Dependencies", + ValueTable( + { + { + "Build", + ValueTable( + { + { + "mwasplund|Soup.Wren", + ValueTable( + { + { + "Context", + ValueTable( + { + { "Reference", std::string("[Wren]mwasplund|Soup.Wren@0.4.1") }, + { "TargetDirectory", std::string("/(TARGET_mwasplund|Soup.Wren)/") }, + }) + }, + }) + }, + }) + }, + }) + }, + { + "FileSystem", + ValueList({ + std::string("Recipe.sml"), + }) + }, + { + "Parameters", + ValueTable( + { + { "System", std::string("Windows") }, + }) + }, + }) + }, + { + "PackageRoot", + std::string("C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/") + }, + { + "UserDataPath", + std::string("C:/Users/Me/.soup/") + }, + }); + auto soupCppGenerateInputContent = std::stringstream(); + ValueTableWriter::Serialize(soupCppGenerateInput, soupCppGenerateInputContent); + fileSystem->CreateMockFile( + Path("C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/.soup/GenerateInput.bvt"), + std::make_shared(std::move(soupCppGenerateInputContent))); + + auto myPackageGenerateInput = ValueTable({ + { + "Dependencies", + ValueTable( + { + { + "Build", + ValueTable( + { + { + "mwasplund|Soup.Cpp", + ValueTable( + { + { "SoupTargetDirectory", std::string("C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/.soup/") }, + }) + }, + }) + }, + }) + }, + { + "EvaluateMacros", + ValueTable( + { + { "/(PACKAGE_MyPackage)/", std::string("C:/WorkingDirectory/MyPackage/") }, + { "/(TARGET_MyPackage)/", std::string("C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/") }, + }) + }, + { + "EvaluateReadAccess", + ValueList( + { + std::string("/(PACKAGE_MyPackage)/"), + std::string("/(TARGET_MyPackage)/"), + }) + }, + { + "EvaluateWriteAccess", + ValueList( + { + std::string("/(TARGET_MyPackage)/"), + }) + }, + { + "GenerateMacros", + ValueTable( + { + { "/(BUILD_TARGET_mwasplund|Soup.Cpp)/", std::string("C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/") }, + }) + }, + { + "GenerateSubGraphMacros", + ValueTable( + { + { "/(TARGET_mwasplund|Soup.Cpp)/", std::string("/(BUILD_TARGET_mwasplund|Soup.Cpp)/") }, + }) + }, + { + "GlobalState", + ValueTable( + { + { + "Context", + ValueTable( + { + { "HostPlatform", std::string("TestPlatform") }, + { "PackageDirectory", std::string("/(PACKAGE_MyPackage)/") }, + { "TargetDirectory", std::string("/(TARGET_MyPackage)/") }, + }) + }, + { + "Dependencies", + ValueTable( + { + { + "Build", + ValueTable( + { + { + "mwasplund|Soup.Cpp", + ValueTable( + { + { + "Context", + ValueTable( + { + { "Reference", std::string("[Wren]mwasplund|Soup.Cpp@0.8.2") }, + { "TargetDirectory", std::string("/(TARGET_mwasplund|Soup.Cpp)/") }, + }) + }, + }) + }, + }) + }, + }) + }, + { + "FileSystem", + ValueList({ + std::string("Recipe.sml"), + }) + }, + { "Parameters", ValueTable() }, + }) + }, + { + "PackageRoot", + std::string("C:/WorkingDirectory/MyPackage/") + }, + { + "UserDataPath", + std::string("C:/Users/Me/.soup/") + }, + }); + auto myPackageGenerateInputContent = std::stringstream(); + ValueTableWriter::Serialize(myPackageGenerateInput, myPackageGenerateInputContent); + fileSystem->CreateMockFile( + Path("C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/.soup/GenerateInput.bvt"), + std::make_shared(std::move(myPackageGenerateInputContent))); + + auto myPackageGenerateResults = OperationResults({ + { + 1, + OperationResult( + true, + std::chrono::clock_cast( + std::chrono::time_point()), + {}, + {}) + }, + }); + auto myPackageGenerateResultsContent = std::stringstream(); + auto myPackageGenerateResultsFiles = std::set(); + OperationResultsWriter::Serialize(myPackageGenerateResults, myPackageGenerateResultsFiles, fileSystemState, myPackageGenerateResultsContent); + fileSystem->CreateMockFile( + Path("C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/.soup/Generate.bor"), + std::make_shared(std::move(myPackageGenerateResultsContent))); + + auto soupCppGenerateResults = OperationResults({ + { + 1, + OperationResult( + true, + std::chrono::clock_cast( + std::chrono::time_point()), + {}, + {}) + }, + }); + auto soupCppGenerateResultsContent = std::stringstream(); + auto soupCppGenerateResultsFiles = std::set(); + OperationResultsWriter::Serialize(soupCppGenerateResults, soupCppGenerateResultsFiles, fileSystemState, soupCppGenerateResultsContent); + fileSystem->CreateMockFile( + Path("C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/.soup/Generate.bor"), + std::make_shared(std::move(soupCppGenerateResultsContent))); + + auto soupCppEvaluateResults = OperationResults(); + auto soupCppEvaluateResultsContent = std::stringstream(); + auto soupCppEvaluateResultsFiles = std::set(); + OperationResultsWriter::Serialize(soupCppEvaluateResults, soupCppEvaluateResultsFiles, fileSystemState, soupCppEvaluateResultsContent); + fileSystem->CreateMockFile( + Path("C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/.soup/Evaluate.bor"), + std::make_shared(std::move(soupCppEvaluateResultsContent))); + + auto myPackageEvaluateResults = OperationResults(); + auto myPackageEvaluateResultsContent = std::stringstream(); + auto myPackageEvaluateResultsFiles = std::set(); + OperationResultsWriter::Serialize(myPackageEvaluateResults, myPackageEvaluateResultsFiles, fileSystemState, myPackageEvaluateResultsContent); + fileSystem->CreateMockFile( + Path("C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/.soup/Evaluate.bor"), + std::make_shared(std::move(myPackageEvaluateResultsContent))); + + // Register the test process manager + auto processManager = std::make_shared(); + auto scopedProcessManager = ScopedProcessManagerRegister(processManager); + + // Register the test process manager + auto monitorProcessManager = std::make_shared(); + auto scopedMonitorProcessManager = Monitor::ScopedMonitorProcessManagerRegister(monitorProcessManager); + + ankerl::nanobench::Bench().minEpochIterations(3000).run("BuildEngine Execute NoDependencies UpToDate", [&] + { + auto builtInPackageDirectory = Path("C:/BuiltIn/Packages/"); + auto arguments = RecipeBuildArguments(); + arguments.HostPlatform = "TestPlatform"; + arguments.WorkingDirectory = Path("C:/WorkingDirectory/MyPackage/"); + + // Load user config state + auto userDataPath = BuildEngine::GetSoupUserDataPath(); + auto recipeCache = RecipeCache(); + + auto packageProvider = BuildEngine::LoadBuildGraph( + builtInPackageDirectory, + arguments.WorkingDirectory, + arguments.GlobalParameters, + userDataPath, + recipeCache); + + BuildEngine::Execute( + packageProvider, + std::move(arguments), + userDataPath, + recipeCache); + }); + } +} \ No newline at end of file diff --git a/Source/BenchTests/Nanobench.cpp b/Source/BenchTests/Nanobench.cpp new file mode 100644 index 000000000..31725cd34 --- /dev/null +++ b/Source/BenchTests/Nanobench.cpp @@ -0,0 +1,2 @@ +#define ANKERL_NANOBENCH_IMPLEMENT +#include "nanobench.h" \ No newline at end of file diff --git a/Source/BenchTests/PackageLock.sml b/Source/BenchTests/PackageLock.sml new file mode 100644 index 000000000..4136189c2 --- /dev/null +++ b/Source/BenchTests/PackageLock.sml @@ -0,0 +1,34 @@ +Version: 5 +Closures: { + Root: { + 'C++': { + 'Monitor.Host': { Version: '../Monitor/Host/', Build: 'Build0', Tool: 'Tool0' } + 'Monitor.Shared': { Version: '../Monitor/Shared/', Build: 'Build0', Tool: 'Tool0' } + 'Soup.BenchTests': { Version: './', Build: 'Build0', Tool: 'Tool0' } + 'Soup.Core': { Version: '../Client/Core/', Build: 'Build1', Tool: 'Tool0' } + 'mwasplund|CryptoPP': { Version: '1.2.2', Build: 'Build0', Tool: 'Tool0' } + 'mwasplund|Detours': { Version: '4.0.12', Build: 'Build0', Tool: 'Tool0' } + 'mwasplund|Opal': { Version: '0.11.0', Build: 'Build0', Tool: 'Tool0' } + 'mwasplund|Soup.Test.Assert': { Version: '0.4.1', Build: 'Build0', Tool: 'Tool0' } + 'mwasplund|reflex': { Version: '1.0.4', Build: 'Build0', Tool: 'Tool0' } + 'mwasplund|wren': { Version: '1.0.5', Build: 'Build0', Tool: 'Tool0' } + } + } + Build0: { + Wren: { + 'mwasplund|Soup.Cpp': { Version: '0.12.0' } + } + } + Build1: { + Wren: { + 'mwasplund|Soup.Cpp': { Version: '0.12.0' } + 'mwasplund|Soup.Test.Cpp': { Version: '0.11.0' } + } + } + Tool0: { + 'C++': { + 'mwasplund|copy': { Version: '1.1.0' } + 'mwasplund|mkdir': { Version: '1.1.0' } + } + } +} \ No newline at end of file diff --git a/Source/BenchTests/Recipe.sml b/Source/BenchTests/Recipe.sml new file mode 100644 index 000000000..df3e56938 --- /dev/null +++ b/Source/BenchTests/Recipe.sml @@ -0,0 +1,13 @@ +Name: 'Soup.BenchTests' +Language: 'C++|0' +Version: '1.0.0' +Type: 'Executable' +Source: [ + 'Main.cpp' + 'Nanobench.cpp' +] +Dependencies: { + Runtime: [ + '../Client/Core/' + ] +} \ No newline at end of file diff --git a/Source/BenchTests/nanobench.h b/Source/BenchTests/nanobench.h new file mode 100644 index 000000000..d233dccef --- /dev/null +++ b/Source/BenchTests/nanobench.h @@ -0,0 +1,3484 @@ +// __ _ _______ __ _ _____ ______ _______ __ _ _______ _ _ +// | \ | |_____| | \ | | | |_____] |______ | \ | | |_____| +// | \_| | | | \_| |_____| |_____] |______ | \_| |_____ | | +// +// Microbenchmark framework for C++11/14/17/20 +// https://github.com/martinus/nanobench +// +// Licensed under the MIT License . +// SPDX-License-Identifier: MIT +// Copyright (c) 2019-2023 Martin Leitner-Ankerl +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#ifndef ANKERL_NANOBENCH_H_INCLUDED +#define ANKERL_NANOBENCH_H_INCLUDED + +// see https://semver.org/ +#define ANKERL_NANOBENCH_VERSION_MAJOR 4 // incompatible API changes +#define ANKERL_NANOBENCH_VERSION_MINOR 3 // backwards-compatible changes +#define ANKERL_NANOBENCH_VERSION_PATCH 11 // backwards-compatible bug fixes + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// public facing api - as minimal as possible +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#include // high_resolution_clock +#include // memcpy +#include // for std::ostream* custom output target in Config +#include // all names +#include // holds context information of results +#include // holds all results + +#define ANKERL_NANOBENCH(x) ANKERL_NANOBENCH_PRIVATE_##x() + +#define ANKERL_NANOBENCH_PRIVATE_CXX() __cplusplus +#define ANKERL_NANOBENCH_PRIVATE_CXX98() 199711L +#define ANKERL_NANOBENCH_PRIVATE_CXX11() 201103L +#define ANKERL_NANOBENCH_PRIVATE_CXX14() 201402L +#define ANKERL_NANOBENCH_PRIVATE_CXX17() 201703L + +#if ANKERL_NANOBENCH(CXX) >= ANKERL_NANOBENCH(CXX17) +# define ANKERL_NANOBENCH_PRIVATE_NODISCARD() [[nodiscard]] +#else +# define ANKERL_NANOBENCH_PRIVATE_NODISCARD() +#endif + +#if defined(__clang__) +# define ANKERL_NANOBENCH_PRIVATE_IGNORE_PADDED_PUSH() \ + _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored \"-Wpadded\"") +# define ANKERL_NANOBENCH_PRIVATE_IGNORE_PADDED_POP() _Pragma("clang diagnostic pop") +#else +# define ANKERL_NANOBENCH_PRIVATE_IGNORE_PADDED_PUSH() +# define ANKERL_NANOBENCH_PRIVATE_IGNORE_PADDED_POP() +#endif + +#if defined(__GNUC__) +# define ANKERL_NANOBENCH_PRIVATE_IGNORE_EFFCPP_PUSH() _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Weffc++\"") +# define ANKERL_NANOBENCH_PRIVATE_IGNORE_EFFCPP_POP() _Pragma("GCC diagnostic pop") +#else +# define ANKERL_NANOBENCH_PRIVATE_IGNORE_EFFCPP_PUSH() +# define ANKERL_NANOBENCH_PRIVATE_IGNORE_EFFCPP_POP() +#endif + +#if defined(ANKERL_NANOBENCH_LOG_ENABLED) +# include +# define ANKERL_NANOBENCH_LOG(x) \ + do { \ + std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << x << std::endl; \ + } while (0) +#else +# define ANKERL_NANOBENCH_LOG(x) \ + do { \ + } while (0) +#endif + +#define ANKERL_NANOBENCH_PRIVATE_PERF_COUNTERS() 0 +#if defined(__linux__) && !defined(ANKERL_NANOBENCH_DISABLE_PERF_COUNTERS) +# include +# if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 3, 0) +// PERF_COUNT_HW_REF_CPU_CYCLES only available since kernel 3.3 +// PERF_FLAG_FD_CLOEXEC since kernel 3.14 +# undef ANKERL_NANOBENCH_PRIVATE_PERF_COUNTERS +# define ANKERL_NANOBENCH_PRIVATE_PERF_COUNTERS() 1 +# endif +#endif + +#if defined(__clang__) +# define ANKERL_NANOBENCH_NO_SANITIZE(...) __attribute__((no_sanitize(__VA_ARGS__))) +#else +# define ANKERL_NANOBENCH_NO_SANITIZE(...) +#endif + +#if defined(_MSC_VER) +# define ANKERL_NANOBENCH_PRIVATE_NOINLINE() __declspec(noinline) +#else +# define ANKERL_NANOBENCH_PRIVATE_NOINLINE() __attribute__((noinline)) +#endif + +// workaround missing "is_trivially_copyable" in g++ < 5.0 +// See https://stackoverflow.com/a/31798726/48181 +#if defined(__GNUC__) && __GNUC__ < 5 +# define ANKERL_NANOBENCH_IS_TRIVIALLY_COPYABLE(...) __has_trivial_copy(__VA_ARGS__) +#else +# define ANKERL_NANOBENCH_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value +#endif + +// noexcept may be missing for std::string. +// See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58265 +#define ANKERL_NANOBENCH_PRIVATE_NOEXCEPT_STRING_MOVE() std::is_nothrow_move_assignable::value + +// declarations /////////////////////////////////////////////////////////////////////////////////// + +namespace ankerl { +namespace nanobench { + +using Clock = std::conditional::type; +class Bench; +struct Config; +class Result; +class Rng; +class BigO; + +/** + * @brief Renders output from a mustache-like template and benchmark results. + * + * The templating facility here is heavily inspired by [mustache - logic-less templates](https://mustache.github.io/). + * It adds a few more features that are necessary to get all of the captured data out of nanobench. Please read the + * excellent [mustache manual](https://mustache.github.io/mustache.5.html) to see what this is all about. + * + * nanobench output has two nested layers, *result* and *measurement*. Here is a hierarchy of the allowed tags: + * + * * `{{#result}}` Marks the begin of the result layer. Whatever comes after this will be instantiated as often as + * a benchmark result is available. Within it, you can use these tags: + * + * * `{{title}}` See Bench::title. + * + * * `{{name}}` Benchmark name, usually directly provided with Bench::run, but can also be set with Bench::name. + * + * * `{{unit}}` Unit, e.g. `byte`. Defaults to `op`, see Bench::unit. + * + * * `{{batch}}` Batch size, see Bench::batch. + * + * * `{{complexityN}}` Value used for asymptotic complexity calculation. See Bench::complexityN. + * + * * `{{epochs}}` Number of epochs, see Bench::epochs. + * + * * `{{clockResolution}}` Accuracy of the clock, i.e. what's the smallest time possible to measure with the clock. + * For modern systems, this can be around 20 ns. This value is automatically determined by nanobench at the first + * benchmark that is run, and used as a static variable throughout the application's runtime. + * + * * `{{clockResolutionMultiple}}` Configuration multiplier for `clockResolution`. See Bench::clockResolutionMultiple. + * This is the target runtime for each measurement (epoch). That means the more accurate your clock is, the faster + * will be the benchmark. Basing the measurement's runtime on the clock resolution is the main reason why nanobench is so fast. + * + * * `{{maxEpochTime}}` Configuration for a maximum time each measurement (epoch) is allowed to take. Note that at least + * a single iteration will be performed, even when that takes longer than maxEpochTime. See Bench::maxEpochTime. + * + * * `{{minEpochTime}}` Minimum epoch time, defaults to 1ms. See Bench::minEpochTime. + * + * * `{{minEpochIterations}}` See Bench::minEpochIterations. + * + * * `{{epochIterations}}` See Bench::epochIterations. + * + * * `{{warmup}}` Number of iterations used before measuring starts. See Bench::warmup. + * + * * `{{relative}}` True or false, depending on the setting you have used. See Bench::relative. + * + * * `{{context(variableName)}}` See Bench::context. + * + * Apart from these tags, it is also possible to use some mathematical operations on the measurement data. The operations + * are of the form `{{command(name)}}`. Currently `name` can be one of `elapsed`, `iterations`. If performance counters + * are available (currently only on current Linux systems), you also have `pagefaults`, `cpucycles`, + * `contextswitches`, `instructions`, `branchinstructions`, and `branchmisses`. All the measures (except `iterations`) are + * provided for a single iteration (so `elapsed` is the time a single iteration took). The following tags are available: + * + * * `{{median()}}` Calculate median of a measurement data set, e.g. `{{median(elapsed)}}`. + * + * * `{{average()}}` Average (mean) calculation. + * + * * `{{medianAbsolutePercentError()}}` Calculates MdAPE, the Median Absolute Percentage Error. The MdAPE is an excellent + * metric for the variation of measurements. It is more robust to outliers than the + * [Mean absolute percentage error (M-APE)](https://en.wikipedia.org/wiki/Mean_absolute_percentage_error). + * @f[ + * \mathrm{MdAPE}(e) = \mathrm{med}\{| \frac{e_i - \mathrm{med}\{e\}}{e_i}| \} + * @f] + * E.g. for *elapsed*: First, @f$ \mathrm{med}\{e\} @f$ calculates the median by sorting and then taking the middle element + * of all *elapsed* measurements. This is used to calculate the absolute percentage + * error to this median for each measurement, as in @f$ | \frac{e_i - \mathrm{med}\{e\}}{e_i}| @f$. All these results + * are sorted, and the middle value is chosen as the median absolute percent error. + * + * This measurement is a bit hard to interpret, but it is very robust against outliers. E.g. a value of 5% means that half of the + * measurements deviate less than 5% from the median, and the other deviate more than 5% from the median. + * + * * `{{sum()}}` Sum of all the measurements. E.g. `{{sum(iterations)}}` will give you the total number of iterations +* measured in this benchmark. + * + * * `{{minimum()}}` Minimum of all measurements. + * + * * `{{maximum()}}` Maximum of all measurements. + * + * * `{{sumProduct(, )}}` Calculates the sum of the products of corresponding measures: + * @f[ + * \mathrm{sumProduct}(a,b) = \sum_{i=1}^{n}a_i\cdot b_i + * @f] + * E.g. to calculate total runtime of the benchmark, you multiply iterations with elapsed time for each measurement, and + * sum these results up: + * `{{sumProduct(iterations, elapsed)}}`. + * + * * `{{#measurement}}` To access individual measurement results, open the begin tag for measurements. + * + * * `{{elapsed}}` Average elapsed wall clock time per iteration, in seconds. + * + * * `{{iterations}}` Number of iterations in the measurement. The number of iterations will fluctuate due + * to some applied randomness, to enhance accuracy. + * + * * `{{pagefaults}}` Average number of pagefaults per iteration. + * + * * `{{cpucycles}}` Average number of CPU cycles processed per iteration. + * + * * `{{contextswitches}}` Average number of context switches per iteration. + * + * * `{{instructions}}` Average number of retired instructions per iteration. + * + * * `{{branchinstructions}}` Average number of branches executed per iteration. + * + * * `{{branchmisses}}` Average number of branches that were missed per iteration. + * + * * `{{/measurement}}` Ends the measurement tag. + * + * * `{{/result}}` Marks the end of the result layer. This is the end marker for the template part that will be instantiated + * for each benchmark result. + * + * + * For the layer tags *result* and *measurement* you additionally can use these special markers: + * + * * ``{{#-first}}`` - Begin marker of a template that will be instantiated *only for the first* entry in the layer. Use is only + * allowed between the begin and end marker of the layer. So between ``{{#result}}`` and ``{{/result}}``, or between + * ``{{#measurement}}`` and ``{{/measurement}}``. Finish the template with ``{{/-first}}``. + * + * * ``{{^-first}}`` - Begin marker of a template that will be instantiated *for each except the first* entry in the layer. This, + * this is basically the inversion of ``{{#-first}}``. Use is only allowed between the begin and end marker of the layer. + * So between ``{{#result}}`` and ``{{/result}}``, or between ``{{#measurement}}`` and ``{{/measurement}}``. + * + * * ``{{/-first}}`` - End marker for either ``{{#-first}}`` or ``{{^-first}}``. + * + * * ``{{#-last}}`` - Begin marker of a template that will be instantiated *only for the last* entry in the layer. Use is only + * allowed between the begin and end marker of the layer. So between ``{{#result}}`` and ``{{/result}}``, or between + * ``{{#measurement}}`` and ``{{/measurement}}``. Finish the template with ``{{/-last}}``. + * + * * ``{{^-last}}`` - Begin marker of a template that will be instantiated *for each except the last* entry in the layer. This, + * this is basically the inversion of ``{{#-last}}``. Use is only allowed between the begin and end marker of the layer. + * So between ``{{#result}}`` and ``{{/result}}``, or between ``{{#measurement}}`` and ``{{/measurement}}``. + * + * * ``{{/-last}}`` - End marker for either ``{{#-last}}`` or ``{{^-last}}``. + * + @verbatim embed:rst + + For an overview of all the possible data you can get out of nanobench, please see the tutorial at :ref:`tutorial-template-json`. + + The templates that ship with nanobench are: + + * :cpp:func:`templates::csv() ` + * :cpp:func:`templates::json() ` + * :cpp:func:`templates::htmlBoxplot() ` + * :cpp:func:`templates::pyperf() ` + + @endverbatim + * + * @param mustacheTemplate The template. + * @param bench Benchmark, containing all the results. + * @param out Output for the generated output. + */ +void render(char const* mustacheTemplate, Bench const& bench, std::ostream& out); +void render(std::string const& mustacheTemplate, Bench const& bench, std::ostream& out); + +/** + * Same as render(char const* mustacheTemplate, Bench const& bench, std::ostream& out), but for when + * you only have results available. + * + * @param mustacheTemplate The template. + * @param results All the results to be used for rendering. + * @param out Output for the generated output. + */ +void render(char const* mustacheTemplate, std::vector const& results, std::ostream& out); +void render(std::string const& mustacheTemplate, std::vector const& results, std::ostream& out); + +// Contains mustache-like templates +namespace templates { + +/*! + @brief CSV data for the benchmark results. + + Generates a comma-separated values dataset. First line is the header, each following line is a summary of each benchmark run. + + @verbatim embed:rst + See the tutorial at :ref:`tutorial-template-csv` for an example. + @endverbatim + */ +char const* csv() noexcept; + +/*! + @brief HTML output that uses plotly to generate an interactive boxplot chart. See the tutorial for an example output. + + The output uses only the elapsed wall clock time, and displays each epoch as a single dot. + @verbatim embed:rst + See the tutorial at :ref:`tutorial-template-html` for an example. + @endverbatim + + @see also ankerl::nanobench::render() + */ +char const* htmlBoxplot() noexcept; + +/*! + @brief Output in pyperf compatible JSON format, which can be used for more analyzation. + @verbatim embed:rst + See the tutorial at :ref:`tutorial-template-pyperf` for an example how to further analyze the output. + @endverbatim + */ +char const* pyperf() noexcept; + +/*! + @brief Template to generate JSON data. + + The generated JSON data contains *all* data that has been generated. All times are as double values, in seconds. The output can get + quite large. + @verbatim embed:rst + See the tutorial at :ref:`tutorial-template-json` for an example. + @endverbatim + */ +char const* json() noexcept; + +} // namespace templates + +namespace detail { + +template +struct PerfCountSet; + +class IterationLogic; +class PerformanceCounters; + +#if ANKERL_NANOBENCH(PERF_COUNTERS) +class LinuxPerformanceCounters; +#endif + +} // namespace detail +} // namespace nanobench +} // namespace ankerl + +// definitions //////////////////////////////////////////////////////////////////////////////////// + +namespace ankerl { +namespace nanobench { +namespace detail { + +template +struct PerfCountSet { + T pageFaults{}; + T cpuCycles{}; + T contextSwitches{}; + T instructions{}; + T branchInstructions{}; + T branchMisses{}; +}; + +} // namespace detail + +ANKERL_NANOBENCH(IGNORE_PADDED_PUSH) +struct Config { + // actual benchmark config + std::string mBenchmarkTitle = "benchmark"; // NOLINT(misc-non-private-member-variables-in-classes) + std::string mBenchmarkName = "noname"; // NOLINT(misc-non-private-member-variables-in-classes) + std::string mUnit = "op"; // NOLINT(misc-non-private-member-variables-in-classes) + double mBatch = 1.0; // NOLINT(misc-non-private-member-variables-in-classes) + double mComplexityN = -1.0; // NOLINT(misc-non-private-member-variables-in-classes) + size_t mNumEpochs = 11; // NOLINT(misc-non-private-member-variables-in-classes) + size_t mClockResolutionMultiple = static_cast(1000); // NOLINT(misc-non-private-member-variables-in-classes) + std::chrono::nanoseconds mMaxEpochTime = std::chrono::milliseconds(100); // NOLINT(misc-non-private-member-variables-in-classes) + std::chrono::nanoseconds mMinEpochTime = std::chrono::milliseconds(1); // NOLINT(misc-non-private-member-variables-in-classes) + uint64_t mMinEpochIterations{1}; // NOLINT(misc-non-private-member-variables-in-classes) + // If not 0, run *exactly* these number of iterations per epoch. + uint64_t mEpochIterations{0}; // NOLINT(misc-non-private-member-variables-in-classes) + uint64_t mWarmup = 0; // NOLINT(misc-non-private-member-variables-in-classes) + std::ostream* mOut = nullptr; // NOLINT(misc-non-private-member-variables-in-classes) + std::chrono::duration mTimeUnit = std::chrono::nanoseconds{1}; // NOLINT(misc-non-private-member-variables-in-classes) + std::string mTimeUnitName = "ns"; // NOLINT(misc-non-private-member-variables-in-classes) + bool mShowPerformanceCounters = true; // NOLINT(misc-non-private-member-variables-in-classes) + bool mIsRelative = false; // NOLINT(misc-non-private-member-variables-in-classes) + std::unordered_map mContext{}; // NOLINT(misc-non-private-member-variables-in-classes) + + Config(); + ~Config(); + Config& operator=(Config const& other); + Config& operator=(Config&& other) noexcept(ANKERL_NANOBENCH(NOEXCEPT_STRING_MOVE)); + Config(Config const& other); + Config(Config&& other) noexcept; +}; +ANKERL_NANOBENCH(IGNORE_PADDED_POP) + +// Result returned after a benchmark has finished. Can be used as a baseline for relative(). +ANKERL_NANOBENCH(IGNORE_PADDED_PUSH) +class Result { +public: + enum class Measure : size_t { + elapsed, + iterations, + pagefaults, + cpucycles, + contextswitches, + instructions, + branchinstructions, + branchmisses, + _size + }; + + explicit Result(Config benchmarkConfig); + + ~Result(); + Result& operator=(Result const& other); + Result& operator=(Result&& other) noexcept(ANKERL_NANOBENCH(NOEXCEPT_STRING_MOVE)); + Result(Result const& other); + Result(Result&& other) noexcept; + + // adds new measurement results + // all values are scaled by iters (except iters...) + void add(Clock::duration totalElapsed, uint64_t iters, detail::PerformanceCounters const& pc); + + ANKERL_NANOBENCH(NODISCARD) Config const& config() const noexcept; + + ANKERL_NANOBENCH(NODISCARD) double median(Measure m) const; + ANKERL_NANOBENCH(NODISCARD) double medianAbsolutePercentError(Measure m) const; + ANKERL_NANOBENCH(NODISCARD) double average(Measure m) const; + ANKERL_NANOBENCH(NODISCARD) double sum(Measure m) const noexcept; + ANKERL_NANOBENCH(NODISCARD) double sumProduct(Measure m1, Measure m2) const noexcept; + ANKERL_NANOBENCH(NODISCARD) double minimum(Measure m) const noexcept; + ANKERL_NANOBENCH(NODISCARD) double maximum(Measure m) const noexcept; + ANKERL_NANOBENCH(NODISCARD) std::string const& context(char const* variableName) const; + ANKERL_NANOBENCH(NODISCARD) std::string const& context(std::string const& variableName) const; + + ANKERL_NANOBENCH(NODISCARD) bool has(Measure m) const noexcept; + ANKERL_NANOBENCH(NODISCARD) double get(size_t idx, Measure m) const; + ANKERL_NANOBENCH(NODISCARD) bool empty() const noexcept; + ANKERL_NANOBENCH(NODISCARD) size_t size() const noexcept; + + // Finds string, if not found, returns _size. + static Measure fromString(std::string const& str); + +private: + Config mConfig{}; + std::vector> mNameToMeasurements{}; +}; +ANKERL_NANOBENCH(IGNORE_PADDED_POP) + +/** + * An extremely fast random generator. Currently, this implements *RomuDuoJr*, developed by Mark Overton. Source: + * http://www.romu-random.org/ + * + * RomuDuoJr is extremely fast and provides reasonable good randomness. Not enough for large jobs, but definitely + * good enough for a benchmarking framework. + * + * * Estimated capacity: @f$ 2^{51} @f$ bytes + * * Register pressure: 4 + * * State size: 128 bits + * + * This random generator is a drop-in replacement for the generators supplied by ````. It is not + * cryptographically secure. It's intended purpose is to be very fast so that benchmarks that make use + * of randomness are not distorted too much by the random generator. + * + * Rng also provides a few non-standard helpers, optimized for speed. + */ +class Rng final { +public: + /** + * @brief This RNG provides 64bit randomness. + */ + using result_type = uint64_t; + + static constexpr uint64_t(min)(); + static constexpr uint64_t(max)(); + + /** + * As a safety precaution, we don't allow copying. Copying a PRNG would mean you would have two random generators that produce the + * same sequence, which is generally not what one wants. Instead create a new rng with the default constructor Rng(), which is + * automatically seeded from `std::random_device`. If you really need a copy, use `copy()`. + */ + Rng(Rng const&) = delete; + + /** + * Same as Rng(Rng const&), we don't allow assignment. If you need a new Rng create one with the default constructor Rng(). + */ + Rng& operator=(Rng const&) = delete; + + // moving is ok + Rng(Rng&&) noexcept = default; + Rng& operator=(Rng&&) noexcept = default; + ~Rng() noexcept = default; + + /** + * @brief Creates a new Random generator with random seed. + * + * Instead of a default seed (as the random generators from the STD), this properly seeds the random generator from + * `std::random_device`. It guarantees correct seeding. Note that seeding can be relatively slow, depending on the source of + * randomness used. So it is best to create a Rng once and use it for all your randomness purposes. + */ + Rng(); + + /*! + Creates a new Rng that is seeded with a specific seed. Each Rng created from the same seed will produce the same randomness + sequence. This can be useful for deterministic behavior. + + @verbatim embed:rst + .. note:: + + The random algorithm might change between nanobench releases. Whenever a faster and/or better random + generator becomes available, I will switch the implementation. + @endverbatim + + As per the Romu paper, this seeds the Rng with splitMix64 algorithm and performs 10 initial rounds for further mixing up of the + internal state. + + @param seed The 64bit seed. All values are allowed, even 0. + */ + explicit Rng(uint64_t seed) noexcept; + Rng(uint64_t x, uint64_t y) noexcept; + explicit Rng(std::vector const& data); + + /** + * Creates a copy of the Rng, thus the copy provides exactly the same random sequence as the original. + */ + ANKERL_NANOBENCH(NODISCARD) Rng copy() const noexcept; + + /** + * @brief Produces a 64bit random value. This should be very fast, thus it is marked as inline. In my benchmark, this is ~46 times + * faster than `std::default_random_engine` for producing 64bit random values. It seems that the fastest std contender is + * `std::mt19937_64`. Still, this RNG is 2-3 times as fast. + * + * @return uint64_t The next 64 bit random value. + */ + inline uint64_t operator()() noexcept; + + // This is slightly biased. See + + /** + * Generates a random number between 0 and range (excluding range). + * + * The algorithm only produces 32bit numbers, and is slightly biased. The effect is quite small unless your range is close to the + * maximum value of an integer. It is possible to correct the bias with rejection sampling (see + * [here](https://lemire.me/blog/2016/06/30/fast-random-shuffling/), but this is most likely irrelevant in practices for the + * purposes of this Rng. + * + * See Daniel Lemire's blog post [A fast alternative to the modulo + * reduction](https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/) + * + * @param range Upper exclusive range. E.g a value of 3 will generate random numbers 0, 1, 2. + * @return uint32_t Generated random values in range [0, range(. + */ + inline uint32_t bounded(uint32_t range) noexcept; + + // random double in range [0, 1( + // see http://prng.di.unimi.it/ + + /** + * Provides a random uniform double value between 0 and 1. This uses the method described in [Generating uniform doubles in the + * unit interval](http://prng.di.unimi.it/), and is extremely fast. + * + * @return double Uniformly distributed double value in range [0,1(, excluding 1. + */ + inline double uniform01() noexcept; + + /** + * Shuffles all entries in the given container. Although this has a slight bias due to the implementation of bounded(), this is + * preferable to `std::shuffle` because it is over 5 times faster. See Daniel Lemire's blog post [Fast random + * shuffling](https://lemire.me/blog/2016/06/30/fast-random-shuffling/). + * + * @param container The whole container will be shuffled. + */ + template + void shuffle(Container& container) noexcept; + + /** + * Extracts the full state of the generator, e.g. for serialization. For this RNG this is just 2 values, but to stay API compatible + * with future implementations that potentially use more state, we use a vector. + * + * @return Vector containing the full state: + */ + ANKERL_NANOBENCH(NODISCARD) std::vector state() const; + +private: + static constexpr uint64_t rotl(uint64_t x, unsigned k) noexcept; + + uint64_t mX; + uint64_t mY; +}; + +/** + * @brief Main entry point to nanobench's benchmarking facility. + * + * It holds configuration and results from one or more benchmark runs. Usually it is used in a single line, where the object is + * constructed, configured, and then a benchmark is run. E.g. like this: + * + * ankerl::nanobench::Bench().unit("byte").batch(1000).run("random fluctuations", [&] { + * // here be the benchmark code + * }); + * + * In that example Bench() constructs the benchmark, it is then configured with unit() and batch(), and after configuration a + * benchmark is executed with run(). Once run() has finished, it prints the result to `std::cout`. It would also store the results + * in the Bench instance, but in this case the object is immediately destroyed so it's not available any more. + */ +ANKERL_NANOBENCH(IGNORE_PADDED_PUSH) +class Bench { +public: + /** + * @brief Creates a new benchmark for configuration and running of benchmarks. + */ + Bench(); + + Bench(Bench&& other) noexcept; + Bench& operator=(Bench&& other) noexcept(ANKERL_NANOBENCH(NOEXCEPT_STRING_MOVE)); + Bench(Bench const& other); + Bench& operator=(Bench const& other); + ~Bench() noexcept; + + /*! + @brief Repeatedly calls `op()` based on the configuration, and performs measurements. + + This call is marked with `noinline` to prevent the compiler to optimize beyond different benchmarks. This can have quite a big + effect on benchmark accuracy. + + @verbatim embed:rst + .. note:: + + Each call to your lambda must have a side effect that the compiler can't possibly optimize it away. E.g. add a result to an + externally defined number (like `x` in the above example), and finally call `doNotOptimizeAway` on the variables the compiler + must not remove. You can also use :cpp:func:`ankerl::nanobench::doNotOptimizeAway` directly in the lambda, but be aware that + this has a small overhead. + + @endverbatim + + @tparam Op The code to benchmark. + */ + template + ANKERL_NANOBENCH(NOINLINE) + Bench& run(char const* benchmarkName, Op&& op); + + template + ANKERL_NANOBENCH(NOINLINE) + Bench& run(std::string const& benchmarkName, Op&& op); + + /** + * @brief Same as run(char const* benchmarkName, Op op), but instead uses the previously set name. + * @tparam Op The code to benchmark. + */ + template + ANKERL_NANOBENCH(NOINLINE) + Bench& run(Op&& op); + + /** + * @brief Title of the benchmark, will be shown in the table header. Changing the title will start a new markdown table. + * + * @param benchmarkTitle The title of the benchmark. + */ + Bench& title(char const* benchmarkTitle); + Bench& title(std::string const& benchmarkTitle); + + /** + * @brief Gets the title of the benchmark + */ + ANKERL_NANOBENCH(NODISCARD) std::string const& title() const noexcept; + + /// Name of the benchmark, will be shown in the table row. + Bench& name(char const* benchmarkName); + Bench& name(std::string const& benchmarkName); + ANKERL_NANOBENCH(NODISCARD) std::string const& name() const noexcept; + + /** + * @brief Set context information. + * + * The information can be accessed using custom render templates via `{{context(variableName)}}`. + * Trying to render a variable that hasn't been set before raises an exception. + * Not included in (default) markdown table. + * + * @see clearContext, render + * + * @param variableName The name of the context variable. + * @param variableValue The value of the context variable. + */ + Bench& context(char const* variableName, char const* variableValue); + Bench& context(std::string const& variableName, std::string const& variableValue); + + /** + * @brief Reset context information. + * + * This may improve efficiency when using many context entries, + * or improve robustness by removing spurious context entries. + * + * @see context + */ + Bench& clearContext(); + + /** + * @brief Sets the batch size. + * + * E.g. number of processed byte, or some other metric for the size of the processed data in each iteration. If you benchmark + * hashing of a 1000 byte long string and want byte/sec as a result, you can specify 1000 as the batch size. + * + * @tparam T Any input type is internally cast to `double`. + * @param b batch size + */ + template + Bench& batch(T b) noexcept; + ANKERL_NANOBENCH(NODISCARD) double batch() const noexcept; + + /** + * @brief Sets the operation unit. + * + * Defaults to "op". Could be e.g. "byte" for string processing. This is used for the table header, e.g. to show `ns/byte`. Use + * singular (*byte*, not *bytes*). A change clears the currently collected results. + * + * @param unit The unit name. + */ + Bench& unit(char const* unit); + Bench& unit(std::string const& unit); + ANKERL_NANOBENCH(NODISCARD) std::string const& unit() const noexcept; + + /** + * @brief Sets the time unit to be used for the default output. + * + * Nanobench defaults to using ns (nanoseconds) as output in the markdown. For some benchmarks this is too coarse, so it is + * possible to configure this. E.g. use `timeUnit(1ms, "ms")` to show `ms/op` instead of `ns/op`. + * + * @param tu Time unit to display the results in, default is 1ns. + * @param tuName Name for the time unit, default is "ns" + */ + Bench& timeUnit(std::chrono::duration const& tu, std::string const& tuName); + ANKERL_NANOBENCH(NODISCARD) std::string const& timeUnitName() const noexcept; + ANKERL_NANOBENCH(NODISCARD) std::chrono::duration const& timeUnit() const noexcept; + + /** + * @brief Set the output stream where the resulting markdown table will be printed to. + * + * The default is `&std::cout`. You can disable all output by setting `nullptr`. + * + * @param outstream Pointer to output stream, can be `nullptr`. + */ + Bench& output(std::ostream* outstream) noexcept; + ANKERL_NANOBENCH(NODISCARD) std::ostream* output() const noexcept; + + /** + * Modern processors have a very accurate clock, being able to measure as low as 20 nanoseconds. This is the main trick nanobech to + * be so fast: we find out how accurate the clock is, then run the benchmark only so often that the clock's accuracy is good enough + * for accurate measurements. + * + * The default is to run one epoch for 1000 times the clock resolution. So for 20ns resolution and 11 epochs, this gives a total + * runtime of + * + * @f[ + * 20ns * 1000 * 11 \approx 0.2ms + * @f] + * + * To be precise, nanobench adds a 0-20% random noise to each evaluation. This is to prevent any aliasing effects, and further + * improves accuracy. + * + * Total runtime will be higher though: Some initial time is needed to find out the target number of iterations for each epoch, and + * there is some overhead involved to start & stop timers and calculate resulting statistics and writing the output. + * + * @param multiple Target number of times of clock resolution. Usually 1000 is a good compromise between runtime and accuracy. + */ + Bench& clockResolutionMultiple(size_t multiple) noexcept; + ANKERL_NANOBENCH(NODISCARD) size_t clockResolutionMultiple() const noexcept; + + /** + * @brief Controls number of epochs, the number of measurements to perform. + * + * The reported result will be the median of evaluation of each epoch. The higher you choose this, the more + * deterministic the result be and outliers will be more easily removed. Also the `err%` will be more accurate the higher this + * number is. Note that the `err%` will not necessarily decrease when number of epochs is increased. But it will be a more accurate + * representation of the benchmarked code's runtime stability. + * + * Choose the value wisely. In practice, 11 has been shown to be a reasonable choice between runtime performance and accuracy. + * This setting goes hand in hand with minEpochIterations() (or minEpochTime()). If you are more interested in *median* runtime, + * you might want to increase epochs(). If you are more interested in *mean* runtime, you might want to increase + * minEpochIterations() instead. + * + * @param numEpochs Number of epochs. + */ + Bench& epochs(size_t numEpochs) noexcept; + ANKERL_NANOBENCH(NODISCARD) size_t epochs() const noexcept; + + /** + * @brief Upper limit for the runtime of each epoch. + * + * As a safety precaution if the clock is not very accurate, we can set an upper limit for the maximum evaluation time per + * epoch. Default is 100ms. At least a single evaluation of the benchmark is performed. + * + * @see minEpochTime, minEpochIterations + * + * @param t Maximum target runtime for a single epoch. + */ + Bench& maxEpochTime(std::chrono::nanoseconds t) noexcept; + ANKERL_NANOBENCH(NODISCARD) std::chrono::nanoseconds maxEpochTime() const noexcept; + + /** + * @brief Minimum time each epoch should take. + * + * Default is 1ms, so we are mostly relying on clockResolutionMultiple(). In most cases this is exactly what you want. If you see + * that the evaluation is unreliable with a high `err%`, you can increase either minEpochTime() or minEpochIterations(). + * + * @see maxEpochTime, minEpochIterations + * + * @param t Minimum time each epoch should take. + */ + Bench& minEpochTime(std::chrono::nanoseconds t) noexcept; + ANKERL_NANOBENCH(NODISCARD) std::chrono::nanoseconds minEpochTime() const noexcept; + + /** + * @brief Sets the minimum number of iterations each epoch should take. + * + * Default is 1, and we rely on clockResolutionMultiple(). If the `err%` is high and you want a more smooth result, you might want + * to increase the minimum number of iterations, or increase the minEpochTime(). + * + * @see minEpochTime, maxEpochTime, minEpochIterations + * + * @param numIters Minimum number of iterations per epoch. + */ + Bench& minEpochIterations(uint64_t numIters) noexcept; + ANKERL_NANOBENCH(NODISCARD) uint64_t minEpochIterations() const noexcept; + + /** + * Sets exactly the number of iterations for each epoch. Ignores all other epoch limits. This forces nanobench to use exactly + * the given number of iterations for each epoch, not more and not less. Default is 0 (disabled). + * + * @param numIters Exact number of iterations to use. Set to 0 to disable. + */ + Bench& epochIterations(uint64_t numIters) noexcept; + ANKERL_NANOBENCH(NODISCARD) uint64_t epochIterations() const noexcept; + + /** + * @brief Sets a number of iterations that are initially performed without any measurements. + * + * Some benchmarks need a few evaluations to warm up caches / database / whatever access. Normally this should not be needed, since + * we show the median result so initial outliers will be filtered away automatically. If the warmup effect is large though, you + * might want to set it. Default is 0. + * + * @param numWarmupIters Number of warmup iterations. + */ + Bench& warmup(uint64_t numWarmupIters) noexcept; + ANKERL_NANOBENCH(NODISCARD) uint64_t warmup() const noexcept; + + /** + * @brief Marks the next run as the baseline. + * + * Call `relative(true)` to mark the run as the baseline. Successive runs will be compared to this run. It is calculated by + * + * @f[ + * 100\% * \frac{baseline}{runtime} + * @f] + * + * * 100% means it is exactly as fast as the baseline + * * >100% means it is faster than the baseline. E.g. 200% means the current run is twice as fast as the baseline. + * * <100% means it is slower than the baseline. E.g. 50% means it is twice as slow as the baseline. + * + * See the tutorial section "Comparing Results" for example usage. + * + * @param isRelativeEnabled True to enable processing + */ + Bench& relative(bool isRelativeEnabled) noexcept; + ANKERL_NANOBENCH(NODISCARD) bool relative() const noexcept; + + /** + * @brief Enables/disables performance counters. + * + * On Linux nanobench has a powerful feature to use performance counters. This enables counting of retired instructions, count + * number of branches, missed branches, etc. On default this is enabled, but you can disable it if you don't need that feature. + * + * @param showPerformanceCounters True to enable, false to disable. + */ + Bench& performanceCounters(bool showPerformanceCounters) noexcept; + ANKERL_NANOBENCH(NODISCARD) bool performanceCounters() const noexcept; + + /** + * @brief Retrieves all benchmark results collected by the bench object so far. + * + * Each call to run() generates a Result that is stored within the Bench instance. This is mostly for advanced users who want to + * see all the nitty gritty details. + * + * @return All results collected so far. + */ + ANKERL_NANOBENCH(NODISCARD) std::vector const& results() const noexcept; + + /*! + @verbatim embed:rst + + Convenience shortcut to :cpp:func:`ankerl::nanobench::doNotOptimizeAway`. + + @endverbatim + */ + template + Bench& doNotOptimizeAway(Arg&& arg); + + /*! + @verbatim embed:rst + + Sets N for asymptotic complexity calculation, so it becomes possible to calculate `Big O + `_ from multiple benchmark evaluations. + + Use :cpp:func:`ankerl::nanobench::Bench::complexityBigO` when the evaluation has finished. See the tutorial + :ref:`asymptotic-complexity` for details. + + @endverbatim + + @tparam T Any type is cast to `double`. + @param n Length of N for the next benchmark run, so it is possible to calculate `bigO`. + */ + template + Bench& complexityN(T n) noexcept; + ANKERL_NANOBENCH(NODISCARD) double complexityN() const noexcept; + + /*! + Calculates [Big O](https://en.wikipedia.org/wiki/Big_O_notation>) of the results with all preconfigured complexity functions. + Currently these complexity functions are fitted into the benchmark results: + + @f$ \mathcal{O}(1) @f$, + @f$ \mathcal{O}(n) @f$, + @f$ \mathcal{O}(\log{}n) @f$, + @f$ \mathcal{O}(n\log{}n) @f$, + @f$ \mathcal{O}(n^2) @f$, + @f$ \mathcal{O}(n^3) @f$. + + If we e.g. evaluate the complexity of `std::sort`, this is the result of `std::cout << bench.complexityBigO()`: + + ``` + | coefficient | err% | complexity + |--------------:|-------:|------------ + | 5.08935e-09 | 2.6% | O(n log n) + | 6.10608e-08 | 8.0% | O(n) + | 1.29307e-11 | 47.2% | O(n^2) + | 2.48677e-15 | 69.6% | O(n^3) + | 9.88133e-06 | 132.3% | O(log n) + | 5.98793e-05 | 162.5% | O(1) + ``` + + So in this case @f$ \mathcal{O}(n\log{}n) @f$ provides the best approximation. + + @verbatim embed:rst + See the tutorial :ref:`asymptotic-complexity` for details. + @endverbatim + @return Evaluation results, which can be printed or otherwise inspected. + */ + std::vector complexityBigO() const; + + /** + * @brief Calculates bigO for a custom function. + * + * E.g. to calculate the mean squared error for @f$ \mathcal{O}(\log{}\log{}n) @f$, which is not part of the default set of + * complexityBigO(), you can do this: + * + * ``` + * auto logLogN = bench.complexityBigO("O(log log n)", [](double n) { + * return std::log2(std::log2(n)); + * }); + * ``` + * + * The resulting mean squared error can be printed with `std::cout << logLogN`. E.g. it prints something like this: + * + * ```text + * 2.46985e-05 * O(log log n), rms=1.48121 + * ``` + * + * @tparam Op Type of mapping operation. + * @param name Name for the function, e.g. "O(log log n)" + * @param op Op's operator() maps a `double` with the desired complexity function, e.g. `log2(log2(n))`. + * @return BigO Error calculation, which is streamable to std::cout. + */ + template + BigO complexityBigO(char const* name, Op op) const; + + template + BigO complexityBigO(std::string const& name, Op op) const; + + /*! + @verbatim embed:rst + + Convenience shortcut to :cpp:func:`ankerl::nanobench::render`. + + @endverbatim + */ + Bench& render(char const* templateContent, std::ostream& os); + Bench& render(std::string const& templateContent, std::ostream& os); + + Bench& config(Config const& benchmarkConfig); + ANKERL_NANOBENCH(NODISCARD) Config const& config() const noexcept; + +private: + Config mConfig{}; + std::vector mResults{}; +}; +ANKERL_NANOBENCH(IGNORE_PADDED_POP) + +/** + * @brief Makes sure none of the given arguments are optimized away by the compiler. + * + * @tparam Arg Type of the argument that shouldn't be optimized away. + * @param arg The input that we mark as being used, even though we don't do anything with it. + */ +template +void doNotOptimizeAway(Arg&& arg); + +namespace detail { + +#if defined(_MSC_VER) +void doNotOptimizeAwaySink(void const*); + +template +void doNotOptimizeAway(T const& val); + +#else + +// These assembly magic is directly from what Google Benchmark is doing. I have previously used what facebook's folly was doing, but +// this seemed to have compilation problems in some cases. Google Benchmark seemed to be the most well tested anyways. +// see https://github.com/google/benchmark/blob/v1.7.1/include/benchmark/benchmark.h#L443-L446 +template +void doNotOptimizeAway(T const& val) { + // NOLINTNEXTLINE(hicpp-no-assembler) + asm volatile("" : : "r,m"(val) : "memory"); +} + +template +void doNotOptimizeAway(T& val) { +# if defined(__clang__) + // NOLINTNEXTLINE(hicpp-no-assembler) + asm volatile("" : "+r,m"(val) : : "memory"); +# else + // NOLINTNEXTLINE(hicpp-no-assembler) + asm volatile("" : "+m,r"(val) : : "memory"); +# endif +} +#endif + +// internally used, but visible because run() is templated. +// Not movable/copy-able, so we simply use a pointer instead of unique_ptr. This saves us from +// having to include , and the template instantiation overhead of unique_ptr which is unfortunately quite significant. +ANKERL_NANOBENCH(IGNORE_EFFCPP_PUSH) +class IterationLogic { +public: + explicit IterationLogic(Bench const& bench); + IterationLogic(IterationLogic&&) = delete; + IterationLogic& operator=(IterationLogic&&) = delete; + IterationLogic(IterationLogic const&) = delete; + IterationLogic& operator=(IterationLogic const&) = delete; + ~IterationLogic(); + + ANKERL_NANOBENCH(NODISCARD) uint64_t numIters() const noexcept; + void add(std::chrono::nanoseconds elapsed, PerformanceCounters const& pc) noexcept; + void moveResultTo(std::vector& results) noexcept; + +private: + struct Impl; + Impl* mPimpl; +}; +ANKERL_NANOBENCH(IGNORE_EFFCPP_POP) + +ANKERL_NANOBENCH(IGNORE_PADDED_PUSH) +class PerformanceCounters { +public: + PerformanceCounters(PerformanceCounters const&) = delete; + PerformanceCounters(PerformanceCounters&&) = delete; + PerformanceCounters& operator=(PerformanceCounters const&) = delete; + PerformanceCounters& operator=(PerformanceCounters&&) = delete; + + PerformanceCounters(); + ~PerformanceCounters(); + + void beginMeasure(); + void endMeasure(); + void updateResults(uint64_t numIters); + + ANKERL_NANOBENCH(NODISCARD) PerfCountSet const& val() const noexcept; + ANKERL_NANOBENCH(NODISCARD) PerfCountSet const& has() const noexcept; + +private: +#if ANKERL_NANOBENCH(PERF_COUNTERS) + LinuxPerformanceCounters* mPc = nullptr; +#endif + PerfCountSet mVal{}; + PerfCountSet mHas{}; +}; +ANKERL_NANOBENCH(IGNORE_PADDED_POP) + +// Gets the singleton +PerformanceCounters& performanceCounters(); + +} // namespace detail + +class BigO { +public: + using RangeMeasure = std::vector>; + + template + static RangeMeasure mapRangeMeasure(RangeMeasure data, Op op) { + for (auto& rangeMeasure : data) { + rangeMeasure.first = op(rangeMeasure.first); + } + return data; + } + + static RangeMeasure collectRangeMeasure(std::vector const& results); + + template + BigO(char const* bigOName, RangeMeasure const& rangeMeasure, Op rangeToN) + : BigO(bigOName, mapRangeMeasure(rangeMeasure, rangeToN)) {} + + template + BigO(std::string bigOName, RangeMeasure const& rangeMeasure, Op rangeToN) + : BigO(std::move(bigOName), mapRangeMeasure(rangeMeasure, rangeToN)) {} + + BigO(char const* bigOName, RangeMeasure const& scaledRangeMeasure); + BigO(std::string bigOName, RangeMeasure const& scaledRangeMeasure); + ANKERL_NANOBENCH(NODISCARD) std::string const& name() const noexcept; + ANKERL_NANOBENCH(NODISCARD) double constant() const noexcept; + ANKERL_NANOBENCH(NODISCARD) double normalizedRootMeanSquare() const noexcept; + ANKERL_NANOBENCH(NODISCARD) bool operator<(BigO const& other) const noexcept; + +private: + std::string mName{}; + double mConstant{}; + double mNormalizedRootMeanSquare{}; +}; +std::ostream& operator<<(std::ostream& os, BigO const& bigO); +std::ostream& operator<<(std::ostream& os, std::vector const& bigOs); + +} // namespace nanobench +} // namespace ankerl + +// implementation ///////////////////////////////////////////////////////////////////////////////// + +namespace ankerl { +namespace nanobench { + +constexpr uint64_t(Rng::min)() { + return 0; +} + +constexpr uint64_t(Rng::max)() { + return (std::numeric_limits::max)(); +} + +ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined") +uint64_t Rng::operator()() noexcept { + auto x = mX; + + mX = UINT64_C(15241094284759029579) * mY; + mY = rotl(mY - x, 27); + + return x; +} + +ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined") +uint32_t Rng::bounded(uint32_t range) noexcept { + uint64_t const r32 = static_cast(operator()()); + auto multiresult = r32 * range; + return static_cast(multiresult >> 32U); +} + +double Rng::uniform01() noexcept { + auto i = (UINT64_C(0x3ff) << 52U) | (operator()() >> 12U); + // can't use union in c++ here for type puning, it's undefined behavior. + // std::memcpy is optimized anyways. + double d{}; + std::memcpy(&d, &i, sizeof(double)); + return d - 1.0; +} + +template +void Rng::shuffle(Container& container) noexcept { + auto i = container.size(); + while (i > 1U) { + using std::swap; + auto n = operator()(); + // using decltype(i) instead of size_t to be compatible to containers with 32bit index (see #80) + auto b1 = static_cast((static_cast(n) * static_cast(i)) >> 32U); + swap(container[--i], container[b1]); + + auto b2 = static_cast(((n >> 32U) * static_cast(i)) >> 32U); + swap(container[--i], container[b2]); + } +} + +ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined") +constexpr uint64_t Rng::rotl(uint64_t x, unsigned k) noexcept { + return (x << k) | (x >> (64U - k)); +} + +template +ANKERL_NANOBENCH_NO_SANITIZE("integer") +Bench& Bench::run(Op&& op) { + // It is important that this method is kept short so the compiler can do better optimizations/ inlining of op() + detail::IterationLogic iterationLogic(*this); + auto& pc = detail::performanceCounters(); + + while (auto n = iterationLogic.numIters()) { + pc.beginMeasure(); + Clock::time_point const before = Clock::now(); + while (n-- > 0) { + op(); + } + Clock::time_point const after = Clock::now(); + pc.endMeasure(); + pc.updateResults(iterationLogic.numIters()); + iterationLogic.add(after - before, pc); + } + iterationLogic.moveResultTo(mResults); + return *this; +} + +// Performs all evaluations. +template +Bench& Bench::run(char const* benchmarkName, Op&& op) { + name(benchmarkName); + return run(std::forward(op)); +} + +template +Bench& Bench::run(std::string const& benchmarkName, Op&& op) { + name(benchmarkName); + return run(std::forward(op)); +} + +template +BigO Bench::complexityBigO(char const* benchmarkName, Op op) const { + return BigO(benchmarkName, BigO::collectRangeMeasure(mResults), op); +} + +template +BigO Bench::complexityBigO(std::string const& benchmarkName, Op op) const { + return BigO(benchmarkName, BigO::collectRangeMeasure(mResults), op); +} + +// Set the batch size, e.g. number of processed bytes, or some other metric for the size of the processed data in each iteration. +// Any argument is cast to double. +template +Bench& Bench::batch(T b) noexcept { + mConfig.mBatch = static_cast(b); + return *this; +} + +// Sets the computation complexity of the next run. Any argument is cast to double. +template +Bench& Bench::complexityN(T n) noexcept { + mConfig.mComplexityN = static_cast(n); + return *this; +} + +// Convenience: makes sure none of the given arguments are optimized away by the compiler. +template +Bench& Bench::doNotOptimizeAway(Arg&& arg) { + detail::doNotOptimizeAway(std::forward(arg)); + return *this; +} + +// Makes sure none of the given arguments are optimized away by the compiler. +template +void doNotOptimizeAway(Arg&& arg) { + detail::doNotOptimizeAway(std::forward(arg)); +} + +namespace detail { + +#if defined(_MSC_VER) +template +void doNotOptimizeAway(T const& val) { + doNotOptimizeAwaySink(&val); +} + +#endif + +} // namespace detail +} // namespace nanobench +} // namespace ankerl + +#if defined(ANKERL_NANOBENCH_IMPLEMENT) + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// implementation part - only visible in .cpp +/////////////////////////////////////////////////////////////////////////////////////////////////// + +# include // sort, reverse +# include // compare_exchange_strong in loop overhead +# include // getenv +# include // strstr, strncmp +# include // ifstream to parse proc files +# include // setw, setprecision +# include // cout +# include // accumulate +# include // random_device +# include // to_s in Number +# include // throw for rendering templates +# include // std::tie +# if defined(__linux__) +# include //sysconf +# endif +# if ANKERL_NANOBENCH(PERF_COUNTERS) +# include // map + +# include +# include +# include +# endif + +// declarations /////////////////////////////////////////////////////////////////////////////////// + +namespace ankerl { +namespace nanobench { + +// helper stuff that is only intended to be used internally +namespace detail { + +struct TableInfo; + +// formatting utilities +namespace fmt { + +class NumSep; +class StreamStateRestorer; +class Number; +class MarkDownColumn; +class MarkDownCode; + +} // namespace fmt +} // namespace detail +} // namespace nanobench +} // namespace ankerl + +// definitions //////////////////////////////////////////////////////////////////////////////////// + +namespace ankerl { +namespace nanobench { + +uint64_t splitMix64(uint64_t& state) noexcept; + +namespace detail { + +// helpers to get double values +template +inline double d(T t) noexcept { + return static_cast(t); +} +inline double d(Clock::duration duration) noexcept { + return std::chrono::duration_cast>(duration).count(); +} + +// Calculates clock resolution once, and remembers the result +inline Clock::duration clockResolution() noexcept; + +} // namespace detail + +namespace templates { + +char const* csv() noexcept { + return R"DELIM("title";"name";"unit";"batch";"elapsed";"error %";"instructions";"branches";"branch misses";"total" +{{#result}}"{{title}}";"{{name}}";"{{unit}}";{{batch}};{{median(elapsed)}};{{medianAbsolutePercentError(elapsed)}};{{median(instructions)}};{{median(branchinstructions)}};{{median(branchmisses)}};{{sumProduct(iterations, elapsed)}} +{{/result}})DELIM"; +} + +char const* htmlBoxplot() noexcept { + return R"DELIM( + + + + + + +
+ + + +)DELIM"; +} + +char const* pyperf() noexcept { + return R"DELIM({ + "benchmarks": [ + { + "runs": [ + { + "values": [ +{{#measurement}} {{elapsed}}{{^-last}}, +{{/last}}{{/measurement}} + ] + } + ] + } + ], + "metadata": { + "loops": {{sum(iterations)}}, + "inner_loops": {{batch}}, + "name": "{{title}}", + "unit": "second" + }, + "version": "1.0" +})DELIM"; +} + +char const* json() noexcept { + return R"DELIM({ + "results": [ +{{#result}} { + "title": "{{title}}", + "name": "{{name}}", + "unit": "{{unit}}", + "batch": {{batch}}, + "complexityN": {{complexityN}}, + "epochs": {{epochs}}, + "clockResolution": {{clockResolution}}, + "clockResolutionMultiple": {{clockResolutionMultiple}}, + "maxEpochTime": {{maxEpochTime}}, + "minEpochTime": {{minEpochTime}}, + "minEpochIterations": {{minEpochIterations}}, + "epochIterations": {{epochIterations}}, + "warmup": {{warmup}}, + "relative": {{relative}}, + "median(elapsed)": {{median(elapsed)}}, + "medianAbsolutePercentError(elapsed)": {{medianAbsolutePercentError(elapsed)}}, + "median(instructions)": {{median(instructions)}}, + "medianAbsolutePercentError(instructions)": {{medianAbsolutePercentError(instructions)}}, + "median(cpucycles)": {{median(cpucycles)}}, + "median(contextswitches)": {{median(contextswitches)}}, + "median(pagefaults)": {{median(pagefaults)}}, + "median(branchinstructions)": {{median(branchinstructions)}}, + "median(branchmisses)": {{median(branchmisses)}}, + "totalTime": {{sumProduct(iterations, elapsed)}}, + "measurements": [ +{{#measurement}} { + "iterations": {{iterations}}, + "elapsed": {{elapsed}}, + "pagefaults": {{pagefaults}}, + "cpucycles": {{cpucycles}}, + "contextswitches": {{contextswitches}}, + "instructions": {{instructions}}, + "branchinstructions": {{branchinstructions}}, + "branchmisses": {{branchmisses}} + }{{^-last}},{{/-last}} +{{/measurement}} ] + }{{^-last}},{{/-last}} +{{/result}} ] +})DELIM"; +} + +ANKERL_NANOBENCH(IGNORE_PADDED_PUSH) +struct Node { + enum class Type { tag, content, section, inverted_section }; + + char const* begin; + char const* end; + std::vector children; + Type type; + + template + // NOLINTNEXTLINE(hicpp-avoid-c-arrays,modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays) + bool operator==(char const (&str)[N]) const noexcept { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) + return static_cast(std::distance(begin, end) + 1) == N && 0 == strncmp(str, begin, N - 1); + } +}; +ANKERL_NANOBENCH(IGNORE_PADDED_POP) + +// NOLINTNEXTLINE(misc-no-recursion) +static std::vector parseMustacheTemplate(char const** tpl) { + std::vector nodes; + + while (true) { + auto const* begin = std::strstr(*tpl, "{{"); + auto const* end = begin; + if (begin != nullptr) { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + begin += 2; + end = std::strstr(begin, "}}"); + } + + if (begin == nullptr || end == nullptr) { + // nothing found, finish node + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + nodes.emplace_back(Node{*tpl, *tpl + std::strlen(*tpl), std::vector{}, Node::Type::content}); + return nodes; + } + + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + nodes.emplace_back(Node{*tpl, begin - 2, std::vector{}, Node::Type::content}); + + // we found a tag + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + *tpl = end + 2; + switch (*begin) { + case '/': + // finished! bail out + return nodes; + + case '#': + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + nodes.emplace_back(Node{begin + 1, end, parseMustacheTemplate(tpl), Node::Type::section}); + break; + + case '^': + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + nodes.emplace_back(Node{begin + 1, end, parseMustacheTemplate(tpl), Node::Type::inverted_section}); + break; + + default: + nodes.emplace_back(Node{begin, end, std::vector{}, Node::Type::tag}); + break; + } + } +} + +static bool generateFirstLast(Node const& n, size_t idx, size_t size, std::ostream& out) { + ANKERL_NANOBENCH_LOG("n.type=" << static_cast(n.type)); + bool const matchFirst = n == "-first"; + bool const matchLast = n == "-last"; + if (!matchFirst && !matchLast) { + return false; + } + + bool doWrite = false; + if (n.type == Node::Type::section) { + doWrite = (matchFirst && idx == 0) || (matchLast && idx == size - 1); + } else if (n.type == Node::Type::inverted_section) { + doWrite = (matchFirst && idx != 0) || (matchLast && idx != size - 1); + } + + if (doWrite) { + for (auto const& child : n.children) { + if (child.type == Node::Type::content) { + out.write(child.begin, std::distance(child.begin, child.end)); + } + } + } + return true; +} + +static bool matchCmdArgs(std::string const& str, std::vector& matchResult) { + matchResult.clear(); + auto idxOpen = str.find('('); + auto idxClose = str.find(')', idxOpen); + if (idxClose == std::string::npos) { + return false; + } + + matchResult.emplace_back(str.substr(0, idxOpen)); + + // split by comma + matchResult.emplace_back(); + for (size_t i = idxOpen + 1; i != idxClose; ++i) { + if (str[i] == ' ' || str[i] == '\t') { + // skip whitespace + continue; + } + if (str[i] == ',') { + // got a comma => new string + matchResult.emplace_back(); + continue; + } + // no whitespace no comma, append + matchResult.back() += str[i]; + } + return true; +} + +static bool generateConfigTag(Node const& n, Config const& config, std::ostream& out) { + using detail::d; + + if (n == "title") { + out << config.mBenchmarkTitle; + return true; + } + if (n == "name") { + out << config.mBenchmarkName; + return true; + } + if (n == "unit") { + out << config.mUnit; + return true; + } + if (n == "batch") { + out << config.mBatch; + return true; + } + if (n == "complexityN") { + out << config.mComplexityN; + return true; + } + if (n == "epochs") { + out << config.mNumEpochs; + return true; + } + if (n == "clockResolution") { + out << d(detail::clockResolution()); + return true; + } + if (n == "clockResolutionMultiple") { + out << config.mClockResolutionMultiple; + return true; + } + if (n == "maxEpochTime") { + out << d(config.mMaxEpochTime); + return true; + } + if (n == "minEpochTime") { + out << d(config.mMinEpochTime); + return true; + } + if (n == "minEpochIterations") { + out << config.mMinEpochIterations; + return true; + } + if (n == "epochIterations") { + out << config.mEpochIterations; + return true; + } + if (n == "warmup") { + out << config.mWarmup; + return true; + } + if (n == "relative") { + out << config.mIsRelative; + return true; + } + return false; +} + +// NOLINTNEXTLINE(readability-function-cognitive-complexity) +static std::ostream& generateResultTag(Node const& n, Result const& r, std::ostream& out) { + if (generateConfigTag(n, r.config(), out)) { + return out; + } + // match e.g. "median(elapsed)" + // g++ 4.8 doesn't implement std::regex :( + // static std::regex const regOpArg1("^([a-zA-Z]+)\\(([a-zA-Z]*)\\)$"); + // std::cmatch matchResult; + // if (std::regex_match(n.begin, n.end, matchResult, regOpArg1)) { + std::vector matchResult; + if (matchCmdArgs(std::string(n.begin, n.end), matchResult)) { + if (matchResult.size() == 2) { + if (matchResult[0] == "context") { + return out << r.context(matchResult[1]); + } + + auto m = Result::fromString(matchResult[1]); + if (m == Result::Measure::_size) { + return out << 0.0; + } + + if (matchResult[0] == "median") { + return out << r.median(m); + } + if (matchResult[0] == "average") { + return out << r.average(m); + } + if (matchResult[0] == "medianAbsolutePercentError") { + return out << r.medianAbsolutePercentError(m); + } + if (matchResult[0] == "sum") { + return out << r.sum(m); + } + if (matchResult[0] == "minimum") { + return out << r.minimum(m); + } + if (matchResult[0] == "maximum") { + return out << r.maximum(m); + } + } else if (matchResult.size() == 3) { + auto m1 = Result::fromString(matchResult[1]); + auto m2 = Result::fromString(matchResult[2]); + if (m1 == Result::Measure::_size || m2 == Result::Measure::_size) { + return out << 0.0; + } + + if (matchResult[0] == "sumProduct") { + return out << r.sumProduct(m1, m2); + } + } + } + + // match e.g. "sumProduct(elapsed, iterations)" + // static std::regex const regOpArg2("^([a-zA-Z]+)\\(([a-zA-Z]*)\\s*,\\s+([a-zA-Z]*)\\)$"); + + // nothing matches :( + throw std::runtime_error("command '" + std::string(n.begin, n.end) + "' not understood"); +} + +static void generateResultMeasurement(std::vector const& nodes, size_t idx, Result const& r, std::ostream& out) { + for (auto const& n : nodes) { + if (!generateFirstLast(n, idx, r.size(), out)) { + ANKERL_NANOBENCH_LOG("n.type=" << static_cast(n.type)); + switch (n.type) { + case Node::Type::content: + out.write(n.begin, std::distance(n.begin, n.end)); + break; + + case Node::Type::inverted_section: + throw std::runtime_error("got a inverted section inside measurement"); + + case Node::Type::section: + throw std::runtime_error("got a section inside measurement"); + + case Node::Type::tag: { + auto m = Result::fromString(std::string(n.begin, n.end)); + if (m == Result::Measure::_size || !r.has(m)) { + out << 0.0; + } else { + out << r.get(idx, m); + } + break; + } + } + } + } +} + +static void generateResult(std::vector const& nodes, size_t idx, std::vector const& results, std::ostream& out) { + auto const& r = results[idx]; + for (auto const& n : nodes) { + if (!generateFirstLast(n, idx, results.size(), out)) { + ANKERL_NANOBENCH_LOG("n.type=" << static_cast(n.type)); + switch (n.type) { + case Node::Type::content: + out.write(n.begin, std::distance(n.begin, n.end)); + break; + + case Node::Type::inverted_section: + throw std::runtime_error("got a inverted section inside result"); + + case Node::Type::section: + if (n == "measurement") { + for (size_t i = 0; i < r.size(); ++i) { + generateResultMeasurement(n.children, i, r, out); + } + } else { + throw std::runtime_error("got a section inside result"); + } + break; + + case Node::Type::tag: + generateResultTag(n, r, out); + break; + } + } + } +} + +} // namespace templates + +// helper stuff that only intended to be used internally +namespace detail { + +char const* getEnv(char const* name); +bool isEndlessRunning(std::string const& name); +bool isWarningsEnabled(); + +template +T parseFile(std::string const& filename, bool* fail); + +void gatherStabilityInformation(std::vector& warnings, std::vector& recommendations); +void printStabilityInformationOnce(std::ostream* outStream); + +// remembers the last table settings used. When it changes, a new table header is automatically written for the new entry. +uint64_t& singletonHeaderHash() noexcept; + +// determines resolution of the given clock. This is done by measuring multiple times and returning the minimum time difference. +Clock::duration calcClockResolution(size_t numEvaluations) noexcept; + +// formatting utilities +namespace fmt { + +// adds thousands separator to numbers +ANKERL_NANOBENCH(IGNORE_PADDED_PUSH) +class NumSep : public std::numpunct { +public: + explicit NumSep(char sep); + char do_thousands_sep() const override; + std::string do_grouping() const override; + +private: + char mSep; +}; +ANKERL_NANOBENCH(IGNORE_PADDED_POP) + +// RAII to save & restore a stream's state +ANKERL_NANOBENCH(IGNORE_PADDED_PUSH) +class StreamStateRestorer { +public: + explicit StreamStateRestorer(std::ostream& s); + ~StreamStateRestorer(); + + // sets back all stream info that we remembered at construction + void restore(); + + // don't allow copying / moving + StreamStateRestorer(StreamStateRestorer const&) = delete; + StreamStateRestorer& operator=(StreamStateRestorer const&) = delete; + StreamStateRestorer(StreamStateRestorer&&) = delete; + StreamStateRestorer& operator=(StreamStateRestorer&&) = delete; + +private: + std::ostream& mStream; + std::locale mLocale; + std::streamsize const mPrecision; + std::streamsize const mWidth; + std::ostream::char_type const mFill; + std::ostream::fmtflags const mFmtFlags; +}; +ANKERL_NANOBENCH(IGNORE_PADDED_POP) + +// Number formatter +class Number { +public: + Number(int width, int precision, double value); + Number(int width, int precision, int64_t value); + ANKERL_NANOBENCH(NODISCARD) std::string to_s() const; + +private: + friend std::ostream& operator<<(std::ostream& os, Number const& n); + std::ostream& write(std::ostream& os) const; + + int mWidth; + int mPrecision; + double mValue; +}; + +// helper replacement for std::to_string of signed/unsigned numbers so we are locale independent +std::string to_s(uint64_t n); + +std::ostream& operator<<(std::ostream& os, Number const& n); + +class MarkDownColumn { +public: + MarkDownColumn(int w, int prec, std::string tit, std::string suff, double val) noexcept; + ANKERL_NANOBENCH(NODISCARD) std::string title() const; + ANKERL_NANOBENCH(NODISCARD) std::string separator() const; + ANKERL_NANOBENCH(NODISCARD) std::string invalid() const; + ANKERL_NANOBENCH(NODISCARD) std::string value() const; + +private: + int mWidth; + int mPrecision; + std::string mTitle; + std::string mSuffix; + double mValue; +}; + +// Formats any text as markdown code, escaping backticks. +class MarkDownCode { +public: + explicit MarkDownCode(std::string const& what); + +private: + friend std::ostream& operator<<(std::ostream& os, MarkDownCode const& mdCode); + std::ostream& write(std::ostream& os) const; + + std::string mWhat{}; +}; + +std::ostream& operator<<(std::ostream& os, MarkDownCode const& mdCode); + +} // namespace fmt +} // namespace detail +} // namespace nanobench +} // namespace ankerl + +// implementation ///////////////////////////////////////////////////////////////////////////////// + +namespace ankerl { +namespace nanobench { + +// NOLINTNEXTLINE(readability-function-cognitive-complexity) +void render(char const* mustacheTemplate, std::vector const& results, std::ostream& out) { + detail::fmt::StreamStateRestorer const restorer(out); + + out.precision(std::numeric_limits::digits10); + auto nodes = templates::parseMustacheTemplate(&mustacheTemplate); + + for (auto const& n : nodes) { + ANKERL_NANOBENCH_LOG("n.type=" << static_cast(n.type)); + switch (n.type) { + case templates::Node::Type::content: + out.write(n.begin, std::distance(n.begin, n.end)); + break; + + case templates::Node::Type::inverted_section: + throw std::runtime_error("unknown list '" + std::string(n.begin, n.end) + "'"); + + case templates::Node::Type::section: + if (n == "result") { + const size_t nbResults = results.size(); + for (size_t i = 0; i < nbResults; ++i) { + generateResult(n.children, i, results, out); + } + } else if (n == "measurement") { + if (results.size() != 1) { + throw std::runtime_error( + "render: can only use section 'measurement' here if there is a single result, but there are " + + detail::fmt::to_s(results.size())); + } + // when we only have a single result, we can immediately go into its measurement. + auto const& r = results.front(); + for (size_t i = 0; i < r.size(); ++i) { + generateResultMeasurement(n.children, i, r, out); + } + } else { + throw std::runtime_error("render: unknown section '" + std::string(n.begin, n.end) + "'"); + } + break; + + case templates::Node::Type::tag: + if (results.size() == 1) { + // result & config are both supported there + generateResultTag(n, results.front(), out); + } else { + // This just uses the last result's config. + if (!generateConfigTag(n, results.back().config(), out)) { + throw std::runtime_error("unknown tag '" + std::string(n.begin, n.end) + "'"); + } + } + break; + } + } +} + +void render(std::string const& mustacheTemplate, std::vector const& results, std::ostream& out) { + render(mustacheTemplate.c_str(), results, out); +} + +void render(char const* mustacheTemplate, const Bench& bench, std::ostream& out) { + render(mustacheTemplate, bench.results(), out); +} + +void render(std::string const& mustacheTemplate, const Bench& bench, std::ostream& out) { + render(mustacheTemplate.c_str(), bench.results(), out); +} + +namespace detail { + +PerformanceCounters& performanceCounters() { +# if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +# endif + static PerformanceCounters pc; +# if defined(__clang__) +# pragma clang diagnostic pop +# endif + return pc; +} + +// Windows version of doNotOptimizeAway +// see https://github.com/google/benchmark/blob/v1.7.1/include/benchmark/benchmark.h#L514 +// see https://github.com/facebook/folly/blob/v2023.01.30.00/folly/lang/Hint-inl.h#L54-L58 +// see https://learn.microsoft.com/en-us/cpp/preprocessor/optimize +# if defined(_MSC_VER) +# pragma optimize("", off) +void doNotOptimizeAwaySink(void const*) {} +# pragma optimize("", on) +# endif + +template +T parseFile(std::string const& filename, bool* fail) { + std::ifstream fin(filename); // NOLINT(misc-const-correctness) + T num{}; + fin >> num; + if (fail != nullptr) { + *fail = fin.fail(); + } + return num; +} + +char const* getEnv(char const* name) { +# if defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4996) // getenv': This function or variable may be unsafe. +# endif + return std::getenv(name); // NOLINT(concurrency-mt-unsafe) +# if defined(_MSC_VER) +# pragma warning(pop) +# endif +} + +bool isEndlessRunning(std::string const& name) { + auto const* const endless = getEnv("NANOBENCH_ENDLESS"); + return nullptr != endless && endless == name; +} + +// True when environment variable NANOBENCH_SUPPRESS_WARNINGS is either not set at all, or set to "0" +bool isWarningsEnabled() { + auto const* const suppression = getEnv("NANOBENCH_SUPPRESS_WARNINGS"); + return nullptr == suppression || suppression == std::string("0"); +} + +void gatherStabilityInformation(std::vector& warnings, std::vector& recommendations) { + warnings.clear(); + recommendations.clear(); + +# if defined(DEBUG) + warnings.emplace_back("DEBUG defined"); + bool const recommendCheckFlags = true; +# else + bool const recommendCheckFlags = false; +# endif + + bool recommendPyPerf = false; +# if defined(__linux__) + auto nprocs = sysconf(_SC_NPROCESSORS_CONF); + if (nprocs <= 0) { + warnings.emplace_back("couldn't figure out number of processors - no governor, turbo check possible"); + } else { + // check frequency scaling + for (long id = 0; id < nprocs; ++id) { + auto idStr = detail::fmt::to_s(static_cast(id)); + auto sysCpu = "/sys/devices/system/cpu/cpu" + idStr; + auto minFreq = parseFile(sysCpu + "/cpufreq/scaling_min_freq", nullptr); + auto maxFreq = parseFile(sysCpu + "/cpufreq/scaling_max_freq", nullptr); + if (minFreq != maxFreq) { + auto minMHz = d(minFreq) / 1000.0; + auto maxMHz = d(maxFreq) / 1000.0; + warnings.emplace_back("CPU frequency scaling enabled: CPU " + idStr + " between " + + detail::fmt::Number(1, 1, minMHz).to_s() + " and " + detail::fmt::Number(1, 1, maxMHz).to_s() + + " MHz"); + recommendPyPerf = true; + break; + } + } + + auto fail = false; + auto currentGovernor = parseFile("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", &fail); + if (!fail && "performance" != currentGovernor) { + warnings.emplace_back("CPU governor is '" + currentGovernor + "' but should be 'performance'"); + recommendPyPerf = true; + } + + auto noTurbo = parseFile("/sys/devices/system/cpu/intel_pstate/no_turbo", &fail); + if (!fail && noTurbo == 0) { + warnings.emplace_back("Turbo is enabled, CPU frequency will fluctuate"); + recommendPyPerf = true; + } + } +# endif + + if (recommendCheckFlags) { + recommendations.emplace_back("Make sure you compile for Release"); + } + if (recommendPyPerf) { + recommendations.emplace_back("Use 'pyperf system tune' before benchmarking. See https://github.com/psf/pyperf"); + } +} + +void printStabilityInformationOnce(std::ostream* outStream) { + static bool shouldPrint = true; + if (shouldPrint && (nullptr != outStream) && isWarningsEnabled()) { + auto& os = *outStream; + shouldPrint = false; + std::vector warnings; + std::vector recommendations; + gatherStabilityInformation(warnings, recommendations); + if (warnings.empty()) { + return; + } + + os << "Warning, results might be unstable:" << std::endl; + for (auto const& w : warnings) { + os << "* " << w << std::endl; + } + + os << std::endl << "Recommendations" << std::endl; + for (auto const& r : recommendations) { + os << "* " << r << std::endl; + } + } +} + +// remembers the last table settings used. When it changes, a new table header is automatically written for the new entry. +uint64_t& singletonHeaderHash() noexcept { + static uint64_t sHeaderHash{}; + return sHeaderHash; +} + +ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined") +inline uint64_t hash_combine(uint64_t seed, uint64_t val) { + return seed ^ (val + UINT64_C(0x9e3779b9) + (seed << 6U) + (seed >> 2U)); +} + +// determines resolution of the given clock. This is done by measuring multiple times and returning the minimum time difference. +Clock::duration calcClockResolution(size_t numEvaluations) noexcept { + auto bestDuration = Clock::duration::max(); + Clock::time_point tBegin; + Clock::time_point tEnd; + for (size_t i = 0; i < numEvaluations; ++i) { + tBegin = Clock::now(); + do { + tEnd = Clock::now(); + } while (tBegin == tEnd); + bestDuration = (std::min)(bestDuration, tEnd - tBegin); + } + return bestDuration; +} + +// Calculates clock resolution once, and remembers the result +Clock::duration clockResolution() noexcept { + static Clock::duration const sResolution = calcClockResolution(20); + return sResolution; +} + +ANKERL_NANOBENCH(IGNORE_PADDED_PUSH) +struct IterationLogic::Impl { + enum class State { warmup, upscaling_runtime, measuring, endless }; + + explicit Impl(Bench const& bench) + : mBench(bench) + , mResult(bench.config()) { + printStabilityInformationOnce(mBench.output()); + + // determine target runtime per epoch + mTargetRuntimePerEpoch = detail::clockResolution() * mBench.clockResolutionMultiple(); + if (mTargetRuntimePerEpoch > mBench.maxEpochTime()) { + mTargetRuntimePerEpoch = mBench.maxEpochTime(); + } + if (mTargetRuntimePerEpoch < mBench.minEpochTime()) { + mTargetRuntimePerEpoch = mBench.minEpochTime(); + } + + if (isEndlessRunning(mBench.name())) { + std::cerr << "NANOBENCH_ENDLESS set: running '" << mBench.name() << "' endlessly" << std::endl; + mNumIters = (std::numeric_limits::max)(); + mState = State::endless; + } else if (0 != mBench.warmup()) { + mNumIters = mBench.warmup(); + mState = State::warmup; + } else if (0 != mBench.epochIterations()) { + // exact number of iterations + mNumIters = mBench.epochIterations(); + mState = State::measuring; + } else { + mNumIters = mBench.minEpochIterations(); + mState = State::upscaling_runtime; + } + } + + // directly calculates new iters based on elapsed&iters, and adds a 10% noise. Makes sure we don't underflow. + ANKERL_NANOBENCH(NODISCARD) uint64_t calcBestNumIters(std::chrono::nanoseconds elapsed, uint64_t iters) noexcept { + auto doubleElapsed = d(elapsed); + auto doubleTargetRuntimePerEpoch = d(mTargetRuntimePerEpoch); + auto doubleNewIters = doubleTargetRuntimePerEpoch / doubleElapsed * d(iters); + + auto doubleMinEpochIters = d(mBench.minEpochIterations()); + if (doubleNewIters < doubleMinEpochIters) { + doubleNewIters = doubleMinEpochIters; + } + doubleNewIters *= 1.0 + 0.2 * mRng.uniform01(); + + // +0.5 for correct rounding when casting + // NOLINTNEXTLINE(bugprone-incorrect-roundings) + return static_cast(doubleNewIters + 0.5); + } + + ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined") void upscale(std::chrono::nanoseconds elapsed) { + if (elapsed * 10 < mTargetRuntimePerEpoch) { + // we are far below the target runtime. Multiply iterations by 10 (with overflow check) + if (mNumIters * 10 < mNumIters) { + // overflow :-( + showResult("iterations overflow. Maybe your code got optimized away?"); + mNumIters = 0; + return; + } + mNumIters *= 10; + } else { + mNumIters = calcBestNumIters(elapsed, mNumIters); + } + } + + void add(std::chrono::nanoseconds elapsed, PerformanceCounters const& pc) noexcept { +# if defined(ANKERL_NANOBENCH_LOG_ENABLED) + auto oldIters = mNumIters; +# endif + + switch (mState) { + case State::warmup: + if (isCloseEnoughForMeasurements(elapsed)) { + // if elapsed is close enough, we can skip upscaling and go right to measurements + // still, we don't add the result to the measurements. + mState = State::measuring; + mNumIters = calcBestNumIters(elapsed, mNumIters); + } else { + // not close enough: switch to upscaling + mState = State::upscaling_runtime; + upscale(elapsed); + } + break; + + case State::upscaling_runtime: + if (isCloseEnoughForMeasurements(elapsed)) { + // if we are close enough, add measurement and switch to always measuring + mState = State::measuring; + mTotalElapsed += elapsed; + mTotalNumIters += mNumIters; + mResult.add(elapsed, mNumIters, pc); + mNumIters = calcBestNumIters(mTotalElapsed, mTotalNumIters); + } else { + upscale(elapsed); + } + break; + + case State::measuring: + // just add measurements - no questions asked. Even when runtime is low. But we can't ignore + // that fluctuation, or else we would bias the result + mTotalElapsed += elapsed; + mTotalNumIters += mNumIters; + mResult.add(elapsed, mNumIters, pc); + if (0 != mBench.epochIterations()) { + mNumIters = mBench.epochIterations(); + } else { + mNumIters = calcBestNumIters(mTotalElapsed, mTotalNumIters); + } + break; + + case State::endless: + mNumIters = (std::numeric_limits::max)(); + break; + } + + if (static_cast(mResult.size()) == mBench.epochs()) { + // we got all the results that we need, finish it + showResult(""); + mNumIters = 0; + } + + ANKERL_NANOBENCH_LOG(mBench.name() << ": " << detail::fmt::Number(20, 3, d(elapsed.count())) << " elapsed, " + << detail::fmt::Number(20, 3, d(mTargetRuntimePerEpoch.count())) << " target. oldIters=" + << oldIters << ", mNumIters=" << mNumIters << ", mState=" << static_cast(mState)); + } + + // NOLINTNEXTLINE(readability-function-cognitive-complexity) + void showResult(std::string const& errorMessage) const { + ANKERL_NANOBENCH_LOG(errorMessage); + + if (mBench.output() != nullptr) { + // prepare column data /////// + std::vector columns; + + auto rMedian = mResult.median(Result::Measure::elapsed); + + if (mBench.relative()) { + double d = 100.0; + if (!mBench.results().empty()) { + d = rMedian <= 0.0 ? 0.0 : mBench.results().front().median(Result::Measure::elapsed) / rMedian * 100.0; + } + columns.emplace_back(11, 1, "relative", "%", d); + } + + if (mBench.complexityN() > 0) { + columns.emplace_back(14, 0, "complexityN", "", mBench.complexityN()); + } + + columns.emplace_back(22, 2, mBench.timeUnitName() + "/" + mBench.unit(), "", + rMedian / (mBench.timeUnit().count() * mBench.batch())); + columns.emplace_back(22, 2, mBench.unit() + "/s", "", rMedian <= 0.0 ? 0.0 : mBench.batch() / rMedian); + + double const rErrorMedian = mResult.medianAbsolutePercentError(Result::Measure::elapsed); + columns.emplace_back(10, 1, "err%", "%", rErrorMedian * 100.0); + + double rInsMedian = -1.0; + if (mBench.performanceCounters() && mResult.has(Result::Measure::instructions)) { + rInsMedian = mResult.median(Result::Measure::instructions); + columns.emplace_back(18, 2, "ins/" + mBench.unit(), "", rInsMedian / mBench.batch()); + } + + double rCycMedian = -1.0; + if (mBench.performanceCounters() && mResult.has(Result::Measure::cpucycles)) { + rCycMedian = mResult.median(Result::Measure::cpucycles); + columns.emplace_back(18, 2, "cyc/" + mBench.unit(), "", rCycMedian / mBench.batch()); + } + if (rInsMedian > 0.0 && rCycMedian > 0.0) { + columns.emplace_back(9, 3, "IPC", "", rCycMedian <= 0.0 ? 0.0 : rInsMedian / rCycMedian); + } + if (mBench.performanceCounters() && mResult.has(Result::Measure::branchinstructions)) { + double const rBraMedian = mResult.median(Result::Measure::branchinstructions); + columns.emplace_back(17, 2, "bra/" + mBench.unit(), "", rBraMedian / mBench.batch()); + if (mResult.has(Result::Measure::branchmisses)) { + double p = 0.0; + if (rBraMedian >= 1e-9) { + p = 100.0 * mResult.median(Result::Measure::branchmisses) / rBraMedian; + } + columns.emplace_back(10, 1, "miss%", "%", p); + } + } + + columns.emplace_back(12, 2, "total", "", mResult.sumProduct(Result::Measure::iterations, Result::Measure::elapsed)); + + // write everything + auto& os = *mBench.output(); + + // combine all elements that are relevant for printing the header + uint64_t hash = 0; + hash = hash_combine(std::hash{}(mBench.unit()), hash); + hash = hash_combine(std::hash{}(mBench.title()), hash); + hash = hash_combine(std::hash{}(mBench.timeUnitName()), hash); + hash = hash_combine(std::hash{}(mBench.timeUnit().count()), hash); + hash = hash_combine(std::hash{}(mBench.relative()), hash); + hash = hash_combine(std::hash{}(mBench.performanceCounters()), hash); + + if (hash != singletonHeaderHash()) { + singletonHeaderHash() = hash; + + // no result yet, print header + os << std::endl; + for (auto const& col : columns) { + os << col.title(); + } + os << "| " << mBench.title() << std::endl; + + for (auto const& col : columns) { + os << col.separator(); + } + os << "|:" << std::string(mBench.title().size() + 1U, '-') << std::endl; + } + + if (!errorMessage.empty()) { + for (auto const& col : columns) { + os << col.invalid(); + } + os << "| :boom: " << fmt::MarkDownCode(mBench.name()) << " (" << errorMessage << ')' << std::endl; + } else { + for (auto const& col : columns) { + os << col.value(); + } + os << "| "; + auto showUnstable = isWarningsEnabled() && rErrorMedian >= 0.05; + if (showUnstable) { + os << ":wavy_dash: "; + } + os << fmt::MarkDownCode(mBench.name()); + if (showUnstable) { + auto avgIters = d(mTotalNumIters) / d(mBench.epochs()); + // NOLINTNEXTLINE(bugprone-incorrect-roundings) + auto suggestedIters = static_cast(avgIters * 10 + 0.5); + + os << " (Unstable with ~" << detail::fmt::Number(1, 1, avgIters) + << " iters. Increase `minEpochIterations` to e.g. " << suggestedIters << ")"; + } + os << std::endl; + } + } + } + + ANKERL_NANOBENCH(NODISCARD) bool isCloseEnoughForMeasurements(std::chrono::nanoseconds elapsed) const noexcept { + return elapsed * 3 >= mTargetRuntimePerEpoch * 2; + } + + uint64_t mNumIters = 1; // NOLINT(misc-non-private-member-variables-in-classes) + Bench const& mBench; // NOLINT(misc-non-private-member-variables-in-classes) + std::chrono::nanoseconds mTargetRuntimePerEpoch{}; // NOLINT(misc-non-private-member-variables-in-classes) + Result mResult; // NOLINT(misc-non-private-member-variables-in-classes) + Rng mRng{123}; // NOLINT(misc-non-private-member-variables-in-classes) + std::chrono::nanoseconds mTotalElapsed{}; // NOLINT(misc-non-private-member-variables-in-classes) + uint64_t mTotalNumIters = 0; // NOLINT(misc-non-private-member-variables-in-classes) + State mState = State::upscaling_runtime; // NOLINT(misc-non-private-member-variables-in-classes) +}; +ANKERL_NANOBENCH(IGNORE_PADDED_POP) + +IterationLogic::IterationLogic(Bench const& bench) + : mPimpl(new Impl(bench)) {} + +IterationLogic::~IterationLogic() { + delete mPimpl; +} + +uint64_t IterationLogic::numIters() const noexcept { + ANKERL_NANOBENCH_LOG(mPimpl->mBench.name() << ": mNumIters=" << mPimpl->mNumIters); + return mPimpl->mNumIters; +} + +void IterationLogic::add(std::chrono::nanoseconds elapsed, PerformanceCounters const& pc) noexcept { + mPimpl->add(elapsed, pc); +} + +void IterationLogic::moveResultTo(std::vector& results) noexcept { + results.emplace_back(std::move(mPimpl->mResult)); +} + +# if ANKERL_NANOBENCH(PERF_COUNTERS) + +ANKERL_NANOBENCH(IGNORE_PADDED_PUSH) +class LinuxPerformanceCounters { +public: + struct Target { + Target(uint64_t* targetValue_, bool correctMeasuringOverhead_, bool correctLoopOverhead_) + : targetValue(targetValue_) + , correctMeasuringOverhead(correctMeasuringOverhead_) + , correctLoopOverhead(correctLoopOverhead_) {} + + uint64_t* targetValue{}; // NOLINT(misc-non-private-member-variables-in-classes) + bool correctMeasuringOverhead{}; // NOLINT(misc-non-private-member-variables-in-classes) + bool correctLoopOverhead{}; // NOLINT(misc-non-private-member-variables-in-classes) + }; + + LinuxPerformanceCounters() = default; + LinuxPerformanceCounters(LinuxPerformanceCounters const&) = delete; + LinuxPerformanceCounters(LinuxPerformanceCounters&&) = delete; + LinuxPerformanceCounters& operator=(LinuxPerformanceCounters const&) = delete; + LinuxPerformanceCounters& operator=(LinuxPerformanceCounters&&) = delete; + ~LinuxPerformanceCounters(); + + // quick operation + inline void start() {} + + inline void stop() {} + + bool monitor(perf_sw_ids swId, Target target); + bool monitor(perf_hw_id hwId, Target target); + + ANKERL_NANOBENCH(NODISCARD) bool hasError() const noexcept { + return mHasError; + } + + // Just reading data is faster than enable & disabling. + // we subtract data ourselves. + inline void beginMeasure() { + if (mHasError) { + return; + } + + // NOLINTNEXTLINE(hicpp-signed-bitwise,cppcoreguidelines-pro-type-vararg) + mHasError = -1 == ioctl(mFd, PERF_EVENT_IOC_RESET, PERF_IOC_FLAG_GROUP); + if (mHasError) { + return; + } + + // NOLINTNEXTLINE(hicpp-signed-bitwise,cppcoreguidelines-pro-type-vararg) + mHasError = -1 == ioctl(mFd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP); + } + + inline void endMeasure() { + if (mHasError) { + return; + } + + // NOLINTNEXTLINE(hicpp-signed-bitwise,cppcoreguidelines-pro-type-vararg) + mHasError = (-1 == ioctl(mFd, PERF_EVENT_IOC_DISABLE, PERF_IOC_FLAG_GROUP)); + if (mHasError) { + return; + } + + auto const numBytes = sizeof(uint64_t) * mCounters.size(); + auto ret = read(mFd, mCounters.data(), numBytes); + mHasError = ret != static_cast(numBytes); + } + + void updateResults(uint64_t numIters); + + // rounded integer division + template + static inline T divRounded(T a, T divisor) { + return (a + divisor / 2) / divisor; + } + + ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined") + static inline uint32_t mix(uint32_t x) noexcept { + x ^= x << 13U; + x ^= x >> 17U; + x ^= x << 5U; + return x; + } + + template + ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined") + void calibrate(Op&& op) { + // clear current calibration data, + for (auto& v : mCalibratedOverhead) { + v = UINT64_C(0); + } + + // create new calibration data + auto newCalibration = mCalibratedOverhead; + for (auto& v : newCalibration) { + v = (std::numeric_limits::max)(); + } + for (size_t iter = 0; iter < 100; ++iter) { + beginMeasure(); + op(); + endMeasure(); + if (mHasError) { + return; + } + + for (size_t i = 0; i < newCalibration.size(); ++i) { + auto diff = mCounters[i]; + if (newCalibration[i] > diff) { + newCalibration[i] = diff; + } + } + } + + mCalibratedOverhead = std::move(newCalibration); + + { + // calibrate loop overhead. For branches & instructions this makes sense, not so much for everything else like cycles. + // marsaglia's xorshift: mov, sal/shr, xor. Times 3. + // This has the nice property that the compiler doesn't seem to be able to optimize multiple calls any further. + // see https://godbolt.org/z/49RVQ5 + uint64_t const numIters = 100000U + (std::random_device{}() & 3U); + uint64_t n = numIters; + uint32_t x = 1234567; + + beginMeasure(); + while (n-- > 0) { + x = mix(x); + } + endMeasure(); + detail::doNotOptimizeAway(x); + auto measure1 = mCounters; + + n = numIters; + beginMeasure(); + while (n-- > 0) { + // we now run *twice* so we can easily calculate the overhead + x = mix(x); + x = mix(x); + } + endMeasure(); + detail::doNotOptimizeAway(x); + auto measure2 = mCounters; + + for (size_t i = 0; i < mCounters.size(); ++i) { + // factor 2 because we have two instructions per loop + auto m1 = measure1[i] > mCalibratedOverhead[i] ? measure1[i] - mCalibratedOverhead[i] : 0; + auto m2 = measure2[i] > mCalibratedOverhead[i] ? measure2[i] - mCalibratedOverhead[i] : 0; + auto overhead = m1 * 2 > m2 ? m1 * 2 - m2 : 0; + + mLoopOverhead[i] = divRounded(overhead, numIters); + } + } + } + +private: + bool monitor(uint32_t type, uint64_t eventid, Target target); + + std::map mIdToTarget{}; + + // start with minimum size of 3 for read_format + std::vector mCounters{3}; + std::vector mCalibratedOverhead{3}; + std::vector mLoopOverhead{3}; + + uint64_t mTimeEnabledNanos = 0; + uint64_t mTimeRunningNanos = 0; + int mFd = -1; + bool mHasError = false; +}; +ANKERL_NANOBENCH(IGNORE_PADDED_POP) + +LinuxPerformanceCounters::~LinuxPerformanceCounters() { + if (-1 != mFd) { + close(mFd); + } +} + +bool LinuxPerformanceCounters::monitor(perf_sw_ids swId, LinuxPerformanceCounters::Target target) { + return monitor(PERF_TYPE_SOFTWARE, swId, target); +} + +bool LinuxPerformanceCounters::monitor(perf_hw_id hwId, LinuxPerformanceCounters::Target target) { + return monitor(PERF_TYPE_HARDWARE, hwId, target); +} + +// overflow is ok, it's checked +ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined") +void LinuxPerformanceCounters::updateResults(uint64_t numIters) { + // clear old data + for (auto& id_value : mIdToTarget) { + *id_value.second.targetValue = UINT64_C(0); + } + + if (mHasError) { + return; + } + + mTimeEnabledNanos = mCounters[1] - mCalibratedOverhead[1]; + mTimeRunningNanos = mCounters[2] - mCalibratedOverhead[2]; + + for (uint64_t i = 0; i < mCounters[0]; ++i) { + auto idx = static_cast(3 + i * 2 + 0); + auto id = mCounters[idx + 1U]; + + auto it = mIdToTarget.find(id); + if (it != mIdToTarget.end()) { + + auto& tgt = it->second; + *tgt.targetValue = mCounters[idx]; + if (tgt.correctMeasuringOverhead) { + if (*tgt.targetValue >= mCalibratedOverhead[idx]) { + *tgt.targetValue -= mCalibratedOverhead[idx]; + } else { + *tgt.targetValue = 0U; + } + } + if (tgt.correctLoopOverhead) { + auto correctionVal = mLoopOverhead[idx] * numIters; + if (*tgt.targetValue >= correctionVal) { + *tgt.targetValue -= correctionVal; + } else { + *tgt.targetValue = 0U; + } + } + } + } +} + +bool LinuxPerformanceCounters::monitor(uint32_t type, uint64_t eventid, Target target) { + *target.targetValue = (std::numeric_limits::max)(); + if (mHasError) { + return false; + } + + auto pea = perf_event_attr(); + std::memset(&pea, 0, sizeof(perf_event_attr)); + pea.type = type; + pea.size = sizeof(perf_event_attr); + pea.config = eventid; + pea.disabled = 1; // start counter as disabled + pea.exclude_kernel = 1; + pea.exclude_hv = 1; + + // NOLINTNEXTLINE(hicpp-signed-bitwise) + pea.read_format = PERF_FORMAT_GROUP | PERF_FORMAT_ID | PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING; + + const int pid = 0; // the current process + const int cpu = -1; // all CPUs +# if defined(PERF_FLAG_FD_CLOEXEC) // since Linux 3.14 + const unsigned long flags = PERF_FLAG_FD_CLOEXEC; +# else + const unsigned long flags = 0; +# endif + + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + auto fd = static_cast(syscall(__NR_perf_event_open, &pea, pid, cpu, mFd, flags)); + if (-1 == fd) { + return false; + } + if (-1 == mFd) { + // first call: set to fd, and use this from now on + mFd = fd; + } + uint64_t id = 0; + // NOLINTNEXTLINE(hicpp-signed-bitwise,cppcoreguidelines-pro-type-vararg) + if (-1 == ioctl(fd, PERF_EVENT_IOC_ID, &id)) { + // couldn't get id + return false; + } + + // insert into map, rely on the fact that map's references are constant. + mIdToTarget.emplace(id, target); + + // prepare readformat with the correct size (after the insert) + auto size = 3 + 2 * mIdToTarget.size(); + mCounters.resize(size); + mCalibratedOverhead.resize(size); + mLoopOverhead.resize(size); + + return true; +} + +PerformanceCounters::PerformanceCounters() + : mPc(new LinuxPerformanceCounters()) + , mVal() + , mHas() { + + // HW events + mHas.cpuCycles = mPc->monitor(PERF_COUNT_HW_REF_CPU_CYCLES, LinuxPerformanceCounters::Target(&mVal.cpuCycles, true, false)); + if (!mHas.cpuCycles) { + // Fallback to cycles counter, reference cycles not available in many systems. + mHas.cpuCycles = mPc->monitor(PERF_COUNT_HW_CPU_CYCLES, LinuxPerformanceCounters::Target(&mVal.cpuCycles, true, false)); + } + mHas.instructions = mPc->monitor(PERF_COUNT_HW_INSTRUCTIONS, LinuxPerformanceCounters::Target(&mVal.instructions, true, true)); + mHas.branchInstructions = + mPc->monitor(PERF_COUNT_HW_BRANCH_INSTRUCTIONS, LinuxPerformanceCounters::Target(&mVal.branchInstructions, true, false)); + mHas.branchMisses = mPc->monitor(PERF_COUNT_HW_BRANCH_MISSES, LinuxPerformanceCounters::Target(&mVal.branchMisses, true, false)); + // mHas.branchMisses = false; + + // SW events + mHas.pageFaults = mPc->monitor(PERF_COUNT_SW_PAGE_FAULTS, LinuxPerformanceCounters::Target(&mVal.pageFaults, true, false)); + mHas.contextSwitches = + mPc->monitor(PERF_COUNT_SW_CONTEXT_SWITCHES, LinuxPerformanceCounters::Target(&mVal.contextSwitches, true, false)); + + mPc->start(); + mPc->calibrate([] { + auto before = ankerl::nanobench::Clock::now(); + auto after = ankerl::nanobench::Clock::now(); + (void)before; + (void)after; + }); + + if (mPc->hasError()) { + // something failed, don't monitor anything. + mHas = PerfCountSet{}; + } +} + +PerformanceCounters::~PerformanceCounters() { + // no need to check for nullptr, delete nullptr has no effect + delete mPc; +} + +void PerformanceCounters::beginMeasure() { + mPc->beginMeasure(); +} + +void PerformanceCounters::endMeasure() { + mPc->endMeasure(); +} + +void PerformanceCounters::updateResults(uint64_t numIters) { + mPc->updateResults(numIters); +} + +# else + +PerformanceCounters::PerformanceCounters() = default; +PerformanceCounters::~PerformanceCounters() = default; +void PerformanceCounters::beginMeasure() {} +void PerformanceCounters::endMeasure() {} +void PerformanceCounters::updateResults(uint64_t) {} + +# endif + +ANKERL_NANOBENCH(NODISCARD) PerfCountSet const& PerformanceCounters::val() const noexcept { + return mVal; +} +ANKERL_NANOBENCH(NODISCARD) PerfCountSet const& PerformanceCounters::has() const noexcept { + return mHas; +} + +// formatting utilities +namespace fmt { + +// adds thousands separator to numbers +NumSep::NumSep(char sep) + : mSep(sep) {} + +char NumSep::do_thousands_sep() const { + return mSep; +} + +std::string NumSep::do_grouping() const { + return "\003"; +} + +// RAII to save & restore a stream's state +StreamStateRestorer::StreamStateRestorer(std::ostream& s) + : mStream(s) + , mLocale(s.getloc()) + , mPrecision(s.precision()) + , mWidth(s.width()) + , mFill(s.fill()) + , mFmtFlags(s.flags()) {} + +StreamStateRestorer::~StreamStateRestorer() { + restore(); +} + +// sets back all stream info that we remembered at construction +void StreamStateRestorer::restore() { + mStream.imbue(mLocale); + mStream.precision(mPrecision); + mStream.width(mWidth); + mStream.fill(mFill); + mStream.flags(mFmtFlags); +} + +Number::Number(int width, int precision, int64_t value) + : mWidth(width) + , mPrecision(precision) + , mValue(d(value)) {} + +Number::Number(int width, int precision, double value) + : mWidth(width) + , mPrecision(precision) + , mValue(value) {} + +std::ostream& Number::write(std::ostream& os) const { + StreamStateRestorer const restorer(os); + os.imbue(std::locale(os.getloc(), new NumSep(','))); + os << std::setw(mWidth) << std::setprecision(mPrecision) << std::fixed << mValue; + return os; +} + +std::string Number::to_s() const { + std::stringstream ss; + write(ss); + return ss.str(); +} + +std::string to_s(uint64_t n) { + std::string str; + do { + str += static_cast('0' + static_cast(n % 10)); + n /= 10; + } while (n != 0); + std::reverse(str.begin(), str.end()); + return str; +} + +std::ostream& operator<<(std::ostream& os, Number const& n) { + return n.write(os); +} + +MarkDownColumn::MarkDownColumn(int w, int prec, std::string tit, std::string suff, double val) noexcept + : mWidth(w) + , mPrecision(prec) + , mTitle(std::move(tit)) + , mSuffix(std::move(suff)) + , mValue(val) {} + +std::string MarkDownColumn::title() const { + std::stringstream ss; + ss << '|' << std::setw(mWidth - 2) << std::right << mTitle << ' '; + return ss.str(); +} + +std::string MarkDownColumn::separator() const { + std::string sep(static_cast(mWidth), '-'); + sep.front() = '|'; + sep.back() = ':'; + return sep; +} + +std::string MarkDownColumn::invalid() const { + std::string sep(static_cast(mWidth), ' '); + sep.front() = '|'; + sep[sep.size() - 2] = '-'; + return sep; +} + +std::string MarkDownColumn::value() const { + std::stringstream ss; + auto width = mWidth - 2 - static_cast(mSuffix.size()); + ss << '|' << Number(width, mPrecision, mValue) << mSuffix << ' '; + return ss.str(); +} + +// Formats any text as markdown code, escaping backticks. +MarkDownCode::MarkDownCode(std::string const& what) { + mWhat.reserve(what.size() + 2); + mWhat.push_back('`'); + for (char const c : what) { + mWhat.push_back(c); + if ('`' == c) { + mWhat.push_back('`'); + } + } + mWhat.push_back('`'); +} + +std::ostream& MarkDownCode::write(std::ostream& os) const { + return os << mWhat; +} + +std::ostream& operator<<(std::ostream& os, MarkDownCode const& mdCode) { + return mdCode.write(os); +} +} // namespace fmt +} // namespace detail + +// provide implementation here so it's only generated once +Config::Config() = default; +Config::~Config() = default; +Config& Config::operator=(Config const&) = default; +Config& Config::operator=(Config&&) noexcept(ANKERL_NANOBENCH(NOEXCEPT_STRING_MOVE)) = default; +Config::Config(Config const&) = default; +Config::Config(Config&&) noexcept = default; + +// provide implementation here so it's only generated once +Result::~Result() = default; +Result& Result::operator=(Result const&) = default; +Result& Result::operator=(Result&&) noexcept(ANKERL_NANOBENCH(NOEXCEPT_STRING_MOVE)) = default; +Result::Result(Result const&) = default; +Result::Result(Result&&) noexcept = default; + +namespace detail { +template +inline constexpr typename std::underlying_type::type u(T val) noexcept { + return static_cast::type>(val); +} +} // namespace detail + +// Result returned after a benchmark has finished. Can be used as a baseline for relative(). +Result::Result(Config benchmarkConfig) + : mConfig(std::move(benchmarkConfig)) + , mNameToMeasurements{detail::u(Result::Measure::_size)} {} + +void Result::add(Clock::duration totalElapsed, uint64_t iters, detail::PerformanceCounters const& pc) { + using detail::d; + using detail::u; + + double const dIters = d(iters); + mNameToMeasurements[u(Result::Measure::iterations)].push_back(dIters); + + mNameToMeasurements[u(Result::Measure::elapsed)].push_back(d(totalElapsed) / dIters); + if (pc.has().pageFaults) { + mNameToMeasurements[u(Result::Measure::pagefaults)].push_back(d(pc.val().pageFaults) / dIters); + } + if (pc.has().cpuCycles) { + mNameToMeasurements[u(Result::Measure::cpucycles)].push_back(d(pc.val().cpuCycles) / dIters); + } + if (pc.has().contextSwitches) { + mNameToMeasurements[u(Result::Measure::contextswitches)].push_back(d(pc.val().contextSwitches) / dIters); + } + if (pc.has().instructions) { + mNameToMeasurements[u(Result::Measure::instructions)].push_back(d(pc.val().instructions) / dIters); + } + if (pc.has().branchInstructions) { + double branchInstructions = 0.0; + // correcting branches: remove branch introduced by the while (...) loop for each iteration. + if (pc.val().branchInstructions > iters + 1U) { + branchInstructions = d(pc.val().branchInstructions - (iters + 1U)); + } + mNameToMeasurements[u(Result::Measure::branchinstructions)].push_back(branchInstructions / dIters); + + if (pc.has().branchMisses) { + // correcting branch misses + double branchMisses = d(pc.val().branchMisses); + if (branchMisses > branchInstructions) { + // can't have branch misses when there were branches... + branchMisses = branchInstructions; + } + + // assuming at least one missed branch for the loop + branchMisses -= 1.0; + if (branchMisses < 1.0) { + branchMisses = 1.0; + } + mNameToMeasurements[u(Result::Measure::branchmisses)].push_back(branchMisses / dIters); + } + } +} + +Config const& Result::config() const noexcept { + return mConfig; +} + +inline double calcMedian(std::vector& data) { + if (data.empty()) { + return 0.0; + } + std::sort(data.begin(), data.end()); + + auto midIdx = data.size() / 2U; + if (1U == (data.size() & 1U)) { + return data[midIdx]; + } + return (data[midIdx - 1U] + data[midIdx]) / 2U; +} + +double Result::median(Measure m) const { + // create a copy so we can sort + auto data = mNameToMeasurements[detail::u(m)]; + return calcMedian(data); +} + +double Result::average(Measure m) const { + using detail::d; + auto const& data = mNameToMeasurements[detail::u(m)]; + if (data.empty()) { + return 0.0; + } + + // create a copy so we can sort + return sum(m) / d(data.size()); +} + +double Result::medianAbsolutePercentError(Measure m) const { + // create copy + auto data = mNameToMeasurements[detail::u(m)]; + + // calculates MdAPE which is the median of percentage error + // see https://support.numxl.com/hc/en-us/articles/115001223503-MdAPE-Median-Absolute-Percentage-Error + auto med = calcMedian(data); + + // transform the data to absolute error + for (auto& x : data) { + x = (x - med) / x; + if (x < 0) { + x = -x; + } + } + return calcMedian(data); +} + +double Result::sum(Measure m) const noexcept { + auto const& data = mNameToMeasurements[detail::u(m)]; + return std::accumulate(data.begin(), data.end(), 0.0); +} + +double Result::sumProduct(Measure m1, Measure m2) const noexcept { + auto const& data1 = mNameToMeasurements[detail::u(m1)]; + auto const& data2 = mNameToMeasurements[detail::u(m2)]; + + if (data1.size() != data2.size()) { + return 0.0; + } + + double result = 0.0; + for (size_t i = 0, s = data1.size(); i != s; ++i) { + result += data1[i] * data2[i]; + } + return result; +} + +bool Result::has(Measure m) const noexcept { + return !mNameToMeasurements[detail::u(m)].empty(); +} + +double Result::get(size_t idx, Measure m) const { + auto const& data = mNameToMeasurements[detail::u(m)]; + return data.at(idx); +} + +bool Result::empty() const noexcept { + return 0U == size(); +} + +size_t Result::size() const noexcept { + auto const& data = mNameToMeasurements[detail::u(Measure::elapsed)]; + return data.size(); +} + +double Result::minimum(Measure m) const noexcept { + auto const& data = mNameToMeasurements[detail::u(m)]; + if (data.empty()) { + return 0.0; + } + + // here its save to assume that at least one element is there + return *std::min_element(data.begin(), data.end()); +} + +double Result::maximum(Measure m) const noexcept { + auto const& data = mNameToMeasurements[detail::u(m)]; + if (data.empty()) { + return 0.0; + } + + // here its save to assume that at least one element is there + return *std::max_element(data.begin(), data.end()); +} + +std::string const& Result::context(char const* variableName) const { + return mConfig.mContext.at(variableName); +} + +std::string const& Result::context(std::string const& variableName) const { + return mConfig.mContext.at(variableName); +} + +Result::Measure Result::fromString(std::string const& str) { + if (str == "elapsed") { + return Measure::elapsed; + } + if (str == "iterations") { + return Measure::iterations; + } + if (str == "pagefaults") { + return Measure::pagefaults; + } + if (str == "cpucycles") { + return Measure::cpucycles; + } + if (str == "contextswitches") { + return Measure::contextswitches; + } + if (str == "instructions") { + return Measure::instructions; + } + if (str == "branchinstructions") { + return Measure::branchinstructions; + } + if (str == "branchmisses") { + return Measure::branchmisses; + } + // not found, return _size + return Measure::_size; +} + +// Configuration of a microbenchmark. +Bench::Bench() { + mConfig.mOut = &std::cout; +} + +Bench::Bench(Bench&&) noexcept = default; +Bench& Bench::operator=(Bench&&) noexcept(ANKERL_NANOBENCH(NOEXCEPT_STRING_MOVE)) = default; +Bench::Bench(Bench const&) = default; +Bench& Bench::operator=(Bench const&) = default; +Bench::~Bench() noexcept = default; + +double Bench::batch() const noexcept { + return mConfig.mBatch; +} + +double Bench::complexityN() const noexcept { + return mConfig.mComplexityN; +} + +// Set a baseline to compare it to. 100% it is exactly as fast as the baseline, >100% means it is faster than the baseline, <100% +// means it is slower than the baseline. +Bench& Bench::relative(bool isRelativeEnabled) noexcept { + mConfig.mIsRelative = isRelativeEnabled; + return *this; +} +bool Bench::relative() const noexcept { + return mConfig.mIsRelative; +} + +Bench& Bench::performanceCounters(bool showPerformanceCounters) noexcept { + mConfig.mShowPerformanceCounters = showPerformanceCounters; + return *this; +} +bool Bench::performanceCounters() const noexcept { + return mConfig.mShowPerformanceCounters; +} + +// Operation unit. Defaults to "op", could be e.g. "byte" for string processing. +// If u differs from currently set unit, the stored results will be cleared. +// Use singular (byte, not bytes). +Bench& Bench::unit(char const* u) { + if (u != mConfig.mUnit) { + mResults.clear(); + } + mConfig.mUnit = u; + return *this; +} + +Bench& Bench::unit(std::string const& u) { + return unit(u.c_str()); +} + +std::string const& Bench::unit() const noexcept { + return mConfig.mUnit; +} + +Bench& Bench::timeUnit(std::chrono::duration const& tu, std::string const& tuName) { + mConfig.mTimeUnit = tu; + mConfig.mTimeUnitName = tuName; + return *this; +} + +std::string const& Bench::timeUnitName() const noexcept { + return mConfig.mTimeUnitName; +} + +std::chrono::duration const& Bench::timeUnit() const noexcept { + return mConfig.mTimeUnit; +} + +// If benchmarkTitle differs from currently set title, the stored results will be cleared. +Bench& Bench::title(const char* benchmarkTitle) { + if (benchmarkTitle != mConfig.mBenchmarkTitle) { + mResults.clear(); + } + mConfig.mBenchmarkTitle = benchmarkTitle; + return *this; +} +Bench& Bench::title(std::string const& benchmarkTitle) { + if (benchmarkTitle != mConfig.mBenchmarkTitle) { + mResults.clear(); + } + mConfig.mBenchmarkTitle = benchmarkTitle; + return *this; +} + +std::string const& Bench::title() const noexcept { + return mConfig.mBenchmarkTitle; +} + +Bench& Bench::name(const char* benchmarkName) { + mConfig.mBenchmarkName = benchmarkName; + return *this; +} + +Bench& Bench::name(std::string const& benchmarkName) { + mConfig.mBenchmarkName = benchmarkName; + return *this; +} + +std::string const& Bench::name() const noexcept { + return mConfig.mBenchmarkName; +} + +Bench& Bench::context(char const* variableName, char const* variableValue) { + mConfig.mContext[variableName] = variableValue; + return *this; +} + +Bench& Bench::context(std::string const& variableName, std::string const& variableValue) { + mConfig.mContext[variableName] = variableValue; + return *this; +} + +Bench& Bench::clearContext() { + mConfig.mContext.clear(); + return *this; +} + +// Number of epochs to evaluate. The reported result will be the median of evaluation of each epoch. +Bench& Bench::epochs(size_t numEpochs) noexcept { + mConfig.mNumEpochs = numEpochs; + return *this; +} +size_t Bench::epochs() const noexcept { + return mConfig.mNumEpochs; +} + +// Desired evaluation time is a multiple of clock resolution. Default is to be 1000 times above this measurement precision. +Bench& Bench::clockResolutionMultiple(size_t multiple) noexcept { + mConfig.mClockResolutionMultiple = multiple; + return *this; +} +size_t Bench::clockResolutionMultiple() const noexcept { + return mConfig.mClockResolutionMultiple; +} + +// Sets the maximum time each epoch should take. Default is 100ms. +Bench& Bench::maxEpochTime(std::chrono::nanoseconds t) noexcept { + mConfig.mMaxEpochTime = t; + return *this; +} +std::chrono::nanoseconds Bench::maxEpochTime() const noexcept { + return mConfig.mMaxEpochTime; +} + +// Sets the maximum time each epoch should take. Default is 100ms. +Bench& Bench::minEpochTime(std::chrono::nanoseconds t) noexcept { + mConfig.mMinEpochTime = t; + return *this; +} +std::chrono::nanoseconds Bench::minEpochTime() const noexcept { + return mConfig.mMinEpochTime; +} + +Bench& Bench::minEpochIterations(uint64_t numIters) noexcept { + mConfig.mMinEpochIterations = (numIters == 0) ? 1 : numIters; + return *this; +} +uint64_t Bench::minEpochIterations() const noexcept { + return mConfig.mMinEpochIterations; +} + +Bench& Bench::epochIterations(uint64_t numIters) noexcept { + mConfig.mEpochIterations = numIters; + return *this; +} +uint64_t Bench::epochIterations() const noexcept { + return mConfig.mEpochIterations; +} + +Bench& Bench::warmup(uint64_t numWarmupIters) noexcept { + mConfig.mWarmup = numWarmupIters; + return *this; +} +uint64_t Bench::warmup() const noexcept { + return mConfig.mWarmup; +} + +Bench& Bench::config(Config const& benchmarkConfig) { + mConfig = benchmarkConfig; + return *this; +} +Config const& Bench::config() const noexcept { + return mConfig; +} + +Bench& Bench::output(std::ostream* outstream) noexcept { + mConfig.mOut = outstream; + return *this; +} + +ANKERL_NANOBENCH(NODISCARD) std::ostream* Bench::output() const noexcept { + return mConfig.mOut; +} + +std::vector const& Bench::results() const noexcept { + return mResults; +} + +Bench& Bench::render(char const* templateContent, std::ostream& os) { + ::ankerl::nanobench::render(templateContent, *this, os); + return *this; +} + +Bench& Bench::render(std::string const& templateContent, std::ostream& os) { + ::ankerl::nanobench::render(templateContent, *this, os); + return *this; +} + +std::vector Bench::complexityBigO() const { + std::vector bigOs; + auto rangeMeasure = BigO::collectRangeMeasure(mResults); + bigOs.emplace_back("O(1)", rangeMeasure, [](double) { + return 1.0; + }); + bigOs.emplace_back("O(n)", rangeMeasure, [](double n) { + return n; + }); + bigOs.emplace_back("O(log n)", rangeMeasure, [](double n) { + return std::log2(n); + }); + bigOs.emplace_back("O(n log n)", rangeMeasure, [](double n) { + return n * std::log2(n); + }); + bigOs.emplace_back("O(n^2)", rangeMeasure, [](double n) { + return n * n; + }); + bigOs.emplace_back("O(n^3)", rangeMeasure, [](double n) { + return n * n * n; + }); + std::sort(bigOs.begin(), bigOs.end()); + return bigOs; +} + +Rng::Rng() + : mX(0) + , mY(0) { + std::random_device rd; + std::uniform_int_distribution dist; + do { + mX = dist(rd); + mY = dist(rd); + } while (mX == 0 && mY == 0); +} + +ANKERL_NANOBENCH_NO_SANITIZE("integer", "undefined") +uint64_t splitMix64(uint64_t& state) noexcept { + uint64_t z = (state += UINT64_C(0x9e3779b97f4a7c15)); + z = (z ^ (z >> 30U)) * UINT64_C(0xbf58476d1ce4e5b9); + z = (z ^ (z >> 27U)) * UINT64_C(0x94d049bb133111eb); + return z ^ (z >> 31U); +} + +// Seeded as described in romu paper (update april 2020) +Rng::Rng(uint64_t seed) noexcept + : mX(splitMix64(seed)) + , mY(splitMix64(seed)) { + for (size_t i = 0; i < 10; ++i) { + operator()(); + } +} + +// only internally used to copy the RNG. +Rng::Rng(uint64_t x, uint64_t y) noexcept + : mX(x) + , mY(y) {} + +Rng Rng::copy() const noexcept { + return Rng{mX, mY}; +} + +Rng::Rng(std::vector const& data) + : mX(0) + , mY(0) { + if (data.size() != 2) { + throw std::runtime_error("ankerl::nanobench::Rng::Rng: needed exactly 2 entries in data, but got " + + detail::fmt::to_s(data.size())); + } + mX = data[0]; + mY = data[1]; +} + +std::vector Rng::state() const { + std::vector data(2); + data[0] = mX; + data[1] = mY; + return data; +} + +BigO::RangeMeasure BigO::collectRangeMeasure(std::vector const& results) { + BigO::RangeMeasure rangeMeasure; + for (auto const& result : results) { + if (result.config().mComplexityN > 0.0) { + rangeMeasure.emplace_back(result.config().mComplexityN, result.median(Result::Measure::elapsed)); + } + } + return rangeMeasure; +} + +BigO::BigO(std::string bigOName, RangeMeasure const& rangeMeasure) + : mName(std::move(bigOName)) { + + // estimate the constant factor + double sumRangeMeasure = 0.0; + double sumRangeRange = 0.0; + + for (const auto& rm : rangeMeasure) { + sumRangeMeasure += rm.first * rm.second; + sumRangeRange += rm.first * rm.first; + } + mConstant = sumRangeMeasure / sumRangeRange; + + // calculate root mean square + double err = 0.0; + double sumMeasure = 0.0; + for (const auto& rm : rangeMeasure) { + auto diff = mConstant * rm.first - rm.second; + err += diff * diff; + + sumMeasure += rm.second; + } + + auto n = detail::d(rangeMeasure.size()); + auto mean = sumMeasure / n; + mNormalizedRootMeanSquare = std::sqrt(err / n) / mean; +} + +BigO::BigO(const char* bigOName, RangeMeasure const& rangeMeasure) + : BigO(std::string(bigOName), rangeMeasure) {} + +std::string const& BigO::name() const noexcept { + return mName; +} + +double BigO::constant() const noexcept { + return mConstant; +} + +double BigO::normalizedRootMeanSquare() const noexcept { + return mNormalizedRootMeanSquare; +} + +bool BigO::operator<(BigO const& other) const noexcept { + return std::tie(mNormalizedRootMeanSquare, mName) < std::tie(other.mNormalizedRootMeanSquare, other.mName); +} + +std::ostream& operator<<(std::ostream& os, BigO const& bigO) { + return os << bigO.constant() << " * " << bigO.name() << ", rms=" << bigO.normalizedRootMeanSquare(); +} + +std::ostream& operator<<(std::ostream& os, std::vector const& bigOs) { + detail::fmt::StreamStateRestorer const restorer(os); + os << std::endl << "| coefficient | err% | complexity" << std::endl << "|--------------:|-------:|------------" << std::endl; + for (auto const& bigO : bigOs) { + os << "|" << std::setw(14) << std::setprecision(7) << std::scientific << bigO.constant() << " "; + os << "|" << detail::fmt::Number(6, 1, bigO.normalizedRootMeanSquare() * 100.0) << "% "; + os << "| " << bigO.name(); + os << std::endl; + } + return os; +} + +} // namespace nanobench +} // namespace ankerl + +#endif // ANKERL_NANOBENCH_IMPLEMENT +#endif // ANKERL_NANOBENCH_H_INCLUDED diff --git a/Source/Client/CLI/PackageLock.sml b/Source/Client/CLI/PackageLock.sml index 61cb18115..d841329f8 100644 --- a/Source/Client/CLI/PackageLock.sml +++ b/Source/Client/CLI/PackageLock.sml @@ -8,7 +8,7 @@ Closures: { 'Monitor.Shared': { Version: '../../Monitor/Shared/', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|CryptoPP': { Version: '1.2.2', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|Detours': { Version: '4.0.12', Build: 'Build0', Tool: 'Tool0' } - 'mwasplund|Opal': { Version: '0.10.2', Build: 'Build0', Tool: 'Tool0' } + 'mwasplund|Opal': { Version: '0.11.0', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|reflex': { Version: '1.0.4', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|Soup.Test.Assert': { Version: '0.4.0', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|wren': { Version: '1.0.5', Build: 'Build0', Tool: 'Tool0' } diff --git a/Source/Client/CLI/Source/Commands/BuildCommand.h b/Source/Client/CLI/Source/Commands/BuildCommand.h index 1e596015a..62d4b3de6 100644 --- a/Source/Client/CLI/Source/Commands/BuildCommand.h +++ b/Source/Client/CLI/Source/Commands/BuildCommand.h @@ -38,7 +38,8 @@ namespace Soup::Client } else { - workingDirectory = Path(std::format("{}/", _options.Path)); + // Parse the path in any system valid format + workingDirectory = Path::Parse(std::format("{}/", _options.Path)); // Check if this is relative to current directory if (!workingDirectory.HasRoot()) @@ -77,7 +78,7 @@ namespace Soup::Client // Find the built in folder root auto processFilename = System::IProcessManager::Current().GetCurrentProcessFileName(); auto processDirectory = processFilename.GetParent(); - auto builtInPackageDirectory = processDirectory + Path("BuiltIn/"); + auto builtInPackageDirectory = processDirectory + Path("./BuiltIn/"); // Load user config state auto userDataPath = Core::BuildEngine::GetSoupUserDataPath(); diff --git a/Source/Client/CLI/Source/Commands/InitializeCommand.h b/Source/Client/CLI/Source/Commands/InitializeCommand.h index a23a6e3de..26844e78a 100644 --- a/Source/Client/CLI/Source/Commands/InitializeCommand.h +++ b/Source/Client/CLI/Source/Commands/InitializeCommand.h @@ -37,7 +37,8 @@ namespace Soup::Client } else { - workingDirectory = Path(_options.Path); + // Parse the path in any system valid format + workingDirectory = Path::Parse(std::format("{}/", _options.Path)); // Check if this is relative to current directory if (!workingDirectory.HasRoot()) diff --git a/Source/Client/CLI/Source/Commands/InstallCommand.h b/Source/Client/CLI/Source/Commands/InstallCommand.h index 837a941af..9c7918b04 100644 --- a/Source/Client/CLI/Source/Commands/InstallCommand.h +++ b/Source/Client/CLI/Source/Commands/InstallCommand.h @@ -37,7 +37,8 @@ namespace Soup::Client } else { - workingDirectory = Path(_options.Path); + // Parse the path in any system valid format + workingDirectory = Path::Parse(std::format("{}/", _options.Path)); // Check if this is relative to current directory if (!workingDirectory.HasRoot()) diff --git a/Source/Client/CLI/Source/Commands/PublishCommand.h b/Source/Client/CLI/Source/Commands/PublishCommand.h index b4f6af3d2..711362720 100644 --- a/Source/Client/CLI/Source/Commands/PublishCommand.h +++ b/Source/Client/CLI/Source/Commands/PublishCommand.h @@ -37,7 +37,8 @@ namespace Soup::Client } else { - workingDirectory = Path(_options.Path); + // Parse the path in any system valid format + workingDirectory = Path::Parse(std::format("{}/", _options.Path)); // Check if this is relative to current directory if (!workingDirectory.HasRoot()) diff --git a/Source/Client/CLI/Source/Commands/RestoreCommand.h b/Source/Client/CLI/Source/Commands/RestoreCommand.h index 19d34e1e4..4a7de4f6d 100644 --- a/Source/Client/CLI/Source/Commands/RestoreCommand.h +++ b/Source/Client/CLI/Source/Commands/RestoreCommand.h @@ -37,7 +37,8 @@ namespace Soup::Client } else { - workingDirectory = Path(_options.Path); + // Parse the path in any system valid format + workingDirectory = Path::Parse(std::format("{}/", _options.Path)); // Check if this is relative to current directory if (!workingDirectory.HasRoot()) diff --git a/Source/Client/CLI/Source/Commands/RunCommand.h b/Source/Client/CLI/Source/Commands/RunCommand.h index da65b9fe4..d26ff8108 100644 --- a/Source/Client/CLI/Source/Commands/RunCommand.h +++ b/Source/Client/CLI/Source/Commands/RunCommand.h @@ -37,7 +37,8 @@ namespace Soup::Client } else { - workingDirectory = Path(_options.Path); + // Parse the path in any system valid format + workingDirectory = Path::Parse(std::format("{}/", _options.Path)); // Check if this is relative to current directory if (!workingDirectory.HasRoot()) diff --git a/Source/Client/CLI/Source/Commands/TargetCommand.h b/Source/Client/CLI/Source/Commands/TargetCommand.h index 256d49fad..c5d003f21 100644 --- a/Source/Client/CLI/Source/Commands/TargetCommand.h +++ b/Source/Client/CLI/Source/Commands/TargetCommand.h @@ -37,7 +37,8 @@ namespace Soup::Client } else { - workingDirectory = Path(_options.Path); + // Parse the path in any system valid format + workingDirectory = Path::Parse(std::format("{}/", _options.Path)); // Check if this is relative to current directory if (!workingDirectory.HasRoot()) diff --git a/Source/Client/CLI/Source/Commands/ViewCommand.h b/Source/Client/CLI/Source/Commands/ViewCommand.h index 25e25943f..014708dc7 100644 --- a/Source/Client/CLI/Source/Commands/ViewCommand.h +++ b/Source/Client/CLI/Source/Commands/ViewCommand.h @@ -37,7 +37,8 @@ namespace Soup::Client } else { - workingDirectory = Path(std::format("{}/", _options.Path)); + // Parse the path in any system valid format + workingDirectory = Path::Parse(std::format("{}/", _options.Path)); // Check if this is relative to current directory if (!workingDirectory.HasRoot()) @@ -56,11 +57,11 @@ namespace Soup::Client auto moduleName = System::IProcessManager::Current().GetCurrentProcessFileName(); auto moduleFolder = moduleName.GetParent(); - auto soupViewFolder = moduleFolder + Path("View/"); + auto soupViewFolder = moduleFolder + Path("./View/"); #if defined(_WIN32) - auto executable = soupViewFolder + Path("SoupView.exe"); + auto executable = soupViewFolder + Path("./SoupView.exe"); #elif defined(__linux__) - auto executable = soupViewFolder + Path("SoupView"); + auto executable = soupViewFolder + Path("./SoupView"); #else #error "Unknown platform" #endif diff --git a/Source/Client/Core/Recipe.sml b/Source/Client/Core/Recipe.sml index 9741b41c1..6faf5ad17 100644 --- a/Source/Client/Core/Recipe.sml +++ b/Source/Client/Core/Recipe.sml @@ -2,7 +2,6 @@ Name: 'Soup.Core' Language: 'C++|0' Version: '0.1.1' Defines: [ - # 'LOCAL_DEBUG' # 'TRACE_SYSTEM_ACCESS' # 'TRACE_FILE_SYSTEM_STATE' ] diff --git a/Source/Client/Core/Source/Build/BuildConstants.h b/Source/Client/Core/Source/Build/BuildConstants.h index b1ba20d1d..ce0589cc2 100644 --- a/Source/Client/Core/Source/Build/BuildConstants.h +++ b/Source/Client/Core/Source/Build/BuildConstants.h @@ -17,73 +17,73 @@ namespace Soup::Core public: static const Path& EvaluateGraphFileName() { - static const auto value = Path("Evaluate.bog"); + static const auto value = Path("./Evaluate.bog"); return value; } static const Path& EvaluateResultsFileName() { - static const auto value = Path("Evaluate.bor"); + static const auto value = Path("./Evaluate.bor"); return value; } static const Path& GenerateInputFileName() { - static const auto value = Path("GenerateInput.bvt"); + static const auto value = Path("./GenerateInput.bvt"); return value; } static const Path& GenerateSharedStateFileName() { - static const auto value = Path("GenerateSharedState.bvt"); + static const auto value = Path("./GenerateSharedState.bvt"); return value; } static const Path& GenerateResultsFileName() { - static const auto value = Path("Generate.bor"); + static const auto value = Path("./Generate.bor"); return value; } static const Path& LocalUserConfigFileName() { - static const auto value = Path("LocalUserConfig.sml"); + static const auto value = Path("./LocalUserConfig.sml"); return value; } static const Path& PackageLockFileName() { - static const auto value = Path("PackageLock.sml"); + static const auto value = Path("./PackageLock.sml"); return value; } static const Path& SoupLocalStoreDirectory() { - static const auto value = Path(".soup/"); + static const auto value = Path("./.soup/"); return value; } static const Path& RecipeFileName() { - static const auto value = Path("Recipe.sml"); + static const auto value = Path("./Recipe.sml"); return value; } static const Path& GenerateInfoFileName() { - static const auto value = Path("GenerateInfo.bvt"); + static const auto value = Path("./GenerateInfo.bvt"); return value; } static const Path& SoupTargetDirectory() { - static const auto value = Path(".soup/"); + static const auto value = Path("./.soup/"); return value; } static const Path& TemporaryFolderName() { - static const auto value = Path("temp/"); + static const auto value = Path("./temp/"); return value; } }; diff --git a/Source/Client/Core/Source/Build/BuildEngine.h b/Source/Client/Core/Source/Build/BuildEngine.h index a59c466f1..cf1864b45 100644 --- a/Source/Client/Core/Source/Build/BuildEngine.h +++ b/Source/Client/Core/Source/Build/BuildEngine.h @@ -93,7 +93,7 @@ namespace Soup::Core auto endTime = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast>(endTime - startTime); - std::cout << "LoadSystemState: " << std::to_string(duration.count()) << " seconds." << std::endl; + // Log::Info("LoadSystemState: {} seconds", duration.count()); startTime = std::chrono::high_resolution_clock::now(); @@ -113,7 +113,7 @@ namespace Soup::Core endTime = std::chrono::high_resolution_clock::now(); duration = std::chrono::duration_cast>(endTime - startTime); - std::cout << "BuildLoadEngine: " << std::to_string(duration.count()) << " seconds." << std::endl; + // Log::Info("BuildLoadEngine: {} seconds", duration.count()); return packageProvider; } @@ -138,7 +138,7 @@ namespace Soup::Core auto endTime = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast>(endTime - startTime); - std::cout << "PreloadFileSystemState: " << std::to_string(duration.count()) << " seconds." << std::endl; + // Log::Info("PreloadFileSystemState: {} seconds", duration.count()); startTime = std::chrono::high_resolution_clock::now(); @@ -184,7 +184,7 @@ namespace Soup::Core auto endTime = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast>(endTime - startTime); - std::cout << "BuildRunner: " << std::to_string(duration.count()) << " seconds." << std::endl; + // Log::Info("BuildRunner: {} seconds", duration.count()); } static Path GetSoupUserDataPath() diff --git a/Source/Client/Core/Source/Build/BuildEvaluateEngine.h b/Source/Client/Core/Source/Build/BuildEvaluateEngine.h index 9029ec831..4dd5cca2b 100644 --- a/Source/Client/Core/Source/Build/BuildEvaluateEngine.h +++ b/Source/Client/Core/Source/Build/BuildEvaluateEngine.h @@ -242,7 +242,7 @@ namespace Soup::Core { // Check if the executable has changed since the last run bool executableOutOfDate = false; - if (operationInfo.Command.Executable != Path("writefile.exe")) + if (operationInfo.Command.Executable != Path("./writefile.exe")) { // Only check for "real" executables auto executableFileId = _fileSystemState.ToFileId( @@ -295,7 +295,7 @@ namespace Soup::Core auto operationResult = OperationResult(); // Check for special in-process write operations - if (operationInfo.Command.Executable == Path("writefile.exe")) + if (operationInfo.Command.Executable == Path("./writefile.exe")) { ExecuteWriteFileOperation( operationInfo, @@ -440,7 +440,7 @@ namespace Soup::Core auto input = std::vector(); for (auto& value : callback->GetInput()) { - auto path = Path(value); + auto path = Path::Parse(value); // Log::Diag("ObservedInput: {}", path.ToString()); input.push_back(std::move(path)); } @@ -448,7 +448,7 @@ namespace Soup::Core auto output = std::vector(); for (auto& value : callback->GetOutput()) { - auto path = Path(value); + auto path = Path::Parse(value); // Log::Diag("ObservedOutput: {}", path.ToString()); output.push_back(std::move(path)); } diff --git a/Source/Client/Core/Source/Build/BuildLoadEngine.h b/Source/Client/Core/Source/Build/BuildLoadEngine.h index 31eefd7c3..0ee81f32e 100644 --- a/Source/Client/Core/Source/Build/BuildLoadEngine.h +++ b/Source/Client/Core/Source/Build/BuildLoadEngine.h @@ -34,7 +34,7 @@ namespace Soup::Core { private: const int _packageLockVersion = 5; - const Path _builtInPackageOutPath = Path("out/"); + const Path _builtInPackageOutPath = Path("./out/"); const std::string _builtInWrenLanguage = "Wren"; const std::string _dependencyTypeBuild = "Build"; const std::string _dependencyTypeTool = "Tool"; @@ -293,13 +293,13 @@ namespace Soup::Core else { // Build the global store location path - auto packageStore = _userDataPath + Path("packages/"); + auto packageStore = _userDataPath + Path("./packages/"); auto& languageSafeName = GetLanguageSafeName(activeReference.GetLanguage()); auto activeVersionString = activeReference.GetVersion().ToString(); packagePath = packageStore + Path( std::format( - "{}/{}/{}/{}/", + "./{}/{}/{}/{}/", languageSafeName, activeReference.GetOwner(), activeReference.GetName(), @@ -339,11 +339,11 @@ namespace Soup::Core else { // Build the global store location path - auto packageStore = _userDataPath + Path("locks/"); + auto packageStore = _userDataPath + Path("./locks/"); auto& languageSafeName = GetLanguageSafeName(activeReference.GetLanguage()); packagePath = packageStore + Path(std::format( - "{}/{}/{}/{}/", + "./{}/{}/{}/{}/", languageSafeName, activeReference.GetOwner(), activeReference.GetName(), @@ -1185,7 +1185,7 @@ namespace Soup::Core // Use the prebuilt version in the install folder auto packageRoot = _builtInPackageDirectory + Path(std::format( - "{}/{}/{}/", + "./{}/{}/{}/", activeReference.GetOwner(), activeReference.GetName(), activeReference.GetVersion().ToString())); diff --git a/Source/Client/Core/Source/Build/BuildRunner.h b/Source/Client/Core/Source/Build/BuildRunner.h index 4c7094577..20b4f8bed 100644 --- a/Source/Client/Core/Source/Build/BuildRunner.h +++ b/Source/Client/Core/Source/Build/BuildRunner.h @@ -122,7 +122,7 @@ namespace Soup::Core packageInfo.Name.ToString(), Path(std::format("/(TARGET_{})/", packageInfo.Name.ToString())), packageInfo.TargetDirectory, - packageInfo.TargetDirectory + Path(".soup/"), + packageInfo.TargetDirectory + Path("./.soup/"), {}, {})); } @@ -429,9 +429,9 @@ namespace Soup::Core auto generateFolder = moduleName.GetParent(); #if defined(_WIN32) - auto generateExecutable = generateFolder + Path("Soup.Generate.exe"); + auto generateExecutable = generateFolder + Path("./Soup.Generate.exe"); #elif defined(__linux__) - auto generateExecutable = generateFolder + Path("generate"); + auto generateExecutable = generateFolder + Path("./generate"); #else #error "Unknown platform" #endif diff --git a/Source/Client/Core/Source/Build/RecipeBuildLocationManager.h b/Source/Client/Core/Source/Build/RecipeBuildLocationManager.h index 14ba30aba..ca45c02e9 100644 --- a/Source/Client/Core/Source/Build/RecipeBuildLocationManager.h +++ b/Source/Client/Core/Source/Build/RecipeBuildLocationManager.h @@ -42,7 +42,7 @@ namespace Soup::Core RecipeCache& recipeCache) { // Set the default output directory to be relative to the package - auto rootOutput = packageRoot + Path("out/"); + auto rootOutput = packageRoot + Path("./out/"); // Check for root recipe file with overrides Path rootRecipeFile; @@ -66,22 +66,22 @@ namespace Soup::Core // Add the language sub folder auto language = recipe.GetLanguage().GetName(); auto languageSafeName = GetLanguageSafeName(language); - rootOutput = rootOutput + Path(languageSafeName + "/"); + rootOutput = rootOutput + Path(std::format("./{}/", languageSafeName)); if (name.HasOwner()) { // Add the unique owner - rootOutput = rootOutput + Path(name.GetOwner() + "/"); + rootOutput = rootOutput + Path(std::format("./{}/", name.GetOwner())); } else { // Label as local - rootOutput = rootOutput + Path("Local/"); + rootOutput = rootOutput + Path("./Local/"); } // Add the unique recipe name/version rootOutput = rootOutput + - Path(std::format("{}/{}/", name.GetName(), recipe.GetVersion().ToString())); + Path(std::format("./{}/{}/", name.GetName(), recipe.GetVersion().ToString())); // Ensure there is a root relative to the file itself if (!rootOutput.HasRoot()) @@ -97,7 +97,7 @@ namespace Soup::Core auto parametersStream = std::stringstream(); ValueTableWriter::Serialize(globalParameters, parametersStream); auto hashParameters = CryptoPP::Sha1::HashBase64(parametersStream.str()); - auto uniqueParametersFolder = Path(hashParameters + "/"); + auto uniqueParametersFolder = Path(std::format("./{}/", hashParameters)); rootOutput = rootOutput + uniqueParametersFolder; return rootOutput; diff --git a/Source/Client/Core/Source/OperationGraph/OperationGraphReader.h b/Source/Client/Core/Source/OperationGraph/OperationGraphReader.h index 48168603c..c59dab11f 100644 --- a/Source/Client/Core/Source/OperationGraph/OperationGraphReader.h +++ b/Source/Client/Core/Source/OperationGraph/OperationGraphReader.h @@ -22,9 +22,6 @@ namespace Soup::Core public: static OperationGraph Deserialize(std::istream& stream, FileSystemState& fileSystemState) { - // BUG: Why does this need to be at the start of the file? - auto activeFileIdMap = std::unordered_map(); - // Read the entire file for fastest read operation stream.seekg(0, std::ios_base::end); auto size = stream.tellg(); @@ -32,11 +29,29 @@ namespace Soup::Core auto contentBuffer = std::vector(size); stream.read(contentBuffer.data(), size); - auto content = contentBuffer.data(); + auto data = contentBuffer.data(); + size_t offset = 0; + + auto result = Deserialize(data, size, offset, fileSystemState); + + if (offset != contentBuffer.size()) + { + throw std::runtime_error("Value Table file corrupted - Did not read the entire file"); + } + + return result; + } + + private: + static OperationGraph Deserialize( + char* data, size_t size, size_t& offset, FileSystemState& fileSystemState) + { + // BUG: Why does this need to be at the start of the file? + auto activeFileIdMap = std::unordered_map(); // Read the File Header with version auto headerBuffer = std::array(); - Read(content, headerBuffer.data(), 4); + Read(data, size, offset, headerBuffer.data(), 4); if (headerBuffer[0] != 'B' || headerBuffer[1] != 'O' || headerBuffer[2] != 'G' || @@ -45,14 +60,14 @@ namespace Soup::Core throw std::runtime_error("Invalid operation graph file header"); } - auto fileVersion = ReadUInt32(content); + auto fileVersion = ReadUInt32(data, size, offset); if (fileVersion != FileVersion) { throw std::runtime_error("Operation graph file version does not match expected"); } // Read the set of files - Read(content, headerBuffer.data(), 4); + Read(data, size, offset, headerBuffer.data(), 4); if (headerBuffer[0] != 'F' || headerBuffer[1] != 'I' || headerBuffer[2] != 'S' || @@ -62,14 +77,14 @@ namespace Soup::Core } // Map up the incoming file ids to the active file system state ids - auto fileCount = ReadUInt32(content); + auto fileCount = ReadUInt32(data, size, offset); for (auto i = 0u; i < fileCount; i++) { // Read the command working directory - auto fileId = ReadUInt32(content); + auto fileId = ReadUInt32(data, size, offset); - auto fileString = ReadString(content); - auto file = Path::Load(std::move(fileString)); + auto fileString = ReadString(data, size, offset); + auto file = Path(std::move(fileString)); auto activeFileId = fileSystemState.ToFileId(file); auto insertResult = activeFileIdMap.emplace(fileId, activeFileId); @@ -78,7 +93,7 @@ namespace Soup::Core } // Read the set of operations - Read(content, headerBuffer.data(), 4); + Read(data, size, offset, headerBuffer.data(), 4); if (headerBuffer[0] != 'R' || headerBuffer[1] != 'O' || headerBuffer[2] != 'P' || @@ -88,10 +103,10 @@ namespace Soup::Core } // Read the root operation ids - auto rootOperationIds = ReadOperationIdList(content); + auto rootOperationIds = ReadOperationIdList(data, size, offset); // Read the set of operations - Read(content, headerBuffer.data(), 4); + Read(data, size, offset, headerBuffer.data(), 4); if (headerBuffer[0] != 'O' || headerBuffer[1] != 'P' || headerBuffer[2] != 'S' || @@ -100,16 +115,11 @@ namespace Soup::Core throw std::runtime_error("Invalid operation graph operations header"); } - auto operationCount = ReadUInt32(content); + auto operationCount = ReadUInt32(data, size, offset); auto operations = std::vector(operationCount); for (auto i = 0u; i < operationCount; i++) { - operations[i] = ReadOperationInfo(content, activeFileIdMap); - } - - if (stream.peek() != std::char_traits::eof()) - { - throw std::runtime_error("Operation graph file corrupted - Did not read the entire file"); + operations[i] = ReadOperationInfo(data, size, offset, activeFileIdMap); } return OperationGraph( @@ -117,41 +127,41 @@ namespace Soup::Core std::move(operations)); } - private: - static OperationInfo ReadOperationInfo(char*& content, const std::unordered_map& activeFileIdMap) + static OperationInfo ReadOperationInfo( + char* data, size_t size, size_t& offset, const std::unordered_map& activeFileIdMap) { // Write out the operation id - auto id = ReadUInt32(content); + auto id = ReadUInt32(data, size, offset); // Write the operation title - auto title = ReadString(content); + auto title = ReadString(data, size, offset); // Write the command working directory - auto workingDirectory = ReadString(content); + auto workingDirectory = ReadString(data, size, offset); // Write the command executable - auto executable = ReadString(content); + auto executable = ReadString(data, size, offset); // Write the command arguments - auto arguments = ReadStringList(content); + auto arguments = ReadStringList(data, size, offset); // Write out the declared input files - auto declaredInput = ReadFileIdList(content, activeFileIdMap); + auto declaredInput = ReadFileIdList(data, size, offset, activeFileIdMap); // Write out the declared output files - auto declaredOutput = ReadFileIdList(content, activeFileIdMap); + auto declaredOutput = ReadFileIdList(data, size, offset, activeFileIdMap); // Write out the read access list - auto readAccess = ReadFileIdList(content, activeFileIdMap); + auto readAccess = ReadFileIdList(data, size, offset, activeFileIdMap); // Write out the write access list - auto writeAccess = ReadFileIdList(content, activeFileIdMap); + auto writeAccess = ReadFileIdList(data, size, offset, activeFileIdMap); // Write out the child operation ids - auto children = ReadOperationIdList(content); + auto children = ReadOperationIdList(data, size, offset); // Write out the dependency count - auto dependencyCount = ReadUInt32(content); + auto dependencyCount = ReadUInt32(data, size, offset); return OperationInfo( id, @@ -168,42 +178,43 @@ namespace Soup::Core dependencyCount); } - static uint32_t ReadUInt32(char*& content) + static uint32_t ReadUInt32(char* data, size_t size, size_t& offset) { uint32_t result = 0; - Read(content, reinterpret_cast(&result), sizeof(uint32_t)); + Read(data, size, offset, reinterpret_cast(&result), sizeof(uint32_t)); return result; } - static std::string ReadString(char*& content) + static std::string ReadString(char* data, size_t size, size_t& offset) { - auto size = ReadUInt32(content); - auto result = std::string(size, '\0'); - Read(content, result.data(), size); + auto stringLength = ReadUInt32(data, size, offset); + auto result = std::string(stringLength, '\0'); + Read(data, size, offset, result.data(), stringLength); return result; } - static std::vector ReadStringList(char*& content) + static std::vector ReadStringList(char* data, size_t size, size_t& offset) { - auto size = ReadUInt32(content); - auto result = std::vector(size); - for (auto i = 0u; i < size; i++) + auto listSize = ReadUInt32(data, size, offset); + auto result = std::vector(listSize); + for (auto i = 0u; i < listSize; i++) { - result[i] = ReadString(content); + result[i] = ReadString(data, size, offset); } return result; } - static std::vector ReadFileIdList(char*& content, const std::unordered_map& activeFileIdMap) + static std::vector ReadFileIdList( + char* data, size_t size, size_t& offset, const std::unordered_map& activeFileIdMap) { - auto size = ReadUInt32(content); - auto result = std::vector(size); - for (auto i = 0u; i < size; i++) + auto listSize = ReadUInt32(data, size, offset); + auto result = std::vector(listSize); + for (auto i = 0u; i < listSize; i++) { - auto fileId = ReadUInt32(content); + auto fileId = ReadUInt32(data, size, offset); // Find the active file id that maps to the cached file id auto findActiveFileId = activeFileIdMap.find(fileId); @@ -216,22 +227,24 @@ namespace Soup::Core return result; } - static std::vector ReadOperationIdList(char*& content) + static std::vector ReadOperationIdList(char* data, size_t size, size_t& offset) { - auto size = ReadUInt32(content); - auto result = std::vector(size); - for (auto i = 0u; i < size; i++) + auto listSize = ReadUInt32(data, size, offset); + auto result = std::vector(listSize); + for (auto i = 0u; i < listSize; i++) { - result[i] = ReadUInt32(content); + result[i] = ReadUInt32(data, size, offset); } return result; } - static void Read(char*& data, char* buffer, size_t count) + static void Read(char* data, size_t size, size_t& offset, char* buffer, size_t count) { - memcpy(buffer, data, count); - data += count; + if (offset + count > size) + throw new std::runtime_error("Tried to read past end of data"); + memcpy(buffer, data + offset, count); + offset += count; } }; } diff --git a/Source/Client/Core/Source/OperationGraph/OperationGraphWriter.h b/Source/Client/Core/Source/OperationGraph/OperationGraphWriter.h index 91d3d8c4d..cec2430b6 100644 --- a/Source/Client/Core/Source/OperationGraph/OperationGraphWriter.h +++ b/Source/Client/Core/Source/OperationGraph/OperationGraphWriter.h @@ -9,7 +9,7 @@ namespace Soup::Core /// /// The operation graph state writer /// - class OperationGraphWriter + export class OperationGraphWriter { private: // Binary Operation graph file format diff --git a/Source/Client/Core/Source/OperationGraph/OperationResultsReader.h b/Source/Client/Core/Source/OperationGraph/OperationResultsReader.h index e9e2b38ba..7c2bdd5e5 100644 --- a/Source/Client/Core/Source/OperationGraph/OperationResultsReader.h +++ b/Source/Client/Core/Source/OperationGraph/OperationResultsReader.h @@ -34,11 +34,29 @@ namespace Soup::Core auto contentBuffer = std::vector(size); stream.read(contentBuffer.data(), size); - auto content = contentBuffer.data(); + auto data = contentBuffer.data(); + size_t offset = 0; + auto result = Deserialize(data, size, offset, fileSystemState); + + if (offset != contentBuffer.size()) + { + throw std::runtime_error("Operation results file corrupted - Did not read the entire file"); + } + + return result; + } + + private: + static OperationResults Deserialize( + char* data, + size_t size, + size_t& offset, + FileSystemState& fileSystemState) + { // Read the File Header with version auto headerBuffer = std::array(); - Read(content, headerBuffer.data(), 4); + Read(data, size, offset, headerBuffer.data(), 4); if (headerBuffer[0] != 'B' || headerBuffer[1] != 'O' || headerBuffer[2] != 'R' || @@ -47,14 +65,14 @@ namespace Soup::Core throw std::runtime_error("Invalid operation results file header"); } - auto fileVersion = ReadUInt32(content); + auto fileVersion = ReadUInt32(data, size, offset); if (fileVersion != FileVersion) { throw std::runtime_error("Operation results file version does not match expected"); } // Read the set of files - Read(content, headerBuffer.data(), 4); + Read(data, size, offset, headerBuffer.data(), 4); if (headerBuffer[0] != 'F' || headerBuffer[1] != 'I' || headerBuffer[2] != 'S' || @@ -64,15 +82,15 @@ namespace Soup::Core } // Map up the incoming file ids to the active file system state ids - auto fileCount = ReadUInt32(content); + auto fileCount = ReadUInt32(data, size, offset); auto activeFileIdMap = std::unordered_map(); for (auto i = 0u; i < fileCount; i++) { // Read the command working directory - auto fileId = ReadUInt32(content); + auto fileId = ReadUInt32(data, size, offset); - auto fileString = ReadString(content); - auto file = Path::Load(std::move(fileString)); + auto fileString = ReadString(data, size, offset); + auto file = Path(std::move(fileString)); auto activeFileId = fileSystemState.ToFileId(file); auto insertMapResult = activeFileIdMap.emplace(fileId, activeFileId); @@ -81,7 +99,7 @@ namespace Soup::Core } // Read the set of operations - Read(content, headerBuffer.data(), 4); + Read(data, size, offset, headerBuffer.data(), 4); if (headerBuffer[0] != 'R' || headerBuffer[1] != 'T' || headerBuffer[2] != 'S' || @@ -90,35 +108,31 @@ namespace Soup::Core throw std::runtime_error("Invalid operation results results header"); } - auto resultCount = ReadUInt32(content); + auto resultCount = ReadUInt32(data, size, offset); auto results = OperationResults(); for (auto i = 0u; i < resultCount; i++) { - ReadOperationResult(content, activeFileIdMap, results); - } - - if (stream.peek() != std::char_traits::eof()) - { - throw std::runtime_error("Operation results file corrupted - Did not read the entire file"); + ReadOperationResult(data, size, offset, activeFileIdMap, results); } return results; } - private: static void ReadOperationResult( - char*& content, + char* data, + size_t size, + size_t& offset, const std::unordered_map& activeFileIdMap, OperationResults& results) { // Read the operation id - auto operationId = ReadUInt32(content); + auto operationId = ReadUInt32(data, size, offset); // Read the value indicating if there was a successful run - auto wasSuccessfulRun = ReadBoolean(content); + auto wasSuccessfulRun = ReadBoolean(data, size, offset); // Read the tick offset of the system clock since its epoch - auto evaluateTimeTicks = ReadInt64(content); + auto evaluateTimeTicks = ReadInt64(data, size, offset); auto evaluateTimeDuration = ContentDuration(evaluateTimeTicks); // Use system clock with a known epoch @@ -130,10 +144,10 @@ namespace Soup::Core #endif // Read the observed input files - auto observedInput = ReadFileIdList(content, activeFileIdMap); + auto observedInput = ReadFileIdList(data, size, offset, activeFileIdMap); // Read the observed output files - auto observedOutput = ReadFileIdList(content, activeFileIdMap); + auto observedOutput = ReadFileIdList(data, size, offset, activeFileIdMap); auto result = OperationResult( wasSuccessfulRun, @@ -144,46 +158,47 @@ namespace Soup::Core results.AddOrUpdateOperationResult(operationId, std::move(result)); } - static uint32_t ReadUInt32(char*& content) + static uint32_t ReadUInt32(char* data, size_t size, size_t& offset) { uint32_t result = 0; - Read(content, reinterpret_cast(&result), sizeof(uint32_t)); + Read(data, size, offset, reinterpret_cast(&result), sizeof(uint32_t)); return result; } - static int64_t ReadInt64(char*& content) + static int64_t ReadInt64(char* data, size_t size, size_t& offset) { int64_t result = 0; - Read(content, reinterpret_cast(&result), sizeof(int64_t)); + Read(data, size, offset, reinterpret_cast(&result), sizeof(int64_t)); return result; } - static bool ReadBoolean(char*& content) + static bool ReadBoolean(char* data, size_t size, size_t& offset) { uint32_t result = 0; - Read(content, reinterpret_cast(&result), sizeof(uint32_t)); + Read(data, size, offset, reinterpret_cast(&result), sizeof(uint32_t)); return result != 0; } - static std::string ReadString(char*& content) + static std::string ReadString(char* data, size_t size, size_t& offset) { - auto size = ReadUInt32(content); - auto result = std::string(size, '\0'); - Read(content, result.data(), size); + auto stringLength = ReadUInt32(data, size, offset); + auto result = std::string(stringLength, '\0'); + Read(data, size, offset, result.data(), stringLength); return result; } - static std::vector ReadFileIdList(char*& content, const std::unordered_map& activeFileIdMap) + static std::vector ReadFileIdList( + char* data, size_t size, size_t& offset, const std::unordered_map& activeFileIdMap) { - auto size = ReadUInt32(content); - auto result = std::vector(size); - for (auto i = 0u; i < size; i++) + auto listLength = ReadUInt32(data, size, offset); + auto result = std::vector(listLength); + for (auto i = 0u; i < listLength; i++) { - auto fileId = ReadUInt32(content); + auto fileId = ReadUInt32(data, size, offset); // Find the active file id that maps to the cached file id auto findActiveFileId = activeFileIdMap.find(fileId); @@ -196,22 +211,25 @@ namespace Soup::Core return result; } - static std::vector ReadOperationIdList(char*& content) + static std::vector ReadOperationIdList( + char* data, size_t size, size_t& offset) { - auto size = ReadUInt32(content); - auto result = std::vector(size); - for (auto i = 0u; i < size; i++) + auto listLength = ReadUInt32(data, size, offset); + auto result = std::vector(listLength); + for (auto i = 0u; i < listLength; i++) { - result[i] = ReadUInt32(content); + result[i] = ReadUInt32(data, size, offset); } return result; } - static void Read(char*& data, char* buffer, size_t count) + static void Read(char* data, size_t size, size_t& offset, char* buffer, size_t count) { - memcpy(buffer, data, count); - data += count; + if (offset + count > size) + throw new std::runtime_error("Tried to read past end of data"); + memcpy(buffer, data + offset, count); + offset += count; } }; } diff --git a/Source/Client/Core/Source/Package/PackageManager.h b/Source/Client/Core/Source/Package/PackageManager.h index ea22116b5..8e4e8f463 100644 --- a/Source/Client/Core/Source/Package/PackageManager.h +++ b/Source/Client/Core/Source/Package/PackageManager.h @@ -85,11 +85,11 @@ namespace Soup::Core { auto moduleName = System::IProcessManager::Current().GetCurrentProcessFileName(); auto moduleFolder = moduleName.GetParent(); - auto packageManagerFolder = moduleFolder + Path("PackageManager/"); + auto packageManagerFolder = moduleFolder + Path("./PackageManager/"); #if defined(_WIN32) - auto executable = packageManagerFolder + Path("Soup.Build.PackageManager.exe"); + auto executable = packageManagerFolder + Path("./Soup.Build.PackageManager.exe"); #elif defined(__linux__) - auto executable = packageManagerFolder + Path("Soup.Build.PackageManager"); + auto executable = packageManagerFolder + Path("./Soup.Build.PackageManager"); #else #error "Unknown platform" #endif diff --git a/Source/Client/Core/Source/Recipe/RecipeSML.h b/Source/Client/Core/Source/Recipe/RecipeSML.h index 463859169..fe728dcd0 100644 --- a/Source/Client/Core/Source/Recipe/RecipeSML.h +++ b/Source/Client/Core/Source/Recipe/RecipeSML.h @@ -26,8 +26,16 @@ namespace Soup::Core { try { + // Read the entire file for fastest read operation + stream.seekg(0, std::ios_base::end); + auto size = stream.tellg(); + stream.seekg(0, std::ios_base::beg); + + auto contentBuffer = std::vector(size); + stream.read(contentBuffer.data(), size); + // Read the contents of the recipe file - auto root = SMLDocument::Parse(stream); + auto root = SMLDocument::Parse(contentBuffer.data(), size); // Load the entire root table auto table = RecipeTable(); diff --git a/Source/Client/Core/Source/Recipe/RootRecipeExtensions.h b/Source/Client/Core/Source/Recipe/RootRecipeExtensions.h index 10ec592f5..89110125c 100644 --- a/Source/Client/Core/Source/Recipe/RootRecipeExtensions.h +++ b/Source/Client/Core/Source/Recipe/RootRecipeExtensions.h @@ -61,7 +61,7 @@ namespace Soup::Core auto done = false; while (!done) { - auto checkRootRecipeFile = parentDirectory + Path("RootRecipe.sml"); + auto checkRootRecipeFile = parentDirectory + Path("./RootRecipe.sml"); if (System::IFileSystem::Current().Exists(checkRootRecipeFile)) { // We found one! diff --git a/Source/Client/Core/Source/SML/SML.h b/Source/Client/Core/Source/SML/SML.h index a403e5420..43d29e9d7 100644 --- a/Source/Client/Core/Source/SML/SML.h +++ b/Source/Client/Core/Source/SML/SML.h @@ -80,6 +80,7 @@ namespace Soup::Core /// Load from stream /// static SMLDocument Parse(std::istream& stream); + static SMLDocument Parse(const char* data, size_t size); public: SMLDocument(SMLTable root) : diff --git a/Source/Client/Core/Source/SML/SMLParser.cpp b/Source/Client/Core/Source/SML/SMLParser.cpp index 8211b3d96..982cc0c15 100644 --- a/Source/Client/Core/Source/SML/SMLParser.cpp +++ b/Source/Client/Core/Source/SML/SMLParser.cpp @@ -749,6 +749,7 @@ class SMLParser : public SML::Lexer /*static*/ SMLDocument SMLDocument::Parse(std::istream& stream) { + auto input = reflex::Input(stream); auto parser = SMLParser(stream); if (parser.TryParse()) { @@ -766,6 +767,26 @@ class SMLParser : public SML::Lexer } } +/*static*/ SMLDocument SMLDocument::Parse(const char* data, size_t size) +{ + auto input = reflex::Input(data, size); + auto parser = SMLParser(input); + if (parser.TryParse()) + { + return parser.GetResult(); + } + else + { + auto line = parser.lineno(); + auto column = parser.columno(); + auto text = parser.text(); + + std::stringstream message; + message << "Failed to parse at " << line << ":" << column << " " << text; + throw std::runtime_error(message.str()); + } +} + } //////////////////////////////////////////////////////////////////////////////// diff --git a/Source/Client/Core/Source/SML/SMLParser.l b/Source/Client/Core/Source/SML/SMLParser.l index fd0a8bd6d..64e014fff 100644 --- a/Source/Client/Core/Source/SML/SMLParser.l +++ b/Source/Client/Core/Source/SML/SMLParser.l @@ -528,6 +528,7 @@ private: /*static*/ SMLDocument SMLDocument::Parse(std::istream& stream) { + auto input = reflex::Input(stream); auto parser = SMLParser(stream); if (parser.TryParse()) { @@ -545,4 +546,24 @@ private: } } +/*static*/ SMLDocument SMLDocument::Parse(const char* data, size_t size) +{ + auto input = reflex::Input(data, size); + auto parser = SMLParser(input); + if (parser.TryParse()) + { + return parser.GetResult(); + } + else + { + auto line = parser.lineno(); + auto column = parser.columno(); + auto text = parser.text(); + + std::stringstream message; + message << "Failed to parse at " << line << ":" << column << " " << text; + throw std::runtime_error(message.str()); + } +} + } \ No newline at end of file diff --git a/Source/Client/Core/Source/ValueTable/ValueTableReader.h b/Source/Client/Core/Source/ValueTable/ValueTableReader.h index ec5d95da3..6b0296177 100644 --- a/Source/Client/Core/Source/ValueTable/ValueTableReader.h +++ b/Source/Client/Core/Source/ValueTable/ValueTableReader.h @@ -29,11 +29,26 @@ namespace Soup::Core auto contentBuffer = std::vector(size); stream.read(contentBuffer.data(), size); - auto content = contentBuffer.data(); + auto data = contentBuffer.data(); + size_t offset = 0; + + auto result = Deserialize(data, size, offset); + if (offset != contentBuffer.size()) + { + throw std::runtime_error("Value Table file corrupted - Did not read the entire file"); + } + + return result; + } + + private: + static ValueTable Deserialize( + char* data, size_t size, size_t& offset) + { // Read the File Header with version auto headerBuffer = std::array(); - Read(content, headerBuffer.data(), 4); + Read(data, size, offset, headerBuffer.data(), 4); if (headerBuffer[0] != 'B' || headerBuffer[1] != 'V' || headerBuffer[2] != 'T' || @@ -42,14 +57,14 @@ namespace Soup::Core throw std::runtime_error("Invalid Value Table file header"); } - auto fileVersion = ReadUInt32(content); + auto fileVersion = ReadUInt32(data, size, offset); if (fileVersion != FileVersion) { throw std::runtime_error("Value Table file version does not match expected"); } // Read the root table - Read(content, headerBuffer.data(), 4); + Read(data, size, offset, headerBuffer.data(), 4); if (headerBuffer[0] != 'T' || headerBuffer[1] != 'B' || headerBuffer[2] != 'L' || @@ -58,54 +73,48 @@ namespace Soup::Core throw std::runtime_error("Invalid Value Table table header"); } - auto rootTable = ReadValueTable(content); - - if (content != (contentBuffer.data() + size)) - { - throw std::runtime_error("Value Table file corrupted - Did not read the entire file"); - } + auto rootTable = ReadValueTable(data, size, offset); return rootTable; } - private: - static Value ReadValue(char*& content) + static Value ReadValue(char* data, size_t size, size_t& offset) { // Read the value type - auto valueType = static_cast(ReadUInt32(content)); + auto valueType = static_cast(ReadUInt32(data, size, offset)); switch (valueType) { case ValueType::Table: - return Value(ReadValueTable(content)); + return Value(ReadValueTable(data, size, offset)); case ValueType::List: - return Value(ReadValueList(content)); + return Value(ReadValueList(data, size, offset)); case ValueType::String: - return Value(ReadString(content)); + return Value(ReadString(data, size, offset)); case ValueType::Integer: - return Value(ReadInt64(content)); + return Value(ReadInt64(data, size, offset)); case ValueType::Float: - return Value(ReadDouble(content)); + return Value(ReadDouble(data, size, offset)); case ValueType::Boolean: - return Value(ReadBoolean(content)); + return Value(ReadBoolean(data, size, offset)); default: throw std::runtime_error("Unknown ValueType"); } } - static ValueTable ReadValueTable(char*& content) + static ValueTable ReadValueTable(char* data, size_t size, size_t& offset) { // Write out the table size - auto size = ReadUInt32(content); + auto tableSize = ReadUInt32(data, size, offset); auto table = ValueTable(); - for (auto i = 0u; i < size; i++) + for (auto i = 0u; i < tableSize; i++) { // Read the key - auto key = ReadString(content); + auto key = ReadString(data, size, offset); // Read the value - auto value = ReadValue(content); + auto value = ReadValue(data, size, offset); table.emplace(std::move(key), std::move(value)); } @@ -113,16 +122,16 @@ namespace Soup::Core return table; } - static ValueList ReadValueList(char*& content) + static ValueList ReadValueList(char* data, size_t size, size_t& offset) { // Write out the list size - auto size = ReadUInt32(content); + auto listSize = ReadUInt32(data, size, offset); auto list = ValueList(); - for (auto i = 0u; i < size; i++) + for (auto i = 0u; i < listSize; i++) { // Read the value - auto value = ReadValue(content); + auto value = ReadValue(data, size, offset); list.push_back(std::move(value)); } @@ -130,51 +139,53 @@ namespace Soup::Core return list; } - static int64_t ReadInt64(char*& content) + static int64_t ReadInt64(char* data, size_t size, size_t& offset) { int64_t result = 0; - Read(content, reinterpret_cast(&result), sizeof(int64_t)); + Read(data, size, offset, reinterpret_cast(&result), sizeof(int64_t)); return result; } - static uint32_t ReadUInt32(char*& content) + static uint32_t ReadUInt32(char* data, size_t size, size_t& offset) { uint32_t result = 0; - Read(content, reinterpret_cast(&result), sizeof(uint32_t)); + Read(data, size, offset, reinterpret_cast(&result), sizeof(uint32_t)); return result; } - static double ReadDouble(char*& content) + static double ReadDouble(char* data, size_t size, size_t& offset) { double result = 0; - Read(content, reinterpret_cast(&result), sizeof(double)); + Read(data, size, offset, reinterpret_cast(&result), sizeof(double)); return result; } - static bool ReadBoolean(char*& content) + static bool ReadBoolean(char* data, size_t size, size_t& offset) { uint32_t result = 0; - Read(content, reinterpret_cast(&result), sizeof(uint32_t)); + Read(data, size, offset, reinterpret_cast(&result), sizeof(uint32_t)); return result != 0; } - static std::string ReadString(char*& content) + static std::string ReadString(char* data, size_t size, size_t& offset) { - auto size = ReadUInt32(content); - auto result = std::string(size, '\0'); - Read(content, result.data(), size); + auto stringLength = ReadUInt32(data, size, offset); + auto result = std::string(stringLength, '\0'); + Read(data, size, offset, result.data(), stringLength); return result; } - static void Read(char*& data, char* buffer, size_t count) + static void Read(char* data, size_t size, size_t& offset, char* buffer, size_t count) { - memcpy(buffer, data, count); - data += count; + if (offset + count > size) + throw new std::runtime_error("Tried to read past end of data"); + memcpy(buffer, data + offset, count); + offset += count; } }; } diff --git a/Source/Client/Core/UnitTests/Build/BuildEngineTests.h b/Source/Client/Core/UnitTests/Build/BuildEngineTests.h index 3694131bd..8179f570c 100644 --- a/Source/Client/Core/UnitTests/Build/BuildEngineTests.h +++ b/Source/Client/Core/UnitTests/Build/BuildEngineTests.h @@ -3,7 +3,6 @@ // #pragma once -#include "OperationGraph/OperationGraphWriter.h" namespace Soup::Core::UnitTests { @@ -28,19 +27,19 @@ namespace Soup::Core::UnitTests fileSystem->CreateMockDirectory( Path("C:/WorkingDirectory/MyPackage/"), std::make_shared(std::vector({ - Path("Recipe.sml"), + Path("./Recipe.sml"), }))); fileSystem->CreateMockDirectory( Path("C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/"), std::make_shared(std::vector({ - Path("Recipe.sml"), + Path("./Recipe.sml"), }))); fileSystem->CreateMockDirectory( Path("C:/BuiltIn/Packages/mwasplund/Soup.Wren/0.4.1/"), std::make_shared(std::vector({ - Path("Recipe.sml"), + Path("./Recipe.sml"), }))); // Create the Recipe to build @@ -648,29 +647,45 @@ namespace Soup::Core::UnitTests fileSystem->CreateMockDirectory( Path("C:/WorkingDirectory/MyPackage/"), std::make_shared(std::vector({ - Path("Recipe.sml"), + Path("./Recipe.sml"), }))); fileSystem->CreateMockDirectory( Path("C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/"), std::make_shared(std::vector({}))); + fileSystem->CreateMockDirectory( + Path("C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/.soup/"), + std::make_shared(std::vector({}))); + + fileSystem->CreateMockDirectory( + Path("C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/temp/"), + std::make_shared(std::vector({}))); + fileSystem->CreateMockDirectory( Path("C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/"), std::make_shared(std::vector({ - Path("Recipe.sml"), + Path("./Recipe.sml"), }))); fileSystem->CreateMockDirectory( Path("C:/BuiltIn/Packages/mwasplund/Soup.Wren/0.4.1/"), std::make_shared(std::vector({ - Path("Recipe.sml"), + Path("./Recipe.sml"), }))); fileSystem->CreateMockDirectory( Path("C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/"), std::make_shared(std::vector({}))); + fileSystem->CreateMockDirectory( + Path("C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/.soup/"), + std::make_shared(std::vector({}))); + + fileSystem->CreateMockDirectory( + Path("C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/temp/"), + std::make_shared(std::vector({}))); + // Create the Recipe to build fileSystem->CreateMockFile( Path("C:/WorkingDirectory/MyPackage/Recipe.sml"), @@ -1101,7 +1116,6 @@ namespace Soup::Core::UnitTests "INFO: 2>Checking for existing Evaluate Operation Results", "DIAG: 2>C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/.soup/Evaluate.bor", "INFO: 2>Previous results found", - "INFO: 2>Create Directory: C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/.soup/", "INFO: 2>Check outdated generate input file: C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/.soup/GenerateInput.bvt", "INFO: 2>Checking for existing Generate Operation Results", "DIAG: 2>C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/.soup/Generate.bor", @@ -1111,7 +1125,6 @@ namespace Soup::Core::UnitTests "INFO: 2>Up to date", "INFO: 2>Generate: [Wren]mwasplund|Soup.Cpp", "DIAG: 2>Build evaluation end", - "INFO: 2>Create Directory: C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/temp/", "DIAG: 2>Build evaluation start", "DIAG: 2>Build evaluation end", "INFO: 2>Done", @@ -1123,7 +1136,6 @@ namespace Soup::Core::UnitTests "INFO: 1>Checking for existing Evaluate Operation Results", "DIAG: 1>C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/.soup/Evaluate.bor", "INFO: 1>Previous results found", - "INFO: 1>Create Directory: C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/.soup/", "INFO: 1>Check outdated generate input file: C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/.soup/GenerateInput.bvt", "INFO: 1>Checking for existing Generate Operation Results", "DIAG: 1>C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/.soup/Generate.bor", @@ -1133,7 +1145,6 @@ namespace Soup::Core::UnitTests "INFO: 1>Up to date", "INFO: 1>Generate: [C++]MyPackage", "DIAG: 1>Build evaluation end", - "INFO: 1>Create Directory: C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/temp/", "DIAG: 1>Build evaluation start", "DIAG: 1>Build evaluation end", "INFO: 1>Done", @@ -1171,23 +1182,19 @@ namespace Soup::Core::UnitTests "TryOpenReadBinary: C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/.soup/Evaluate.bog", "TryOpenReadBinary: C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/.soup/Evaluate.bor", "Exists: C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/.soup/", - "CreateDirectory: C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/.soup/", "TryOpenReadBinary: C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/.soup/GenerateInput.bvt", "TryOpenReadBinary: C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/.soup/Generate.bor", "TryGetLastWriteTime: C:/testlocation/Soup.Generate.exe", "Exists: C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/temp/", - "CreateDirectory: C:/Users/Me/.soup/packages/Wren/mwasplund/Soup.Cpp/0.8.2/out/tsWW3RZ_9Jb7Xbk2kTzx3n6uQUM/temp/", "Exists: C:/WorkingDirectory/RootRecipe.sml", "Exists: C:/RootRecipe.sml", "TryGetDirectoryFilesLastWriteTime: C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/", "TryOpenReadBinary: C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/.soup/Evaluate.bog", "TryOpenReadBinary: C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/.soup/Evaluate.bor", "Exists: C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/.soup/", - "CreateDirectory: C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/.soup/", "TryOpenReadBinary: C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/.soup/GenerateInput.bvt", "TryOpenReadBinary: C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/.soup/Generate.bor", "Exists: C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/temp/", - "CreateDirectory: C:/WorkingDirectory/MyPackage/out/J_HqSstV55vlb-x6RWC_hLRFRDU/temp/", }), fileSystem->GetRequests(), "Verify file system requests match expected."); diff --git a/Source/Client/Core/UnitTests/Build/BuildEvaluateEngineTests.h b/Source/Client/Core/UnitTests/Build/BuildEvaluateEngineTests.h index e74185e82..ac59dfac3 100644 --- a/Source/Client/Core/UnitTests/Build/BuildEvaluateEngineTests.h +++ b/Source/Client/Core/UnitTests/Build/BuildEvaluateEngineTests.h @@ -111,8 +111,8 @@ namespace Soup::Core::UnitTests "CreateMonitorProcess: 1 [C:/TestWorkingDirectory/] ./Command.exe Arguments Environment [2] 1 AllowedRead [0] AllowedWrite [0]", [](Monitor::IMonitorCallback& callback) { - callback.TouchFileRead(Path("InputFile2.in"), true, false); - callback.TouchFileWrite(Path("OutputFile2.out"), false); + callback.TouchFileRead(Path("./InputFile2.in"), true, false); + callback.TouchFileWrite(Path("./OutputFile2.out"), false); }); // Setup the input build state @@ -239,8 +239,8 @@ namespace Soup::Core::UnitTests [](Monitor::IMonitorCallback& callback) { // Read and write the same file - callback.TouchFileRead(Path("File.txt"), true, false); - callback.TouchFileWrite(Path("File.txt"), false); + callback.TouchFileRead(Path("./File.txt"), true, false); + callback.TouchFileWrite(Path("./File.txt"), false); }); // Setup the input build state @@ -372,8 +372,8 @@ namespace Soup::Core::UnitTests [](Monitor::IMonitorCallback& callback) { // Read and write the same file - callback.TouchFileRead(Path("File.txt"), true, false); - callback.TouchFileWrite(Path("File.txt"), false); + callback.TouchFileRead(Path("./File.txt"), true, false); + callback.TouchFileWrite(Path("./File.txt"), false); }); // Setup the input build state @@ -1196,7 +1196,7 @@ namespace Soup::Core::UnitTests "CreateMonitorProcess: 1 [C:/TestWorkingDirectory/] ./Command1.exe Arguments Environment [2] 1 AllowedRead [0] AllowedWrite [0]", [](Monitor::IMonitorCallback& callback) { - callback.TouchFileWrite(Path("OutputFile.out"), false); + callback.TouchFileWrite(Path("./OutputFile.out"), false); }); // Setup the input build state @@ -1332,7 +1332,7 @@ namespace Soup::Core::UnitTests "CreateMonitorProcess: 1 [C:/TestWorkingDirectory/] ./Command1.exe Arguments Environment [2] 1 AllowedRead [0] AllowedWrite [0]", [](Monitor::IMonitorCallback& callback) { - callback.TouchFileWrite(Path("File.txt"), false); + callback.TouchFileWrite(Path("./File.txt"), false); }); // Setup the input build state @@ -1468,7 +1468,7 @@ namespace Soup::Core::UnitTests "CreateMonitorProcess: 1 [C:/TestWorkingDirectory/] ./Command1.exe Arguments Environment [2] 1 AllowedRead [0] AllowedWrite [0]", [](Monitor::IMonitorCallback& callback) { - callback.TouchFileRead(Path("File.txt"), true, false); + callback.TouchFileRead(Path("./File.txt"), true, false); }); // Setup the input build state diff --git a/Source/Client/Core/UnitTests/Build/BuildLoadEngineTests.h b/Source/Client/Core/UnitTests/Build/BuildLoadEngineTests.h index b4570fa03..d99e90d1f 100644 --- a/Source/Client/Core/UnitTests/Build/BuildLoadEngineTests.h +++ b/Source/Client/Core/UnitTests/Build/BuildLoadEngineTests.h @@ -277,7 +277,7 @@ namespace Soup::Core::UnitTests PackageName(std::nullopt, "MyPackage"), false, Path("C:/WorkingDirectory/MyPackage/"), - Path(""), + Path(), &recipeCache.GetRecipe(Path("C:/WorkingDirectory/MyPackage/Recipe.sml")), PackageChildrenMap({ { @@ -1433,7 +1433,7 @@ namespace Soup::Core::UnitTests PackageName("User1", "TestTool"), false, Path("C:/Users/Me/.soup/packages/Cpp/User1/TestTool/3.3.3/"), - Path(""), + Path(), &recipeCache.GetRecipe(Path("C:/Users/Me/.soup/packages/Cpp/User1/TestTool/3.3.3/Recipe.sml")), PackageChildrenMap({ { @@ -2157,7 +2157,7 @@ namespace Soup::Core::UnitTests PackageName("User1", "TestBuild2"), false, Path("C:/Users/Me/.soup/packages/Wren/User1/TestBuild2/3.3.3/"), - Path(""), + Path(), &recipeCache.GetRecipe(Path("C:/Users/Me/.soup/packages/Wren/User1/TestBuild2/3.3.3/Recipe.sml")), PackageChildrenMap({ { @@ -2504,7 +2504,7 @@ namespace Soup::Core::UnitTests PackageName("User1", "TestBuild2"), false, Path("C:/Users/Me/.soup/packages/Wren/User1/TestBuild2/4.4.4/"), - Path(""), + Path(), &recipeCache.GetRecipe(Path("C:/Users/Me/.soup/packages/Wren/User1/TestBuild2/4.4.4/Recipe.sml")), PackageChildrenMap({ { @@ -2859,7 +2859,7 @@ namespace Soup::Core::UnitTests PackageName("User1", "TestTool"), false, Path("C:/Users/Me/.soup/packages/Cpp/User1/TestTool/3.3.3/"), - Path(""), + Path(), &recipeCache.GetRecipe(Path("C:/Users/Me/.soup/packages/Cpp/User1/TestTool/3.3.3/Recipe.sml")), PackageChildrenMap({ { diff --git a/Source/Client/Core/UnitTests/Build/FileSystemStateTests.h b/Source/Client/Core/UnitTests/Build/FileSystemStateTests.h index 3111f3f5b..5d8861b0f 100644 --- a/Source/Client/Core/UnitTests/Build/FileSystemStateTests.h +++ b/Source/Client/Core/UnitTests/Build/FileSystemStateTests.h @@ -176,7 +176,7 @@ namespace Soup::Core::UnitTests Path("C:/Root/DoStuff.exe"), }})); - FileId fileId = uut.ToFileId(Path("DoStuff.exe"), Path("C:/Root/")); + FileId fileId = uut.ToFileId(Path("./DoStuff.exe"), Path("C:/Root/")); Assert::AreEqual(8, fileId, "Verify file id matches expected."); @@ -202,7 +202,7 @@ namespace Soup::Core::UnitTests Path("C:/Root/DoStuff.exe"), }})); - FileId fileId = uut.ToFileId(Path("DoStuff2.exe"), Path("C:/Root/")); + FileId fileId = uut.ToFileId(Path("./DoStuff2.exe"), Path("C:/Root/")); Assert::AreEqual(11, fileId, "Verify file id matches expected."); diff --git a/Source/Client/Core/UnitTests/Build/RecipeBuildLocationManagerTests.h b/Source/Client/Core/UnitTests/Build/RecipeBuildLocationManagerTests.h index eb54c544e..9ddfe9cd4 100644 --- a/Source/Client/Core/UnitTests/Build/RecipeBuildLocationManagerTests.h +++ b/Source/Client/Core/UnitTests/Build/RecipeBuildLocationManagerTests.h @@ -67,7 +67,7 @@ namespace Soup::Core::UnitTests fileSystem->CreateMockFile( Path("C:/RootRecipe.sml"), std::make_shared(std::stringstream(R"( - OutputRoot: 'BuildOut/' + OutputRoot: './BuildOut/' )"))); auto packageName = Core::PackageName(std::nullopt, "MyPackage"); @@ -95,7 +95,10 @@ namespace Soup::Core::UnitTests globalParameters, recipeCache); - Assert::AreEqual(Path("C:/BuildOut/Cpp/Local/MyPackage/1.2.3/J_HqSstV55vlb-x6RWC_hLRFRDU/"), targetDirectory, "Verify target directory matches expected."); + Assert::AreEqual( + Path("C:/BuildOut/Cpp/Local/MyPackage/1.2.3/J_HqSstV55vlb-x6RWC_hLRFRDU/"), + targetDirectory, + "Verify target directory matches expected."); // Verify expected logs Assert::AreEqual( diff --git a/Source/Client/Core/UnitTests/LocalUserConfig/LocalUserConfigExtensionsTests.h b/Source/Client/Core/UnitTests/LocalUserConfig/LocalUserConfigExtensionsTests.h index d8128b50d..000dc179f 100644 --- a/Source/Client/Core/UnitTests/LocalUserConfig/LocalUserConfigExtensionsTests.h +++ b/Source/Client/Core/UnitTests/LocalUserConfig/LocalUserConfigExtensionsTests.h @@ -20,7 +20,7 @@ namespace Soup::Core::UnitTests auto fileSystem = std::make_shared(); auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); - auto directory = Path("TestFiles/NoFile/LocalUserConfig.sml"); + auto directory = Path("./TestFiles/NoFile/LocalUserConfig.sml"); LocalUserConfig actual; auto result = LocalUserConfigExtensions::TryLoadLocalUserConfigFromFile(directory, actual); @@ -55,10 +55,10 @@ namespace Soup::Core::UnitTests auto fileSystem = std::make_shared(); auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); fileSystem->CreateMockFile( - Path("TestFiles/GarbageLocalUserConfig/LocalUserConfig.sml"), + Path("./TestFiles/GarbageLocalUserConfig/LocalUserConfig.sml"), std::make_shared(std::stringstream("garbage"))); - auto directory = Path("TestFiles/GarbageLocalUserConfig/LocalUserConfig.sml"); + auto directory = Path("./TestFiles/GarbageLocalUserConfig/LocalUserConfig.sml"); LocalUserConfig actual; auto result = LocalUserConfigExtensions::TryLoadLocalUserConfigFromFile(directory, actual); @@ -94,11 +94,11 @@ namespace Soup::Core::UnitTests auto fileSystem = std::make_shared(); auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); fileSystem->CreateMockFile( - Path("TestFiles/SimpleLocalUserConfig/LocalUserConfig.sml"), + Path("./TestFiles/SimpleLocalUserConfig/LocalUserConfig.sml"), std::make_shared(std::stringstream(R"( )"))); - auto directory = Path("TestFiles/SimpleLocalUserConfig/LocalUserConfig.sml"); + auto directory = Path("./TestFiles/SimpleLocalUserConfig/LocalUserConfig.sml"); LocalUserConfig actual; auto result = LocalUserConfigExtensions::TryLoadLocalUserConfigFromFile(directory, actual); diff --git a/Source/Client/Core/UnitTests/OperationGraph/OperationGraphManagerTests.h b/Source/Client/Core/UnitTests/OperationGraph/OperationGraphManagerTests.h index 5e98aadc6..4552da58d 100644 --- a/Source/Client/Core/UnitTests/OperationGraph/OperationGraphManagerTests.h +++ b/Source/Client/Core/UnitTests/OperationGraph/OperationGraphManagerTests.h @@ -20,7 +20,7 @@ namespace Soup::Core::UnitTests auto fileSystem = std::make_shared(); auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); - auto filePath = Path("TestFiles/NoFile/.soup/OperationGraph.bog"); + auto filePath = Path("./TestFiles/NoFile/.soup/OperationGraph.bog"); auto fileSystemState = std::make_shared(); auto actual = OperationGraph(); auto result = OperationGraphManager::TryLoadState(filePath, actual, *fileSystemState); @@ -55,10 +55,10 @@ namespace Soup::Core::UnitTests auto fileSystem = std::make_shared(); auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); fileSystem->CreateMockFile( - Path("TestFiles/GarbageOperationGraph/.soup/OperationGraph.bog"), + Path("./TestFiles/GarbageOperationGraph/.soup/OperationGraph.bog"), std::make_shared(std::stringstream("garbage"))); - auto filePath = Path("TestFiles/GarbageOperationGraph/.soup/OperationGraph.bog"); + auto filePath = Path("./TestFiles/GarbageOperationGraph/.soup/OperationGraph.bog"); auto fileSystemState = std::make_shared(); auto actual = OperationGraph(); auto result = OperationGraphManager::TryLoadState(filePath, actual, *fileSystemState); @@ -113,16 +113,12 @@ namespace Soup::Core::UnitTests 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x9b, 0x4f, 0xc9, 0xb4, 0xa6, 0xf1, 0xfc, 0xff, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, }); fileSystem->CreateMockFile( - Path("TestFiles/SimpleOperationGraph/.soup/OperationGraph.bog"), + Path("./TestFiles/SimpleOperationGraph/.soup/OperationGraph.bog"), std::make_shared(std::stringstream(std::string((char*)binaryFileContent.data(), binaryFileContent.size())))); - auto filePath = Path("TestFiles/SimpleOperationGraph/.soup/OperationGraph.bog"); + auto filePath = Path("./TestFiles/SimpleOperationGraph/.soup/OperationGraph.bog"); auto fileSystemState = std::make_shared(); auto actual = OperationGraph(); auto result = OperationGraphManager::TryLoadState(filePath, actual, *fileSystemState); @@ -145,7 +141,7 @@ namespace Soup::Core::UnitTests "TestOperation", CommandInfo( Path("C:/Root/"), - Path("DoStuff.exe"), + Path("./DoStuff.exe"), { "arg1", "arg2" }), { }, { }, @@ -181,7 +177,7 @@ namespace Soup::Core::UnitTests auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); auto fileSystemState = std::make_shared(); - auto filePath = Path("TestFiles/.soup/OperationGraph.bog"); + auto filePath = Path("./TestFiles/.soup/OperationGraph.bog"); auto operationGraph = OperationGraph( std::vector({ 5, @@ -192,7 +188,7 @@ namespace Soup::Core::UnitTests "TestOperation", CommandInfo( Path("C:/Root/"), - Path("DoStuff.exe"), + Path("./DoStuff.exe"), { "arg1", "arg2" }), { }, { }, diff --git a/Source/Client/Core/UnitTests/OperationGraph/OperationGraphReaderTests.h b/Source/Client/Core/UnitTests/OperationGraph/OperationGraphReaderTests.h index 802b7c8f9..fda84c74c 100644 --- a/Source/Client/Core/UnitTests/OperationGraph/OperationGraphReaderTests.h +++ b/Source/Client/Core/UnitTests/OperationGraph/OperationGraphReaderTests.h @@ -139,7 +139,7 @@ namespace Soup::Core::UnitTests 0x05, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'O', 'p', 'e', 'r', 'a', 't', 'i', 'o', 'n', 0x08, 0x00, 0x00, 0x00, 'C', ':', '/', 'R', 'o', 'o', 't', '/', - 0x0B, 0x00, 0x00, 0x00, 'D', 'o', 'S', 't', 'u', 'f', 'f', '.', 'e', 'x', 'e', + 0x0D, 0x00, 0x00, 0x00, '.', '/', 'D', 'o', 'S', 't', 'u', 'f', 'f', '.', 'e', 'x', 'e', 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 'a', 'r', 'g', '1', 0x04, 0x00, 0x00, 0x00, 'a', 'r', 'g', '2', @@ -167,7 +167,7 @@ namespace Soup::Core::UnitTests "TestOperation", CommandInfo( Path("C:/Root/"), - Path("DoStuff.exe"), + Path("./DoStuff.exe"), { "arg1", "arg2" }), { }, { }, @@ -202,7 +202,7 @@ namespace Soup::Core::UnitTests 0x05, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'O', 'p', 'e', 'r', 'a', 't', 'i', 'o', 'n', 0x08, 0x00, 0x00, 0x00, 'C', ':', '/', 'R', 'o', 'o', 't', '/', - 0x0B, 0x00, 0x00, 0x00, 'D', 'o', 'S', 't', 'u', 'f', 'f', '.', 'e', 'x', 'e', + 0x0D, 0x00, 0x00, 0x00, '.', '/', 'D', 'o', 'S', 't', 'u', 'f', 'f', '.', 'e', 'x', 'e', 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 'a', 'r', 'g', '1', 0x04, 0x00, 0x00, 0x00, 'a', 'r', 'g', '2', @@ -230,7 +230,7 @@ namespace Soup::Core::UnitTests "TestOperation", CommandInfo( Path("C:/Root/"), - Path("DoStuff.exe"), + Path("./DoStuff.exe"), { "arg1", "arg2" }), { 11, }, { 12, }, @@ -269,7 +269,7 @@ namespace Soup::Core::UnitTests 0x05, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'O', 'p', 'e', 'r', 'a', 't', 'i', 'o', 'n', '1', 0x08, 0x00, 0x00, 0x00, 'C', ':', '/', 'R', 'o', 'o', 't', '/', - 0x0C, 0x00, 0x00, 0x00, 'D', 'o', 'S', 't', 'u', 'f', 'f', '1', '.', 'e', 'x', 'e', + 0x0E, 0x00, 0x00, 0x00, '.', '/', 'D', 'o', 'S', 't', 'u', 'f', 'f', '1', '.', 'e', 'x', 'e', 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 'a', 'r', 'g', '1', 0x04, 0x00, 0x00, 0x00, 'a', 'r', 'g', '2', @@ -282,7 +282,7 @@ namespace Soup::Core::UnitTests 0x06, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'O', 'p', 'e', 'r', 'a', 't', 'i', 'o', 'n', '2', 0x08, 0x00, 0x00, 0x00, 'C', ':', '/', 'R', 'o', 'o', 't', '/', - 0x0C, 0x00, 0x00, 0x00, 'D', 'o', 'S', 't', 'u', 'f', 'f', '2', '.', 'e', 'x', 'e', + 0x0E, 0x00, 0x00, 0x00, '.', '/', 'D', 'o', 'S', 't', 'u', 'f', 'f', '2', '.', 'e', 'x', 'e', 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 'a', 'r', 'g', '3', 0x04, 0x00, 0x00, 0x00, 'a', 'r', 'g', '4', @@ -306,7 +306,7 @@ namespace Soup::Core::UnitTests "TestOperation1", CommandInfo( Path("C:/Root/"), - Path("DoStuff1.exe"), + Path("./DoStuff1.exe"), { "arg1", "arg2" }), { 11, }, { 12, }, @@ -322,7 +322,7 @@ namespace Soup::Core::UnitTests "TestOperation2", CommandInfo( Path("C:/Root/"), - Path("DoStuff2.exe"), + Path("./DoStuff2.exe"), { "arg3", "arg4" }), { 15, }, { 16, }, diff --git a/Source/Client/Core/UnitTests/OperationGraph/OperationGraphTests.h b/Source/Client/Core/UnitTests/OperationGraph/OperationGraphTests.h index 9c2939dde..0dcddba73 100644 --- a/Source/Client/Core/UnitTests/OperationGraph/OperationGraphTests.h +++ b/Source/Client/Core/UnitTests/OperationGraph/OperationGraphTests.h @@ -37,7 +37,7 @@ namespace Soup::Core::UnitTests "TestOperation", CommandInfo( Path("C:/Root/"), - Path("DoStuff.exe"), + Path("./DoStuff.exe"), { "arg1", "arg2" }), { 1, }, { 2, }, @@ -62,7 +62,7 @@ namespace Soup::Core::UnitTests "TestOperation", CommandInfo( Path("C:/Root/"), - Path("DoStuff.exe"), + Path("./DoStuff.exe"), { "arg1", "arg2" }), { 1, }, { 2, }, @@ -87,7 +87,7 @@ namespace Soup::Core::UnitTests "TestOperation", CommandInfo( Path("C:/Root/"), - Path("DoStuff.exe"), + Path("./DoStuff.exe"), { "arg1", "arg2" }), { 1, }, { 2 }, @@ -142,7 +142,7 @@ namespace Soup::Core::UnitTests "TestOperation", CommandInfo( Path("C:/Root/"), - Path("DoStuff.exe"), + Path("./DoStuff.exe"), { "arg1", "arg2" }), { 1, }, { 2, }, @@ -160,7 +160,7 @@ namespace Soup::Core::UnitTests "TestOperation", CommandInfo( Path("C:/Root/"), - Path("DoStuff.exe"), + Path("./DoStuff.exe"), { "arg1", "arg2" }), { 1, }, { 2, }, @@ -185,7 +185,7 @@ namespace Soup::Core::UnitTests "TestOperation", CommandInfo( Path("C:/Root/"), - Path("DoStuff.exe"), + Path("./DoStuff.exe"), { "arg1", "arg2" }), { 1, }, { 2, }, @@ -203,7 +203,7 @@ namespace Soup::Core::UnitTests "TestOperation", CommandInfo( Path("C:/Root/"), - Path("DoStuff.exe"), + Path("./DoStuff.exe"), { "arg1", "arg2" }), { 1, }, { 2, }, diff --git a/Source/Client/Core/UnitTests/OperationGraph/OperationGraphWriterTests.h b/Source/Client/Core/UnitTests/OperationGraph/OperationGraphWriterTests.h index b10615e02..ee0a6b261 100644 --- a/Source/Client/Core/UnitTests/OperationGraph/OperationGraphWriterTests.h +++ b/Source/Client/Core/UnitTests/OperationGraph/OperationGraphWriterTests.h @@ -45,7 +45,7 @@ namespace Soup::Core::UnitTests "TestOperation", CommandInfo( Path("C:/Root/"), - Path("DoStuff.exe"), + Path("./DoStuff.exe"), { "arg1", "arg2" }), { }, { }, @@ -99,7 +99,7 @@ namespace Soup::Core::UnitTests "TestOperation", CommandInfo( Path("C:/Root/"), - Path("DoStuff.exe"), + Path("./DoStuff.exe"), { "arg1", "arg2" }), { 1, }, { 2, }, @@ -152,7 +152,7 @@ namespace Soup::Core::UnitTests "TestOperation1", CommandInfo( Path("C:/Root/"), - Path("DoStuff1.exe"), + Path("./DoStuff1.exe"), { "arg1", "arg2" }), { 1, }, { 2, }, @@ -165,7 +165,7 @@ namespace Soup::Core::UnitTests "TestOperation2", CommandInfo( Path("C:/Root/"), - Path("DoStuff2.exe"), + Path("./DoStuff2.exe"), { "arg3", "arg4" }), { 5, }, { 6, }, diff --git a/Source/Client/Core/UnitTests/OperationGraph/OperationResultsManagerTests.h b/Source/Client/Core/UnitTests/OperationGraph/OperationResultsManagerTests.h index 350dbc80a..843ff651b 100644 --- a/Source/Client/Core/UnitTests/OperationGraph/OperationResultsManagerTests.h +++ b/Source/Client/Core/UnitTests/OperationGraph/OperationResultsManagerTests.h @@ -20,7 +20,7 @@ namespace Soup::Core::UnitTests auto fileSystem = std::make_shared(); auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); - auto filePath = Path("TestFiles/NoFile/.soup/OperationResults.bor"); + auto filePath = Path("./TestFiles/NoFile/.soup/OperationResults.bor"); auto fileSystemState = std::make_shared(); auto actual = OperationResults(); auto result = OperationResultsManager::TryLoadState(filePath, actual, *fileSystemState); @@ -55,10 +55,10 @@ namespace Soup::Core::UnitTests auto fileSystem = std::make_shared(); auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); fileSystem->CreateMockFile( - Path("TestFiles/GarbageOperationResults/.soup/OperationResults.bor"), + Path("./TestFiles/GarbageOperationResults/.soup/OperationResults.bor"), std::make_shared(std::stringstream("garbage"))); - auto filePath = Path("TestFiles/GarbageOperationResults/.soup/OperationResults.bor"); + auto filePath = Path("./TestFiles/GarbageOperationResults/.soup/OperationResults.bor"); auto fileSystemState = std::make_shared(); auto actual = OperationResults(); auto result = OperationResultsManager::TryLoadState(filePath, actual, *fileSystemState); @@ -105,10 +105,10 @@ namespace Soup::Core::UnitTests 0x00, 0x00, 0x00, 0x00, }); fileSystem->CreateMockFile( - Path("TestFiles/SimpleOperationResults/.soup/OperationResults.bor"), + Path("./TestFiles/SimpleOperationResults/.soup/OperationResults.bor"), std::make_shared(std::stringstream(std::string((char*)binaryFileContent.data(), binaryFileContent.size())))); - auto filePath = Path("TestFiles/SimpleOperationResults/.soup/OperationResults.bor"); + auto filePath = Path("./TestFiles/SimpleOperationResults/.soup/OperationResults.bor"); auto fileSystemState = std::make_shared(); auto actual = OperationResults(); auto result = OperationResultsManager::TryLoadState(filePath, actual, *fileSystemState); @@ -154,7 +154,7 @@ namespace Soup::Core::UnitTests auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); auto fileSystemState = std::make_shared(); - auto filePath = Path("TestFiles/.soup/OperationResults.bor"); + auto filePath = Path("./TestFiles/.soup/OperationResults.bor"); auto operationResults = OperationResults({ { 5, diff --git a/Source/Client/Core/UnitTests/Recipe/RecipeExtensionsTests.h b/Source/Client/Core/UnitTests/Recipe/RecipeExtensionsTests.h index 91329517d..83b311afb 100644 --- a/Source/Client/Core/UnitTests/Recipe/RecipeExtensionsTests.h +++ b/Source/Client/Core/UnitTests/Recipe/RecipeExtensionsTests.h @@ -20,7 +20,7 @@ namespace Soup::Core::UnitTests auto fileSystem = std::make_shared(); auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); - auto directory = Path("TestFiles/NoFile/Recipe.sml"); + auto directory = Path("./TestFiles/NoFile/Recipe.sml"); Recipe actual; auto result = RecipeExtensions::TryLoadRecipeFromFile(directory, actual); @@ -55,10 +55,10 @@ namespace Soup::Core::UnitTests auto fileSystem = std::make_shared(); auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); fileSystem->CreateMockFile( - Path("TestFiles/GarbageRecipe/Recipe.sml"), + Path("./TestFiles/GarbageRecipe/Recipe.sml"), std::make_shared(std::stringstream("garbage"))); - auto directory = Path("TestFiles/GarbageRecipe/Recipe.sml"); + auto directory = Path("./TestFiles/GarbageRecipe/Recipe.sml"); Recipe actual; auto result = RecipeExtensions::TryLoadRecipeFromFile(directory, actual); @@ -94,13 +94,13 @@ namespace Soup::Core::UnitTests auto fileSystem = std::make_shared(); auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); fileSystem->CreateMockFile( - Path("TestFiles/SimpleRecipe/Recipe.sml"), + Path("./TestFiles/SimpleRecipe/Recipe.sml"), std::make_shared(std::stringstream(R"( Name: 'MyPackage' Language: 'C++|1' )"))); - auto directory = Path("TestFiles/SimpleRecipe/Recipe.sml"); + auto directory = Path("./TestFiles/SimpleRecipe/Recipe.sml"); Recipe actual; auto result = RecipeExtensions::TryLoadRecipeFromFile(directory, actual); @@ -142,7 +142,7 @@ namespace Soup::Core::UnitTests auto fileSystem = std::make_shared(); auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); - auto directory = Path("TestFiles/SimpleRecipe/Recipe.sml"); + auto directory = Path("./TestFiles/SimpleRecipe/Recipe.sml"); auto recipe = Recipe(RecipeTable( { { "Name", "MyPackage" }, @@ -170,7 +170,7 @@ namespace Soup::Core::UnitTests R"(Name: 'MyPackage' Language: 'C++|1' )"; - auto mockBuildFile = fileSystem->GetMockFile(Path("TestFiles/SimpleRecipe/Recipe.sml")); + auto mockBuildFile = fileSystem->GetMockFile(Path("./TestFiles/SimpleRecipe/Recipe.sml")); Assert::AreEqual(expectedBuildFile, mockBuildFile->Content.str(), "Verify file contents."); } }; diff --git a/Source/Client/Core/UnitTests/Recipe/RecipeSMLTests.h b/Source/Client/Core/UnitTests/Recipe/RecipeSMLTests.h index 062e2e1b2..6b9ecb556 100644 --- a/Source/Client/Core/UnitTests/Recipe/RecipeSMLTests.h +++ b/Source/Client/Core/UnitTests/Recipe/RecipeSMLTests.h @@ -12,7 +12,7 @@ namespace Soup::Core::UnitTests // [[Fact]] void Deserialize_GarbageThrows() { - auto recipeFile = Path("Recipe.sml"); + auto recipeFile = Path("./Recipe.sml"); auto recipe = std::stringstream("garbage"); auto exception = Assert::Throws([&recipeFile, &recipe]() { auto actual = RecipeSML::Deserialize(recipeFile, recipe); @@ -22,7 +22,7 @@ namespace Soup::Core::UnitTests // [[Fact]] void Deserialize_Simple() { - auto recipeFile = Path("Recipe.sml"); + auto recipeFile = Path("./Recipe.sml"); auto recipe = std::stringstream( R"( Name: 'MyPackage' @@ -42,7 +42,7 @@ namespace Soup::Core::UnitTests // [[Fact]] void Deserialize_Comments() { - auto recipeFile = Path("Recipe.sml"); + auto recipeFile = Path("./Recipe.sml"); auto recipe = std::stringstream( R"( # This is an awesome project @@ -63,7 +63,7 @@ namespace Soup::Core::UnitTests // [[Fact]] void Deserialize_AllProperties() { - auto recipeFile = Path("Recipe.sml"); + auto recipeFile = Path("./Recipe.sml"); auto recipe = std::stringstream( R"( Name: 'MyPackage' @@ -99,7 +99,7 @@ namespace Soup::Core::UnitTests // [[Fact]] void Serialize_Simple() { - auto recipeFile = Path("Recipe.sml"); + auto recipeFile = Path("./Recipe.sml"); auto recipe = Recipe(RecipeTable( { { "Name", "MyPackage" }, @@ -120,7 +120,7 @@ Language: 'C++|1' // [[Fact]] void Serialize_AllProperties() { - auto recipeFile = Path("Recipe.sml"); + auto recipeFile = Path("./Recipe.sml"); auto recipe = Recipe(RecipeTable( { { "Name", "MyPackage" }, diff --git a/Source/Client/Core/UnitTests/ValueTable/ValueTableManagerTests.h b/Source/Client/Core/UnitTests/ValueTable/ValueTableManagerTests.h index 0ba614e8a..0f966f75d 100644 --- a/Source/Client/Core/UnitTests/ValueTable/ValueTableManagerTests.h +++ b/Source/Client/Core/UnitTests/ValueTable/ValueTableManagerTests.h @@ -20,7 +20,7 @@ namespace Soup::Core::UnitTests auto fileSystem = std::make_shared(); auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); - auto directory = Path("TestFiles/NoFile/.soup/ValueTable.bin"); + auto directory = Path("./TestFiles/NoFile/.soup/ValueTable.bin"); auto actual = ValueTable(); auto result = ValueTableManager::TryLoadState(directory, actual); @@ -54,10 +54,10 @@ namespace Soup::Core::UnitTests auto fileSystem = std::make_shared(); auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); fileSystem->CreateMockFile( - Path("TestFiles/GarbageValueTable/.soup/ValueTable.bin"), + Path("./TestFiles/GarbageValueTable/.soup/ValueTable.bin"), std::make_shared(std::stringstream("garbage"))); - auto directory = Path("TestFiles/GarbageValueTable/.soup/ValueTable.bin"); + auto directory = Path("./TestFiles/GarbageValueTable/.soup/ValueTable.bin"); auto actual = ValueTable(); auto result = ValueTableManager::TryLoadState(directory, actual); @@ -99,10 +99,10 @@ namespace Soup::Core::UnitTests 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }); fileSystem->CreateMockFile( - Path("TestFiles/SimpleValueTable/.soup/ValueTable.bin"), + Path("./TestFiles/SimpleValueTable/.soup/ValueTable.bin"), std::make_shared(std::stringstream(std::string(binaryFileContent.data(), binaryFileContent.size())))); - auto directory = Path("TestFiles/SimpleValueTable/.soup/ValueTable.bin"); + auto directory = Path("./TestFiles/SimpleValueTable/.soup/ValueTable.bin"); auto actual = ValueTable(); auto result = ValueTableManager::TryLoadState(directory, actual); @@ -143,7 +143,7 @@ namespace Soup::Core::UnitTests auto fileSystem = std::make_shared(); auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); - auto valueTableFile = Path("TestFiles/.soup/ValueTable.bin"); + auto valueTableFile = Path("./TestFiles/.soup/ValueTable.bin"); auto valueTable = ValueTable( { { "TestValue", Value(false) }, diff --git a/Source/Client/Core/UnitTests/ValueTable/ValueTableReaderTests.h b/Source/Client/Core/UnitTests/ValueTable/ValueTableReaderTests.h index 65370f619..3b6faf458 100644 --- a/Source/Client/Core/UnitTests/ValueTable/ValueTableReaderTests.h +++ b/Source/Client/Core/UnitTests/ValueTable/ValueTableReaderTests.h @@ -253,5 +253,76 @@ namespace Soup::Core::UnitTests actual, "Verify value table matches expected."); } + + // [[Fact]] + void Deserialize_Complex() + { + auto binaryFileContent = std::vector( + { + 'B', 'V', 'T', '\0', 0x02, 0x00, 0x00, 0x00, + 'T', 'B', 'L', '\0', 0x08, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'B', 'o', 'o', 'l', 'e', 'a', 'n', 0x6, 0x0, 0x0, 0x0, 0x1, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'D', 'e', 'e', 'p', 'T', 'a', 'b', 'l', 'e', 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 'V', 'a', 'l', 'u', 'e', '1', 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 'V', 'a', 'l', 'u', 'e', '2', 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 'V', 'a', 'l', 'u', 'e', '3', 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 'V', 'a', 'l', 'u', 'e', '4', 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0d, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'E', 'm', 'p', 't', 'y', 'L', 'i', 's', 't', 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0e, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'E', 'm', 'p', 't', 'y', 'T', 'a', 'b', 'l', 'e', 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x09, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'F', 'l', 'o', 'a', 't', 0x5, 0x0, 0x0, 0x0, 0xae, 0x47, 0xe1, 0x7a, 0x14, 0xae, 0xf3, 0x3f, + 0x0b, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'I', 'n', 't', 'e', 'g', 'e', 'r', 0x4, 0x0, 0x0, 0x0, 0x85, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0f, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'I', 'n', 't', 'e', 'g', 'e', 'r', 'L', 'i', 's', 't', 0x2, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0a, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'S', 't', 'r', 'i', 'n', 'g', 0x3, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 'V', 'a', 'l', 'u', 'e', + }); + auto content = std::stringstream( + std::string(reinterpret_cast(binaryFileContent.data()), binaryFileContent.size())); + + auto actual = ValueTableReader::Deserialize(content); + + Assert::AreEqual( + ValueTable( + { + { "TestEmptyTable", Value(ValueTable()) }, + { "TestEmptyList", Value(ValueList()) }, + { "TestString", Value(std::string("Value")) }, + { "TestInteger", Value(static_cast(-123)) }, + { "TestFloat", Value(1.23) }, + { "TestBoolean", Value(true) }, + { + "TestIntegerList", + Value(ValueList({ + Value(static_cast(1)), + Value(static_cast(2)), + Value(static_cast(3)), + Value(static_cast(4)), + Value(static_cast(5)), + Value(static_cast(6)), + Value(static_cast(7)), + Value(static_cast(8)), + Value(static_cast(9)), + Value(static_cast(10)), + })) + }, + { + "TestDeepTable", + Value(ValueTable({ + { + "Value1", + Value(ValueTable({ + { + "Value2", + Value(ValueTable({ + { + "Value3", + Value(ValueTable({ + { "Value4", Value(ValueTable()) }, + })) + }, + })) + }, + })) + }, + })) + }, + }), + actual, + "Verify value table matches expected."); + } }; } diff --git a/Source/Client/Core/UnitTests/ValueTable/ValueTableWriterTests.h b/Source/Client/Core/UnitTests/ValueTable/ValueTableWriterTests.h index 694f3a016..f16fb2aae 100644 --- a/Source/Client/Core/UnitTests/ValueTable/ValueTableWriterTests.h +++ b/Source/Client/Core/UnitTests/ValueTable/ValueTableWriterTests.h @@ -172,5 +172,77 @@ namespace Soup::Core::UnitTests content.str(), "Verify file content match expected."); } + + // [[Fact]] + void Serialize_Complex() + { + auto valueTable = ValueTable( + { + { "TestEmptyTable", Value(ValueTable()) }, + { "TestEmptyList", Value(ValueList()) }, + { "TestString", Value(std::string("Value")) }, + { "TestInteger", Value(static_cast(-123)) }, + { "TestFloat", Value(1.23) }, + { "TestBoolean", Value(true) }, + { + "TestIntegerList", + Value(ValueList({ + Value(static_cast(1)), + Value(static_cast(2)), + Value(static_cast(3)), + Value(static_cast(4)), + Value(static_cast(5)), + Value(static_cast(6)), + Value(static_cast(7)), + Value(static_cast(8)), + Value(static_cast(9)), + Value(static_cast(10)), + })) + }, + { + "TestDeepTable", + Value(ValueTable({ + { + "Value1", + Value(ValueTable({ + { + "Value2", + Value(ValueTable({ + { + "Value3", + Value(ValueTable({ + { "Value4", Value(ValueTable()) }, + })) + }, + })) + }, + })) + }, + })) + }, + }); + auto content = std::stringstream(); + + ValueTableWriter::Serialize(valueTable, content); + + auto binaryFileContent = std::vector( + { + 'B', 'V', 'T', '\0', 0x02, 0x00, 0x00, 0x00, + 'T', 'B', 'L', '\0', 0x08, 0x00, 0x00, 0x00, + 0x0b, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'B', 'o', 'o', 'l', 'e', 'a', 'n', 0x6, 0x0, 0x0, 0x0, 0x1, 0x00, 0x00, 0x00, + 0x0d, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'D', 'e', 'e', 'p', 'T', 'a', 'b', 'l', 'e', 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 'V', 'a', 'l', 'u', 'e', '1', 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 'V', 'a', 'l', 'u', 'e', '2', 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 'V', 'a', 'l', 'u', 'e', '3', 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 'V', 'a', 'l', 'u', 'e', '4', 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0d, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'E', 'm', 'p', 't', 'y', 'L', 'i', 's', 't', 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0e, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'E', 'm', 'p', 't', 'y', 'T', 'a', 'b', 'l', 'e', 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x09, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'F', 'l', 'o', 'a', 't', 0x5, 0x0, 0x0, 0x0, 0xae, 0x47, 0xe1, 0x7a, 0x14, 0xae, 0xf3, 0x3f, + 0x0b, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'I', 'n', 't', 'e', 'g', 'e', 'r', 0x4, 0x0, 0x0, 0x0, 0x85, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x0f, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'I', 'n', 't', 'e', 'g', 'e', 'r', 'L', 'i', 's', 't', 0x2, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0a, 0x00, 0x00, 0x00, 'T', 'e', 's', 't', 'S', 't', 'r', 'i', 'n', 'g', 0x3, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 'V', 'a', 'l', 'u', 'e', + }); + + Assert::AreEqual( + std::string(reinterpret_cast(binaryFileContent.data()), binaryFileContent.size()), + content.str(), + "Verify file content match expected."); + } }; } diff --git a/Source/Client/Core/UnitTests/gen/ValueTable/ValueTableReaderTests.gen.h b/Source/Client/Core/UnitTests/gen/ValueTable/ValueTableReaderTests.gen.h index 469bf7309..105b71578 100644 --- a/Source/Client/Core/UnitTests/gen/ValueTable/ValueTableReaderTests.gen.h +++ b/Source/Client/Core/UnitTests/gen/ValueTable/ValueTableReaderTests.gen.h @@ -18,6 +18,7 @@ TestState RunValueTableReaderTests() state += Soup::Test::RunTest(className, "Deserialize_SingleInteger", [&testClass]() { testClass->Deserialize_SingleInteger(); }); state += Soup::Test::RunTest(className, "Deserialize_SingleFloat", [&testClass]() { testClass->Deserialize_SingleFloat(); }); state += Soup::Test::RunTest(className, "Deserialize_SingleBoolean", [&testClass]() { testClass->Deserialize_SingleBoolean(); }); + state += Soup::Test::RunTest(className, "Deserialize_Complex", [&testClass]() { testClass->Deserialize_Complex(); }); return state; } \ No newline at end of file diff --git a/Source/Client/Core/UnitTests/gen/ValueTable/ValueTableWriterTests.gen.h b/Source/Client/Core/UnitTests/gen/ValueTable/ValueTableWriterTests.gen.h index 0834f05b7..317ead6fc 100644 --- a/Source/Client/Core/UnitTests/gen/ValueTable/ValueTableWriterTests.gen.h +++ b/Source/Client/Core/UnitTests/gen/ValueTable/ValueTableWriterTests.gen.h @@ -13,6 +13,7 @@ TestState RunValueTableWriterTests() state += Soup::Test::RunTest(className, "Serialize_SingleInteger", [&testClass]() { testClass->Serialize_SingleInteger(); }); state += Soup::Test::RunTest(className, "Serialize_SingleFloat", [&testClass]() { testClass->Serialize_SingleFloat(); }); state += Soup::Test::RunTest(className, "Serialize_SingleBoolean", [&testClass]() { testClass->Serialize_SingleBoolean(); }); + state += Soup::Test::RunTest(className, "Serialize_Complex", [&testClass]() { testClass->Serialize_Complex(); }); return state; } \ No newline at end of file diff --git a/Source/Client/Tools/PackageLock.sml b/Source/Client/Tools/PackageLock.sml index 0e739e04c..7fe783669 100644 --- a/Source/Client/Tools/PackageLock.sml +++ b/Source/Client/Tools/PackageLock.sml @@ -7,7 +7,7 @@ Closures: { 'mwasplund|CryptoPP': { Version: '1.2.2', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|Detours': { Version: '4.0.12', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|json11': { Version: '1.1.1', Build: 'Build0', Tool: 'Tool0' } - 'mwasplund|Opal': { Version: '0.10.2', Build: 'Build0', Tool: 'Tool0' } + 'mwasplund|Opal': { Version: '0.11.0', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|reflex': { Version: '1.0.4', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|Soup.Test.Assert': { Version: '0.4.0', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|wren': { Version: '1.0.5', Build: 'Build0', Tool: 'Tool0' } diff --git a/Source/Generate/PackageLock.sml b/Source/Generate/PackageLock.sml index b60f63d37..5133a0696 100644 --- a/Source/Generate/PackageLock.sml +++ b/Source/Generate/PackageLock.sml @@ -6,7 +6,7 @@ Closures: { 'Monitor.Shared': { Version: '../Monitor/Shared/', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|CryptoPP': { Version: '1.2.2', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|Detours': { Version: '4.0.12', Build: 'Build0', Tool: 'Tool0' } - 'mwasplund|Opal': { Version: '0.10.2', Build: 'Build0', Tool: 'Tool0' } + 'mwasplund|Opal': { Version: '0.11.0', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|reflex': { Version: '1.0.4', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|Soup.Test.Assert': { Version: '0.4.0', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|wren': { Version: '1.0.5', Build: 'Build0', Tool: 'Tool0' } diff --git a/Source/GenerateSharp/Opal.UnitTests/Utilities/PathUnitTests.cs b/Source/GenerateSharp/Opal.UnitTests/Utilities/PathUnitTests.cs index fb068347e..c79ee6ddf 100644 --- a/Source/GenerateSharp/Opal.UnitTests/Utilities/PathUnitTests.cs +++ b/Source/GenerateSharp/Opal.UnitTests/Utilities/PathUnitTests.cs @@ -9,69 +9,45 @@ namespace Opal.UnitTests; public class PathUnitTests { [Fact] - public void DefaultInitializer() + public void Initialize_Default() { var uut = new Path(); Assert.False(uut.HasRoot); Assert.False(uut.HasFileName); - Assert.Equal("", uut.FileName); Assert.False(uut.HasFileStem); - Assert.Equal("", uut.FileStem); Assert.False(uut.HasFileExtension); - Assert.Equal("", uut.FileExtension); Assert.Equal("./", uut.ToString()); Assert.Equal(".\\", uut.ToAlternateString()); } [Fact] - public void Empty() - { - var uut = new Path(""); - Assert.False(uut.HasRoot); - Assert.False(uut.HasFileName); - Assert.Equal("", uut.FileName); - Assert.False(uut.HasFileStem); - Assert.Equal("", uut.FileStem); - Assert.False(uut.HasFileExtension); - Assert.Equal("", uut.FileExtension); - Assert.Equal("./", uut.ToString()); - Assert.Equal(".\\", uut.ToAlternateString()); - } - - [Fact] - public void RelativePath_Simple() + public void Initialize_RelativePath_Simple() { var uut = new Path("./"); Assert.False(uut.HasRoot); Assert.False(uut.HasFileName); - Assert.Equal("", uut.FileName); Assert.False(uut.HasFileStem); - Assert.Equal("", uut.FileStem); Assert.False(uut.HasFileExtension); - Assert.Equal("", uut.FileExtension); Assert.Equal("./", uut.ToString()); Assert.Equal(".\\", uut.ToAlternateString()); } [Fact] - public void RelativePath_Parent() + public void Initialize_RelativePath_Parent() { var uut = new Path("../"); Assert.False(uut.HasRoot); Assert.False(uut.HasFileName); - Assert.Equal("", uut.FileName); Assert.False(uut.HasFileStem); - Assert.Equal("", uut.FileStem); Assert.False(uut.HasFileExtension); - Assert.Equal("", uut.FileExtension); Assert.Equal("../", uut.ToString()); Assert.Equal("..\\", uut.ToAlternateString()); } [Fact] - public void RelativePath_Complex() + public void Initialize_RelativePath_Complex() { - var uut = new Path("myfolder/anotherFolder/file.txt"); + var uut = new Path("./myfolder/anotherFolder/file.txt"); Assert.False(uut.HasRoot); Assert.True(uut.HasFileName); Assert.Equal("file.txt", uut.FileName); @@ -84,23 +60,20 @@ public void RelativePath_Complex() } [Fact] - public void LinuxRoot() + public void Initialize_LinuxRoot() { var uut = new Path("/"); Assert.True(uut.HasRoot); Assert.Equal("", uut.Root); Assert.False(uut.HasFileName); - Assert.Equal("", uut.FileName); Assert.False(uut.HasFileStem); - Assert.Equal("", uut.FileStem); Assert.False(uut.HasFileExtension); - Assert.Equal("", uut.FileExtension); Assert.Equal("/", uut.ToString()); Assert.Equal("\\", uut.ToAlternateString()); } [Fact] - public void SimpleAbsolutePath() + public void Initialize_SimpleAbsolutePath() { var uut = new Path("C:/myfolder/anotherFolder/file.txt"); Assert.True(uut.HasRoot, "Verify is root."); @@ -115,9 +88,21 @@ public void SimpleAbsolutePath() } [Fact] - public void AlternativeDirectoriesPath() + public void Parse_Empty() + { + var uut = Path.Parse(""); + Assert.False(uut.HasRoot); + Assert.False(uut.HasFileName); + Assert.False(uut.HasFileStem); + Assert.False(uut.HasFileExtension); + Assert.Equal("./", uut.ToString()); + Assert.Equal(".\\", uut.ToAlternateString()); + } + + [Fact] + public void Parse_AlternativeDirectoriesPath() { - var uut = new Path("C:\\myfolder/anotherFolder\\file.txt"); + var uut = Path.Parse("C:\\myfolder/anotherFolder\\file.txt"); Assert.True(uut.HasRoot, "Verify is root."); Assert.Equal("C:", uut.Root); Assert.True(uut.HasFileName); @@ -130,52 +115,52 @@ public void AlternativeDirectoriesPath() } [Fact] - public void RemoveEmptyDirectoryInside() + public void Parse_RemoveEmptyDirectoryInside() { - var uut = new Path("C:/myfolder//file.txt"); + var uut = Path.Parse("C:/myfolder//file.txt"); Assert.Equal("C:/myfolder/file.txt", uut.ToString()); } [Fact] - public void RemoveParentDirectoryInside() + public void ParseRemoveParentDirectoryInside() { - var uut = new Path("C:/myfolder/../file.txt"); + var uut = Path.Parse("C:/myfolder/../file.txt"); Assert.Equal("C:/file.txt", uut.ToString()); } [Fact] - public void RemoveTwoParentDirectoryInside() + public void Parse_RemoveTwoParentDirectoryInside() { - var uut = new Path("C:/myfolder/myfolder2/../../file.txt"); + var uut = Path.Parse("C:/myfolder/myfolder2/../../file.txt"); Assert.Equal("C:/file.txt", uut.ToString()); } [Fact] - public void LeaveParentDirectoryAtStart() + public void Parse_LeaveParentDirectoryAtStart() { - var uut = new Path("../file.txt"); + var uut = Path.Parse("../file.txt"); Assert.Equal("../file.txt", uut.ToString()); } [Fact] - public void CurrentDirectoryAtStart() + public void Parse_CurrentDirectoryAtStart() { - var uut = new Path("./file.txt"); + var uut = Path.Parse("./file.txt"); Assert.Equal("./file.txt", uut.ToString()); } [Fact] - public void CurrentDirectoryAtStartAlternate() + public void Parse_CurrentDirectoryAtStartAlternate() { - var uut = new Path(".\\../file.txt"); + var uut = Path.Parse(".\\../file.txt"); Assert.Equal("../file.txt", uut.ToString()); } [Fact] public void Concatenate_Simple() { - var path1 = new Path("C:/MyRootFolder"); - var path2 = new Path("MyFolder/MyFile.txt"); + var path1 = new Path("C:/MyRootFolder/"); + var path2 = new Path("./MyFolder/MyFile.txt"); var uut = path1 + path2; Assert.Equal("C:/MyRootFolder/MyFolder/MyFile.txt", uut.ToString()); @@ -184,8 +169,8 @@ public void Concatenate_Simple() [Fact] public void Concatenate_Empty() { - var path1 = new Path("C:/MyRootFolder"); - var path2 = new Path(""); + var path1 = new Path("C:/MyRootFolder/"); + var path2 = new Path(); var uut = path1 + path2; // Changes the assumed file into a folder @@ -195,8 +180,8 @@ public void Concatenate_Empty() [Fact] public void Concatenate_RootFile() { - var path1 = new Path("C:"); - var path2 = new Path("MyFile.txt"); + var path1 = new Path("C:/"); + var path2 = new Path("./MyFile.txt"); var uut = path1 + path2; Assert.Equal("C:/MyFile.txt", uut.ToString()); @@ -205,8 +190,8 @@ public void Concatenate_RootFile() [Fact] public void Concatenate_RootFolder() { - var path1 = new Path("C:"); - var path2 = new Path("MyFolder/"); + var path1 = new Path("C:/"); + var path2 = new Path("./MyFolder/"); var uut = path1 + path2; Assert.Equal("C:/MyFolder/", uut.ToString()); @@ -215,7 +200,7 @@ public void Concatenate_RootFolder() [Fact] public void Concatenate_UpDirectory() { - var path1 = new Path("C:/MyRootFolder"); + var path1 = new Path("C:/MyRootFolder/"); var path2 = new Path("../NewRoot/MyFile.txt"); var uut = path1 + path2; @@ -225,7 +210,7 @@ public void Concatenate_UpDirectory() [Fact] public void Concatenate_UpDirectoryBeginning() { - var path1 = new Path("../MyRootFolder"); + var path1 = new Path("../MyRootFolder/"); var path2 = new Path("../NewRoot/MyFile.txt"); var uut = path1 + path2; @@ -262,8 +247,8 @@ public void SetFileExtension_Add() [Fact] public void GetRelativeTo_Empty() { - var uut = new Path("File.txt"); - var basePath = new Path(""); + var uut = new Path("./File.txt"); + var basePath = new Path(); var result = uut.GetRelativeTo(basePath); @@ -273,8 +258,8 @@ public void GetRelativeTo_Empty() [Fact] public void GetRelativeTo_SingleRelative() { - var uut = new Path("Folder/File.txt"); - var basePath = new Path("Folder/"); + var uut = new Path("./Folder/File.txt"); + var basePath = new Path("./Folder/"); var result = uut.GetRelativeTo(basePath); @@ -284,19 +269,19 @@ public void GetRelativeTo_SingleRelative() [Fact] public void GetRelativeTo_UpParentRelative() { - var uut = new Path("../Folder/Target"); - var basePath = new Path("../Folder"); + var uut = new Path("../Folder/Target/"); + var basePath = new Path("../Folder/"); var result = uut.GetRelativeTo(basePath); - Assert.Equal("./Target", result.ToString()); + Assert.Equal("./Target/", result.ToString()); } [Fact] public void GetRelativeTo_MismatchRelative() { - var uut = new Path("Folder1/File.txt"); - var basePath = new Path("Folder2/"); + var uut = new Path("./Folder1/File.txt"); + var basePath = new Path("./Folder2/"); var result = uut.GetRelativeTo(basePath); diff --git a/Source/GenerateSharp/Opal/System/RuntimeFileSystem.cs b/Source/GenerateSharp/Opal/System/RuntimeFileSystem.cs index 31fd1de2e..266ea207a 100644 --- a/Source/GenerateSharp/Opal/System/RuntimeFileSystem.cs +++ b/Source/GenerateSharp/Opal/System/RuntimeFileSystem.cs @@ -24,7 +24,7 @@ public Path GetUserProfileDirectory() ? Environment.GetEnvironmentVariable("USERPROFILE") : Environment.GetEnvironmentVariable("HOME")) ?? throw new InvalidOperationException("Unable to retrieve user profile"); - return new Path(userProfileFolder); + return Path.Parse($"{userProfileFolder}\\"); } public Path GetCurrentDirectory() @@ -109,7 +109,7 @@ public IReadOnlyList GetChildDirectories(Path path) { result.Add(new DirectoryEntry() { - Path = new Path(directory), + Path = new Path($"{directory}/"), IsDirectory = true, }); } diff --git a/Source/GenerateSharp/Opal/Utilities/Path.cs b/Source/GenerateSharp/Opal/Utilities/Path.cs index 95e8ae5c3..7459198ce 100644 --- a/Source/GenerateSharp/Opal/Utilities/Path.cs +++ b/Source/GenerateSharp/Opal/Utilities/Path.cs @@ -16,17 +16,32 @@ namespace Opal; /// public class Path : IEquatable { - private string value; - private int rootEndLocation; - private int fileNameStartLocation; + private const char DirectorySeparator = '/'; + + private const char AlternateDirectorySeparator = '\\'; + + private static char[] AllValidDirectorySeparators => ['/', '\\']; + + private const char LetterDriveSpecifier = ':'; + + private const char FileExtensionSeparator = '.'; + + private const string RelativeDirectory = "."; + + private const string RelativeParentDirectory = ".."; + + private string _value; + private int _rootEndLocation; + private int _fileNameStartLocation; /// /// Initializes a new instance of the class. /// public Path() { - this.value = string.Empty; - this.SetState([RelativeDirectory], null, null); + _value = "./"; + _rootEndLocation = -1; + _fileNameStartLocation = 2; } /// @@ -35,33 +50,105 @@ public Path() /// The value. public Path(string value) { - this.value = string.Empty; - this.ParsePath(value); + _value = value; + LoadDirect(); } /// /// Gets a value indicating whether the path is empty. /// - public bool IsEmpty => this.value == "./"; + public bool IsEmpty => _value == "./"; /// /// Gets a value indicating whether the path has a root. /// - public bool HasRoot => this.rootEndLocation >= 0; + public bool HasRoot => _rootEndLocation >= 0; - private static char DirectorySeparator => '/'; + /// + /// Gets a value indicating whether the path has a file name. + /// + public bool HasFileName => _fileNameStartLocation < _value.Length; - private static char AlternateDirectorySeparator => '\\'; + /// + /// Gets the file name. + /// + public string FileName + { + get + { + if (!HasFileName) + throw new InvalidOperationException("No filename"); - private static char[] AllValidDirectorySeparators => ['/', '\\']; + // Use the start location to return the end of the value that is the filename + return _value[_fileNameStartLocation..]; + } + } - private static char LetterDriveSpecifier => ':'; + /// + /// Gets a value indicating whether the file name has a stem. + /// + public bool HasFileStem => HasFileName && !string.IsNullOrEmpty(FileStem); - private static char FileExtensionSeparator => '.'; + /// + /// Gets the file name minus the extension. + /// + public string FileStem + { + get + { + // Everything before the last period is the stem + var fileName = FileName; + var lastSeparator = fileName.LastIndexOf(FileExtensionSeparator); + if (lastSeparator != -1) + { + return fileName[..lastSeparator]; + } + else + { + // Return the entire filename if no extension + return fileName; + } + } + } + + /// + /// Gets a value indicating whether the file name has an extension. + /// + public bool HasFileExtension => HasFileName && !string.IsNullOrEmpty(FileExtension); - private static string RelativeDirectory => "."; + /// + /// Gets the file extension. + /// + public string FileExtension + { + get + { + // Everything after and including the last period is the extension + var fileName = FileName; + var lastSeparator = fileName.LastIndexOf(FileExtensionSeparator); + return lastSeparator != -1 ? fileName[lastSeparator..] : string.Empty; + } + } + + /// + /// Gets the path root. + /// + public string Root + { + get + { + if (_rootEndLocation < 0) + throw new InvalidOperationException("Cannot access root on path that has none"); + return _value[.._rootEndLocation]; + } + } - private static string RelativeParentDirectory => ".."; + public static Path Parse(string value) + { + var result = new Path(); + result.ParsePath(value); + return result; + } public static bool operator ==(Path? lhs, Path? rhs) { @@ -78,7 +165,12 @@ public Path(string value) /// public int CompareTo(Path other) { - return string.Compare(this.value, other.value, StringComparison.Ordinal); + return string.Compare(_value, other._value, StringComparison.Ordinal); + } + + public IList DecomposeDirectories() + { + return DecomposeDirectoriesString(GetDirectories()); } /// @@ -118,19 +210,6 @@ public int CompareTo(Path other) return result; } - /// - /// Gets the path root. - /// - public string Root - { - get - { - if (this.rootEndLocation < 0) - throw new InvalidOperationException("Cannot access root on path that has none"); - return this.value[..this.rootEndLocation]; - } - } - /// /// Gets the parent directory. /// @@ -139,22 +218,22 @@ public Path GetParent() var result = new Path() { // Take the root from the left hand side - rootEndLocation = this.rootEndLocation + _rootEndLocation = _rootEndLocation }; // If there is a filename then return the directory // Otherwise return one less directory - if (this.HasFileName) + if (HasFileName) { // Pass along the path minus the filename - result.value = this.value[..this.fileNameStartLocation]; - result.fileNameStartLocation = result.value.Length; + result._value = _value[.._fileNameStartLocation]; + result._fileNameStartLocation = result._value.Length; } else { // Pull apart the directories and remove the last one // TODO: This can be done in place and then a substring returned for perf gains - var directories = DecomposeDirectoriesString(this.GetDirectories()); + var directories = DecomposeDirectoriesString(GetDirectories()); if (directories.Count == 0) { // No-op when at the root @@ -178,76 +257,13 @@ public Path GetParent() // Set the state of the result path result.SetState( directories, - this.HasRoot ? this.Root : null, + HasRoot ? Root : null, null); } return result; } - /// - /// Gets a value indicating whether the path has a file name. - /// - public bool HasFileName => this.fileNameStartLocation < this.value.Length; - - /// - /// Gets the file name. - /// - public string FileName - { - get - { - // Use the start location to return the end of the value that is the filename - return this.value[this.fileNameStartLocation..]; - } - } - - /// - /// Gets a value indicating whether the file name has a stem. - /// - public bool HasFileStem => !string.IsNullOrEmpty(this.FileStem); - - /// - /// Gets the file name minus the extension. - /// - public string FileStem - { - get - { - // Everything before the last period is the stem - var fileName = this.FileName; - var lastSeparator = fileName.LastIndexOf(FileExtensionSeparator); - if (lastSeparator != -1) - { - return fileName[..lastSeparator]; - } - else - { - // Return the entire filename if no extension - return fileName; - } - } - } - - /// - /// Gets a value indicating whether the file name has an extension. - /// - public bool HasFileExtension => !string.IsNullOrEmpty(this.FileExtension); - - /// - /// Gets the file extension. - /// - public string FileExtension - { - get - { - // Everything after and including the last period is the extension - var fileName = this.FileName; - var lastSeparator = fileName.LastIndexOf(FileExtensionSeparator); - return lastSeparator != -1 ? fileName[lastSeparator..] : string.Empty; - } - } - /// /// Set the filename. /// @@ -255,9 +271,9 @@ public string FileExtension public void SetFilename(string value) { // Build the new final string - this.SetState( - DecomposeDirectoriesString(this.GetDirectories()), - this.HasRoot ? this.Root : null, + SetState( + DecomposeDirectoriesString(GetDirectories()), + HasRoot ? Root : null, value); } @@ -268,7 +284,7 @@ public void SetFilename(string value) public void SetFileExtension(string value) { // Build up the new filename and set the active state - this.SetFilename($"{this.FileStem}{FileExtensionSeparator}{value}"); + SetFilename($"{FileStem}{FileExtensionSeparator}{value}"); } /// @@ -279,8 +295,8 @@ public Path GetRelativeTo(Path basePath) { // If the root does not match then there is no way to get a relative path // simply return a copy of this path - if ((basePath.HasRoot && this.HasRoot && basePath.Root != this.Root) || - (basePath.HasRoot ^ this.HasRoot)) + if ((basePath.HasRoot && HasRoot && basePath.Root != Root) || + (basePath.HasRoot ^ HasRoot)) { return this; } @@ -293,7 +309,7 @@ public Path GetRelativeTo(Path basePath) } // Determine how many of the directories match - var directories = DecomposeDirectoriesString(this.GetDirectories()); + var directories = DecomposeDirectoriesString(GetDirectories()); var minDirectories = Math.Min(baseDirectories.Count, directories.Count); int countMatching = 0; for (var i = 0; i < minDirectories; i++) @@ -332,7 +348,7 @@ public Path GetRelativeTo(Path basePath) result.SetState( resultDirectories, null, - this.HasFileName ? this.FileName : null); + HasFileName ? FileName : null); return result; } @@ -345,17 +361,17 @@ public bool Equals(Path? other) { if (other is null) return false; - return this.value == other.value; + return _value == other._value; } public override bool Equals(object? obj) { - return this.Equals(obj as Path); + return Equals(obj as Path); } public override int GetHashCode() { - return this.value.GetHashCode(StringComparison.Ordinal); + return _value.GetHashCode(StringComparison.Ordinal); } /// @@ -363,19 +379,19 @@ public override int GetHashCode() /// public override string ToString() { - return this.value; + return _value; } public string ToAlternateString() { // Replace all normal separators with the windows version - var result = this.value.Replace(DirectorySeparator, AlternateDirectorySeparator); + var result = _value.Replace(DirectorySeparator, AlternateDirectorySeparator); return result; } private static bool IsRelativeDirectory(string directory) { - return directory == RelativeDirectory || directory == RelativeParentDirectory; + return directory is RelativeDirectory or RelativeParentDirectory; } private static List DecomposeDirectoriesString(string value) @@ -465,6 +481,49 @@ private static void NormalizeDirectories( } } + /// + /// Helper that loads a string directly into the path value + /// + private void LoadDirect() + { + var firstAlternateDirectory = _value.IndexOf(AlternateDirectorySeparator, 0); + if (firstAlternateDirectory != -1) + throw new ArgumentException("Debug check for windows ridiculous directory separator"); + + var firstSeparator = _value.IndexOf(DirectorySeparator, 0); + if (firstSeparator == -1) + { + throw new ArgumentException("A path must have a directory separator"); + } + + var root = _value[..firstSeparator]; + if (IsRoot(root)) + { + // Absolute path + _rootEndLocation = firstSeparator; + } + else if (root is RelativeDirectory or RelativeParentDirectory) + { + // Relative path + _rootEndLocation = -1; + } + else + { + throw new ArgumentException($"Unknown directory root {root}"); + } + + // Check if has file name + var lastSeparator = _value.LastIndexOf(DirectorySeparator); + if (lastSeparator != -1 && lastSeparator != _value.Length - 1) + { + _fileNameStartLocation = lastSeparator + 1; + } + else + { + _fileNameStartLocation = _value.Length; + } + } + private void ParsePath(string value) { // Break out the individual components of the path @@ -480,7 +539,7 @@ private void ParsePath(string value) NormalizeDirectories(directories, hasRoot); // Rebuild the string value - this.SetState( + SetState( directories, root, fileName); @@ -597,23 +656,22 @@ private void SetState( } // Store the persistent state - this.value = stringBuilder.ToString(); - + _value = stringBuilder.ToString(); - this.rootEndLocation = root is not null ? root.Length : -1; - this.fileNameStartLocation = fileName is not null ? this.value.Length - fileName.Length : this.value.Length; + _rootEndLocation = root is not null ? root.Length : -1; + _fileNameStartLocation = fileName is not null ? _value.Length - fileName.Length : _value.Length; } private string GetDirectories() { - if (this.rootEndLocation >= 0) + if (_rootEndLocation >= 0) { - return this.value[this.rootEndLocation..this.fileNameStartLocation]; + return _value[_rootEndLocation.._fileNameStartLocation]; } else { - return this.value[..this.fileNameStartLocation]; + return _value[.._fileNameStartLocation]; } } } diff --git a/Source/GenerateSharp/PackageManager.Core/ClosureManager.cs b/Source/GenerateSharp/PackageManager.Core/ClosureManager.cs index 50e03c959..926fb6dbf 100644 --- a/Source/GenerateSharp/PackageManager.Core/ClosureManager.cs +++ b/Source/GenerateSharp/PackageManager.Core/ClosureManager.cs @@ -135,13 +135,11 @@ private async Task CheckGenerateAndRestoreSubGraphDependencyLocksAsync( else { var languageSafeName = GetLanguageSafeName(languageName); - var userFolder = projectName.Owner is not null ? new Path(projectName.Owner) : new Path("Local"); + var userFolder = projectName.Owner is not null ? new Path($"./{projectName.Owner}/") : new Path("./Local/"); var packageLanguageNameVersionPath = - new Path(languageSafeName) + + new Path($"./{languageSafeName}/") + userFolder + - new Path(projectName.Name) + - new Path(version.ToString()) + - new Path(); + new Path($"./{projectName.Name}/{version}/"); var packageContentDirectory = packageStoreDirectory + packageLanguageNameVersionPath; // Place the lock in the lock store @@ -785,9 +783,9 @@ private async Task EnsurePackageDownloadedAsync( Log.HighPriority($"Install Package: {languageName} {ownerName} {packageName}@{packageVersion}"); var languageSafeName = GetLanguageSafeName(languageName); - var languageRootFolder = packageStore + new Path(languageSafeName); - var packageRootFolder = languageRootFolder + new Path(ownerName) + new Path(packageName); - var packageVersionFolder = packageRootFolder + new Path(packageVersion.ToString()) + new Path(); + var languageRootFolder = packageStore + new Path($"./{languageSafeName}/"); + var packageRootFolder = languageRootFolder + new Path($"./{ownerName}/{packageName}/"); + var packageVersionFolder = packageRootFolder + new Path($"./{packageVersion}/"); // Check if the package version already exists if (!isRuntime && @@ -803,7 +801,7 @@ private async Task EnsurePackageDownloadedAsync( { // Download the archive Log.HighPriority("Downloading package"); - var archivePath = stagingDirectory + new Path(packageName + ".zip"); + var archivePath = stagingDirectory + new Path($"./{packageName}.zip"); var client = new Api.Client.PackageVersionsClient(_httpClient, null) { @@ -833,7 +831,7 @@ private async Task EnsurePackageDownloadedAsync( } // Create the package folder to extract to - var stagingVersionFolder = stagingDirectory + new Path($"{languageName}_{packageName}_{packageVersion}/"); + var stagingVersionFolder = stagingDirectory + new Path($"./{languageName}_{packageName}_{packageVersion}/"); LifetimeManager.Get().CreateDirectory2(stagingVersionFolder); // Unpack the contents of the archive diff --git a/Source/GenerateSharp/PackageManager.Core/InitializeCommand.cs b/Source/GenerateSharp/PackageManager.Core/InitializeCommand.cs index 9c5adbc6f..988c5575d 100644 --- a/Source/GenerateSharp/PackageManager.Core/InitializeCommand.cs +++ b/Source/GenerateSharp/PackageManager.Core/InitializeCommand.cs @@ -68,7 +68,7 @@ int main() return 0; }"; - var mainFilePath = workingDirectory + new Path("Main.cpp"); + var mainFilePath = workingDirectory + new Path("./Main.cpp"); using var mainFile = LifetimeManager.Get().OpenWrite(mainFilePath, false); using var mainFileWriter = new System.IO.StreamWriter(mainFile.GetOutStream(), null, -1, true); diff --git a/Source/GenerateSharp/PackageManager.Core/PackageManager.cs b/Source/GenerateSharp/PackageManager.Core/PackageManager.cs index e44a3d8a0..7aef705ed 100644 --- a/Source/GenerateSharp/PackageManager.Core/PackageManager.cs +++ b/Source/GenerateSharp/PackageManager.Core/PackageManager.cs @@ -17,7 +17,7 @@ namespace Soup.Build.PackageManager; [SuppressMessage("Naming", "CA1724:Type names should not match namespaces", Justification = "Primary class")] public class PackageManager { - private const string StagingFolderName = ".staging/"; + private static readonly Path StagingFolder = new Path("./.staging/"); private readonly Uri _apiEndpoint; @@ -41,8 +41,8 @@ public PackageManager( public async Task RestorePackagesAsync(Path workingDirectory) { var userProfileDirectory = LifetimeManager.Get().GetUserProfileDirectory(); - var packageStore = userProfileDirectory + new Path(".soup/packages/"); - var lockStore = userProfileDirectory + new Path(".soup/locks/"); + var packageStore = userProfileDirectory + new Path("./.soup/packages/"); + var lockStore = userProfileDirectory + new Path("./.soup/locks/"); Log.Diag("Using Package Store: " + packageStore.ToString()); Log.Diag("Using Lock Store: " + lockStore.ToString()); @@ -85,8 +85,8 @@ public async Task InstallPackageReferenceAsync(Path workingDirectory, string pac } var userProfileDirectory = LifetimeManager.Get().GetUserProfileDirectory(); - var packageStore = userProfileDirectory + new Path(".soup/packages/"); - var lockStore = userProfileDirectory + new Path(".soup/locks/"); + var packageStore = userProfileDirectory + new Path("./.soup/packages/"); + var lockStore = userProfileDirectory + new Path("./.soup/locks/"); Log.Diag("Using Package Store: " + packageStore.ToString()); Log.Diag("Using Lock Store: " + lockStore.ToString()); @@ -182,7 +182,7 @@ public async Task PublishPackageAsync(Path workingDirectory) } var packageStore = LifetimeManager.Get().GetUserProfileDirectory() + - new Path(".soup/packages/"); + new Path("./.soup/packages/"); Log.Info("Using Package Store: " + packageStore.ToString()); // Create the staging directory @@ -190,7 +190,7 @@ public async Task PublishPackageAsync(Path workingDirectory) try { - var archivePath = stagingPath + new Path(recipe.Name + ".zip"); + var archivePath = stagingPath + new Path($"./{recipe.Name}.zip"); // Create the archive of the package using (var zipArchive = LifetimeManager.Get().OpenCreate(archivePath)) @@ -366,7 +366,7 @@ private static void AddAllFilesRecursive(Path directory, Path workingDirectory, /// private static Path EnsureStagingDirectoryExists(Path packageStore) { - var stagingDirectory = packageStore + new Path(StagingFolderName); + var stagingDirectory = packageStore + StagingFolder; if (LifetimeManager.Get().Exists(stagingDirectory)) { Log.Warning("The staging directory already exists from a previous failed run"); diff --git a/Source/GenerateSharp/PackageManager.UnitTests/ClosureManagerUnitTests.cs b/Source/GenerateSharp/PackageManager.UnitTests/ClosureManagerUnitTests.cs index eaf7d6622..915c0511e 100644 --- a/Source/GenerateSharp/PackageManager.UnitTests/ClosureManagerUnitTests.cs +++ b/Source/GenerateSharp/PackageManager.UnitTests/ClosureManagerUnitTests.cs @@ -536,15 +536,15 @@ await uut.GenerateAndRestoreRecursiveLocksAsync( "OpenWriteTruncate: C:/Staging/Package1.zip", "CreateDirectory: C:/Staging/Wren_Package1_1.2.3/", "DeleteFile: C:/Staging/Package1.zip", - "Exists: C:/PackageStore/Wren/User1/Package1", - "CreateDirectory: C:/PackageStore/Wren/User1/Package1", + "Exists: C:/PackageStore/Wren/User1/Package1/", + "CreateDirectory: C:/PackageStore/Wren/User1/Package1/", "Rename: [C:/Staging/Wren_Package1_1.2.3/] -> [C:/PackageStore/Wren/User1/Package1/1.2.3/]", "Exists: C:/PackageStore/Wren/User1/Package2/3.2.1/", "OpenWriteTruncate: C:/Staging/Package2.zip", "CreateDirectory: C:/Staging/Wren_Package2_3.2.1/", "DeleteFile: C:/Staging/Package2.zip", - "Exists: C:/PackageStore/Wren/User1/Package2", - "CreateDirectory: C:/PackageStore/Wren/User1/Package2", + "Exists: C:/PackageStore/Wren/User1/Package2/", + "CreateDirectory: C:/PackageStore/Wren/User1/Package2/", "Rename: [C:/Staging/Wren_Package2_3.2.1/] -> [C:/PackageStore/Wren/User1/Package2/3.2.1/]", ], mockFileSystem.Requests); @@ -938,15 +938,15 @@ await uut.GenerateAndRestoreRecursiveLocksAsync( "OpenWriteTruncate: C:/Staging/Soup.Cpp.zip", "CreateDirectory: C:/Staging/Wren_Soup.Cpp_5.0.0/", "DeleteFile: C:/Staging/Soup.Cpp.zip", - "Exists: C:/PackageStore/Wren/mwasplund/Soup.Cpp", - "CreateDirectory: C:/PackageStore/Wren/mwasplund/Soup.Cpp", + "Exists: C:/PackageStore/Wren/mwasplund/Soup.Cpp/", + "CreateDirectory: C:/PackageStore/Wren/mwasplund/Soup.Cpp/", "Rename: [C:/Staging/Wren_Soup.Cpp_5.0.0/] -> [C:/PackageStore/Wren/mwasplund/Soup.Cpp/5.0.0/]", "Exists: C:/PackageStore/Wren/User1/Package1/1.2.3/", "OpenWriteTruncate: C:/Staging/Package1.zip", "CreateDirectory: C:/Staging/Wren_Package1_1.2.3/", "DeleteFile: C:/Staging/Package1.zip", - "Exists: C:/PackageStore/Wren/User1/Package1", - "CreateDirectory: C:/PackageStore/Wren/User1/Package1", + "Exists: C:/PackageStore/Wren/User1/Package1/", + "CreateDirectory: C:/PackageStore/Wren/User1/Package1/", "Rename: [C:/Staging/Wren_Package1_1.2.3/] -> [C:/PackageStore/Wren/User1/Package1/1.2.3/]", "Exists: C:/LockStore/Wren/mwasplund/Soup.Cpp/5.0.0/", "CreateDirectory: C:/LockStore/Wren/mwasplund/Soup.Cpp/5.0.0/", @@ -1579,15 +1579,15 @@ await uut.GenerateAndRestoreRecursiveLocksAsync( "OpenWriteTruncate: C:/Staging/Soup.Cpp.zip", "CreateDirectory: C:/Staging/Wren_Soup.Cpp_5.0.0/", "DeleteFile: C:/Staging/Soup.Cpp.zip", - "Exists: C:/PackageStore/Wren/mwasplund/Soup.Cpp", - "CreateDirectory: C:/PackageStore/Wren/mwasplund/Soup.Cpp", + "Exists: C:/PackageStore/Wren/mwasplund/Soup.Cpp/", + "CreateDirectory: C:/PackageStore/Wren/mwasplund/Soup.Cpp/", "Rename: [C:/Staging/Wren_Soup.Cpp_5.0.0/] -> [C:/PackageStore/Wren/mwasplund/Soup.Cpp/5.0.0/]", "Exists: C:/PackageStore/Wren/User1/Package1/1.2.3/", "OpenWriteTruncate: C:/Staging/Package1.zip", "CreateDirectory: C:/Staging/Wren_Package1_1.2.3/", "DeleteFile: C:/Staging/Package1.zip", - "Exists: C:/PackageStore/Wren/User1/Package1", - "CreateDirectory: C:/PackageStore/Wren/User1/Package1", + "Exists: C:/PackageStore/Wren/User1/Package1/", + "CreateDirectory: C:/PackageStore/Wren/User1/Package1/", "Rename: [C:/Staging/Wren_Package1_1.2.3/] -> [C:/PackageStore/Wren/User1/Package1/1.2.3/]", "Exists: C:/LockStore/Wren/mwasplund/Soup.Cpp/5.0.0/", "CreateDirectory: C:/LockStore/Wren/mwasplund/Soup.Cpp/5.0.0/", @@ -1605,8 +1605,8 @@ await uut.GenerateAndRestoreRecursiveLocksAsync( "OpenWriteTruncate: C:/Staging/Package2.zip", "CreateDirectory: C:/Staging/C++_Package2_2.3.4/", "DeleteFile: C:/Staging/Package2.zip", - "Exists: C:/PackageStore/Cpp/User1/Package2", - "CreateDirectory: C:/PackageStore/Cpp/User1/Package2", + "Exists: C:/PackageStore/Cpp/User1/Package2/", + "CreateDirectory: C:/PackageStore/Cpp/User1/Package2/", "Rename: [C:/Staging/C++_Package2_2.3.4/] -> [C:/PackageStore/Cpp/User1/Package2/2.3.4/]", "Exists: C:/LockStore/Cpp/User1/Package2/2.3.4/", "CreateDirectory: C:/LockStore/Cpp/User1/Package2/2.3.4/", @@ -3014,22 +3014,22 @@ await uut.GenerateAndRestoreRecursiveLocksAsync( "OpenWriteTruncate: C:/Staging/Package1.zip", "CreateDirectory: C:/Staging/Wren_Package1_1.2.4/", "DeleteFile: C:/Staging/Package1.zip", - "Exists: C:/PackageStore/Wren/User1/Package1", - "CreateDirectory: C:/PackageStore/Wren/User1/Package1", + "Exists: C:/PackageStore/Wren/User1/Package1/", + "CreateDirectory: C:/PackageStore/Wren/User1/Package1/", "Rename: [C:/Staging/Wren_Package1_1.2.4/] -> [C:/PackageStore/Wren/User1/Package1/1.2.4/]", "Exists: C:/PackageStore/Wren/User1/Package2/3.2.1/", "OpenWriteTruncate: C:/Staging/Package2.zip", "CreateDirectory: C:/Staging/Wren_Package2_3.2.1/", "DeleteFile: C:/Staging/Package2.zip", - "Exists: C:/PackageStore/Wren/User1/Package2", - "CreateDirectory: C:/PackageStore/Wren/User1/Package2", + "Exists: C:/PackageStore/Wren/User1/Package2/", + "CreateDirectory: C:/PackageStore/Wren/User1/Package2/", "Rename: [C:/Staging/Wren_Package2_3.2.1/] -> [C:/PackageStore/Wren/User1/Package2/3.2.1/]", "Exists: C:/PackageStore/Wren/mwasplund/Soup.Wren/4.5.5/", "OpenWriteTruncate: C:/Staging/Soup.Wren.zip", "CreateDirectory: C:/Staging/Wren_Soup.Wren_4.5.5/", "DeleteFile: C:/Staging/Soup.Wren.zip", - "Exists: C:/PackageStore/Wren/mwasplund/Soup.Wren", - "CreateDirectory: C:/PackageStore/Wren/mwasplund/Soup.Wren", + "Exists: C:/PackageStore/Wren/mwasplund/Soup.Wren/", + "CreateDirectory: C:/PackageStore/Wren/mwasplund/Soup.Wren/", "Rename: [C:/Staging/Wren_Soup.Wren_4.5.5/] -> [C:/PackageStore/Wren/mwasplund/Soup.Wren/4.5.5/]", "Exists: C:/LockStore/Wren/mwasplund/Soup.Wren/4.5.5/", "CreateDirectory: C:/LockStore/Wren/mwasplund/Soup.Wren/4.5.5/", diff --git a/Source/GenerateSharp/SoupView/ViewModels/OperationGraphViewModel.cs b/Source/GenerateSharp/SoupView/ViewModels/OperationGraphViewModel.cs index 93f00f99c..ef05ea5cf 100644 --- a/Source/GenerateSharp/SoupView/ViewModels/OperationGraphViewModel.cs +++ b/Source/GenerateSharp/SoupView/ViewModels/OperationGraphViewModel.cs @@ -57,7 +57,7 @@ public async Task LoadProjectAsync(Path? packageFolder) { var targetPath = await GetTargetPathAsync(packageFolder); - var soupTargetDirectory = targetPath + new Path(".soup/"); + var soupTargetDirectory = targetPath + new Path("./.soup/"); var evaluateGraphFile = soupTargetDirectory + BuildConstants.EvaluateGraphFileName; if (!OperationGraphManager.TryLoadState(evaluateGraphFile, fileSystemState, out var evaluateGraph)) diff --git a/Source/GenerateSharp/SoupView/ViewModels/TaskGraphViewModel.cs b/Source/GenerateSharp/SoupView/ViewModels/TaskGraphViewModel.cs index 3a6776405..e367f421c 100644 --- a/Source/GenerateSharp/SoupView/ViewModels/TaskGraphViewModel.cs +++ b/Source/GenerateSharp/SoupView/ViewModels/TaskGraphViewModel.cs @@ -66,7 +66,7 @@ public async Task LoadProjectAsync(Path? packageFolder) { var targetPath = await GetTargetPathAsync(packageFolder); - var soupTargetDirectory = targetPath + new Path(".soup/"); + var soupTargetDirectory = targetPath + new Path("./.soup/"); var generateInfoStateFile = soupTargetDirectory + BuildConstants.GenerateInfoFileName; if (!ValueTableManager.TryLoadState(generateInfoStateFile, out var generateInfoTable)) diff --git a/Source/GenerateSharp/Swhere.Core.UnitTests/Utilities/DotNetSDKUtilitiesUnitTests.cs b/Source/GenerateSharp/Swhere.Core.UnitTests/Utilities/DotNetSDKUtilitiesUnitTests.cs index e9e947734..b858cd5d7 100644 --- a/Source/GenerateSharp/Swhere.Core.UnitTests/Utilities/DotNetSDKUtilitiesUnitTests.cs +++ b/Source/GenerateSharp/Swhere.Core.UnitTests/Utilities/DotNetSDKUtilitiesUnitTests.cs @@ -35,13 +35,13 @@ public async Task FindDotNet() "Microsoft.AspNetCore.App 3.1.32 [C:\\Program Files\\dotnet\\shared\\Microsoft.AspNetCore.App]\r\nMicrosoft.AspNetCore.App 5.0.17 [C:\\Program Files\\dotnet\\shared\\Microsoft.AspNetCore.App]\r\nMicrosoft.AspNetCore.App 6.0.14 [C:\\Program Files\\dotnet\\shared\\Microsoft.AspNetCore.App]\r\nMicrosoft.AspNetCore.App 6.0.16 [C:\\Program Files\\dotnet\\shared\\Microsoft.AspNetCore.App]\r\nMicrosoft.AspNetCore.App 6.0.18 [C:\\Program Files\\dotnet\\shared\\Microsoft.AspNetCore.App]\r\nMicrosoft.AspNetCore.App 7.0.3 [C:\\Program Files\\dotnet\\shared\\Microsoft.AspNetCore.App]\r\nMicrosoft.AspNetCore.App 7.0.5 [C:\\Program Files\\dotnet\\shared\\Microsoft.AspNetCore.App]\r\nMicrosoft.AspNetCore.App 7.0.7 [C:\\Program Files\\dotnet\\shared\\Microsoft.AspNetCore.App]\r\nMicrosoft.NETCore.App 3.1.32 [C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App]\r\nMicrosoft.NETCore.App 5.0.17 [C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App]\r\nMicrosoft.NETCore.App 6.0.12 [C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App]\r\nMicrosoft.NETCore.App 6.0.14 [C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App]\r\nMicrosoft.NETCore.App 6.0.15 [C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App]\r\nMicrosoft.NETCore.App 6.0.16 [C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App]\r\nMicrosoft.NETCore.App 6.0.18 [C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App]\r\nMicrosoft.NETCore.App 6.0.20 [C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App]\r\nMicrosoft.NETCore.App 7.0.3 [C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App]\r\nMicrosoft.NETCore.App 7.0.5 [C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App]\r\nMicrosoft.NETCore.App 7.0.7 [C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App]\r\nMicrosoft.WindowsDesktop.App 3.1.32 [C:\\Program Files\\dotnet\\shared\\Microsoft.WindowsDesktop.App]\r\nMicrosoft.WindowsDesktop.App 5.0.17 [C:\\Program Files\\dotnet\\shared\\Microsoft.WindowsDesktop.App]\r\nMicrosoft.WindowsDesktop.App 6.0.14 [C:\\Program Files\\dotnet\\shared\\Microsoft.WindowsDesktop.App]\r\nMicrosoft.WindowsDesktop.App 6.0.16 [C:\\Program Files\\dotnet\\shared\\Microsoft.WindowsDesktop.App]\r\nMicrosoft.WindowsDesktop.App 6.0.18 [C:\\Program Files\\dotnet\\shared\\Microsoft.WindowsDesktop.App]\r\nMicrosoft.WindowsDesktop.App 7.0.3 [C:\\Program Files\\dotnet\\shared\\Microsoft.WindowsDesktop.App]\r\nMicrosoft.WindowsDesktop.App 7.0.5 [C:\\Program Files\\dotnet\\shared\\Microsoft.WindowsDesktop.App]\r\nMicrosoft.WindowsDesktop.App 7.0.7 [C:\\Program Files\\dotnet\\shared\\Microsoft.WindowsDesktop.App]\r\n"); mockFileSystem.RegisterChildren( - new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref"), + new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/"), [ - new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/5.0.0"), IsDirectory = true, }, - new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.7"), IsDirectory = true, }, - new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.8"), IsDirectory = true, }, - new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.9"), IsDirectory = true, }, - new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/7.0.7"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/5.0.0/"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.7/"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.8/"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.9/"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/7.0.7/"), IsDirectory = true, }, ]); var result = await DotNetSDKUtilities.FindDotNetAsync(); @@ -49,12 +49,12 @@ public async Task FindDotNet() Assert.Equal(new Path("C:/Program Files/dotnet/dotnet.exe"), result.DotNetExecutable); Assert.Equal( [ - ("5.0.0", new Path("C:/Program Files/dotnet/sdk")), - ("6.0.8", new Path("C:/Program Files/dotnet/sdk")), - ("7.0.201", new Path("C:/Program Files/dotnet/sdk")), - ("7.0.300-preview.23179.2", new Path("C:/Program Files/dotnet/sdk")), - ("7.0.304", new Path("C:/Program Files/dotnet/sdk")), - ("7.0.400-preview.23274.1", new Path("C:/Program Files/dotnet/sdk")), + ("5.0.0", new Path("C:/Program Files/dotnet/sdk/")), + ("6.0.8", new Path("C:/Program Files/dotnet/sdk/")), + ("7.0.201", new Path("C:/Program Files/dotnet/sdk/")), + ("7.0.300-preview.23179.2", new Path("C:/Program Files/dotnet/sdk/")), + ("7.0.304", new Path("C:/Program Files/dotnet/sdk/")), + ("7.0.400-preview.23274.1", new Path("C:/Program Files/dotnet/sdk/")), ], result.SDKVersions); Assert.Equal( @@ -64,45 +64,45 @@ public async Task FindDotNet() "Microsoft.AspNetCore.App", new List<(string Version, Path InstallDirectory)>() { - ("3.1.32", new Path("C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App")), - ("5.0.17", new Path("C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App")), - ("6.0.14", new Path("C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App")), - ("6.0.16", new Path("C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App")), - ("6.0.18", new Path("C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App")), - ("7.0.3", new Path("C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App")), - ("7.0.5", new Path("C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App")), - ("7.0.7", new Path("C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App")), + ("3.1.32", new Path("C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/")), + ("5.0.17", new Path("C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/")), + ("6.0.14", new Path("C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/")), + ("6.0.16", new Path("C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/")), + ("6.0.18", new Path("C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/")), + ("7.0.3", new Path("C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/")), + ("7.0.5", new Path("C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/")), + ("7.0.7", new Path("C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/")), } }, { "Microsoft.NETCore.App", new List<(string Version, Path InstallDirectory)>() { - ("3.1.32", new Path("C:/Program Files/dotnet/shared/Microsoft.NETCore.App")), - ("5.0.17", new Path("C:/Program Files/dotnet/shared/Microsoft.NETCore.App")), - ("6.0.12", new Path("C:/Program Files/dotnet/shared/Microsoft.NETCore.App")), - ("6.0.14", new Path("C:/Program Files/dotnet/shared/Microsoft.NETCore.App")), - ("6.0.15", new Path("C:/Program Files/dotnet/shared/Microsoft.NETCore.App")), - ("6.0.16", new Path("C:/Program Files/dotnet/shared/Microsoft.NETCore.App")), - ("6.0.18", new Path("C:/Program Files/dotnet/shared/Microsoft.NETCore.App")), - ("6.0.20", new Path("C:/Program Files/dotnet/shared/Microsoft.NETCore.App")), - ("7.0.3", new Path("C:/Program Files/dotnet/shared/Microsoft.NETCore.App")), - ("7.0.5", new Path("C:/Program Files/dotnet/shared/Microsoft.NETCore.App")), - ("7.0.7", new Path("C:/Program Files/dotnet/shared/Microsoft.NETCore.App")), + ("3.1.32", new Path("C:/Program Files/dotnet/shared/Microsoft.NETCore.App/")), + ("5.0.17", new Path("C:/Program Files/dotnet/shared/Microsoft.NETCore.App/")), + ("6.0.12", new Path("C:/Program Files/dotnet/shared/Microsoft.NETCore.App/")), + ("6.0.14", new Path("C:/Program Files/dotnet/shared/Microsoft.NETCore.App/")), + ("6.0.15", new Path("C:/Program Files/dotnet/shared/Microsoft.NETCore.App/")), + ("6.0.16", new Path("C:/Program Files/dotnet/shared/Microsoft.NETCore.App/")), + ("6.0.18", new Path("C:/Program Files/dotnet/shared/Microsoft.NETCore.App/")), + ("6.0.20", new Path("C:/Program Files/dotnet/shared/Microsoft.NETCore.App/")), + ("7.0.3", new Path("C:/Program Files/dotnet/shared/Microsoft.NETCore.App/")), + ("7.0.5", new Path("C:/Program Files/dotnet/shared/Microsoft.NETCore.App/")), + ("7.0.7", new Path("C:/Program Files/dotnet/shared/Microsoft.NETCore.App/")), } }, { "Microsoft.WindowsDesktop.App", new List<(string Version, Path InstallDirectory)>() { - ("3.1.32", new Path("C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App")), - ("5.0.17", new Path("C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App")), - ("6.0.14", new Path("C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App")), - ("6.0.16", new Path("C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App")), - ("6.0.18", new Path("C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App")), - ("7.0.3", new Path("C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App")), - ("7.0.5", new Path("C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App")), - ("7.0.7", new Path("C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App")), + ("3.1.32", new Path("C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/")), + ("5.0.17", new Path("C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/")), + ("6.0.14", new Path("C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/")), + ("6.0.16", new Path("C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/")), + ("6.0.18", new Path("C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/")), + ("7.0.3", new Path("C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/")), + ("7.0.5", new Path("C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/")), + ("7.0.7", new Path("C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/")), } }, }, @@ -116,11 +116,11 @@ public async Task FindDotNet() "Microsoft.NETCore.App.Ref", new List<(string Version, Path InstallDirectory)>() { - ("5.0.0", new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref")), - ("6.0.7", new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref")), - ("6.0.8", new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref")), - ("6.0.9", new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref")), - ("7.0.7", new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref")), + ("5.0.0", new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/")), + ("6.0.7", new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/")), + ("6.0.8", new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/")), + ("6.0.9", new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/")), + ("7.0.7", new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/")), } }, @@ -135,50 +135,50 @@ public async Task FindDotNet() "HIGH: Using DotNet: C:/Program Files/dotnet/dotnet.exe", "HIGH: Find DotNet SDK Versions", "INFO: C:/Program Files/dotnet/dotnet.exe --list-sdks", - "INFO: Found SDK: 5.0.0 C:/Program Files/dotnet/sdk", - "INFO: Found SDK: 6.0.8 C:/Program Files/dotnet/sdk", - "INFO: Found SDK: 7.0.201 C:/Program Files/dotnet/sdk", - "INFO: Found SDK: 7.0.300-preview.23179.2 C:/Program Files/dotnet/sdk", - "INFO: Found SDK: 7.0.304 C:/Program Files/dotnet/sdk", - "INFO: Found SDK: 7.0.400-preview.23274.1 C:/Program Files/dotnet/sdk", + "INFO: Found SDK: 5.0.0 C:/Program Files/dotnet/sdk/", + "INFO: Found SDK: 6.0.8 C:/Program Files/dotnet/sdk/", + "INFO: Found SDK: 7.0.201 C:/Program Files/dotnet/sdk/", + "INFO: Found SDK: 7.0.300-preview.23179.2 C:/Program Files/dotnet/sdk/", + "INFO: Found SDK: 7.0.304 C:/Program Files/dotnet/sdk/", + "INFO: Found SDK: 7.0.400-preview.23274.1 C:/Program Files/dotnet/sdk/", "HIGH: Find DotNet Runtime Versions", "INFO: C:/Program Files/dotnet/dotnet.exe --list-runtimes", - "INFO: Found Runtime: Microsoft.AspNetCore.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.12 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.15 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.20 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "HIGH: FindDotNetPackVersions: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref", + "INFO: Found Runtime: Microsoft.AspNetCore.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.12 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.15 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.20 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "HIGH: FindDotNetPackVersions: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/", ], testListener.Messages); // Verify expected file system requests Assert.Equal( [ - "Exists: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref", - "GetChildDirectories: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref", + "Exists: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/", + "GetChildDirectories: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/", ], mockFileSystem.Requests); diff --git a/Source/GenerateSharp/Swhere.Core.UnitTests/Utilities/NugetSDKUtilitiesUnitTests.cs b/Source/GenerateSharp/Swhere.Core.UnitTests/Utilities/NugetSDKUtilitiesUnitTests.cs index c334a1697..d1a6198cc 100644 --- a/Source/GenerateSharp/Swhere.Core.UnitTests/Utilities/NugetSDKUtilitiesUnitTests.cs +++ b/Source/GenerateSharp/Swhere.Core.UnitTests/Utilities/NugetSDKUtilitiesUnitTests.cs @@ -28,7 +28,7 @@ public void FindNugetPackages_NotFound() Assert.False(result.HasNuget); Assert.Equal( - new Path("C:/Users/Me/.nuget/packages"), + new Path("C:/Users/Me/.nuget/packages/"), result.NugetPackagesPath); Assert.Equal( [], @@ -45,7 +45,7 @@ public void FindNugetPackages_NotFound() Assert.Equal( [ "GetUserProfileDirectory", - "Exists: C:/Users/Me/.nuget/packages", + "Exists: C:/Users/Me/.nuget/packages/", ], mockFileSystem.Requests); } @@ -61,22 +61,22 @@ public void FindNugetPackages_MissingNupac() using var scopedFileSystem = new ScopedSingleton(mockFileSystem); mockFileSystem.RegisterChildren( - new Path("C:/Users/Me/.nuget/packages"), + new Path("C:/Users/Me/.nuget/packages/"), [ - new DirectoryEntry() { Path = new Path("C:/Users/Me/.nuget/packages/Package1"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Users/Me/.nuget/packages/Package1/"), IsDirectory = true, }, ]); mockFileSystem.RegisterChildren( - new Path("C:/Users/Me/.nuget/packages/Package1"), + new Path("C:/Users/Me/.nuget/packages/Package1/"), [ - new DirectoryEntry() { Path = new Path("C:/Users/Me/.nuget/packages/Package1/1.2.3"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Users/Me/.nuget/packages/Package1/1.2.3/"), IsDirectory = true, }, ]); var result = NugetSDKUtilities.FindNugetPackages(); Assert.True(result.HasNuget); Assert.Equal( - new Path("C:/Users/Me/.nuget/packages"), + new Path("C:/Users/Me/.nuget/packages/"), result.NugetPackagesPath); Assert.Equal( [], @@ -85,7 +85,7 @@ public void FindNugetPackages_MissingNupac() // Verify expected logs Assert.Equal( [ - "INFO: Discover Nuget Packages: C:/Users/Me/.nuget/packages", + "INFO: Discover Nuget Packages: C:/Users/Me/.nuget/packages/", "WARN: Missing Nuspec file: C:/Users/Me/.nuget/packages/Package1/1.2.3/Package1.nuspec", ], testListener.Messages); @@ -94,9 +94,9 @@ public void FindNugetPackages_MissingNupac() Assert.Equal( [ "GetUserProfileDirectory", - "Exists: C:/Users/Me/.nuget/packages", - "GetChildDirectories: C:/Users/Me/.nuget/packages", - "GetChildDirectories: C:/Users/Me/.nuget/packages/Package1", + "Exists: C:/Users/Me/.nuget/packages/", + "GetChildDirectories: C:/Users/Me/.nuget/packages/", + "GetChildDirectories: C:/Users/Me/.nuget/packages/Package1/", "Exists: C:/Users/Me/.nuget/packages/Package1/1.2.3/Package1.nuspec", ], mockFileSystem.Requests); @@ -113,15 +113,15 @@ public void FindNugetPackages_Success() using var scopedFileSystem = new ScopedSingleton(mockFileSystem); mockFileSystem.RegisterChildren( - new Path("C:/Users/Me/.nuget/packages"), + new Path("C:/Users/Me/.nuget/packages/"), [ - new DirectoryEntry() { Path = new Path("C:/Users/Me/.nuget/packages/Package1"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Users/Me/.nuget/packages/Package1/"), IsDirectory = true, }, ]); mockFileSystem.RegisterChildren( - new Path("C:/Users/Me/.nuget/packages/Package1"), + new Path("C:/Users/Me/.nuget/packages/Package1/"), [ - new DirectoryEntry() { Path = new Path("C:/Users/Me/.nuget/packages/Package1/1.2.3"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Users/Me/.nuget/packages/Package1/1.2.3/"), IsDirectory = true, }, ]); var nuspecContent = @@ -139,7 +139,7 @@ public void FindNugetPackages_Success() https://github.com/Package1/README.md This is the implementation of the package https://github.com/Package1/CHANGELOG.md - ©Test Corporation. All rights reserved. + �Test Corporation. All rights reserved. Test Package @@ -165,7 +165,7 @@ public void FindNugetPackages_Success() Assert.True(result.HasNuget); Assert.Equal( - new Path("C:/Users/Me/.nuget/packages"), + new Path("C:/Users/Me/.nuget/packages/"), result.NugetPackagesPath); var expectedPackages = new List() { @@ -220,7 +220,7 @@ public void FindNugetPackages_Success() // Verify expected logs Assert.Equal( [ - "INFO: Discover Nuget Packages: C:/Users/Me/.nuget/packages", + "INFO: Discover Nuget Packages: C:/Users/Me/.nuget/packages/", ], testListener.Messages); @@ -228,9 +228,9 @@ public void FindNugetPackages_Success() Assert.Equal( [ "GetUserProfileDirectory", - "Exists: C:/Users/Me/.nuget/packages", - "GetChildDirectories: C:/Users/Me/.nuget/packages", - "GetChildDirectories: C:/Users/Me/.nuget/packages/Package1", + "Exists: C:/Users/Me/.nuget/packages/", + "GetChildDirectories: C:/Users/Me/.nuget/packages/", + "GetChildDirectories: C:/Users/Me/.nuget/packages/Package1/", "Exists: C:/Users/Me/.nuget/packages/Package1/1.2.3/Package1.nuspec", "OpenRead: C:/Users/Me/.nuget/packages/Package1/1.2.3/Package1.nuspec", "Exists: C:/Users/Me/.nuget/packages/Package1/1.2.3/lib/net461/", diff --git a/Source/GenerateSharp/Swhere.Core.UnitTests/Utilities/SwhereManagerUnitTests.cs b/Source/GenerateSharp/Swhere.Core.UnitTests/Utilities/SwhereManagerUnitTests.cs index 79fa85661..0ee6ea3d4 100644 --- a/Source/GenerateSharp/Swhere.Core.UnitTests/Utilities/SwhereManagerUnitTests.cs +++ b/Source/GenerateSharp/Swhere.Core.UnitTests/Utilities/SwhereManagerUnitTests.cs @@ -47,7 +47,7 @@ public async Task Discover() mockFileSystem.RegisterChildren( new Path("C:/Program Files (x86)/Windows Kits/10/include/"), [ - new DirectoryEntry() { Path = new Path("C:/Program Files (x86)/Windows Kits/10/include/10.0.19041.0"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Program Files (x86)/Windows Kits/10/include/10.0.19041.0/"), IsDirectory = true, }, ]); mockFileSystem.CreateMockFile( @@ -55,13 +55,13 @@ public async Task Discover() new MockFile(new System.IO.MemoryStream(Encoding.UTF8.GetBytes("14.33.31629\r\n")))); mockFileSystem.RegisterChildren( - new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref"), + new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/"), [ - new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/5.0.0"), IsDirectory = true, }, - new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.7"), IsDirectory = true, }, - new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.8"), IsDirectory = true, }, - new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.9"), IsDirectory = true, }, - new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/7.0.7"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/5.0.0/"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.7/"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.8/"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.9/"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/7.0.7/"), IsDirectory = true, }, ]); bool includePrerelease = false; @@ -77,44 +77,44 @@ public async Task Discover() "HIGH: Using DotNet: C:/Program Files/dotnet/dotnet.exe", "HIGH: Find DotNet SDK Versions", "INFO: C:/Program Files/dotnet/dotnet.exe --list-sdks", - "INFO: Found SDK: 5.0.0 C:/Program Files/dotnet/sdk", - "INFO: Found SDK: 6.0.8 C:/Program Files/dotnet/sdk", - "INFO: Found SDK: 7.0.201 C:/Program Files/dotnet/sdk", - "INFO: Found SDK: 7.0.300-preview.23179.2 C:/Program Files/dotnet/sdk", - "INFO: Found SDK: 7.0.304 C:/Program Files/dotnet/sdk", - "INFO: Found SDK: 7.0.400-preview.23274.1 C:/Program Files/dotnet/sdk", + "INFO: Found SDK: 5.0.0 C:/Program Files/dotnet/sdk/", + "INFO: Found SDK: 6.0.8 C:/Program Files/dotnet/sdk/", + "INFO: Found SDK: 7.0.201 C:/Program Files/dotnet/sdk/", + "INFO: Found SDK: 7.0.300-preview.23179.2 C:/Program Files/dotnet/sdk/", + "INFO: Found SDK: 7.0.304 C:/Program Files/dotnet/sdk/", + "INFO: Found SDK: 7.0.400-preview.23274.1 C:/Program Files/dotnet/sdk/", "HIGH: Find DotNet Runtime Versions", "INFO: C:/Program Files/dotnet/dotnet.exe --list-runtimes", - "INFO: Found Runtime: Microsoft.AspNetCore.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.12 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.15 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.20 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "HIGH: FindDotNetPackVersions: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref", + "INFO: Found Runtime: Microsoft.AspNetCore.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.12 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.15 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.20 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "HIGH: FindDotNetPackVersions: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/", "INFO: C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath", - "HIGH: Using VS Installation: C:/Program Files/Microsoft Visual Studio/2022/Community", + "HIGH: Using VS Installation: C:/Program Files/Microsoft Visual Studio/2022/Community/", "HIGH: Using VC Version: 14.33.31629", "HIGH: FindNewestWindows10KitVersion: C:/Program Files (x86)/Windows Kits/10/", "INFO: CheckFile: 10.0.19041.0", @@ -128,14 +128,14 @@ public async Task Discover() [ "GetUserProfileDirectory", "Exists: C:/Users/Me/.soup/LocalUserConfig.sml", - "Exists: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref", - "GetChildDirectories: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref", + "Exists: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/", + "GetChildDirectories: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/", "Exists: C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe", "Exists: C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build/Microsoft.VCToolsVersion.default.txt", "OpenRead: C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build/Microsoft.VCToolsVersion.default.txt", "GetChildDirectories: C:/Program Files (x86)/Windows Kits/10/include/", "GetUserProfileDirectory", - "Exists: C:/Users/Me/.nuget/packages", + "Exists: C:/Users/Me/.nuget/packages/", "Exists: C:/Users/Me/.soup/", "CreateDirectory: C:/Users/Me/.soup/", "OpenWriteTruncate: C:/Users/Me/.soup/LocalUserConfig.sml", @@ -182,60 +182,60 @@ public async Task Discover() { Name: 'DotNet' SourceDirectories: [ - 'C:/Program Files/dotnet' + 'C:/Program Files/dotnet/' ] Properties: { DotNetExecutable: 'C:/Program Files/dotnet/dotnet.exe' SDKs: { - '5.0.0': 'C:/Program Files/dotnet/sdk' - '6.0.8': 'C:/Program Files/dotnet/sdk' - '7.0.201': 'C:/Program Files/dotnet/sdk' - '7.0.300-preview.23179.2': 'C:/Program Files/dotnet/sdk' - '7.0.304': 'C:/Program Files/dotnet/sdk' - '7.0.400-preview.23274.1': 'C:/Program Files/dotnet/sdk' + '5.0.0': 'C:/Program Files/dotnet/sdk/' + '6.0.8': 'C:/Program Files/dotnet/sdk/' + '7.0.201': 'C:/Program Files/dotnet/sdk/' + '7.0.300-preview.23179.2': 'C:/Program Files/dotnet/sdk/' + '7.0.304': 'C:/Program Files/dotnet/sdk/' + '7.0.400-preview.23274.1': 'C:/Program Files/dotnet/sdk/' } Runtimes: { 'Microsoft.AspNetCore.App': { - '3.1.32': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' - '5.0.17': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' - '6.0.14': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' - '6.0.16': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' - '6.0.18': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' - '7.0.3': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' - '7.0.5': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' - '7.0.7': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' + '3.1.32': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' + '5.0.17': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' + '6.0.14': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' + '6.0.16': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' + '6.0.18': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' + '7.0.3': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' + '7.0.5': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' + '7.0.7': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' } 'Microsoft.NETCore.App': { - '3.1.32': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '5.0.17': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '6.0.12': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '6.0.14': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '6.0.15': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '6.0.16': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '6.0.18': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '6.0.20': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '7.0.3': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '7.0.5': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '7.0.7': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' + '3.1.32': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '5.0.17': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '6.0.12': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '6.0.14': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '6.0.15': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '6.0.16': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '6.0.18': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '6.0.20': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '7.0.3': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '7.0.5': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '7.0.7': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' } 'Microsoft.WindowsDesktop.App': { - '3.1.32': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' - '5.0.17': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' - '6.0.14': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' - '6.0.16': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' - '6.0.18': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' - '7.0.3': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' - '7.0.5': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' - '7.0.7': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' + '3.1.32': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' + '5.0.17': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' + '6.0.14': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' + '6.0.16': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' + '6.0.18': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' + '7.0.3': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' + '7.0.5': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' + '7.0.7': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' } } TargetingPacks: { 'Microsoft.NETCore.App.Ref': { - '5.0.0': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref' - '6.0.7': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref' - '6.0.8': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref' - '6.0.9': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref' - '7.0.7': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref' + '5.0.0': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/' + '6.0.7': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/' + '6.0.8': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/' + '6.0.9': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/' + '7.0.7': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/' } } } @@ -308,7 +308,7 @@ public async Task Discover_Prerelease() mockFileSystem.RegisterChildren( new Path("C:/Program Files (x86)/Windows Kits/10/include/"), [ - new DirectoryEntry() { Path = new Path("C:/Program Files (x86)/Windows Kits/10/include/10.0.19041.0"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Program Files (x86)/Windows Kits/10/include/10.0.19041.0/"), IsDirectory = true, }, ]); mockFileSystem.CreateMockFile( @@ -316,13 +316,13 @@ public async Task Discover_Prerelease() new MockFile(new System.IO.MemoryStream(Encoding.UTF8.GetBytes("14.34.31823\r\n")))); mockFileSystem.RegisterChildren( - new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref"), + new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/"), [ - new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/5.0.0"), IsDirectory = true, }, - new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.7"), IsDirectory = true, }, - new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.8"), IsDirectory = true, }, - new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.9"), IsDirectory = true, }, - new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/7.0.7"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/5.0.0/"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.7/"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.8/"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.9/"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/7.0.7/"), IsDirectory = true, }, ]); bool includePrerelease = true; @@ -338,44 +338,44 @@ public async Task Discover_Prerelease() "HIGH: Using DotNet: C:/Program Files/dotnet/dotnet.exe", "HIGH: Find DotNet SDK Versions", "INFO: C:/Program Files/dotnet/dotnet.exe --list-sdks", - "INFO: Found SDK: 5.0.0 C:/Program Files/dotnet/sdk", - "INFO: Found SDK: 6.0.8 C:/Program Files/dotnet/sdk", - "INFO: Found SDK: 7.0.201 C:/Program Files/dotnet/sdk", - "INFO: Found SDK: 7.0.300-preview.23179.2 C:/Program Files/dotnet/sdk", - "INFO: Found SDK: 7.0.304 C:/Program Files/dotnet/sdk", - "INFO: Found SDK: 7.0.400-preview.23274.1 C:/Program Files/dotnet/sdk", + "INFO: Found SDK: 5.0.0 C:/Program Files/dotnet/sdk/", + "INFO: Found SDK: 6.0.8 C:/Program Files/dotnet/sdk/", + "INFO: Found SDK: 7.0.201 C:/Program Files/dotnet/sdk/", + "INFO: Found SDK: 7.0.300-preview.23179.2 C:/Program Files/dotnet/sdk/", + "INFO: Found SDK: 7.0.304 C:/Program Files/dotnet/sdk/", + "INFO: Found SDK: 7.0.400-preview.23274.1 C:/Program Files/dotnet/sdk/", "HIGH: Find DotNet Runtime Versions", "INFO: C:/Program Files/dotnet/dotnet.exe --list-runtimes", - "INFO: Found Runtime: Microsoft.AspNetCore.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.12 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.15 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.20 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "HIGH: FindDotNetPackVersions: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref", + "INFO: Found Runtime: Microsoft.AspNetCore.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.12 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.15 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.20 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "HIGH: FindDotNetPackVersions: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/", "INFO: C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath -prerelease", - "HIGH: Using VS Installation: C:/Program Files/Microsoft Visual Studio/2022/Preview", + "HIGH: Using VS Installation: C:/Program Files/Microsoft Visual Studio/2022/Preview/", "HIGH: Using VC Version: 14.34.31823", "HIGH: FindNewestWindows10KitVersion: C:/Program Files (x86)/Windows Kits/10/", "INFO: CheckFile: 10.0.19041.0", @@ -389,14 +389,14 @@ public async Task Discover_Prerelease() [ "GetUserProfileDirectory", "Exists: C:/Users/Me/.soup/LocalUserConfig.sml", - "Exists: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref", - "GetChildDirectories: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref", + "Exists: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/", + "GetChildDirectories: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/", "Exists: C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe", "Exists: C:/Program Files/Microsoft Visual Studio/2022/Preview/VC/Auxiliary/Build/Microsoft.VCToolsVersion.default.txt", "OpenRead: C:/Program Files/Microsoft Visual Studio/2022/Preview/VC/Auxiliary/Build/Microsoft.VCToolsVersion.default.txt", "GetChildDirectories: C:/Program Files (x86)/Windows Kits/10/include/", "GetUserProfileDirectory", - "Exists: C:/Users/Me/.nuget/packages", + "Exists: C:/Users/Me/.nuget/packages/", "Exists: C:/Users/Me/.soup/", "CreateDirectory: C:/Users/Me/.soup/", "OpenWriteTruncate: C:/Users/Me/.soup/LocalUserConfig.sml", @@ -444,60 +444,60 @@ public async Task Discover_Prerelease() { Name: 'DotNet' SourceDirectories: [ - 'C:/Program Files/dotnet' + 'C:/Program Files/dotnet/' ] Properties: { DotNetExecutable: 'C:/Program Files/dotnet/dotnet.exe' SDKs: { - '5.0.0': 'C:/Program Files/dotnet/sdk' - '6.0.8': 'C:/Program Files/dotnet/sdk' - '7.0.201': 'C:/Program Files/dotnet/sdk' - '7.0.300-preview.23179.2': 'C:/Program Files/dotnet/sdk' - '7.0.304': 'C:/Program Files/dotnet/sdk' - '7.0.400-preview.23274.1': 'C:/Program Files/dotnet/sdk' + '5.0.0': 'C:/Program Files/dotnet/sdk/' + '6.0.8': 'C:/Program Files/dotnet/sdk/' + '7.0.201': 'C:/Program Files/dotnet/sdk/' + '7.0.300-preview.23179.2': 'C:/Program Files/dotnet/sdk/' + '7.0.304': 'C:/Program Files/dotnet/sdk/' + '7.0.400-preview.23274.1': 'C:/Program Files/dotnet/sdk/' } Runtimes: { 'Microsoft.AspNetCore.App': { - '3.1.32': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' - '5.0.17': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' - '6.0.14': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' - '6.0.16': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' - '6.0.18': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' - '7.0.3': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' - '7.0.5': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' - '7.0.7': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' + '3.1.32': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' + '5.0.17': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' + '6.0.14': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' + '6.0.16': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' + '6.0.18': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' + '7.0.3': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' + '7.0.5': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' + '7.0.7': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' } 'Microsoft.NETCore.App': { - '3.1.32': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '5.0.17': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '6.0.12': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '6.0.14': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '6.0.15': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '6.0.16': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '6.0.18': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '6.0.20': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '7.0.3': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '7.0.5': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '7.0.7': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' + '3.1.32': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '5.0.17': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '6.0.12': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '6.0.14': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '6.0.15': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '6.0.16': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '6.0.18': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '6.0.20': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '7.0.3': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '7.0.5': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '7.0.7': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' } 'Microsoft.WindowsDesktop.App': { - '3.1.32': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' - '5.0.17': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' - '6.0.14': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' - '6.0.16': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' - '6.0.18': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' - '7.0.3': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' - '7.0.5': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' - '7.0.7': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' + '3.1.32': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' + '5.0.17': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' + '6.0.14': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' + '6.0.16': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' + '6.0.18': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' + '7.0.3': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' + '7.0.5': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' + '7.0.7': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' } } TargetingPacks: { 'Microsoft.NETCore.App.Ref': { - '5.0.0': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref' - '6.0.7': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref' - '6.0.8': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref' - '6.0.9': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref' - '7.0.7': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref' + '5.0.0': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/' + '6.0.7': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/' + '6.0.8': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/' + '6.0.9': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/' + '7.0.7': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/' } } } @@ -630,7 +630,7 @@ public async Task Discover_UpdateExisting() mockFileSystem.RegisterChildren( new Path("C:/Program Files (x86)/Windows Kits/10/include/"), [ - new DirectoryEntry() { Path = new Path("C:/Program Files (x86)/Windows Kits/10/include/10.0.19041.0"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Program Files (x86)/Windows Kits/10/include/10.0.19041.0/"), IsDirectory = true, }, ]); mockFileSystem.CreateMockFile( @@ -638,13 +638,13 @@ public async Task Discover_UpdateExisting() new MockFile(new System.IO.MemoryStream(Encoding.UTF8.GetBytes("14.33.31629\r\n")))); mockFileSystem.RegisterChildren( - new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref"), + new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/"), [ - new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/5.0.0"), IsDirectory = true, }, - new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.7"), IsDirectory = true, }, - new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.8"), IsDirectory = true, }, - new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.9"), IsDirectory = true, }, - new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/7.0.7"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/5.0.0/"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.7/"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.8/"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/6.0.9/"), IsDirectory = true, }, + new DirectoryEntry() { Path = new Path("C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/7.0.7/"), IsDirectory = true, }, ]); bool includePrerelease = false; @@ -658,44 +658,44 @@ public async Task Discover_UpdateExisting() "HIGH: Using DotNet: C:/Program Files/dotnet/dotnet.exe", "HIGH: Find DotNet SDK Versions", "INFO: C:/Program Files/dotnet/dotnet.exe --list-sdks", - "INFO: Found SDK: 5.0.0 C:/Program Files/dotnet/sdk", - "INFO: Found SDK: 6.0.8 C:/Program Files/dotnet/sdk", - "INFO: Found SDK: 7.0.201 C:/Program Files/dotnet/sdk", - "INFO: Found SDK: 7.0.300-preview.23179.2 C:/Program Files/dotnet/sdk", - "INFO: Found SDK: 7.0.304 C:/Program Files/dotnet/sdk", - "INFO: Found SDK: 7.0.400-preview.23274.1 C:/Program Files/dotnet/sdk", + "INFO: Found SDK: 5.0.0 C:/Program Files/dotnet/sdk/", + "INFO: Found SDK: 6.0.8 C:/Program Files/dotnet/sdk/", + "INFO: Found SDK: 7.0.201 C:/Program Files/dotnet/sdk/", + "INFO: Found SDK: 7.0.300-preview.23179.2 C:/Program Files/dotnet/sdk/", + "INFO: Found SDK: 7.0.304 C:/Program Files/dotnet/sdk/", + "INFO: Found SDK: 7.0.400-preview.23274.1 C:/Program Files/dotnet/sdk/", "HIGH: Find DotNet Runtime Versions", "INFO: C:/Program Files/dotnet/dotnet.exe --list-runtimes", - "INFO: Found Runtime: Microsoft.AspNetCore.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.12 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.15 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 6.0.20 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.NETCore.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.NETCore.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App", - "HIGH: FindDotNetPackVersions: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref", + "INFO: Found Runtime: Microsoft.AspNetCore.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.AspNetCore.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.12 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.15 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 6.0.20 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.NETCore.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.NETCore.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 3.1.32 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 5.0.17 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.14 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.16 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 6.0.18 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.3 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.5 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "INFO: Found Runtime: Microsoft.WindowsDesktop.App 7.0.7 C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/", + "HIGH: FindDotNetPackVersions: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/", "INFO: C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath", - "HIGH: Using VS Installation: C:/Program Files/Microsoft Visual Studio/2022/Community", + "HIGH: Using VS Installation: C:/Program Files/Microsoft Visual Studio/2022/Community/", "HIGH: Using VC Version: 14.33.31629", "HIGH: FindNewestWindows10KitVersion: C:/Program Files (x86)/Windows Kits/10/", "INFO: CheckFile: 10.0.19041.0", @@ -710,14 +710,14 @@ public async Task Discover_UpdateExisting() "GetUserProfileDirectory", "Exists: C:/Users/Me/.soup/LocalUserConfig.sml", "OpenRead: C:/Users/Me/.soup/LocalUserConfig.sml", - "Exists: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref", - "GetChildDirectories: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref", + "Exists: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/", + "GetChildDirectories: C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/", "Exists: C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe", "Exists: C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build/Microsoft.VCToolsVersion.default.txt", "OpenRead: C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build/Microsoft.VCToolsVersion.default.txt", "GetChildDirectories: C:/Program Files (x86)/Windows Kits/10/include/", "GetUserProfileDirectory", - "Exists: C:/Users/Me/.nuget/packages", + "Exists: C:/Users/Me/.nuget/packages/", "Exists: C:/Users/Me/.soup/", "CreateDirectory: C:/Users/Me/.soup/", "OpenWriteTruncate: C:/Users/Me/.soup/LocalUserConfig.sml", @@ -773,60 +773,60 @@ public async Task Discover_UpdateExisting() { Name: 'DotNet' SourceDirectories: [ - 'C:/Program Files/dotnet' + 'C:/Program Files/dotnet/' ] Properties: { DotNetExecutable: 'C:/Program Files/dotnet/dotnet.exe' SDKs: { - '5.0.0': 'C:/Program Files/dotnet/sdk' - '6.0.8': 'C:/Program Files/dotnet/sdk' - '7.0.201': 'C:/Program Files/dotnet/sdk' - '7.0.300-preview.23179.2': 'C:/Program Files/dotnet/sdk' - '7.0.304': 'C:/Program Files/dotnet/sdk' - '7.0.400-preview.23274.1': 'C:/Program Files/dotnet/sdk' + '5.0.0': 'C:/Program Files/dotnet/sdk/' + '6.0.8': 'C:/Program Files/dotnet/sdk/' + '7.0.201': 'C:/Program Files/dotnet/sdk/' + '7.0.300-preview.23179.2': 'C:/Program Files/dotnet/sdk/' + '7.0.304': 'C:/Program Files/dotnet/sdk/' + '7.0.400-preview.23274.1': 'C:/Program Files/dotnet/sdk/' } Runtimes: { 'Microsoft.AspNetCore.App': { - '3.1.32': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' - '5.0.17': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' - '6.0.14': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' - '6.0.16': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' - '6.0.18': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' - '7.0.3': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' - '7.0.5': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' - '7.0.7': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App' + '3.1.32': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' + '5.0.17': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' + '6.0.14': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' + '6.0.16': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' + '6.0.18': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' + '7.0.3': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' + '7.0.5': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' + '7.0.7': 'C:/Program Files/dotnet/shared/Microsoft.AspNetCore.App/' } 'Microsoft.NETCore.App': { - '3.1.32': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '5.0.17': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '6.0.12': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '6.0.14': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '6.0.15': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '6.0.16': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '6.0.18': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '6.0.20': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '7.0.3': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '7.0.5': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' - '7.0.7': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App' + '3.1.32': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '5.0.17': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '6.0.12': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '6.0.14': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '6.0.15': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '6.0.16': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '6.0.18': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '6.0.20': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '7.0.3': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '7.0.5': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' + '7.0.7': 'C:/Program Files/dotnet/shared/Microsoft.NETCore.App/' } 'Microsoft.WindowsDesktop.App': { - '3.1.32': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' - '5.0.17': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' - '6.0.14': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' - '6.0.16': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' - '6.0.18': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' - '7.0.3': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' - '7.0.5': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' - '7.0.7': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App' + '3.1.32': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' + '5.0.17': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' + '6.0.14': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' + '6.0.16': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' + '6.0.18': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' + '7.0.3': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' + '7.0.5': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' + '7.0.7': 'C:/Program Files/dotnet/shared/Microsoft.WindowsDesktop.App/' } } TargetingPacks: { 'Microsoft.NETCore.App.Ref': { - '5.0.0': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref' - '6.0.7': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref' - '6.0.8': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref' - '6.0.9': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref' - '7.0.7': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref' + '5.0.0': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/' + '6.0.7': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/' + '6.0.8': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/' + '6.0.9': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/' + '7.0.7': 'C:/Program Files/dotnet/packs/Microsoft.NETCore.App.Ref/' } } } diff --git a/Source/GenerateSharp/Swhere.Core.UnitTests/Utilities/VSWhereUtilitiesUnitTests.cs b/Source/GenerateSharp/Swhere.Core.UnitTests/Utilities/VSWhereUtilitiesUnitTests.cs index 589630a1c..4820cd05d 100644 --- a/Source/GenerateSharp/Swhere.Core.UnitTests/Utilities/VSWhereUtilitiesUnitTests.cs +++ b/Source/GenerateSharp/Swhere.Core.UnitTests/Utilities/VSWhereUtilitiesUnitTests.cs @@ -48,7 +48,7 @@ public async Task FindMSVCInstallAsync() Assert.Equal( [ "INFO: C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath", - "HIGH: Using VS Installation: C:/Program Files/Microsoft Visual Studio/2022/Community", + "HIGH: Using VS Installation: C:/Program Files/Microsoft Visual Studio/2022/Community/", "HIGH: Using VC Version: 14.33.31629", ], testListener.Messages); @@ -110,7 +110,7 @@ public async Task FindMSVCInstallAsync_Prerelease() Assert.Equal( [ "INFO: C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath -prerelease", - "HIGH: Using VS Installation: C:/Program Files/Microsoft Visual Studio/2022/Preview", + "HIGH: Using VS Installation: C:/Program Files/Microsoft Visual Studio/2022/Preview/", "HIGH: Using VC Version: 14.34.31823", ], testListener.Messages); diff --git a/Source/GenerateSharp/Swhere.Core/DotNet/DotNetSDKUtilities.cs b/Source/GenerateSharp/Swhere.Core/DotNet/DotNetSDKUtilities.cs index ae16bc6c8..c21aaf613 100644 --- a/Source/GenerateSharp/Swhere.Core/DotNet/DotNetSDKUtilities.cs +++ b/Source/GenerateSharp/Swhere.Core/DotNet/DotNetSDKUtilities.cs @@ -24,11 +24,11 @@ public static class DotNetSDKUtilities Path dotnetInstallPath; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - dotnetInstallPath = new Path("C:/Program Files/dotnet"); + dotnetInstallPath = new Path("C:/Program Files/dotnet/"); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - dotnetInstallPath = new Path("/usr/lib/dotnet"); + dotnetInstallPath = new Path("./usr/lib/dotnet/"); } else { @@ -37,9 +37,9 @@ public static class DotNetSDKUtilities // Grant access to the install folder var sourceDirectories = new List() - { - dotnetInstallPath, - }; + { + dotnetInstallPath, + }; var dotnetSDKs = await FindDotNetSDKVersionsAsync(dotnetExecutablePath); var dotnetRuntimes = await FindDotNetRuntimeVersionsAsync(dotnetExecutablePath); @@ -68,7 +68,7 @@ public static class DotNetSDKUtilities var version = sdkValue[..splitIndex]; var installationValue = sdkValue.Substring(splitIndex + 2, sdkValue.Length - splitIndex - 3); - var installationPath = new Path(installationValue); + var installationPath = Path.Parse($"{installationValue}\\"); Log.Info($"Found SDK: {version} {installationPath}"); sdks.Add((version, installationPath)); @@ -99,7 +99,7 @@ public static class DotNetSDKUtilities var name = runtimeValue[..split1Index]; var version = runtimeValue.Substring(split1Index + 1, split2Index - split1Index - 1); var installationValue = runtimeValue.Substring(split2Index + 2, runtimeValue.Length - split2Index - 3); - var installationPath = new Path(installationValue); + var installationPath = Path.Parse($"{installationValue}\\"); Log.Info($"Found Runtime: {name} {version} {installationPath}"); @@ -120,9 +120,9 @@ public static class DotNetSDKUtilities Path dotnetInstallPath) { var knownPacks = new List() - { - "Microsoft.NETCore.App.Ref", - }; + { + "Microsoft.NETCore.App.Ref", + }; var result = new Dictionary>(); foreach (var packageName in knownPacks) @@ -138,7 +138,7 @@ public static class DotNetSDKUtilities Path dotnetInstallPath, string packageName) { - var dotnetPacksPath = dotnetInstallPath + new Path($"./packs/{packageName}"); + var dotnetPacksPath = dotnetInstallPath + new Path($"./packs/{packageName}/"); // Check the default tools version Log.HighPriority("FindDotNetPackVersions: " + dotnetPacksPath.ToString()); @@ -147,7 +147,7 @@ public static class DotNetSDKUtilities { foreach (var child in LifetimeManager.Get().GetChildDirectories(dotnetPacksPath)) { - var folderName = child.Path.FileName; + var folderName = child.Path.DecomposeDirectories().Last(); versions.Add((folderName, dotnetPacksPath)); } } diff --git a/Source/GenerateSharp/Swhere.Core/Nuget/NugetSDKUtilities.cs b/Source/GenerateSharp/Swhere.Core/Nuget/NugetSDKUtilities.cs index 0bda48183..445bdbf8a 100644 --- a/Source/GenerateSharp/Swhere.Core/Nuget/NugetSDKUtilities.cs +++ b/Source/GenerateSharp/Swhere.Core/Nuget/NugetSDKUtilities.cs @@ -16,16 +16,16 @@ public static (bool HasNuget, Path NugetPackagesPath, IList Packag { var fileSystem = LifetimeManager.Get(); var nugetDirectory = fileSystem.GetUserProfileDirectory() + - new Path(".nuget"); + new Path("./.nuget/"); var nugetPackagesDirectory = nugetDirectory + - new Path("packages"); + new Path("./packages/"); if (fileSystem.Exists(nugetPackagesDirectory)) { Log.Info($"Discover Nuget Packages: {nugetPackagesDirectory}"); var packages = new List(); foreach (var nugetPackageDirectory in fileSystem.GetChildDirectories(nugetPackagesDirectory)) { - var packageName = nugetPackageDirectory.Path.FileName; + var packageName = nugetPackageDirectory.Path.DecomposeDirectories().Last(); NugetPackage? package = null; foreach (var nugetPackageVersionDirectory in fileSystem.GetChildDirectories(nugetPackageDirectory.Path)) { @@ -63,7 +63,7 @@ private static (NugetPackage?, NugetPackageVersion?) LoadNugetPackage( var fileSystem = LifetimeManager.Get(); // Load the NuSpec XML file - var nuspecFile = directory + new Path($"{name}.nuspec"); + var nuspecFile = directory + new Path($"./{name}.nuspec"); if (!fileSystem.Exists(nuspecFile)) { Log.Warning($"Missing Nuspec file: {nuspecFile}"); @@ -171,7 +171,7 @@ private static List DiscoverLibraries(Path directory, string targetFrame var fileSystem = LifetimeManager.Get(); var libraries = new List(); - var targetLibrariesPath = directory + new Path($"lib/{targetFrameworkMoniker}/"); + var targetLibrariesPath = directory + new Path($"./lib/{targetFrameworkMoniker}/"); if (fileSystem.Exists(targetLibrariesPath)) { foreach (var file in fileSystem.GetChildFiles(targetLibrariesPath)) diff --git a/Source/GenerateSharp/Swhere.Core/SwhereManager.cs b/Source/GenerateSharp/Swhere.Core/SwhereManager.cs index 0bd2b33dd..a2875fe6d 100644 --- a/Source/GenerateSharp/Swhere.Core/SwhereManager.cs +++ b/Source/GenerateSharp/Swhere.Core/SwhereManager.cs @@ -16,7 +16,7 @@ public static async Task DiscoverAsync(bool includePrerelease) { // Load up the Local User Config var localUserConfigPath = LifetimeManager.Get().GetUserProfileDirectory() + - new Path(".soup/LocalUserConfig.sml"); + new Path("./.soup/LocalUserConfig.sml"); var (loadConfigResult, userConfig) = await LocalUserConfigExtensions.TryLoadLocalUserConfigFromFileAsync(localUserConfigPath); if (!loadConfigResult) @@ -121,10 +121,10 @@ private static async Task DiscoverWindowsPlatformAsync(bool includePrerelease, L if (hasNuget) { var nugetSDK = userConfig.EnsureSDK("Nuget"); - nugetSDK.SourceDirectories.AddRange( + nugetSDK.SourceDirectories = [ nugetPackagesPath, - ]); + ]; nugetSDK.SetProperties( new Dictionary() diff --git a/Source/GenerateSharp/Swhere.Core/VSWhereUtilities.cs b/Source/GenerateSharp/Swhere.Core/VSWhereUtilities.cs index fd7f7fc12..ccd46fc2c 100644 --- a/Source/GenerateSharp/Swhere.Core/VSWhereUtilities.cs +++ b/Source/GenerateSharp/Swhere.Core/VSWhereUtilities.cs @@ -98,7 +98,7 @@ private static async Task FindVSInstallRootAsync(string requires, bool inc throw new HandledException(); } - return new Path(path); + return Path.Parse($"{path}\\"); } private static async Task FindDefaultVCToolsVersionAsync( @@ -106,7 +106,7 @@ private static async Task FindDefaultVCToolsVersionAsync( { // Check the default tools version var visualCompilerToolsDefaultVersionFile = - visualStudioInstallRoot + new Path("VC/Auxiliary/Build/Microsoft.VCToolsVersion.default.txt"); + visualStudioInstallRoot + new Path("./VC/Auxiliary/Build/Microsoft.VCToolsVersion.default.txt"); if (!LifetimeManager.Get().Exists(visualCompilerToolsDefaultVersionFile)) { Log.Error("VisualCompilerToolsDefaultVersionFile file does not exist: " + visualCompilerToolsDefaultVersionFile.ToString()); diff --git a/Source/GenerateSharp/Swhere.Core/WhereIsUtilities.cs b/Source/GenerateSharp/Swhere.Core/WhereIsUtilities.cs index ce6ca959a..53664fa12 100644 --- a/Source/GenerateSharp/Swhere.Core/WhereIsUtilities.cs +++ b/Source/GenerateSharp/Swhere.Core/WhereIsUtilities.cs @@ -23,7 +23,7 @@ public static async Task FindExecutableAsync(string name) } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - executablePath = new Path("/usr/bin/whereis"); + executablePath = new Path("./usr/bin/whereis"); separator = " "; // Whereis sets the name as the first entry @@ -52,6 +52,6 @@ public static async Task FindExecutableAsync(string name) throw new HandledException(); } - return new Path(values.First()); + return Path.Parse(values.First()); } } \ No newline at end of file diff --git a/Source/GenerateSharp/Swhere.Core/WindowsSDKUtilities.cs b/Source/GenerateSharp/Swhere.Core/WindowsSDKUtilities.cs index 08bfc6659..06213b589 100644 --- a/Source/GenerateSharp/Swhere.Core/WindowsSDKUtilities.cs +++ b/Source/GenerateSharp/Swhere.Core/WindowsSDKUtilities.cs @@ -32,7 +32,7 @@ private static string FindNewestWindows10KitVersion(Path windowsSDKInstallPath) var currentVersion = new SemanticVersion(0, 0, 0); foreach (var child in LifetimeManager.Get().GetChildDirectories(windowsSDKIncludePath)) { - var name = child.Path.FileName; + var name = child.Path.DecomposeDirectories().Last(); Log.Info("CheckFile: " + name); var platformVersion = name[..3]; if (platformVersion == "10.") diff --git a/Source/GenerateSharp/Utilities/BuildConstants.cs b/Source/GenerateSharp/Utilities/BuildConstants.cs index 3de24ef4a..2ef999480 100644 --- a/Source/GenerateSharp/Utilities/BuildConstants.cs +++ b/Source/GenerateSharp/Utilities/BuildConstants.cs @@ -14,45 +14,45 @@ public static class BuildConstants /// /// Gets the Recipe file name /// - public static Path RecipeFileName => new Path("Recipe.sml"); + public static Path RecipeFileName => new Path("./Recipe.sml"); /// /// Gets the Package Lock file name /// - public static Path PackageLockFileName => new Path("PackageLock.sml"); + public static Path PackageLockFileName => new Path("./PackageLock.sml"); /// /// Gets the Generate Parameters Value Table file name /// - public static Path GenerateParametersFileName => new Path("GenerateParameters.bvt"); + public static Path GenerateParametersFileName => new Path("./GenerateParameters.bvt"); /// /// Gets the Generate Read Access file name /// - public static Path GenerateReadAccessFileName => new Path("GenerateReadAccess.txt"); + public static Path GenerateReadAccessFileName => new Path("./GenerateReadAccess.txt"); /// /// Gets the Generate Write Access file name /// - public static Path GenerateWriteAccessFileName => new Path("GenerateWriteAccess.txt"); + public static Path GenerateWriteAccessFileName => new Path("./GenerateWriteAccess.txt"); /// /// Gets the Generate Shared State Value Table file name /// - public static Path GenerateSharedStateFileName => new Path("GenerateSharedState.bvt"); + public static Path GenerateSharedStateFileName => new Path("./GenerateSharedState.bvt"); /// /// Gets the Evaluate Graph file name /// - public static Path EvaluateGraphFileName => new Path("Evaluate.bog"); + public static Path EvaluateGraphFileName => new Path("./Evaluate.bog"); /// /// Gets the Evaluate Results file name /// - public static Path EvaluateResultsFileName => new Path("Evaluate.bor"); + public static Path EvaluateResultsFileName => new Path("./Evaluate.bor"); /// /// Gets the Generate info Value Table file name /// - public static Path GenerateInfoFileName => new Path("GenerateInfo.bvt"); + public static Path GenerateInfoFileName => new Path("./GenerateInfo.bvt"); } \ No newline at end of file diff --git a/Source/GenerateTest/PackageLock.sml b/Source/GenerateTest/PackageLock.sml index d79466383..a80c4e0e8 100644 --- a/Source/GenerateTest/PackageLock.sml +++ b/Source/GenerateTest/PackageLock.sml @@ -6,7 +6,7 @@ Closures: { 'Monitor.Shared': { Version: '../Monitor/Shared/', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|CryptoPP': { Version: '1.2.2', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|Detours': { Version: '4.0.12', Build: 'Build0', Tool: 'Tool0' } - 'mwasplund|Opal': { Version: '0.10.2', Build: 'Build0', Tool: 'Tool0' } + 'mwasplund|Opal': { Version: '0.11.0', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|reflex': { Version: '1.0.4', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|Soup.Test.Assert': { Version: '0.4.0', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|wren': { Version: '1.0.5', Build: 'Build0', Tool: 'Tool0' } diff --git a/Source/Monitor/Client/ConnectionManagerBase.h b/Source/Monitor/Client/ConnectionManagerBase.h index 0c5459adb..cf9c2ffe5 100644 --- a/Source/Monitor/Client/ConnectionManagerBase.h +++ b/Source/Monitor/Client/ConnectionManagerBase.h @@ -1,6 +1,7 @@ #pragma once #include "Helpers.h" +#include "MessageBuilder.h" namespace Monitor { @@ -8,10 +9,12 @@ namespace Monitor { private: std::mutex pipeMutex; + bool hadError; public: ConnectionManagerBase() : - pipeMutex() + pipeMutex(), + hadError(false) { DebugTrace("ConnectionManagerBase::ConnectionManagerBase"); } @@ -38,7 +41,12 @@ namespace Monitor Message message; message.Type = MessageType::Shutdown; message.ContentSize = 0; - UnsafeWriteMessage(message); + MessageBuilder::AppendValue(message, hadError); + if (!TryUnsafeWriteMessage(message)) + { + // Not much we can do at the end... + hadError = true; + } Disconnect(); } @@ -46,17 +54,30 @@ namespace Monitor void WriteMessage(const Message& message) { auto lock = std::lock_guard(pipeMutex); - UnsafeWriteMessage(message); + if (!TryUnsafeWriteMessage(message)) + { + // Save the failure for the final response + hadError = true; + } } void DebugError(std::string_view message, uint32_t value) { + #ifdef TRACE_DETOUR_CLIENT printf("DETOUR-CLIENT-ERROR: %s %u\n", message.data(), value); + #else + (message); + (value); + #endif } void DebugError(std::string_view message) { + #ifdef TRACE_DETOUR_CLIENT printf("DETOUR-CLIENT-ERROR: %s\n", message.data()); + #else + (message); + #endif } #ifdef TRACE_DETOUR_CLIENT @@ -77,6 +98,6 @@ namespace Monitor protected: virtual void Connect(int32_t traceProcessId) = 0; virtual void Disconnect() = 0; - virtual void UnsafeWriteMessage(const Message& message) = 0; + virtual bool TryUnsafeWriteMessage(const Message& message) = 0; }; } \ No newline at end of file diff --git a/Source/Monitor/Client/FileSystemAccessSandbox.h b/Source/Monitor/Client/FileSystemAccessSandbox.h index 33506a8d7..7ce3e2602 100644 --- a/Source/Monitor/Client/FileSystemAccessSandbox.h +++ b/Source/Monitor/Client/FileSystemAccessSandbox.h @@ -36,7 +36,7 @@ namespace Monitor static void UpdateWorkingDirectory(const char* fileName) { - auto path = Opal::Path(fileName); + auto path = Opal::Path::Parse(fileName); if (!path.HasRoot()) { // Updated working directory is relative to the previous one @@ -130,7 +130,7 @@ namespace Monitor static std::string NormalizePath(const char* fileName) { // Normalize the path separators and get absolute path - auto path = Opal::Path(fileName); + auto path = Opal::Path::Parse(fileName); if (!path.HasRoot()) { path = m_workingDirectory + path; diff --git a/Source/Monitor/Client/Linux/ConnectionManager.h b/Source/Monitor/Client/Linux/ConnectionManager.h index 7754943f2..48628c251 100644 --- a/Source/Monitor/Client/Linux/ConnectionManager.h +++ b/Source/Monitor/Client/Linux/ConnectionManager.h @@ -29,9 +29,9 @@ namespace Monitor::Linux close(pipeHandle); } - virtual void UnsafeWriteMessage(const Message& message) + virtual bool TryUnsafeWriteMessage(const Message& message) { - DebugTrace("ConnectionManager::UnsafeWriteMessage"); + DebugTrace("ConnectionManager::TryUnsafeWriteMessage"); // Write the message size_t countBytesToWrite = message.ContentSize + @@ -44,14 +44,16 @@ namespace Monitor::Linux if (countBytesWritten == -1) { DebugError("Failed write event logger"); - exit(-1234); + return false; } if (countBytesWritten != countBytesToWrite) { DebugError("Did not write the expected number of bytes"); - exit(-1234); + return false; } + + return true; } private: diff --git a/Source/Monitor/Client/MessageBuilder.h b/Source/Monitor/Client/MessageBuilder.h new file mode 100644 index 000000000..cb705811f --- /dev/null +++ b/Source/Monitor/Client/MessageBuilder.h @@ -0,0 +1,125 @@ +#pragma once + +namespace Monitor +{ + class MessageBuilder + { + public: + static void AppendValue(Message& message, const char* value) + { + if (value != nullptr) + { + auto stringValue = std::string_view(value); + auto startIndex = message.ContentSize; + auto size = stringValue.size() + 1; + message.ContentSize += static_cast(size); + if (message.ContentSize > sizeof(message.Content)) + { + throw std::runtime_error("Message content too long for const char* value"); + } + + stringValue.copy(reinterpret_cast(message.Content + startIndex), stringValue.size()); + message.Content[message.ContentSize - 1] = 0; + } + else + { + AppendValue(message, ""); + } + } + + static void AppendValue(Message& message, const wchar_t* value) + { + if (value != nullptr) + { + auto stringValue = std::wstring_view(value); + auto startIndex = message.ContentSize; + auto size = 2 * (stringValue.size() + 1); + message.ContentSize += static_cast(size); + if (message.ContentSize > sizeof(message.Content)) + { + throw std::runtime_error("Message content too long for const wchar_t* value"); + } + + stringValue.copy(reinterpret_cast(message.Content + startIndex), stringValue.size()); + message.Content[message.ContentSize - 1] = 0; + message.Content[message.ContentSize - 2] = 0; + } + else + { + AppendValue(message, L""); + } + } + + static void AppendValue(Message& message, void* value) + { + auto startIndex = message.ContentSize; + message.ContentSize += sizeof(void*); + if (message.ContentSize > sizeof(message.Content)) + { + throw std::runtime_error("Message content too long for void* value"); + } + + *reinterpret_cast(message.Content + startIndex) = value; + } + + static void AppendValue(Message& message, int value) + { + auto startIndex = message.ContentSize; + message.ContentSize += sizeof(int); + if (message.ContentSize > sizeof(message.Content)) + { + throw std::runtime_error("Message content too long for int value"); + } + + *reinterpret_cast(message.Content + startIndex) = value; + } + + static void AppendValue(Message& message, long value) + { + auto startIndex = message.ContentSize; + message.ContentSize += sizeof(long); + if (message.ContentSize > sizeof(message.Content)) + { + throw std::runtime_error("Message content too long for long value"); + } + + *reinterpret_cast(message.Content + startIndex) = value; + } + + static void AppendValue(Message& message, unsigned int value) + { + auto startIndex = message.ContentSize; + message.ContentSize += sizeof(unsigned int); + if (message.ContentSize > sizeof(message.Content)) + { + throw std::runtime_error("Message content too long for unsigned int value"); + } + + *reinterpret_cast(message.Content + startIndex) = value; + } + + static void AppendValue(Message& message, unsigned long value) + { + auto startIndex = message.ContentSize; + message.ContentSize += sizeof(unsigned long); + if (message.ContentSize > sizeof(message.Content)) + { + throw std::runtime_error("Message content too long for unsigned long value"); + } + + *reinterpret_cast(message.Content + startIndex) = value; + } + + static void AppendValue(Message& message, unsigned long long value) + { + auto startIndex = message.ContentSize; + message.ContentSize += sizeof(unsigned long long); + if (message.ContentSize > sizeof(message.Content)) + { + throw std::runtime_error("Message content too long for unsigned long long value"); + } + + *reinterpret_cast(message.Content + startIndex) = value; + } + }; +} \ No newline at end of file diff --git a/Source/Monitor/Client/MessageSender.h b/Source/Monitor/Client/MessageSender.h index 3b3a01890..c9a23692e 100644 --- a/Source/Monitor/Client/MessageSender.h +++ b/Source/Monitor/Client/MessageSender.h @@ -1,4 +1,5 @@ #pragma once +#include "MessageBuilder.h" namespace Monitor { @@ -22,47 +23,12 @@ namespace Monitor void AppendValue(const char* value) { - if (value != nullptr) - { - auto stringValue = std::string_view(value); - auto startIndex = message.ContentSize; - auto size = stringValue.size() + 1; - message.ContentSize += static_cast(size); - if (message.ContentSize > sizeof(message.Content)) - { - throw std::runtime_error("Message content too long for const char* value"); - } - - stringValue.copy(reinterpret_cast(message.Content + startIndex), stringValue.size()); - message.Content[message.ContentSize - 1] = 0; - } - else - { - AppendValue(""); - } + MessageBuilder::AppendValue(message, value); } void AppendValue(const wchar_t* value) { - if (value != nullptr) - { - auto stringValue = std::wstring_view(value); - auto startIndex = message.ContentSize; - auto size = 2 * (stringValue.size() + 1); - message.ContentSize += static_cast(size); - if (message.ContentSize > sizeof(message.Content)) - { - throw std::runtime_error("Message content too long for const wchar_t* value"); - } - - stringValue.copy(reinterpret_cast(message.Content + startIndex), stringValue.size()); - message.Content[message.ContentSize - 1] = 0; - message.Content[message.ContentSize - 2] = 0; - } - else - { - AppendValue(L""); - } + MessageBuilder::AppendValue(message, value); } void AppendValue(void* value) @@ -79,62 +45,27 @@ namespace Monitor void AppendValue(int value) { - auto startIndex = message.ContentSize; - message.ContentSize += sizeof(int); - if (message.ContentSize > sizeof(message.Content)) - { - throw std::runtime_error("Message content too long for int value"); - } - - *reinterpret_cast(message.Content + startIndex) = value; + MessageBuilder::AppendValue(message, value); } void AppendValue(long value) { - auto startIndex = message.ContentSize; - message.ContentSize += sizeof(long); - if (message.ContentSize > sizeof(message.Content)) - { - throw std::runtime_error("Message content too long for long value"); - } - - *reinterpret_cast(message.Content + startIndex) = value; + MessageBuilder::AppendValue(message, value); } void AppendValue(unsigned int value) { - auto startIndex = message.ContentSize; - message.ContentSize += sizeof(unsigned int); - if (message.ContentSize > sizeof(message.Content)) - { - throw std::runtime_error("Message content too long for unsigned int value"); - } - - *reinterpret_cast(message.Content + startIndex) = value; + MessageBuilder::AppendValue(message, value); } void AppendValue(unsigned long value) { - auto startIndex = message.ContentSize; - message.ContentSize += sizeof(unsigned long); - if (message.ContentSize > sizeof(message.Content)) - { - throw std::runtime_error("Message content too long for unsigned long value"); - } - - *reinterpret_cast(message.Content + startIndex) = value; + MessageBuilder::AppendValue(message, value); } void AppendValue(unsigned long long value) { - auto startIndex = message.ContentSize; - message.ContentSize += sizeof(unsigned long long); - if (message.ContentSize > sizeof(message.Content)) - { - throw std::runtime_error("Message content too long for unsigned long long value"); - } - - *reinterpret_cast(message.Content + startIndex) = value; + MessageBuilder::AppendValue(message, value); } }; } \ No newline at end of file diff --git a/Source/Monitor/Client/PackageLock.sml b/Source/Monitor/Client/PackageLock.sml index 2c1c9edfc..107c81e75 100644 --- a/Source/Monitor/Client/PackageLock.sml +++ b/Source/Monitor/Client/PackageLock.sml @@ -5,7 +5,7 @@ Closures: { 'Monitor.Client': { Version: '../Client', Build: 'Build0', Tool: 'Tool0' } 'Monitor.Shared': { Version: '../Shared/', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|Detours': { Version: '4.0.12', Build: 'Build0', Tool: 'Tool0' } - 'mwasplund|Opal': { Version: '0.10.2', Build: 'Build0', Tool: 'Tool0' } + 'mwasplund|Opal': { Version: '0.11.0', Build: 'Build0', Tool: 'Tool0' } } } Build0: { diff --git a/Source/Monitor/Client/Windows/ConnectionManager.h b/Source/Monitor/Client/Windows/ConnectionManager.h index 4b586a58d..fc29dcb8f 100644 --- a/Source/Monitor/Client/Windows/ConnectionManager.h +++ b/Source/Monitor/Client/Windows/ConnectionManager.h @@ -7,6 +7,7 @@ namespace Monitor::Windows { class ConnectionManager : public ConnectionManagerBase { + private: public: ConnectionManager() : ConnectionManagerBase(), @@ -87,12 +88,12 @@ namespace Monitor::Windows } } - virtual void UnsafeWriteMessage(const Message& message) + virtual bool TryUnsafeWriteMessage(const Message& message) { if (pipeHandle == INVALID_HANDLE_VALUE) { DebugError("Handle not ready", (uint32_t)message.Type); - exit(-1234); + return false; } // Write the message @@ -107,15 +108,19 @@ namespace Monitor::Windows &countBytesWritten, nullptr)) { + int error = GetLastError(); + (error); DebugError("Failed write event logger"); - exit(-1234); + return false; } if (countBytesWritten != countBytesToWrite) { DebugError("Did not write the expected number of bytes"); - exit(-1234); + return false; } + + return true; } private: diff --git a/Source/Monitor/Host/Linux/DetourCallbackLogger.h b/Source/Monitor/Host/Linux/DetourCallbackLogger.h index 1b8b193fb..0589e2343 100644 --- a/Source/Monitor/Host/Linux/DetourCallbackLogger.h +++ b/Source/Monitor/Host/Linux/DetourCallbackLogger.h @@ -16,9 +16,9 @@ namespace Monitor::Linux m_stream << "Initialize: " << std::endl; } - virtual void OnShutdown() override final + virtual void OnShutdown(bool hadError) override final { - m_stream << "Shutdown: " << std::endl; + m_stream << "Shutdown: " << hadError << std::endl; } virtual void OnError(std::string_view message) override final diff --git a/Source/Monitor/Host/Linux/DetourForkCallback.h b/Source/Monitor/Host/Linux/DetourForkCallback.h index dc539c43d..b4e7dbe6b 100644 --- a/Source/Monitor/Host/Linux/DetourForkCallback.h +++ b/Source/Monitor/Host/Linux/DetourForkCallback.h @@ -17,10 +17,10 @@ namespace Monitor::Linux _callback2->OnInitialize(); } - void OnShutdown() override final + void OnShutdown(bool hadError) override final { - _callback1->OnShutdown(); - _callback2->OnShutdown(); + _callback1->OnShutdown(hadError); + _callback2->OnShutdown(hadError); } void OnError(std::string_view message) override final diff --git a/Source/Monitor/Host/Linux/DetourMonitorCallback.h b/Source/Monitor/Host/Linux/DetourMonitorCallback.h index 3a7e655d4..2d02ff81e 100644 --- a/Source/Monitor/Host/Linux/DetourMonitorCallback.h +++ b/Source/Monitor/Host/Linux/DetourMonitorCallback.h @@ -21,9 +21,11 @@ namespace Monitor::Linux Log::Diag("DetourMonitorCallback::OnInitialize"); } - void OnShutdown() override final + void OnShutdown(bool hadError) override final { - Log::Diag("DetourMonitorCallback::OnShutdown"); + Log::Diag("DetourMonitorCallback::OnShutdown {}", hadError); + + // TODO: Exit with failure } void OnError(std::string_view message) override final @@ -178,7 +180,7 @@ namespace Monitor::Linux // Verify not a special file if (!IsSpecialFile(fileName)) { - _callback->TouchFileRead(Path(fileName), exists, wasBlocked); + _callback->TouchFileRead(Path::Parse(fileName), exists, wasBlocked); } } @@ -193,7 +195,7 @@ namespace Monitor::Linux // Verify not a special file if (!IsSpecialFile(fileName)) { - _callback->TouchFileWrite(Path(fileName), wasBlocked); + _callback->TouchFileWrite(Path::Parse(fileName), wasBlocked); } } @@ -205,7 +207,7 @@ namespace Monitor::Linux void TouchFileDelete(std::string_view fileName, bool wasBlocked) { - _callback->TouchFileDelete(Path(fileName), wasBlocked); + _callback->TouchFileDelete(Path::Parse(fileName), wasBlocked); } void TouchFileDeleteOnClose(std::wstring_view fileName) @@ -216,7 +218,7 @@ namespace Monitor::Linux void TouchFileDeleteOnClose(std::string_view fileName) { - _callback->TouchFileDeleteOnClose(Path(fileName)); + _callback->TouchFileDeleteOnClose(Path::Parse(fileName)); } bool IsSpecialFile(std::string_view fileName) diff --git a/Source/Monitor/Host/Linux/EventListener.h b/Source/Monitor/Host/Linux/EventListener.h index 612997b1a..416f9509a 100644 --- a/Source/Monitor/Host/Linux/EventListener.h +++ b/Source/Monitor/Host/Linux/EventListener.h @@ -41,7 +41,8 @@ namespace Monitor::Linux } case MessageType::Shutdown: { - m_callback->OnShutdown(); + auto hadError = ReadBoolValue(message, offset); + m_callback->OnShutdown(hadError); break; } case MessageType::Error: diff --git a/Source/Monitor/Host/Linux/IDetourCallback.h b/Source/Monitor/Host/Linux/IDetourCallback.h index 5e8cac3f1..7131a8936 100644 --- a/Source/Monitor/Host/Linux/IDetourCallback.h +++ b/Source/Monitor/Host/Linux/IDetourCallback.h @@ -6,7 +6,7 @@ namespace Monitor::Linux { public: virtual void OnInitialize() = 0; - virtual void OnShutdown() = 0; + virtual void OnShutdown(bool hadError) = 0; virtual void OnError(std::string_view message) = 0; // FileApi diff --git a/Source/Monitor/Host/Linux/LinuxMonitorProcess.h b/Source/Monitor/Host/Linux/LinuxMonitorProcess.h index 410fe4d46..57d8e0692 100644 --- a/Source/Monitor/Host/Linux/LinuxMonitorProcess.h +++ b/Source/Monitor/Host/Linux/LinuxMonitorProcess.h @@ -337,7 +337,7 @@ namespace Monitor::Linux void LogMessage(Message& message) { DebugTrace("LogMessage"); - m_eventListener.LogMessage(message); + m_eventListener.SafeLogMessage(message); } void DebugTrace(std::string_view message, uint32_t value) diff --git a/Source/Monitor/Host/Windows/DetourCallbackLogger.h b/Source/Monitor/Host/Windows/DetourCallbackLogger.h index fbbc4d6e3..1bf3ef49a 100644 --- a/Source/Monitor/Host/Windows/DetourCallbackLogger.h +++ b/Source/Monitor/Host/Windows/DetourCallbackLogger.h @@ -16,9 +16,9 @@ namespace Monitor::Windows m_stream << "Initialize: " << std::endl; } - virtual void OnShutdown() override final + virtual void OnShutdown(bool hadError) override final { - m_stream << "Shutdown: " << std::endl; + m_stream << "Shutdown: " << hadError << std::endl; } virtual void OnError(std::string_view message) override final diff --git a/Source/Monitor/Host/Windows/DetourForkCallback.h b/Source/Monitor/Host/Windows/DetourForkCallback.h index ff1d951f7..0efe286d7 100644 --- a/Source/Monitor/Host/Windows/DetourForkCallback.h +++ b/Source/Monitor/Host/Windows/DetourForkCallback.h @@ -17,10 +17,10 @@ namespace Monitor::Windows _callback2->OnInitialize(); } - void OnShutdown() override final + void OnShutdown(bool hadError) override final { - _callback1->OnShutdown(); - _callback2->OnShutdown(); + _callback1->OnShutdown(hadError); + _callback2->OnShutdown(hadError); } void OnError(std::string_view message) override final diff --git a/Source/Monitor/Host/Windows/DetourMonitorCallback.h b/Source/Monitor/Host/Windows/DetourMonitorCallback.h index 2197606f8..0b6ce5555 100644 --- a/Source/Monitor/Host/Windows/DetourMonitorCallback.h +++ b/Source/Monitor/Host/Windows/DetourMonitorCallback.h @@ -21,9 +21,9 @@ namespace Monitor::Windows Log::Diag("DetourMonitorCallback::OnInitialize"); } - void OnShutdown() override final + void OnShutdown(bool hadError) override final { - Log::Diag("DetourMonitorCallback::OnShutdown"); + Log::Diag("DetourMonitorCallback::OnShutdown {}", hadError); } void OnError(std::string_view message) override final @@ -1187,7 +1187,7 @@ namespace Monitor::Windows // Verify not a special file if (!IsSpecialFile(fileName)) { - _callback->TouchFileRead(Path(fileName), exists, wasBlocked); + _callback->TouchFileRead(Path::Parse(fileName), exists, wasBlocked); } } @@ -1202,7 +1202,7 @@ namespace Monitor::Windows // Verify not a special file if (!IsSpecialFile(fileName)) { - _callback->TouchFileWrite(Path(fileName), wasBlocked); + _callback->TouchFileWrite(Path::Parse(fileName), wasBlocked); } } @@ -1214,7 +1214,7 @@ namespace Monitor::Windows void TouchFileDelete(std::string_view fileName, bool wasBlocked) { - _callback->TouchFileDelete(Path(fileName), wasBlocked); + _callback->TouchFileDelete(Path::Parse(fileName), wasBlocked); } void TouchFileDeleteOnClose(std::wstring_view fileName) @@ -1225,7 +1225,7 @@ namespace Monitor::Windows void TouchFileDeleteOnClose(std::string_view fileName) { - _callback->TouchFileDeleteOnClose(Path(fileName)); + _callback->TouchFileDeleteOnClose(Path::Parse(fileName)); } bool IsSpecialFile(std::string_view fileName) diff --git a/Source/Monitor/Host/Windows/EventListener.h b/Source/Monitor/Host/Windows/EventListener.h index 4dc7e94fc..27cd89d71 100644 --- a/Source/Monitor/Host/Windows/EventListener.h +++ b/Source/Monitor/Host/Windows/EventListener.h @@ -28,6 +28,19 @@ namespace Monitor::Windows m_callback->OnError(message); } + void SafeLogMessage(Message& message) + { + try + { + LogMessage(message); + } + catch (std::exception& ex) + { + Log::Error("Event Listener encountered invalid message: {}", ex.what()); + } + } + + private: void LogMessage(Message& message) { uint32_t offset = 0; @@ -41,7 +54,8 @@ namespace Monitor::Windows } case MessageType::Shutdown: { - m_callback->OnShutdown(); + auto hadError = ReadBoolValue(message, offset); + m_callback->OnShutdown(hadError); break; } case MessageType::Error: @@ -68,7 +82,6 @@ namespace Monitor::Windows } } - private: void HandleDetourMessage(Message& message, uint32_t& offset) { auto eventType = static_cast(ReadUInt32Value(message, offset)); @@ -1504,6 +1517,8 @@ namespace Monitor::Windows bool ReadBoolValue(Message& message, uint32_t& offset) { + if (offset >= message.ContentSize) + throw std::runtime_error("ReadBoolValue missing required field"); auto result = *reinterpret_cast(message.Content + offset); offset += sizeof(uint32_t); if (offset > message.ContentSize) @@ -1513,6 +1528,8 @@ namespace Monitor::Windows int32_t ReadInt32Value(Message& message, uint32_t& offset) { + if (offset >= message.ContentSize) + throw std::runtime_error("ReadInt32Value missing required field"); auto result = *reinterpret_cast(message.Content + offset); offset += sizeof(int32_t); if (offset > message.ContentSize) @@ -1522,6 +1539,8 @@ namespace Monitor::Windows uint32_t ReadUInt32Value(Message& message, uint32_t& offset) { + if (offset >= message.ContentSize) + throw std::runtime_error("ReadUInt32Value missing required field"); auto result = *reinterpret_cast(message.Content + offset); offset += sizeof(uint32_t); if (offset > message.ContentSize) @@ -1531,6 +1550,8 @@ namespace Monitor::Windows uint64_t ReadUInt64Value(Message& message, uint32_t& offset) { + if (offset >= message.ContentSize) + throw std::runtime_error("ReadUInt64Value missing required field"); auto result = *reinterpret_cast(message.Content + offset); offset += sizeof(uint64_t); if (offset > message.ContentSize) @@ -1540,6 +1561,8 @@ namespace Monitor::Windows std::string_view ReadStringValue(Message& message, uint32_t& offset) { + if (offset >= message.ContentSize) + throw std::runtime_error("ReadStringValue missing required field"); auto result = std::string_view(reinterpret_cast(message.Content + offset)); offset += static_cast(result.size()) + 1; if (offset > message.ContentSize) @@ -1549,10 +1572,12 @@ namespace Monitor::Windows std::wstring_view ReadWStringValue(Message& message, uint32_t& offset) { + if (offset >= message.ContentSize) + throw std::runtime_error("ReadWStringValue missing required field"); auto result = std::wstring_view(reinterpret_cast(message.Content + offset)); offset += 2 * (static_cast(result.size()) + 1); if (offset > message.ContentSize) - throw std::runtime_error("ReadWStringValue past end of content"); + throw std::runtime_error("ReadWStringValue read past end of content"); return result; } diff --git a/Source/Monitor/Host/Windows/IDetourCallback.h b/Source/Monitor/Host/Windows/IDetourCallback.h index c13da4cba..136dad2d3 100644 --- a/Source/Monitor/Host/Windows/IDetourCallback.h +++ b/Source/Monitor/Host/Windows/IDetourCallback.h @@ -6,7 +6,7 @@ namespace Monitor::Windows { public: virtual void OnInitialize() = 0; - virtual void OnShutdown() = 0; + virtual void OnShutdown(bool hadError) = 0; virtual void OnError(std::string_view message) = 0; // FileApi diff --git a/Source/Monitor/Host/Windows/WindowsMonitorProcess.h b/Source/Monitor/Host/Windows/WindowsMonitorProcess.h index 69213d781..c96765504 100644 --- a/Source/Monitor/Host/Windows/WindowsMonitorProcess.h +++ b/Source/Monitor/Host/Windows/WindowsMonitorProcess.h @@ -181,7 +181,7 @@ namespace Monitor::Windows // Build up the Monitor dlls absolute path auto moduleName = System::IProcessManager::Current().GetCurrentProcessFileName(); auto moduleFolder = moduleName.GetParent(); - auto dllPath = moduleFolder + Path("Monitor.Client.64.dll"); + auto dllPath = moduleFolder + Path("./Monitor.Client.64.dll"); auto dllPathString = dllPath.ToAlternateString(); // Build up the new environment @@ -772,7 +772,7 @@ namespace Monitor::Windows void LogMessage(Message& message) { DebugTrace("LogMessage"); - m_eventListener.LogMessage(message); + m_eventListener.SafeLogMessage(message); } void CleanupConnections() diff --git a/Source/Tools/Mkdir/Main.cpp b/Source/Tools/Mkdir/Main.cpp index 6ab2bb6e0..f8d54fd70 100644 --- a/Source/Tools/Mkdir/Main.cpp +++ b/Source/Tools/Mkdir/Main.cpp @@ -19,7 +19,7 @@ int main(int argc, char** argv) try { - auto directory = Opal::Path(argv[1]); + auto directory = Opal::Path::Parse(argv[1]); auto fileSystem = Opal::System::STLFileSystem(); if (fileSystem.Exists(directory)) diff --git a/Source/Tools/Mkdir/PackageLock.sml b/Source/Tools/Mkdir/PackageLock.sml index c778fcce8..75a835eb3 100644 --- a/Source/Tools/Mkdir/PackageLock.sml +++ b/Source/Tools/Mkdir/PackageLock.sml @@ -3,7 +3,7 @@ Closures: { Root: { 'C++': { mkdir: { Version: '../Mkdir', Build: 'Build0', Tool: 'Tool0' } - 'mwasplund|Opal': { Version: '0.10.2', Build: 'Build0', Tool: 'Tool0' } + 'mwasplund|Opal': { Version: '0.11.0', Build: 'Build0', Tool: 'Tool0' } } } Build0: { diff --git a/Source/Tools/PrintGraph/PackageLock.sml b/Source/Tools/PrintGraph/PackageLock.sml index 0c7e4228b..41d7e8343 100644 --- a/Source/Tools/PrintGraph/PackageLock.sml +++ b/Source/Tools/PrintGraph/PackageLock.sml @@ -6,7 +6,7 @@ Closures: { 'Monitor.Shared': { Version: '../../Monitor/Shared/', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|CryptoPP': { Version: '1.2.2', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|Detours': { Version: '4.0.12', Build: 'Build0', Tool: 'Tool0' } - 'mwasplund|Opal': { Version: '0.10.2', Build: 'Build0', Tool: 'Tool0' } + 'mwasplund|Opal': { Version: '0.11.0', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|reflex': { Version: '1.0.4', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|Soup.Test.Assert': { Version: '0.4.0', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|wren': { Version: '1.0.5', Build: 'Build0', Tool: 'Tool0' } diff --git a/Source/Tools/PrintResults/PackageLock.sml b/Source/Tools/PrintResults/PackageLock.sml index c26f338db..dde01b8f5 100644 --- a/Source/Tools/PrintResults/PackageLock.sml +++ b/Source/Tools/PrintResults/PackageLock.sml @@ -6,7 +6,7 @@ Closures: { 'Monitor.Shared': { Version: '../../Monitor/Shared/', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|CryptoPP': { Version: '1.2.2', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|Detours': { Version: '4.0.12', Build: 'Build0', Tool: 'Tool0' } - 'mwasplund|Opal': { Version: '0.10.2', Build: 'Build0', Tool: 'Tool0' } + 'mwasplund|Opal': { Version: '0.11.0', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|reflex': { Version: '1.0.4', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|Soup.Test.Assert': { Version: '0.4.0', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|wren': { Version: '1.0.5', Build: 'Build0', Tool: 'Tool0' } diff --git a/Source/Tools/PrintValueTable/PackageLock.sml b/Source/Tools/PrintValueTable/PackageLock.sml index 783bedda1..81a440960 100644 --- a/Source/Tools/PrintValueTable/PackageLock.sml +++ b/Source/Tools/PrintValueTable/PackageLock.sml @@ -6,7 +6,7 @@ Closures: { 'Monitor.Shared': { Version: '../../Monitor/Shared/', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|CryptoPP': { Version: '1.2.2', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|Detours': { Version: '4.0.12', Build: 'Build0', Tool: 'Tool0' } - 'mwasplund|Opal': { Version: '0.10.2', Build: 'Build0', Tool: 'Tool0' } + 'mwasplund|Opal': { Version: '0.11.0', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|reflex': { Version: '1.0.4', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|Soup.Test.Assert': { Version: '0.4.0', Build: 'Build0', Tool: 'Tool0' } 'mwasplund|wren': { Version: '1.0.5', Build: 'Build0', Tool: 'Tool0' }