diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7f1795f60..186ef1aaf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,5 +1,4 @@ name: CI - on: [push] jobs: @@ -13,8 +12,37 @@ jobs: - name: Setup Soup uses: SoupBuild/Setup-Soup@v1 with: - version: v0.8.1-alpha - - name: Build Client - run: soup build Source/Client/CLI/ -v:d - - name: Run Tests - run: Scripts/InPath/AllTest.bat + version: v0.8.5-alpha + - name: Soup Install + run: soup install Source/Client/CLI/ -v:d + + - name: Build Execute UnitTests + run: soup build Source/Build/Execute.UnitTests/ + - name: Run Execute UnitTests + run: soup run Source/Build/Execute.UnitTests/ + + - name: Build Utilities UnitTests + run: soup build Source/Build/Utilities.UnitTests/ + - name: Run Utilities UnitTests + run: soup run Source/Build/Utilities.UnitTests/ + + - name: Build Extensions Compiler Core UnitTests + run: soup build Source/Extensions/Compiler/Core.UnitTests/ + - name: Run Extensions Compiler Core UnitTests + run: soup run Source/Extensions/Compiler/Core.UnitTests/ + + - name: Build Extensions Compiler Clang UnitTests + run: soup build Source/Extensions/Compiler/Clang.UnitTests/ + - name: Run Extensions Compiler Clang UnitTests + run: soup run Source/Extensions/Compiler/Clang.UnitTests/ + + - name: Build Extensions Compiler MSVC UnitTests + run: soup build Source/Extensions/Compiler/MSVC.UnitTests/ + - name: Run Extensions Compiler MSVC UnitTests + run: soup run Source/Extensions/Compiler/MSVC.UnitTests/ + + # openssl not available on CI build + # - name: Build Client Core UnitTests + # run: soup build Source/Client/Core.UnitTests/ + # - name: Run Client Core UnitTests + # run: soup run Source/Client/Core.UnitTests/ diff --git a/Docs/Developer-Setup.md b/Docs/Developer-Setup.md index 5efaaec10..dbe3e786b 100644 --- a/Docs/Developer-Setup.md +++ b/Docs/Developer-Setup.md @@ -9,7 +9,7 @@ * [Latest Release](https://github.com/mwasplund/Soup/releases) ### OpenSSL Requirements -* Perl. We recommend ActiveState Perl, available from https://www.activestate.com/ActivePerl +* Perl. We recommend Strawberry http://strawberryperl.com/ * Netwide Assembler, a.k.a. NASM, available from https://www.nasm.us ## Setup diff --git a/Scripts/alltest.cmd b/Scripts/alltest.cmd index baf3d1bb6..b6aeec59f 100644 --- a/Scripts/alltest.cmd +++ b/Scripts/alltest.cmd @@ -2,94 +2,44 @@ SET ScriptsDir=%~dp0 SET SourceDir=%ScriptsDir%..\Source -echo %SourceDir%\Build\Execute.UnitTests\ -pushd %SourceDir%\Build\Execute.UnitTests\ +echo soup build %SourceDir%\Build\Execute.UnitTests\ +call soup build %SourceDir%\Build\Execute.UnitTests\ +if %ERRORLEVEL% NEQ 0 exit /B %ERRORLEVEL% +echo soup run %SourceDir%\Build\Execute.UnitTests\ +call soup run %SourceDir%\Build\Execute.UnitTests\ if %ERRORLEVEL% NEQ 0 exit /B %ERRORLEVEL% -call soup build -if %ERRORLEVEL% NEQ 0 ( - popd - exit /B %ERRORLEVEL% -) -call soup run -if %ERRORLEVEL% NEQ 0 ( - echo Failed - popd - exit /B %ERRORLEVEL% -) -popd -echo %SourceDir%\Build\Utilities.UnitTests\ -pushd %SourceDir%\Build\Utilities.UnitTests\ -if %ERRORLEVEL% NEQ 0 exit /B %ERRORLEVEL% -call soup build -if %ERRORLEVEL% NEQ 0 ( - popd - exit /B %ERRORLEVEL% -) -call soup run -if %ERRORLEVEL% NEQ 0 ( - popd - exit /B %ERRORLEVEL% -) -popd +echo soup build %SourceDir%\Build\Utilities.UnitTests\ +call soup build %SourceDir%\Build\Utilities.UnitTests\ +if %ERRORLEVEL% NEQ 0 exit /B %ERRORLEVEL% +echo soup run %SourceDir%\Build\Utilities.UnitTests\ +call soup run %SourceDir%\Build\Utilities.UnitTests\ +if %ERRORLEVEL% NEQ 0 exit /B %ERRORLEVEL% -echo %SourceDir%\Extensions\Compiler\Core.UnitTests\ -pushd %SourceDir%\Extensions\Compiler\Core.UnitTests\ -if %ERRORLEVEL% NEQ 0 exit /B %ERRORLEVEL% -call soup build -if %ERRORLEVEL% NEQ 0 ( - popd - exit /B %ERRORLEVEL% -) -call soup run -if %ERRORLEVEL% NEQ 0 ( - popd - exit /B %ERRORLEVEL% -) -popd +echo soup build %SourceDir%\Extensions\Compiler\Core.UnitTests\ +call soup build %SourceDir%\Extensions\Compiler\Core.UnitTests\ +if %ERRORLEVEL% NEQ 0 exit /B %ERRORLEVEL% +echo soup run %SourceDir%\Extensions\Compiler\Core.UnitTests\ +call soup run %SourceDir%\Extensions\Compiler\Core.UnitTests\ +if %ERRORLEVEL% NEQ 0 exit /B %ERRORLEVEL% -echo %SourceDir%\Extensions\Compiler\Clang.UnitTests\ -pushd %SourceDir%\Extensions\Compiler\Clang.UnitTests\ +echo soup build %SourceDir%\Extensions\Compiler\Clang.UnitTests\ +call soup build %SourceDir%\Extensions\Compiler\Clang.UnitTests\ +if %ERRORLEVEL% NEQ 0 exit /B %ERRORLEVEL% +echo soup run %SourceDir%\Extensions\Compiler\Clang.UnitTests\ +call soup run %SourceDir%\Extensions\Compiler\Clang.UnitTests\ if %ERRORLEVEL% NEQ 0 exit /B %ERRORLEVEL% -call soup build -if %ERRORLEVEL% NEQ 0 ( - popd - exit /B %ERRORLEVEL% -) -call soup run -if %ERRORLEVEL% NEQ 0 ( - echo Failed - popd - exit /B %ERRORLEVEL% -) -popd -echo %SourceDir%\Extensions\Compiler\MSVC.UnitTests\ -pushd %SourceDir%\Extensions\Compiler\MSVC.UnitTests\ -if %ERRORLEVEL% NEQ 0 exit /B %ERRORLEVEL% -call soup build -if %ERRORLEVEL% NEQ 0 ( - popd - exit /B %ERRORLEVEL% -) -call soup run -if %ERRORLEVEL% NEQ 0 ( - popd - exit /B %ERRORLEVEL% -) -popd +echo soup build %SourceDir%\Extensions\Compiler\MSVC.UnitTests\ +call soup build %SourceDir%\Extensions\Compiler\MSVC.UnitTests\ +if %ERRORLEVEL% NEQ 0 exit /B %ERRORLEVEL% +echo soup run %SourceDir%\Extensions\Compiler\MSVC.UnitTests\ +call soup run %SourceDir%\Extensions\Compiler\MSVC.UnitTests\ +if %ERRORLEVEL% NEQ 0 exit /B %ERRORLEVEL% -echo %SourceDir%\Client\Core.UnitTests\ -pushd %SourceDir%\Client\Core.UnitTests\ -if %ERRORLEVEL% NEQ 0 exit /B %ERRORLEVEL% -call soup build -if %ERRORLEVEL% NEQ 0 ( - popd - exit /B %ERRORLEVEL% -) -call soup run -if %ERRORLEVEL% NEQ 0 ( - popd - exit /B %ERRORLEVEL% -) -popd \ No newline at end of file +echo soup build %SourceDir%\Client\Core.UnitTests\ +call soup build %SourceDir%\Client\Core.UnitTests\ +if %ERRORLEVEL% NEQ 0 exit /B %ERRORLEVEL% +echo soup run %SourceDir%\Client\Core.UnitTests\ +call soup run %SourceDir%\Client\Core.UnitTests\ +if %ERRORLEVEL% NEQ 0 exit /B %ERRORLEVEL% diff --git a/Scripts/Build.cmd b/Scripts/build.cmd similarity index 98% rename from Scripts/Build.cmd rename to Scripts/build.cmd index c470bd230..988cc466c 100644 --- a/Scripts/Build.cmd +++ b/Scripts/build.cmd @@ -1,28 +1,28 @@ -@echo off -SET Flavor=%1 -SET ScriptsDir=%~dp0 -SET SourceDir=%ScriptsDir%..\Source -SET ClientCLIDir=%SourceDir%\Client\CLI -SET DetoursDir=%SourceDir%\Monitor\Detours -SET BinaryDirectorPath=out\bin\MSVC\%Flavor%\win32 -SET ClientCLIBinaryDirectory=%ClientCLIDir%\%BinaryDirectorPath% -SET DetoursBinaryDirectory=%DetoursDir%\%BinaryDirectorPath% - -REM - Build each version of the detours dll -echo soup build %DetoursDir% -architecture x64 -flavor %Flavor% -call soup build %DetoursDir% -architecture x64 -flavor %Flavor% -if %ERRORLEVEL% NEQ 0 exit /B %ERRORLEVEL% -echo soup build %DetoursDir% -architecture x86 -flavor %Flavor% -call soup build %DetoursDir% -architecture x86 -flavor %Flavor% -if %ERRORLEVEL% NEQ 0 exit /B %ERRORLEVEL% - -REM - Build the host -echo soup build %ClientCLIDir% -flavor %Flavor% -call soup build %ClientCLIDir% -flavor %Flavor% -if %ERRORLEVEL% NEQ 0 exit /B %ERRORLEVEL% - -REM - Copy the detour dlls -echo copy %DetoursBinaryDirectory%\x64\Monitor.Detours.dll %ClientCLIBinaryDirectory%\x64\Monitor.Detours.64.dll -copy %DetoursBinaryDirectory%\x64\Monitor.Detours.dll %ClientCLIBinaryDirectory%\x64\Monitor.Detours.64.dll > nul -echo copy %DetoursBinaryDirectory%\x86\Monitor.Detours.dll %ClientCLIBinaryDirectory%\x64\Monitor.Detours.32.dll +@echo off +SET Flavor=%1 +SET ScriptsDir=%~dp0 +SET SourceDir=%ScriptsDir%..\Source +SET ClientCLIDir=%SourceDir%\Client\CLI +SET DetoursDir=%SourceDir%\Monitor\Detours +SET BinaryDirectorPath=out\bin\MSVC\%Flavor%\win32 +SET ClientCLIBinaryDirectory=%ClientCLIDir%\%BinaryDirectorPath% +SET DetoursBinaryDirectory=%DetoursDir%\%BinaryDirectorPath% + +REM - Build each version of the detours dll +echo soup build %DetoursDir% -architecture x64 -flavor %Flavor% +call soup build %DetoursDir% -architecture x64 -flavor %Flavor% +if %ERRORLEVEL% NEQ 0 exit /B %ERRORLEVEL% +echo soup build %DetoursDir% -architecture x86 -flavor %Flavor% +call soup build %DetoursDir% -architecture x86 -flavor %Flavor% +if %ERRORLEVEL% NEQ 0 exit /B %ERRORLEVEL% + +REM - Build the host +echo soup build %ClientCLIDir% -flavor %Flavor% +call soup build %ClientCLIDir% -flavor %Flavor% +if %ERRORLEVEL% NEQ 0 exit /B %ERRORLEVEL% + +REM - Copy the detour dlls +echo copy %DetoursBinaryDirectory%\x64\Monitor.Detours.dll %ClientCLIBinaryDirectory%\x64\Monitor.Detours.64.dll +copy %DetoursBinaryDirectory%\x64\Monitor.Detours.dll %ClientCLIBinaryDirectory%\x64\Monitor.Detours.64.dll > nul +echo copy %DetoursBinaryDirectory%\x86\Monitor.Detours.dll %ClientCLIBinaryDirectory%\x64\Monitor.Detours.32.dll copy %DetoursBinaryDirectory%\x86\Monitor.Detours.dll %ClientCLIBinaryDirectory%\x64\Monitor.Detours.32.dll > nul \ No newline at end of file diff --git a/Scripts/soup.cmd b/Scripts/soup.cmd index 3e6bac8a9..a94a6b789 100644 --- a/Scripts/soup.cmd +++ b/Scripts/soup.cmd @@ -5,7 +5,5 @@ SET OutDir=%ClientDir%\out SET BinaryDir=%OutDir%\bin SET RunDir=%OutDir%\run REM - Use a copy of the final binary in case we are re-buiding itself -rmdir /S /Q %RunDir% -xcopy /s /Y %BinaryDir%\MSVC\release\win32\x64\* %RunDir%\ > nul -copy %ClientDir%\LocalUserConfig.json %RunDir%\LocalUserConfig.json > nul +robocopy %BinaryDir%\MSVC\release\win32\x64\ %RunDir%\ /MIR %RunDir%\Soup.exe %* \ No newline at end of file diff --git a/Scripts/soupd.cmd b/Scripts/soupd.cmd index ca3a4362c..fc47789cc 100644 --- a/Scripts/soupd.cmd +++ b/Scripts/soupd.cmd @@ -5,7 +5,5 @@ SET OutDir=%ClientDir%\out SET BinaryDir=%OutDir%\bin SET RunDir=%OutDir%\run REM - Use a copy of the final binary in case we are re-buiding itself -rmdir /S /Q %RunDir% -xcopy /s /Y %BinaryDir%\MSVC\debug\win32\x64\* %RunDir%\ > nul -copy %ClientDir%\LocalUserConfig.json %RunDir%\LocalUserConfig.json > nul +robocopy %BinaryDir%\MSVC\debug\win32\x64\ %RunDir%\ /MIR %RunDir%\Soup.exe %* \ No newline at end of file diff --git a/Source/Build/Execute.UnitTests/BuildHistoryCheckerTests.h b/Source/Build/Execute.UnitTests/BuildHistoryCheckerTests.h index 73a1eb864..38cba3ede 100644 --- a/Source/Build/Execute.UnitTests/BuildHistoryCheckerTests.h +++ b/Source/Build/Execute.UnitTests/BuildHistoryCheckerTests.h @@ -11,7 +11,7 @@ namespace Soup::Build::Execute::UnitTests { public: [[Fact]] - void IsOutdated_ZeroInput_Throws() + void IsOutdated_ZeroInput() { // Register the test listener auto testListener = std::make_shared(); @@ -30,9 +30,10 @@ namespace Soup::Build::Execute::UnitTests // Perform the check auto uut = BuildHistoryChecker(); - Assert::ThrowsRuntimeError([&uut, &targetFiles, &inputFiles, &rootPath]() { - bool result = uut.IsOutdated(targetFiles, inputFiles, rootPath); - }); + bool result = uut.IsOutdated(targetFiles, inputFiles, rootPath); + + // Verify the results + Assert::IsFalse(result, "Verify the result is false."); // Verify expected file system requests Assert::AreEqual( diff --git a/Source/Build/Execute.UnitTests/BuildHistoryJsonTests.h b/Source/Build/Execute.UnitTests/BuildHistoryJsonTests.h index 92ff3e952..71e19c9ad 100644 --- a/Source/Build/Execute.UnitTests/BuildHistoryJsonTests.h +++ b/Source/Build/Execute.UnitTests/BuildHistoryJsonTests.h @@ -19,7 +19,7 @@ namespace Soup::Build::Execute::UnitTests } [[Fact]] - void Deserialize_MissingKnownFiles() + void Deserialize_MissingOperationsThrows() { auto content = std::stringstream( R"({ @@ -31,13 +31,18 @@ namespace Soup::Build::Execute::UnitTests } [[Fact]] - void Deserialize_MissingFileThrows() + void Deserialize_MissingCommandThrows() { auto content = std::stringstream( R"({ - "knownFiles": [ + "operations": [ { - "includes": [] + "input": [ + "inputfile.txt" + ], + "output": [ + "outputfile.txt" + ] } ] })"); @@ -48,13 +53,36 @@ namespace Soup::Build::Execute::UnitTests } [[Fact]] - void Deserialize_MissingIncludesThrows() + void Deserialize_MissingInputThrows() { auto content = std::stringstream( R"({ - "knownFiles": [ + "operations": [ { - "file": "File.h" + "command": "./ : dostuff.exe arg1 arg2", + "output": [ + "outputfile.txt" + ] + } + ] + })"); + + Assert::ThrowsRuntimeError([&content]() { + auto actual = BuildHistoryJson::Deserialize(content); + }); + } + + [[Fact]] + void Deserialize_MissingOutputThrows() + { + auto content = std::stringstream( + R"({ + "operations": [ + { + "command": "./ : dostuff.exe arg1 arg2", + "input": [ + "inputfile.txt" + ], } ] })"); @@ -69,10 +97,15 @@ namespace Soup::Build::Execute::UnitTests { auto content = std::stringstream( R"({ - "knownFiles": [ + "operations": [ { - "file": "File.h", - "includes": [ "Other.h" ] + "command": "./ : dostuff.exe arg1 arg2", + "input": [ + "inputfile.txt" + ], + "output": [ + "outputfile.txt" + ] } ] })"); @@ -80,7 +113,10 @@ namespace Soup::Build::Execute::UnitTests auto expected = BuildHistory( { - FileInfo(Path("File.h"), { Path("Other.h") }), + OperationInfo( + "./ : dostuff.exe arg1 arg2", + { Path("inputfile.txt") }, + { Path("outputfile.txt") }), }); Assert::AreEqual(expected, actual, "Verify matches expected."); @@ -91,14 +127,24 @@ namespace Soup::Build::Execute::UnitTests { auto content = std::stringstream( R"({ - "knownFiles": [ + "operations": [ { - "file": "File1.h", - "includes": [ "Other1.h" ] + "command": "./ : dostuff1.exe arg1 arg2", + "input": [ + "inputfile1.txt" + ], + "output": [ + "outputfile1.txt" + ] }, { - "file": "File2.h", - "includes": [ "Other2.h" ] + "command": "./ : dostuff2.exe arg1 arg2", + "input": [ + "inputfile2.txt" + ], + "output": [ + "outputfile2.txt" + ] } ] })"); @@ -106,8 +152,14 @@ namespace Soup::Build::Execute::UnitTests auto expected = BuildHistory( { - FileInfo(Path("File1.h"), { Path("Other1.h") }), - FileInfo(Path("File2.h"), { Path("Other2.h") }), + OperationInfo( + "./ : dostuff1.exe arg1 arg2", + { Path("inputfile1.txt") }, + { Path("outputfile1.txt") }), + OperationInfo( + "./ : dostuff2.exe arg1 arg2", + { Path("inputfile2.txt") }, + { Path("outputfile2.txt") }), }); Assert::AreEqual(expected, actual, "Verify matches expected."); @@ -116,19 +168,28 @@ namespace Soup::Build::Execute::UnitTests [[Fact]] void Serialize_Simple() { - auto state = BuildHistory({ - FileInfo(Path("File.h"), { Path("Other.h") }), - }); + auto state = BuildHistory( + { + OperationInfo( + "./ : dostuff.exe arg1 arg2", + { Path("inputfile.txt") }, + { Path("outputfile.txt") }), + }); std::stringstream actual; BuildHistoryJson::Serialize(state, actual); auto expected = R"({ - "knownFiles": [ + "operations": [ { - "file": "File.h", - "includes": [ "Other.h" ] + "command": "./ : dostuff.exe arg1 arg2", + "input": [ + "inputfile.txt" + ], + "output": [ + "outputfile.txt" + ] } ] })"; @@ -139,24 +200,41 @@ namespace Soup::Build::Execute::UnitTests [[Fact]] void Serialize_Multipl() { - auto state = BuildHistory({ - FileInfo(Path("File1.h"), { Path("Other1.h") }), - FileInfo(Path("File2.h"), { Path("Other2.h") }), - }); + auto state = BuildHistory( + { + OperationInfo( + "./ : dostuff1.exe arg1 arg2", + { Path("inputfile1.txt") }, + { Path("outputfile1.txt") }), + OperationInfo( + "./ : dostuff2.exe arg1 arg2", + { Path("inputfile2.txt") }, + { Path("outputfile2.txt") }), + }); std::stringstream actual; BuildHistoryJson::Serialize(state, actual); auto expected = R"({ - "knownFiles": [ + "operations": [ { - "file": "File1.h", - "includes": [ "Other1.h" ] + "command": "./ : dostuff1.exe arg1 arg2", + "input": [ + "inputfile1.txt" + ], + "output": [ + "outputfile1.txt" + ] }, { - "file": "File2.h", - "includes": [ "Other2.h" ] + "command": "./ : dostuff2.exe arg1 arg2", + "input": [ + "inputfile2.txt" + ], + "output": [ + "outputfile2.txt" + ] } ] })"; diff --git a/Source/Build/Execute.UnitTests/BuildHistoryManagerTests.h b/Source/Build/Execute.UnitTests/BuildHistoryManagerTests.h index 84d5efc40..503b9d759 100644 --- a/Source/Build/Execute.UnitTests/BuildHistoryManagerTests.h +++ b/Source/Build/Execute.UnitTests/BuildHistoryManagerTests.h @@ -94,10 +94,15 @@ namespace Soup::Build::Execute::UnitTests fileSystem->CreateMockFile( Path("TestFiles/SimpleBuildHistory/.soup/BuildHistory.json"), std::make_shared(std::stringstream(R"({ - "knownFiles": [ + "operations": [ { - "file": "File.h", - "includes": [ "Other.h" ] + "command": "./ : dostuff.exe arg1 arg2", + "input": [ + "inputfile.txt" + ], + "output": [ + "outputfile.txt" + ] } ] })"))); @@ -108,8 +113,12 @@ namespace Soup::Build::Execute::UnitTests Assert::IsTrue(result, "Verify result is false."); - auto expected = BuildHistory({ - FileInfo(Path("File.h"), { Path("Other.h") }), + auto expected = BuildHistory( + { + OperationInfo( + "./ : dostuff.exe arg1 arg2", + { Path("inputfile.txt") }, + { Path("outputfile.txt") }), }); Assert::AreEqual(expected, actual, "Verify matches expected."); diff --git a/Source/Build/Execute.UnitTests/BuildHistoryTests.h b/Source/Build/Execute.UnitTests/BuildHistoryTests.h index d1a0e9dbc..2b159b6fa 100644 --- a/Source/Build/Execute.UnitTests/BuildHistoryTests.h +++ b/Source/Build/Execute.UnitTests/BuildHistoryTests.h @@ -9,188 +9,5 @@ namespace Soup::Build::Execute::UnitTests class BuildHistoryTests { public: - [[Fact]] - void TryBuildIncludeClosure_SourceFileMissingFails() - { - // Register the test listener - auto testListener = std::make_shared(); - auto scopedTraceListener = ScopedTraceListenerRegister(testListener); - - auto uut = BuildHistory(); - - auto sourceFile = Path("TestFile.cpp"); - auto actualClosure = std::vector(); - auto result = uut.TryBuildIncludeClosure(sourceFile, actualClosure); - - Assert::IsFalse(result, "Verify result is false."); - - auto expectedClosure = std::vector(); - Assert::AreEqual(expectedClosure, actualClosure, "Verify the closure matches."); - - // Verify expected logs - Assert::AreEqual( - std::vector({ - "INFO: Missing file info: TestFile.cpp", - }), - testListener->GetMessages(), - "Verify log messages match expected."); - } - - [[Fact]] - void TryBuildIncludeClosure_DependencyFileMissingFails() - { - // Register the test listener - auto testListener = std::make_shared(); - auto scopedTraceListener = ScopedTraceListenerRegister(testListener); - - auto uut = BuildHistory(std::vector({ - FileInfo( - Path("TestFile.cpp"), - std::vector({ - Path("OtherTestFile.h"), - })), - })); - - auto sourceFile = Path("TestFile.cpp"); - auto actualClosure = std::vector(); - auto result = uut.TryBuildIncludeClosure(sourceFile, actualClosure); - - Assert::IsFalse(result, "Verify result is false."); - - auto expectedClosure = std::vector(); - Assert::AreEqual(expectedClosure, actualClosure, "Verify the closure matches."); - - // Verify expected logs - Assert::AreEqual( - std::vector({ - "INFO: Missing file info: OtherTestFile.h", - }), - testListener->GetMessages(), - "Verify log messages match expected."); - } - - [[Fact]] - void TryBuildIncludeClosure_NoDependencies() - { - // Register the test listener - auto testListener = std::make_shared(); - auto scopedTraceListener = ScopedTraceListenerRegister(testListener); - - auto uut = BuildHistory(std::vector({ - FileInfo( - Path("TestFile.cpp"), - std::vector({ - })), - })); - - auto sourceFile = Path("TestFile.cpp"); - auto actualClosure = std::vector(); - auto result = uut.TryBuildIncludeClosure(sourceFile, actualClosure); - - Assert::IsTrue(result, "Verify result is true."); - - auto expectedClosure = std::vector({}); - Assert::AreEqual(expectedClosure, actualClosure, "Verify the closure matches."); - - // Verify expected logs - Assert::AreEqual( - std::vector({}), - testListener->GetMessages(), - "Verify log messages match expected."); - } - - [[Fact]] - void TryBuildIncludeClosure_MultipleDependencies() - { - // Register the test listener - auto testListener = std::make_shared(); - auto scopedTraceListener = ScopedTraceListenerRegister(testListener); - - auto uut = BuildHistory(std::vector({ - FileInfo( - Path("TestFile.cpp"), - std::vector({ - Path("TestFile1.h"), - Path("TestFile2.h"), - })), - FileInfo( - Path("TestFile1.h"), - std::vector({ - Path("TestFile3.h"), - })), - FileInfo( - Path("TestFile2.h"), - std::vector({})), - FileInfo( - Path("TestFile3.h"), - std::vector({})), - FileInfo( - Path("TestFile4.h"), - std::vector({})), - })); - - auto sourceFile = Path("TestFile.cpp"); - auto actualClosure = std::vector(); - auto result = uut.TryBuildIncludeClosure(sourceFile, actualClosure); - - Assert::IsTrue(result, "Verify result is true."); - - auto expectedClosure = std::vector({ - Path("TestFile1.h"), - Path("TestFile2.h"), - Path("TestFile3.h"), - }); - Assert::AreEqual(expectedClosure, actualClosure, "Verify the closure matches."); - - // Verify expected logs - Assert::AreEqual( - std::vector({}), - testListener->GetMessages(), - "Verify log messages match expected."); - } - - [[Fact]] - void TryBuildIncludeClosure_CircularDependencies() - { - // Register the test listener - auto testListener = std::make_shared(); - auto scopedTraceListener = ScopedTraceListenerRegister(testListener); - - auto uut = BuildHistory(std::vector({ - FileInfo( - Path("TestFile.cpp"), - std::vector({ - Path("TestFile1.h"), - })), - FileInfo( - Path("TestFile1.h"), - std::vector({ - Path("TestFile2.h"), - })), - FileInfo( - Path("TestFile2.h"), - std::vector({ - Path("TestFile1.h"), - })), - })); - - auto sourceFile = Path("TestFile.cpp"); - auto actualClosure = std::vector(); - auto result = uut.TryBuildIncludeClosure(sourceFile, actualClosure); - - Assert::IsTrue(result, "Verify result is true."); - - auto expectedClosure = std::vector({ - Path("TestFile1.h"), - Path("TestFile2.h"), - }); - Assert::AreEqual(expectedClosure, actualClosure, "Verify the closure matches."); - - // Verify expected logs - Assert::AreEqual( - std::vector({}), - testListener->GetMessages(), - "Verify log messages match expected."); - } }; } diff --git a/Source/Build/Execute.UnitTests/BuildRunnerTests.h b/Source/Build/Execute.UnitTests/BuildRunnerTests.h index e49df76fb..2c091ddb1 100644 --- a/Source/Build/Execute.UnitTests/BuildRunnerTests.h +++ b/Source/Build/Execute.UnitTests/BuildRunnerTests.h @@ -137,8 +137,8 @@ namespace Soup::Build::Execute::UnitTests auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); // Register the test process manager - auto processManager = std::make_shared(); - auto scopedProcesManager = ScopedProcessManagerRegister(processManager); + auto detourProcessManager = std::make_shared(); + auto scopedDetourProcesManager = Monitor::ScopedDetourProcessManagerRegister(detourProcessManager); auto uut = BuildRunner(Path("C:/BuildDirectory/")); @@ -189,15 +189,15 @@ namespace Soup::Build::Execute::UnitTests // Verify expected process requests Assert::AreEqual( std::vector({ - "CreateProcess: 1 [C:/TestWorkingDirectory/] Command.exe Arguments", + "CreateDetourProcess: 1 [C:/TestWorkingDirectory/] Command.exe Arguments", "ProcessStart: 1", "WaitForExit: 1", "GetStandardOutput: 1", "GetStandardError: 1", "GetExitCode: 1", }), - processManager->GetRequests(), - "Verify process manager requests match expected."); + detourProcessManager->GetRequests(), + "Verify detour process manager requests match expected."); } [[Fact]] @@ -212,8 +212,8 @@ namespace Soup::Build::Execute::UnitTests auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); // Register the test process manager - auto processManager = std::make_shared(); - auto scopedProcesManager = ScopedProcessManagerRegister(processManager); + auto detourProcessManager = std::make_shared(); + auto scopedDetourProcesManager = Monitor::ScopedDetourProcessManagerRegister(detourProcessManager); auto uut = BuildRunner(Path("C:/BuildDirectory/")); @@ -268,15 +268,15 @@ namespace Soup::Build::Execute::UnitTests // Verify expected process requests Assert::AreEqual( std::vector({ - "CreateProcess: 1 [C:/TestWorkingDirectory/] Command.exe Arguments", + "CreateDetourProcess: 1 [C:/TestWorkingDirectory/] Command.exe Arguments", "ProcessStart: 1", "WaitForExit: 1", "GetStandardOutput: 1", "GetStandardError: 1", "GetExitCode: 1", }), - processManager->GetRequests(), - "Verify process manager requests match expected."); + detourProcessManager->GetRequests(), + "Verify detour process manager requests match expected."); } [[Fact]] @@ -291,8 +291,8 @@ namespace Soup::Build::Execute::UnitTests auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); // Register the test process manager - auto processManager = std::make_shared(); - auto scopedProcesManager = ScopedProcessManagerRegister(processManager); + auto detourProcessManager = std::make_shared(); + auto scopedDetourProcesManager = Monitor::ScopedDetourProcessManagerRegister(detourProcessManager); // Create the initial build state auto initialBuildHistory = BuildHistory(); @@ -331,7 +331,6 @@ namespace Soup::Build::Execute::UnitTests std::vector({ "DIAG: Loading previous build state", "DIAG: Check for updated source", - "INFO: Missing file info: InputFile.cpp", "HIGH: TestCommand: 1", "DIAG: Execute: Command.exe Arguments", "INFO: Saving updated build state", @@ -352,6 +351,19 @@ namespace Soup::Build::Execute::UnitTests }), fileSystem->GetRequests(), "Verify file system requests match expected."); + + // Verify expected process requests + Assert::AreEqual( + std::vector({ + "CreateDetourProcess: 1 [C:/TestWorkingDirectory/] Command.exe Arguments", + "ProcessStart: 1", + "WaitForExit: 1", + "GetStandardOutput: 1", + "GetStandardError: 1", + "GetExitCode: 1", + }), + detourProcessManager->GetRequests(), + "Verify detour process manager requests match expected."); } [[Fact]] @@ -366,12 +378,15 @@ namespace Soup::Build::Execute::UnitTests auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); // Register the test process manager - auto processManager = std::make_shared(); - auto scopedProcesManager = ScopedProcessManagerRegister(processManager); + auto detourProcessManager = std::make_shared(); + auto scopedDetourProcesManager = Monitor::ScopedDetourProcessManagerRegister(detourProcessManager); // Create the initial build state auto initialBuildHistory = BuildHistory({ - FileInfo(Path("InputFile.in"), { }), + OperationInfo( + "C:/TestWorkingDirectory/ : Command.exe Arguments", + { Path("InputFile.in") }, + { Path("OutputFile.out") }), }); std::stringstream initialBuildHistoryJson; BuildHistoryJson::Serialize(initialBuildHistory, initialBuildHistoryJson); @@ -436,6 +451,19 @@ namespace Soup::Build::Execute::UnitTests }), fileSystem->GetRequests(), "Verify file system requests match expected."); + + // Verify expected process requests + Assert::AreEqual( + std::vector({ + "CreateDetourProcess: 1 [C:/TestWorkingDirectory/] Command.exe Arguments", + "ProcessStart: 1", + "WaitForExit: 1", + "GetStandardOutput: 1", + "GetStandardError: 1", + "GetExitCode: 1", + }), + detourProcessManager->GetRequests(), + "Verify detour process manager requests match expected."); } [[Fact]] @@ -450,12 +478,15 @@ namespace Soup::Build::Execute::UnitTests auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); // Register the test process manager - auto processManager = std::make_shared(); - auto scopedProcesManager = ScopedProcessManagerRegister(processManager); + auto detourProcessManager = std::make_shared(); + auto scopedDetourProcesManager = Monitor::ScopedDetourProcessManagerRegister(detourProcessManager); // Create the initial build state auto initialBuildHistory = BuildHistory({ - FileInfo(Path("InputFile.in"), { }), + OperationInfo( + "C:/TestWorkingDirectory/ : Command.exe Arguments", + { Path("InputFile.in") }, + { Path("OutputFile.out") }), }); std::stringstream initialBuildHistoryJson; BuildHistoryJson::Serialize(initialBuildHistory, initialBuildHistoryJson); @@ -532,6 +563,19 @@ namespace Soup::Build::Execute::UnitTests }), fileSystem->GetRequests(), "Verify file system requests match expected."); + + // Verify expected process requests + Assert::AreEqual( + std::vector({ + "CreateDetourProcess: 1 [C:/TestWorkingDirectory/] Command.exe Arguments", + "ProcessStart: 1", + "WaitForExit: 1", + "GetStandardOutput: 1", + "GetStandardError: 1", + "GetExitCode: 1", + }), + detourProcessManager->GetRequests(), + "Verify detour process manager requests match expected."); } [[Fact]] @@ -551,7 +595,10 @@ namespace Soup::Build::Execute::UnitTests // Create the initial build state auto initialBuildHistory = BuildHistory({ - FileInfo(Path("InputFile.in"), { }), + OperationInfo( + "C:/TestWorkingDirectory/ : Command.exe Arguments", + { Path("InputFile.in") }, + { Path("OutputFile.out") }), }); std::stringstream initialBuildHistoryJson; BuildHistoryJson::Serialize(initialBuildHistory, initialBuildHistoryJson); diff --git a/Source/Build/Execute.UnitTests/gen/BuildHistoryCheckerTests.gen.h b/Source/Build/Execute.UnitTests/gen/BuildHistoryCheckerTests.gen.h index 9bb3236bd..22257a830 100644 --- a/Source/Build/Execute.UnitTests/gen/BuildHistoryCheckerTests.gen.h +++ b/Source/Build/Execute.UnitTests/gen/BuildHistoryCheckerTests.gen.h @@ -6,7 +6,7 @@ TestState RunBuildHistoryCheckerTests() auto className = "BuildHistoryCheckerTests"; auto testClass = std::make_shared(); TestState state = { 0, 0 }; - state += SoupTest::RunTest(className, "IsOutdated_ZeroInput_Throws", [&testClass]() { testClass->IsOutdated_ZeroInput_Throws(); }); + state += SoupTest::RunTest(className, "IsOutdated_ZeroInput", [&testClass]() { testClass->IsOutdated_ZeroInput(); }); state += SoupTest::RunTest(className, "IsOutdated_SingleInput_MissingTarget", [&testClass]() { testClass->IsOutdated_SingleInput_MissingTarget(); }); state += SoupTest::RunTest(className, "IsOutdated_SingleInput_TargetExists_MissingInputFile", [&testClass]() { testClass->IsOutdated_SingleInput_TargetExists_MissingInputFile(); }); state += SoupTest::RunTest(className, "IsOutdated_SingleInput_TargetExists_Outdated", [&testClass]() { testClass->IsOutdated_SingleInput_TargetExists_Outdated(); }); diff --git a/Source/Build/Execute.UnitTests/gen/BuildHistoryJsonTests.gen.h b/Source/Build/Execute.UnitTests/gen/BuildHistoryJsonTests.gen.h index fcca59c7e..b47658356 100644 --- a/Source/Build/Execute.UnitTests/gen/BuildHistoryJsonTests.gen.h +++ b/Source/Build/Execute.UnitTests/gen/BuildHistoryJsonTests.gen.h @@ -7,9 +7,10 @@ TestState RunBuildHistoryJsonTests() auto testClass = std::make_shared(); TestState state = { 0, 0 }; state += SoupTest::RunTest(className, "Deserialize_GarbageThrows", [&testClass]() { testClass->Deserialize_GarbageThrows(); }); - state += SoupTest::RunTest(className, "Deserialize_MissingKnownFiles", [&testClass]() { testClass->Deserialize_MissingKnownFiles(); }); - state += SoupTest::RunTest(className, "Deserialize_MissingFileThrows", [&testClass]() { testClass->Deserialize_MissingFileThrows(); }); - state += SoupTest::RunTest(className, "Deserialize_MissingIncludesThrows", [&testClass]() { testClass->Deserialize_MissingIncludesThrows(); }); + state += SoupTest::RunTest(className, "Deserialize_MissingOperationsThrows", [&testClass]() { testClass->Deserialize_MissingOperationsThrows(); }); + state += SoupTest::RunTest(className, "Deserialize_MissingCommandThrows", [&testClass]() { testClass->Deserialize_MissingCommandThrows(); }); + state += SoupTest::RunTest(className, "Deserialize_MissingInputThrows", [&testClass]() { testClass->Deserialize_MissingInputThrows(); }); + state += SoupTest::RunTest(className, "Deserialize_MissingOutputThrows", [&testClass]() { testClass->Deserialize_MissingOutputThrows(); }); state += SoupTest::RunTest(className, "Deserialize_Simple", [&testClass]() { testClass->Deserialize_Simple(); }); state += SoupTest::RunTest(className, "Deserialize_Multiple", [&testClass]() { testClass->Deserialize_Multiple(); }); state += SoupTest::RunTest(className, "Serialize_Simple", [&testClass]() { testClass->Serialize_Simple(); }); diff --git a/Source/Build/Execute.UnitTests/gen/BuildHistoryTests.gen.h b/Source/Build/Execute.UnitTests/gen/BuildHistoryTests.gen.h index 8ae5e0572..2946abe98 100644 --- a/Source/Build/Execute.UnitTests/gen/BuildHistoryTests.gen.h +++ b/Source/Build/Execute.UnitTests/gen/BuildHistoryTests.gen.h @@ -6,11 +6,6 @@ TestState RunBuildHistoryTests() auto className = "BuildHistoryTests"; auto testClass = std::make_shared(); TestState state = { 0, 0 }; - state += SoupTest::RunTest(className, "TryBuildIncludeClosure_SourceFileMissingFails", [&testClass]() { testClass->TryBuildIncludeClosure_SourceFileMissingFails(); }); - state += SoupTest::RunTest(className, "TryBuildIncludeClosure_DependencyFileMissingFails", [&testClass]() { testClass->TryBuildIncludeClosure_DependencyFileMissingFails(); }); - state += SoupTest::RunTest(className, "TryBuildIncludeClosure_NoDependencies", [&testClass]() { testClass->TryBuildIncludeClosure_NoDependencies(); }); - state += SoupTest::RunTest(className, "TryBuildIncludeClosure_MultipleDependencies", [&testClass]() { testClass->TryBuildIncludeClosure_MultipleDependencies(); }); - state += SoupTest::RunTest(className, "TryBuildIncludeClosure_CircularDependencies", [&testClass]() { testClass->TryBuildIncludeClosure_CircularDependencies(); }); return state; } \ No newline at end of file diff --git a/Source/Build/Execute.UnitTests/gen/Main.cpp b/Source/Build/Execute.UnitTests/gen/Main.cpp index 98262481f..05728589e 100644 --- a/Source/Build/Execute.UnitTests/gen/Main.cpp +++ b/Source/Build/Execute.UnitTests/gen/Main.cpp @@ -11,6 +11,7 @@ import Opal; import Soup.Build.Execute; +import Monitor.Shared; import json11; import SoupTest; import SoupTestUtilities; diff --git a/Source/Build/Execute/BuildHistory.h b/Source/Build/Execute/BuildHistory.h index 9eb85a411..c79dbfdb7 100644 --- a/Source/Build/Execute/BuildHistory.h +++ b/Source/Build/Execute/BuildHistory.h @@ -29,6 +29,26 @@ namespace Soup::Build::Execute { } + /// + /// Inequality operator + /// + bool operator ==(const OperationInfo& rhs) const + { + return Command == rhs.Command && + Input == rhs.Input && + Output == rhs.Output; + } + + /// + /// Inequality operator + /// + bool operator !=(const OperationInfo& rhs) const + { + return Command != rhs.Command || + Input != rhs.Input || + Output != rhs.Output; + } + std::string Command; std::vector Input; std::vector Output; @@ -90,6 +110,14 @@ namespace Soup::Build::Execute _operations.emplace(operation.Command, std::move(operation)); } + /// + /// Inequality operator + /// + bool operator !=(const BuildHistory& rhs) const + { + return _operations != rhs._operations; + } + private: std::unordered_map _operations; }; diff --git a/Source/Build/Execute/SystemAccessTracker.h b/Source/Build/Execute/SystemAccessTracker.h index 9c7a6720c..8b147f987 100644 --- a/Source/Build/Execute/SystemAccessTracker.h +++ b/Source/Build/Execute/SystemAccessTracker.h @@ -519,24 +519,24 @@ namespace Soup::Build } // ProcessThreadsApi - void OnCreateProcessA(std::string_view applicationName, bool result) override final + void OnCreateProcessA(bool wasDetoured, std::string_view applicationName, bool result) override final { - OnCreateProcess(applicationName); + OnCreateProcess(wasDetoured, applicationName); } - void OnCreateProcessW(std::wstring_view applicationName, bool result) override final + void OnCreateProcessW(bool wasDetoured, std::wstring_view applicationName, bool result) override final { - OnCreateProcess(applicationName); + OnCreateProcess(wasDetoured, applicationName); } void OnCreateProcessAsUserA(std::string_view applicationName, bool result) override final { - OnCreateProcess(applicationName); + OnCreateProcess(false, applicationName); } void OnCreateProcessAsUserW(std::wstring_view applicationName, bool result) override final { - OnCreateProcess(applicationName); + OnCreateProcess(false, applicationName); } void OnExitProcess(uint32_t exitCode) override final @@ -689,12 +689,12 @@ namespace Soup::Build void OnCreateProcessWithLogonW(std::wstring_view applicationName, bool result) override final { - OnCreateProcess(applicationName); + OnCreateProcess(false, applicationName); } void OnCreateProcessWithTokenW(std::wstring_view applicationName, bool result) override final { - OnCreateProcess(applicationName); + OnCreateProcess(false, applicationName); } void OnCreateSymbolicLinkA(std::string_view symlinkFileName, std::string_view targetFileName, uint32_t flags, bool result) override final @@ -1036,15 +1036,18 @@ namespace Soup::Build } private: - void OnCreateProcess(std::wstring_view applicationName) + void OnCreateProcess(bool wasDetoured, std::wstring_view applicationName) { std::wstring_convert, wchar_t> converter; - OnCreateProcess(converter.to_bytes(applicationName.data())); + OnCreateProcess(wasDetoured, converter.to_bytes(applicationName.data())); } - void OnCreateProcess(std::string_view applicationName) + void OnCreateProcess(bool wasDetoured, std::string_view applicationName) { - Log::Diag("SystemAccessTracker::OnCreateProcess - " + std::string(applicationName)); + if (wasDetoured) + Log::Diag("SystemAccessTracker::OnCreateDetouredProcess - " + std::string(applicationName)); + else + Log::Diag("SystemAccessTracker::OnCreateProcess - " + std::string(applicationName)); } void TouchFileRead(std::wstring_view fileName, bool exists) @@ -1113,7 +1116,7 @@ namespace Soup::Build bool IsSpecialFile(std::string_view fileName) { // Check if the file name is a pipe or the standard input/output streams - return fileName.starts_with("\\\\.\\pipe\\") || + return fileName.starts_with("\\\\.\\") || fileName == "CONIN$" || fileName == "CONOUT$"; } diff --git a/Source/Build/Utilities.UnitTests/Recipe.toml b/Source/Build/Utilities.UnitTests/Recipe.toml index 6bc4a77ea..63401f4b8 100644 --- a/Source/Build/Utilities.UnitTests/Recipe.toml +++ b/Source/Build/Utilities.UnitTests/Recipe.toml @@ -1,4 +1,4 @@ -Name = "Soup.Build.Utlities.UnitTests" +Name = "Soup.Build.Utilities.UnitTests" Version = "1.0.0" Type = "Executable" Dependencies = [ diff --git a/Source/Client/CLI/Commands/InstallCommand.h b/Source/Client/CLI/Commands/InstallCommand.h index dd8323f6a..77ca48706 100644 --- a/Source/Client/CLI/Commands/InstallCommand.h +++ b/Source/Client/CLI/Commands/InstallCommand.h @@ -28,14 +28,34 @@ namespace Soup::Client virtual void Run() override final { Log::Diag("InstallCommand::Run"); - - if (_options.Package.empty()) + + auto workingDirectory = Path(); + if (_options.Path.empty()) + { + // Buildin the current directory + workingDirectory = System::IFileSystem::Current().GetCurrentDirectory2(); + } + else + { + workingDirectory = Path(_options.Path); + + // Check if this is relative to current directory + if (!workingDirectory.HasRoot()) + { + workingDirectory = System::IFileSystem::Current().GetCurrentDirectory2() + workingDirectory; + } + } + + if (_options.PackageReference.empty()) { - PackageManager::InstallPackages(); + PackageManager::InstallPackages( + workingDirectory); } else { - PackageManager::InstallPackageReference(_options.Package); + PackageManager::InstallPackageReference( + workingDirectory, + _options.PackageReference); } } diff --git a/Source/Client/CLI/Commands/RunCommand.h b/Source/Client/CLI/Commands/RunCommand.h index 41d80dd34..5c69c077b 100644 --- a/Source/Client/CLI/Commands/RunCommand.h +++ b/Source/Client/CLI/Commands/RunCommand.h @@ -32,7 +32,23 @@ namespace Soup::Client // Load the user config auto config = LocalUserConfigExtensions::LoadFromFile(); - auto workingDirectory = System::IFileSystem::Current().GetCurrentDirectory2(); + auto workingDirectory = Path(); + if (_options.Path.empty()) + { + // Buildin the current directory + workingDirectory = System::IFileSystem::Current().GetCurrentDirectory2(); + } + else + { + workingDirectory = Path(_options.Path); + + // Check if this is relative to current directory + if (!workingDirectory.HasRoot()) + { + workingDirectory = System::IFileSystem::Current().GetCurrentDirectory2() + workingDirectory; + } + } + auto recipePath = workingDirectory + Path(Constants::RecipeFileName); diff --git a/Source/Client/CLI/Options/ArgumentsParser.h b/Source/Client/CLI/Options/ArgumentsParser.h index 5aab5db0b..19a741868 100644 --- a/Source/Client/CLI/Options/ArgumentsParser.h +++ b/Source/Client/CLI/Options/ArgumentsParser.h @@ -95,7 +95,13 @@ namespace Soup::Client auto argument = std::string(); if (TryGetIndexArgument(unusedArgs, argument)) { - options->Package = std::move(argument); + options->Path = std::move(argument); + } + + auto PackageReference = std::string(); + if (TryGetValueArgument("p", unusedArgs, PackageReference)) + { + options->PackageReference = std::move(PackageReference); } options->Verbosity = CheckVerbosity(unusedArgs); @@ -130,8 +136,19 @@ namespace Soup::Client auto syntheticParams = std::vector(); options->Verbosity = CheckVerbosity(syntheticParams); + // Split all parameters after the args flag to pass into the run command + std::vector runArgs; + SplitArguments("args", unusedArgs, runArgs); + + // Check for required index argument + auto argument = std::string(); + if (TryGetIndexArgument(unusedArgs, argument)) + { + options->Path = std::move(argument); + } + // All remaining arguments are passed to the executable - options->Arguments = std::move(unusedArgs); + options->Arguments = std::move(runArgs); result = std::move(options); } @@ -236,6 +253,20 @@ namespace Soup::Client } } + static void SplitArguments(const char* name, std::vector& unusedArgs, std::vector& splitArgs) + { + auto flagValue = std::string("-") + name; + Log::Diag("IsFlagSet: " + flagValue); + auto flagLocation = std::find(unusedArgs.begin(), unusedArgs.end(), flagValue); + if (flagLocation != unusedArgs.end()) + { + // Consume the flag value + auto argsStart = std::next(flagLocation); + std::move(argsStart, unusedArgs.end(), std::back_inserter(splitArgs)); + unusedArgs.erase(flagLocation, unusedArgs.end()); + } + } + static bool TryGetValueArgument( const char* name, std::vector& unusedArgs, diff --git a/Source/Client/CLI/Options/InstallOptions.h b/Source/Client/CLI/Options/InstallOptions.h index 0dbf91130..d989228e9 100644 --- a/Source/Client/CLI/Options/InstallOptions.h +++ b/Source/Client/CLI/Options/InstallOptions.h @@ -14,11 +14,17 @@ namespace Soup::Client class InstallOptions : public SharedOptions { public: + /// + /// Gets or sets the path to install + /// + [[Args::Option("path", Index = 0, HelpText = "Path to the package to install.")]] + std::string Path; + /// /// Gets or sets the package name /// [[Args::Option(Index = 0, HelpText = "The package reference to install.")]] - std::string Package; + std::string PackageReference; /// /// Show the usage details for this command diff --git a/Source/Client/CLI/Options/RunOptions.h b/Source/Client/CLI/Options/RunOptions.h index 92ce6240f..1d4adb195 100644 --- a/Source/Client/CLI/Options/RunOptions.h +++ b/Source/Client/CLI/Options/RunOptions.h @@ -14,6 +14,12 @@ namespace Soup::Client class RunOptions : public SharedOptions { public: + /// + /// Gets or sets the path to run + /// + [[Args::Option("path", Index = 0, HelpText = "Path to the package to run.")]] + std::string Path; + /// /// Pass through all of the remaining arguments /// diff --git a/Source/Client/Core.UnitTests/Package/PackageManagerTests.h b/Source/Client/Core.UnitTests/Package/PackageManagerTests.h index aeb0deb8d..a1bbfef20 100644 --- a/Source/Client/Core.UnitTests/Package/PackageManagerTests.h +++ b/Source/Client/Core.UnitTests/Package/PackageManagerTests.h @@ -22,18 +22,19 @@ namespace Soup::UnitTests // Create the Recipe fileSystem->CreateMockFile( - Path("Recipe.toml"), + Path("C:/TestLocation/Recipe.toml"), std::make_shared(std::stringstream(R"( Name = "MyPackage" Version = "1.2.3" )"))); - PackageManager::InstallPackages(); + auto workingDirectory = Path("C:/TestLocation"); + PackageManager::InstallPackages(workingDirectory); Assert::AreEqual( std::vector({ "INFO: Using Package Store: C:/Users/Me/.soup/packages/", - "DIAG: Load Recipe: Recipe.toml", + "DIAG: Load Recipe: C:/TestLocation/Recipe.toml", "INFO: Deleting staging directory", }), testListener->GetMessages(), @@ -44,8 +45,8 @@ namespace Soup::UnitTests "GetCurrentDirectory", "Exists: C:/Users/Me/.soup/packages/.staging", "CreateDirectory: C:/Users/Me/.soup/packages/.staging", - "Exists: Recipe.toml", - "OpenReadBinary: Recipe.toml", + "Exists: C:/TestLocation/Recipe.toml", + "OpenReadBinary: C:/TestLocation/Recipe.toml", "DeleteDirectoryRecursive: C:/Users/Me/.soup/packages/.staging", }), fileSystem->GetRequests(), @@ -65,7 +66,7 @@ namespace Soup::UnitTests // Create the Recipe fileSystem->CreateMockFile( - Path("Recipe.toml"), + Path("C:/TestLocation/SubFolder/Recipe.toml"), std::make_shared(std::stringstream(R"( Name = "MyPackage" Version = "1.2.3" @@ -76,14 +77,14 @@ namespace Soup::UnitTests )"))); fileSystem->CreateMockFile( - Path("../MyProject1/Recipe.toml"), + Path("C:/TestLocation/MyProject1/Recipe.toml"), std::make_shared(std::stringstream(R"( Name = "MyProject1" Version = "1.2.3" )"))); fileSystem->CreateMockFile( - Path("../../MyProject2/Recipe.toml"), + Path("C:/MyProject2/Recipe.toml"), std::make_shared(std::stringstream(R"( Name = "MyProject2" Version = "1.2.3" @@ -93,21 +94,22 @@ namespace Soup::UnitTests )"))); fileSystem->CreateMockFile( - Path("../../MyProject3/Recipe.toml"), + Path("C:/MyProject3/Recipe.toml"), std::make_shared(std::stringstream(R"( Name = "MyProject3" Version = "1.2.3" )"))); - PackageManager::InstallPackages(); + auto workingDirectory = Path("C:/TestLocation/SubFolder/"); + PackageManager::InstallPackages(workingDirectory); Assert::AreEqual( std::vector({ "INFO: Using Package Store: C:/Users/Me/.soup/packages/", - "DIAG: Load Recipe: Recipe.toml", - "DIAG: Load Recipe: ../MyProject1/Recipe.toml", - "DIAG: Load Recipe: ../../MyProject2/Recipe.toml", - "DIAG: Load Recipe: ../../MyProject3/Recipe.toml", + "DIAG: Load Recipe: C:/TestLocation/SubFolder/Recipe.toml", + "DIAG: Load Recipe: C:/TestLocation/MyProject1/Recipe.toml", + "DIAG: Load Recipe: C:/MyProject2/Recipe.toml", + "DIAG: Load Recipe: C:/MyProject3/Recipe.toml", "INFO: Deleting staging directory", }), testListener->GetMessages(), @@ -118,14 +120,14 @@ namespace Soup::UnitTests "GetCurrentDirectory", "Exists: C:/Users/Me/.soup/packages/.staging", "CreateDirectory: C:/Users/Me/.soup/packages/.staging", - "Exists: Recipe.toml", - "OpenReadBinary: Recipe.toml", - "Exists: ../MyProject1/Recipe.toml", - "OpenReadBinary: ../MyProject1/Recipe.toml", - "Exists: ../../MyProject2/Recipe.toml", - "OpenReadBinary: ../../MyProject2/Recipe.toml", - "Exists: ../../MyProject3/Recipe.toml", - "OpenReadBinary: ../../MyProject3/Recipe.toml", + "Exists: C:/TestLocation/SubFolder/Recipe.toml", + "OpenReadBinary: C:/TestLocation/SubFolder/Recipe.toml", + "Exists: C:/TestLocation/MyProject1/Recipe.toml", + "OpenReadBinary: C:/TestLocation/MyProject1/Recipe.toml", + "Exists: C:/MyProject2/Recipe.toml", + "OpenReadBinary: C:/MyProject2/Recipe.toml", + "Exists: C:/MyProject3/Recipe.toml", + "OpenReadBinary: C:/MyProject3/Recipe.toml", "DeleteDirectoryRecursive: C:/Users/Me/.soup/packages/.staging", }), fileSystem->GetRequests(), @@ -149,7 +151,7 @@ namespace Soup::UnitTests // Create the Recipe fileSystem->CreateMockFile( - Path("Recipe.toml"), + Path("C:/TestLocation/Recipe.toml"), std::make_shared(std::stringstream(R"( Name = "MyPackage" Version = "1.2.3" @@ -170,12 +172,13 @@ namespace Soup::UnitTests GetTheirPackageArchive()); testHttpClient->AddGetResponse("/v1/packages/TheirPackage/v2.2.2/download", packageContentResponse); - PackageManager::InstallPackages(); + auto workingDirectory = Path("C:/TestLocation"); + PackageManager::InstallPackages(workingDirectory); Assert::AreEqual( std::vector({ "INFO: Using Package Store: C:/Users/Me/.soup/packages/", - "DIAG: Load Recipe: Recipe.toml", + "DIAG: Load Recipe: C:/TestLocation/Recipe.toml", "HIGH: Install Package: TheirPackage@2.2.2", "HIGH: Downloading package", "DIAG: /v1/packages/TheirPackage/v2.2.2/download", @@ -200,8 +203,8 @@ namespace Soup::UnitTests "GetCurrentDirectory", "Exists: C:/Users/Me/.soup/packages/.staging", "CreateDirectory: C:/Users/Me/.soup/packages/.staging", - "Exists: Recipe.toml", - "OpenReadBinary: Recipe.toml", + "Exists: C:/TestLocation/Recipe.toml", + "OpenReadBinary: C:/TestLocation/Recipe.toml", "Exists: C:/Users/Me/.soup/packages/TheirPackage/2.2.2/", "OpenWriteBinary: C:/Users/Me/.soup/packages/.staging/TheirPackage.7z", "CreateDirectory: C:/Users/Me/.soup/packages/.staging/TheirPackage/2.2.2/", @@ -246,14 +249,15 @@ namespace Soup::UnitTests auto fileSystem = std::make_shared(); auto scopedFileSystem = ScopedFileSystemRegister(fileSystem); + auto workingDirectory = Path("C:/TestLocation"); auto packageName = "TheirPackage"; - Assert::ThrowsRuntimeError([&packageName]() { - PackageManager::InstallPackageReference(packageName); + Assert::ThrowsRuntimeError([&workingDirectory, &packageName]() { + PackageManager::InstallPackageReference(workingDirectory, packageName); }); Assert::AreEqual( std::vector({ - "DIAG: Load Recipe: Recipe.toml", + "DIAG: Load Recipe: C:/TestLocation/Recipe.toml", "INFO: Recipe file does not exist.", }), testListener->GetMessages(), @@ -261,7 +265,7 @@ namespace Soup::UnitTests Assert::AreEqual( std::vector({ - "Exists: Recipe.toml", + "Exists: C:/TestLocation/Recipe.toml", }), fileSystem->GetRequests(), "Verify file system requests match expected."); @@ -284,7 +288,7 @@ namespace Soup::UnitTests // Create the Recipe fileSystem->CreateMockFile( - Path("Recipe.toml"), + Path("C:/TestLocation/Recipe.toml"), std::make_shared(std::stringstream(R"( Name = "MyPackage" Version = "1.2.3" @@ -302,12 +306,13 @@ namespace Soup::UnitTests GetTheirPackageArchive()); testHttpClient->AddGetResponse("/v1/packages/TheirPackage/v2.2.2/download", packageContentResponse); + auto workingDirectory = Path("C:/TestLocation"); auto packageName = "TheirPackage@2.2.2"; - PackageManager::InstallPackageReference(packageName); + PackageManager::InstallPackageReference(workingDirectory, packageName); Assert::AreEqual( std::vector({ - "DIAG: Load Recipe: Recipe.toml", + "DIAG: Load Recipe: C:/TestLocation/Recipe.toml", "INFO: Using Package Store: C:/Users/Me/.soup/packages/", "HIGH: Install Package: TheirPackage@2.2.2", "HIGH: Downloading package", @@ -331,8 +336,8 @@ namespace Soup::UnitTests Assert::AreEqual( std::vector({ - "Exists: Recipe.toml", - "OpenReadBinary: Recipe.toml", + "Exists: C:/TestLocation/Recipe.toml", + "OpenReadBinary: C:/TestLocation/Recipe.toml", "GetCurrentDirectory", "Exists: C:/Users/Me/.soup/packages/.staging", "CreateDirectory: C:/Users/Me/.soup/packages/.staging", @@ -350,7 +355,7 @@ namespace Soup::UnitTests "CreateDirectory: C:/Users/Me/.soup/packages/TheirPackage", "Rename: [C:/Users/Me/.soup/packages/.staging/TheirPackage/2.2.2/] -> [C:/Users/Me/.soup/packages/TheirPackage/2.2.2/]", "DeleteDirectoryRecursive: C:/Users/Me/.soup/packages/.staging", - "OpenWrite: Recipe.toml", + "OpenWrite: C:/TestLocation/Recipe.toml", }), fileSystem->GetRequests(), "Verify file system requests match expected."); @@ -377,7 +382,7 @@ Dependencies = [ "TheirPackage@2.2.2", ] )"; - auto& mockRecipeFile = fileSystem->GetMockFile(Path("Recipe.toml")); + auto& mockRecipeFile = fileSystem->GetMockFile(Path("C:/TestLocation/Recipe.toml")); Assert::AreEqual(expectedFinalRecipe, mockRecipeFile->Content.str(), "Verify recipe file contents."); } @@ -398,7 +403,7 @@ Dependencies = [ // Create the Recipe fileSystem->CreateMockFile( - Path("Recipe.toml"), + Path("C:/TestLocation/Recipe.toml"), std::make_shared(std::stringstream(R"( Name = "MyPackage" Version = "1.2.3" @@ -421,12 +426,13 @@ Dependencies = [ GetMyDependencyArchive()); testHttpClient->AddGetResponse("/v1/packages/MyDependency/v1.0.0/download", packageTwoContentResponse); + auto workingDirectory = Path("C:/TestLocation"); auto packageName = "TheirPackage@2.2.2"; - PackageManager::InstallPackageReference(packageName); + PackageManager::InstallPackageReference(workingDirectory, packageName); Assert::AreEqual( std::vector({ - "DIAG: Load Recipe: Recipe.toml", + "DIAG: Load Recipe: C:/TestLocation/Recipe.toml", "INFO: Using Package Store: C:/Users/Me/.soup/packages/", "HIGH: Install Package: TheirPackage@2.2.2", "HIGH: Downloading package", @@ -464,8 +470,8 @@ Dependencies = [ Assert::AreEqual( std::vector({ - "Exists: Recipe.toml", - "OpenReadBinary: Recipe.toml", + "Exists: C:/TestLocation/Recipe.toml", + "OpenReadBinary: C:/TestLocation/Recipe.toml", "GetCurrentDirectory", "Exists: C:/Users/Me/.soup/packages/.staging", "CreateDirectory: C:/Users/Me/.soup/packages/.staging", @@ -496,7 +502,7 @@ Dependencies = [ "CreateDirectory: C:/Users/Me/.soup/packages/TheirPackage", "Rename: [C:/Users/Me/.soup/packages/.staging/TheirPackage/2.2.2/] -> [C:/Users/Me/.soup/packages/TheirPackage/2.2.2/]", "DeleteDirectoryRecursive: C:/Users/Me/.soup/packages/.staging", - "OpenWrite: Recipe.toml", + "OpenWrite: C:/TestLocation/Recipe.toml", }), fileSystem->GetRequests(), "Verify file system requests match expected."); @@ -525,7 +531,7 @@ Dependencies = [ "TheirPackage@2.2.2", ] )"; - auto& mockRecipeFile = fileSystem->GetMockFile(Path("Recipe.toml")); + auto& mockRecipeFile = fileSystem->GetMockFile(Path("C:/TestLocation/Recipe.toml")); Assert::AreEqual(expectedFinalRecipe, mockRecipeFile->Content.str(), "Verify recipe file contents."); } @@ -546,7 +552,7 @@ Dependencies = [ // Create the Recipe fileSystem->CreateMockFile( - Path("Recipe.toml"), + Path("C:/TestLocation/Recipe.toml"), std::make_shared(std::stringstream(R"( Name = "MyPackage" Version = "1.2.3" @@ -572,12 +578,13 @@ Dependencies = [ GetTheirPackageArchive()); testHttpClient->AddGetResponse("/v1/packages/TheirPackage/v2.2.2/download", packageContentResponse); + auto workingDirectory = Path("C:/TestLocation"); auto packageName = "TheirPackage"; - PackageManager::InstallPackageReference(packageName); + PackageManager::InstallPackageReference(workingDirectory, packageName); Assert::AreEqual( std::vector({ - "DIAG: Load Recipe: Recipe.toml", + "DIAG: Load Recipe: C:/TestLocation/Recipe.toml", "INFO: Using Package Store: C:/Users/Me/.soup/packages/", "DIAG: /v1/packages/TheirPackage", "HIGH: Latest Version: 2.2.2", @@ -603,8 +610,8 @@ Dependencies = [ Assert::AreEqual( std::vector({ - "Exists: Recipe.toml", - "OpenReadBinary: Recipe.toml", + "Exists: C:/TestLocation/Recipe.toml", + "OpenReadBinary: C:/TestLocation/Recipe.toml", "GetCurrentDirectory", "Exists: C:/Users/Me/.soup/packages/.staging", "CreateDirectory: C:/Users/Me/.soup/packages/.staging", @@ -622,7 +629,7 @@ Dependencies = [ "CreateDirectory: C:/Users/Me/.soup/packages/TheirPackage", "Rename: [C:/Users/Me/.soup/packages/.staging/TheirPackage/2.2.2/] -> [C:/Users/Me/.soup/packages/TheirPackage/2.2.2/]", "DeleteDirectoryRecursive: C:/Users/Me/.soup/packages/.staging", - "OpenWrite: Recipe.toml", + "OpenWrite: C:/TestLocation/Recipe.toml", }), fileSystem->GetRequests(), "Verify file system requests match expected."); @@ -651,7 +658,7 @@ Dependencies = [ "TheirPackage@2.2.2", ] )"; - auto& mockRecipeFile = fileSystem->GetMockFile(Path("Recipe.toml")); + auto& mockRecipeFile = fileSystem->GetMockFile(Path("C:/TestLocation/Recipe.toml")); Assert::AreEqual(expectedFinalRecipe, mockRecipeFile->Content.str(), "Verify recipe file contents."); } diff --git a/Source/Client/Core/Package/PackageManager.h b/Source/Client/Core/Package/PackageManager.h index 0d252b846..e3a227628 100644 --- a/Source/Client/Core/Package/PackageManager.h +++ b/Source/Client/Core/Package/PackageManager.h @@ -29,9 +29,8 @@ namespace Soup /// /// Install packages /// - static void InstallPackages() + static void InstallPackages(const Path& workingDirectory) { - auto workingDirectory = Path(); auto packageStore = System::IFileSystem::Current().GetUserProfileDirectory() + Path(".soup/packages/"); Log::Info("Using Package Store: " + packageStore.ToString()); @@ -61,9 +60,8 @@ namespace Soup /// /// Install a package /// - static void InstallPackageReference(const std::string& packageReference) + static void InstallPackageReference(const Path& workingDirectory, const std::string& packageReference) { - auto workingDirectory = Path(); auto recipePath = workingDirectory + Path(Constants::RecipeFileName); diff --git a/Source/Extensions/Compiler/Core.UnitTests/BuildEngineTests.h b/Source/Extensions/Compiler/Core.UnitTests/BuildEngineTests.h index b17be0222..ad1057f80 100644 --- a/Source/Extensions/Compiler/Core.UnitTests/BuildEngineTests.h +++ b/Source/Extensions/Compiler/Core.UnitTests/BuildEngineTests.h @@ -26,6 +26,10 @@ namespace Soup::Compiler::UnitTests // Register the mock compiler auto compiler = std::make_shared(); + // Register the test process manager + auto processManager = std::make_shared(); + auto scopedProcesManager = ScopedProcessManagerRegister(processManager); + // Setup the build arguments auto arguments = BuildArguments(); arguments.TargetName = "Program"; @@ -59,6 +63,15 @@ namespace Soup::Compiler::UnitTests testListener->GetMessages(), "Verify log messages match expected."); + // Verify expected process requests + Assert::AreEqual( + std::vector({ + "GetCurrentProcessFileName", + "GetCurrentProcessFileName", + }), + processManager->GetRequests(), + "Verify process manager requests match expected."); + auto expectedCompileArguments = CompileArguments(); expectedCompileArguments.Standard = LanguageStandard::CPP20; expectedCompileArguments.Optimize = OptimizationLevel::None; @@ -127,8 +140,8 @@ namespace Soup::Compiler::UnitTests auto expectedBuildOperations = std::vector>({ new Build::Utilities::BuildOperation( "MakeDir [C:/root/obj]", - Path("C:/Windows/System32/cmd.exe"), - "/C if not exist \"C:/root/obj\" mkdir \"C:/root/obj\"", + Path("C:/testlocation/mkdir.exe"), + "\"C:/root/obj\"", Path("./"), std::vector({}), std::vector({ @@ -139,8 +152,8 @@ namespace Soup::Compiler::UnitTests })), new Build::Utilities::BuildOperation( "MakeDir [C:/root/bin]", - Path("C:/Windows/System32/cmd.exe"), - "/C if not exist \"C:/root/bin\" mkdir \"C:/root/bin\"", + Path("C:/testlocation/mkdir.exe"), + "\"C:/root/bin\"", Path("./"), std::vector({}), std::vector({ @@ -166,7 +179,9 @@ namespace Soup::Compiler::UnitTests "Verify Link Dependencies Result"); Assert::AreEqual( - std::vector({}), + std::vector({ + Path("C:/root/bin/Program.exe"), + }), result.RuntimeDependencies, "Verify Runtime Dependencies Result"); } @@ -178,6 +193,10 @@ namespace Soup::Compiler::UnitTests auto testListener = std::make_shared(); auto scopedTraceListener = ScopedTraceListenerRegister(testListener); + // Register the test process manager + auto processManager = std::make_shared(); + auto scopedProcesManager = ScopedProcessManagerRegister(processManager); + // Register the mock compiler auto compiler = std::make_shared(); @@ -226,6 +245,15 @@ namespace Soup::Compiler::UnitTests testListener->GetMessages(), "Verify log messages match expected."); + // Verify expected process requests + Assert::AreEqual( + std::vector({ + "GetCurrentProcessFileName", + "GetCurrentProcessFileName", + }), + processManager->GetRequests(), + "Verify process manager requests match expected."); + // Setup the shared arguments auto expectedCompileArguments = CompileArguments(); expectedCompileArguments.Standard = LanguageStandard::CPP20; @@ -342,8 +370,8 @@ namespace Soup::Compiler::UnitTests auto expectedBuildOperations = std::vector>({ new Build::Utilities::BuildOperation( "MakeDir [C:/root/obj]", - Path("C:/Windows/System32/cmd.exe"), - "/C if not exist \"C:/root/obj\" mkdir \"C:/root/obj\"", + Path("C:/testlocation/mkdir.exe"), + "\"C:/root/obj\"", Path("./"), std::vector({}), std::vector({ @@ -352,8 +380,8 @@ namespace Soup::Compiler::UnitTests expectedCompileOperations), new Build::Utilities::BuildOperation( "MakeDir [C:/root/bin]", - Path("C:/Windows/System32/cmd.exe"), - "/C if not exist \"C:/root/bin\" mkdir \"C:/root/bin\"", + Path("C:/testlocation/mkdir.exe"), + "\"C:/root/bin\"", Path("./"), std::vector({}), std::vector({ @@ -393,6 +421,10 @@ namespace Soup::Compiler::UnitTests auto testListener = std::make_shared(); auto scopedTraceListener = ScopedTraceListenerRegister(testListener); + // Register the test process manager + auto processManager = std::make_shared(); + auto scopedProcesManager = ScopedProcessManagerRegister(processManager); + // Register the mock compiler auto compiler = std::make_shared(); @@ -448,6 +480,16 @@ namespace Soup::Compiler::UnitTests testListener->GetMessages(), "Verify log messages match expected."); + // Verify expected process requests + Assert::AreEqual( + std::vector({ + "GetCurrentProcessFileName", + "GetCurrentProcessFileName", + "GetCurrentProcessFileName", + }), + processManager->GetRequests(), + "Verify process manager requests match expected."); + // Setup the shared arguments auto expectedCompileArguments = CompileArguments(); expectedCompileArguments.Standard = LanguageStandard::CPP20; @@ -577,8 +619,8 @@ namespace Soup::Compiler::UnitTests Memory::Reference( new Build::Utilities::BuildOperation( "Copy [C:/root/obj/Public.mock.bmi] -> [C:/root/bin/Library.mock.bmi]", - Path("C:/Windows/System32/cmd.exe"), - "/C copy /Y \"C:\\root\\obj\\Public.mock.bmi\" \"C:\\root\\bin\\Library.mock.bmi\"", + Path("C:/testlocation/copy.exe"), + "\"C:\\root\\obj\\Public.mock.bmi\" \"C:\\root\\bin\\Library.mock.bmi\"", Path("./"), std::vector({ Path("C:/root/obj/Public.mock.bmi"), @@ -608,8 +650,8 @@ namespace Soup::Compiler::UnitTests auto expectedBuildOperations = std::vector>({ new Build::Utilities::BuildOperation( "MakeDir [C:/root/obj]", - Path("C:/Windows/System32/cmd.exe"), - "/C if not exist \"C:/root/obj\" mkdir \"C:/root/obj\"", + Path("C:/testlocation/mkdir.exe"), + "\"C:/root/obj\"", Path("./"), std::vector({}), std::vector({ @@ -620,8 +662,8 @@ namespace Soup::Compiler::UnitTests })), new Build::Utilities::BuildOperation( "MakeDir [C:/root/bin]", - Path("C:/Windows/System32/cmd.exe"), - "/C if not exist \"C:/root/bin\" mkdir \"C:/root/bin\"", + Path("C:/testlocation/mkdir.exe"), + "\"C:/root/bin\"", Path("./"), std::vector({}), std::vector({ @@ -665,6 +707,10 @@ namespace Soup::Compiler::UnitTests auto testListener = std::make_shared(); auto scopedTraceListener = ScopedTraceListenerRegister(testListener); + // Register the test process manager + auto processManager = std::make_shared(); + auto scopedProcesManager = ScopedProcessManagerRegister(processManager); + // Register the mock compiler auto compiler = std::make_shared(); @@ -708,6 +754,16 @@ namespace Soup::Compiler::UnitTests testListener->GetMessages(), "Verify log messages match expected."); + // Verify expected process requests + Assert::AreEqual( + std::vector({ + "GetCurrentProcessFileName", + "GetCurrentProcessFileName", + "GetCurrentProcessFileName", + }), + processManager->GetRequests(), + "Verify process manager requests match expected."); + // Setup the shared arguments auto expectedCompileArguments = CompileArguments(); expectedCompileArguments.Standard = LanguageStandard::CPP20; @@ -769,8 +825,8 @@ namespace Soup::Compiler::UnitTests Memory::Reference( new Build::Utilities::BuildOperation( "Copy [C:/root/obj/Public.mock.bmi] -> [C:/root/bin/Library.mock.bmi]", - Path("C:/Windows/System32/cmd.exe"), - "/C copy /Y \"C:\\root\\obj\\Public.mock.bmi\" \"C:\\root\\bin\\Library.mock.bmi\"", + Path("C:/testlocation/copy.exe"), + "\"C:\\root\\obj\\Public.mock.bmi\" \"C:\\root\\bin\\Library.mock.bmi\"", Path("./"), std::vector({ Path("C:/root/obj/Public.mock.bmi"), @@ -802,8 +858,8 @@ namespace Soup::Compiler::UnitTests auto expectedBuildOperations = std::vector>({ new Build::Utilities::BuildOperation( "MakeDir [C:/root/obj]", - Path("C:/Windows/System32/cmd.exe"), - "/C if not exist \"C:/root/obj\" mkdir \"C:/root/obj\"", + Path("C:/testlocation/mkdir.exe"), + "\"C:/root/obj\"", Path("./"), std::vector({}), std::vector({ @@ -814,8 +870,8 @@ namespace Soup::Compiler::UnitTests })), new Build::Utilities::BuildOperation( "MakeDir [C:/root/bin]", - Path("C:/Windows/System32/cmd.exe"), - "/C if not exist \"C:/root/bin\" mkdir \"C:/root/bin\"", + Path("C:/testlocation/mkdir.exe"), + "\"C:/root/bin\"", Path("./"), std::vector({}), std::vector({ diff --git a/Source/Extensions/Compiler/Core/BuildEngine.h b/Source/Extensions/Compiler/Core/BuildEngine.h index fbb35373c..ab71bb8c7 100644 --- a/Source/Extensions/Compiler/Core/BuildEngine.h +++ b/Source/Extensions/Compiler/Core/BuildEngine.h @@ -272,7 +272,7 @@ namespace Soup::Compiler // Add the DLL as a runtime dependency result.RuntimeDependencies.push_back(linkArguments.RootDirectory + linkArguments.TargetFile); - + // Clear out all previous link dependencies and replace with the // single implementation library for the DLL result.LinkDependencies.push_back(linkArguments.RootDirectory + linkArguments.ImplementationFile); @@ -282,6 +282,9 @@ namespace Soup::Compiler { linkArguments.TargetType = LinkTarget::Executable; + // Add the Executable as a runtime dependency + result.RuntimeDependencies.push_back(linkArguments.RootDirectory + linkArguments.TargetFile); + // All link dependencies stop here. break; } diff --git a/Source/Monitor/Detours/EventLogger.h b/Source/Monitor/Detours/EventLogger.h index 933181ed4..13f0fcd55 100644 --- a/Source/Monitor/Detours/EventLogger.h +++ b/Source/Monitor/Detours/EventLogger.h @@ -8,6 +8,7 @@ class EventLogger public: static void Initialize() { + DebugTrace("EventLogger::Initialize"); Connect(); // Notify that we are connected @@ -19,6 +20,7 @@ class EventLogger static void Shutdown() { + DebugTrace("EventLogger::Shutdown"); auto lock = std::lock_guard(s_pipeMutex); Monitor::DetourMessage message; message.Type = Monitor::DetourMessageType::Info_Shutdown; @@ -42,7 +44,10 @@ class EventLogger auto size = stringValue.size() + 1; message.ContentSize += size; if (message.ContentSize > sizeof(Monitor::DetourMessage::Content)) - throw std::runtime_error("Message content too long for string value"); + { + WriteError("Message content too long for const char* value"); + exit(-1234); + } stringValue.copy(reinterpret_cast(message.Content + startIndex), size); } @@ -61,7 +66,10 @@ class EventLogger auto size = 2 * (stringValue.size() + 1); message.ContentSize += size; if (message.ContentSize > sizeof(Monitor::DetourMessage::Content)) - throw std::runtime_error("Message content too long for string value"); + { + WriteError("Message content too long for const wchar_t* value"); + exit(-1234); + } stringValue.copy(reinterpret_cast(message.Content + startIndex), size); } @@ -76,7 +84,10 @@ class EventLogger auto startIndex = message.ContentSize; message.ContentSize += sizeof(void*); if (message.ContentSize > sizeof(Monitor::DetourMessage::Content)) - throw std::runtime_error("Message content too long for int value"); + { + WriteError("Message content too long for void* value"); + exit(-1234); + } *reinterpret_cast(message.Content + startIndex) = value; } @@ -86,7 +97,10 @@ class EventLogger auto startIndex = message.ContentSize; message.ContentSize += sizeof(int); if (message.ContentSize > sizeof(Monitor::DetourMessage::Content)) - throw std::runtime_error("Message content too long for int value"); + { + WriteError("Message content too long for int value"); + exit(-1234); + } *reinterpret_cast(message.Content + startIndex) = value; } @@ -96,7 +110,10 @@ class EventLogger auto startIndex = message.ContentSize; message.ContentSize += sizeof(unsigned int); if (message.ContentSize > sizeof(Monitor::DetourMessage::Content)) - throw std::runtime_error("Message content too long for unsigned int value"); + { + WriteError("Message content too long for unsigned int value"); + exit(-1234); + } *reinterpret_cast(message.Content + startIndex) = value; } @@ -106,7 +123,10 @@ class EventLogger auto startIndex = message.ContentSize; message.ContentSize += sizeof(unsigned long); if (message.ContentSize > sizeof(Monitor::DetourMessage::Content)) - throw std::runtime_error("Message content too long for unsigned long value"); + { + WriteError("Message content too long for unsigned long value"); + exit(-1234); + } *reinterpret_cast(message.Content + startIndex) = value; } @@ -116,14 +136,17 @@ class EventLogger auto startIndex = message.ContentSize; message.ContentSize += sizeof(unsigned long long); if (message.ContentSize > sizeof(Monitor::DetourMessage::Content)) - throw std::runtime_error("Message content too long for unsigned long long value"); + { + WriteError("Message content too long for unsigned long long value"); + exit(-1234); + } *reinterpret_cast(message.Content + startIndex) = value; } static void WriteError(std::string_view value) { - printf("DETOURS-ERROR: %s\n", value.data()); + DebugError(value.data()); Monitor::DetourMessage message; message.Type = Monitor::DetourMessageType::Info_Error; @@ -141,6 +164,7 @@ class EventLogger private: static void Connect() { + DebugTrace("EventLogger::Connect"); std::stringstream pipeNameBuilder; pipeNameBuilder << TBLOG_PIPE_NAMEA << "." << s_nTraceProcessId; auto pipeName = pipeNameBuilder.str(); @@ -150,9 +174,11 @@ class EventLogger { // Wait up to 1 seconds for a pipe to appear. auto timoutMilliseconds = 1000; + DebugTrace("EventLogger::Connect WaitNamedPipeA"); if (WaitNamedPipeA(pipeName.c_str(), timoutMilliseconds) != 0) { // Attempt to open the pipe + DebugTrace("EventLogger::Connect CreateFileA"); s_pipeHandle = Functions::FileApi::Cache::CreateFileA( pipeName.c_str(), GENERIC_WRITE, @@ -164,16 +190,38 @@ class EventLogger if (s_pipeHandle != INVALID_HANDLE_VALUE) { DWORD pipeMode = PIPE_READMODE_MESSAGE; + DebugTrace("EventLogger::Connect SetNamedPipeHandleState"); if (SetNamedPipeHandleState(s_pipeHandle, &pipeMode, nullptr, nullptr)) { // All good! return; } + else + { + auto error = GetLastError(); + DebugTrace("EventLogger::Connect SetNamedPipeHandleState failed" + std::to_string(error)); + throw std::runtime_error("SetNamedPipeHandleState failed with unknown error."); + } + } + } + else + { + auto error = GetLastError(); + switch (error) + { + case ERROR_SEM_TIMEOUT: + // Keep waiting + DebugTrace("EventLogger::Connect WaitNamedPipeA ERROR_SEM_TIMEOUT"); + break; + default: + DebugTrace("EventLogger::Connect WaitNamedPipeA Unknown error " + std::to_string(error)); + throw std::runtime_error("WaitNamedPipeA failed with unknown error."); } } } // Couldn't open pipe. + DebugTrace("EventLogger::Connect failed"); throw std::runtime_error("Failed to open pipe for event logger."); } @@ -181,7 +229,7 @@ class EventLogger { if (s_pipeHandle == INVALID_HANDLE_VALUE) { - printf("DETOURS-ERROR: Handle not ready!! %u\n", (uint32_t)message.Type); + DebugError("Handle not ready", (uint32_t)message.Type); exit(-1234); } @@ -197,17 +245,34 @@ class EventLogger &countBytesWritten, nullptr)) { - printf("DETOURS-ERROR: Failed write event logger\n", (uint32_t)message.Type); + DebugError("DETOURS-CLIENT-ERROR: Failed write event logger"); exit(-1234); } if (countBytesWritten != countBytesToWrite) { - printf("Did not write the expected number of bytes\n", (uint32_t)message.Type); + DebugError("Did not write the expected number of bytes"); exit(-1234); } } + static void DebugError(std::string_view message, uint32_t value) + { + printf("DETOUR-CLIENT: ERROR %s %u", message.data(), value); + } + + static void DebugError(std::string_view message) + { + printf("DETOUR-CLIENT: ERROR %s", message.data()); + } + + static void DebugTrace(std::string_view message) + { +#ifdef TRACE_DETOUR_CLIENT + printf("DETOUR-CLIENT: %s", message.data()); +#endif + } + private: static std::mutex s_pipeMutex; static HANDLE s_pipeHandle; diff --git a/Source/Monitor/Detours/Functions/ProcessThreadsApiOverrides.h b/Source/Monitor/Detours/Functions/ProcessThreadsApiOverrides.h index 43ad89c32..551f34e97 100644 --- a/Source/Monitor/Detours/Functions/ProcessThreadsApiOverrides.h +++ b/Source/Monitor/Detours/Functions/ProcessThreadsApiOverrides.h @@ -16,48 +16,68 @@ namespace Functions::ProcessThreadsApi::Overrides LPPROCESS_INFORMATION lpProcessInformation) { BOOL result = 0; + bool wasDetoured = false; __try { - // If the caller did not request the process information then create a temporary - // one for ourselves - LPPROCESS_INFORMATION lpInternalProcessInformation = lpProcessInformation; - PROCESS_INFORMATION privateProcessInformation; - if (lpInternalProcessInformation == nullptr) + if (IsWhiteListProcess(lpApplicationName)) { - lpInternalProcessInformation = &privateProcessInformation; + result = Cache::CreateProcessA( + lpApplicationName, + lpCommandLine, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + lpProcessInformation); } - - // Pass along the request with an initially suspended process - result = DetourCreateProcessWithDllExA( - lpApplicationName, - lpCommandLine, - lpProcessAttributes, - lpThreadAttributes, - bInheritHandles, - dwCreationFlags | CREATE_SUSPENDED, - lpEnvironment, - lpCurrentDirectory, - lpStartupInfo, - lpInternalProcessInformation, - s_szDllPath, - Cache::CreateProcessA); - - if (result) + else { - // Perform the detour setup - CreateProcessInternals(lpInternalProcessInformation->hProcess); + wasDetoured = true; - // If the caller did not create the process suspended then undo our override - if (!(dwCreationFlags & CREATE_SUSPENDED)) + // If the caller did not request the process information then create a temporary + // one for ourselves + LPPROCESS_INFORMATION lpInternalProcessInformation = lpProcessInformation; + PROCESS_INFORMATION privateProcessInformation; + if (lpInternalProcessInformation == nullptr) { - ResumeThread(lpInternalProcessInformation->hThread); + lpInternalProcessInformation = &privateProcessInformation; } - // Cleanup if we used the private information store - if (lpInternalProcessInformation == &privateProcessInformation) + // Pass along the request with an initially suspended process + result = DetourCreateProcessWithDllExA( + lpApplicationName, + lpCommandLine, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags | CREATE_SUSPENDED, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + lpInternalProcessInformation, + s_szDllPath, + Cache::CreateProcessA); + + if (result) { - CloseHandle(privateProcessInformation.hThread); - CloseHandle(privateProcessInformation.hProcess); + // Perform the detour setup + CreateProcessInternals(lpInternalProcessInformation->hProcess); + + // If the caller did not create the process suspended then undo our override + if (!(dwCreationFlags & CREATE_SUSPENDED)) + { + ResumeThread(lpInternalProcessInformation->hThread); + } + + // Cleanup if we used the private information store + if (lpInternalProcessInformation == &privateProcessInformation) + { + CloseHandle(privateProcessInformation.hThread); + CloseHandle(privateProcessInformation.hProcess); + } } } } @@ -65,6 +85,7 @@ namespace Functions::ProcessThreadsApi::Overrides { auto message = Monitor::DetourMessage(); message.Type = Monitor::DetourMessageType::CreateProcessA; + EventLogger::AppendValue(message, wasDetoured); EventLogger::AppendValue(message, lpApplicationName); EventLogger::AppendValue(message, result); EventLogger::WriteMessage(message); @@ -86,48 +107,68 @@ namespace Functions::ProcessThreadsApi::Overrides LPPROCESS_INFORMATION lpProcessInformation) { BOOL result = 0; + bool wasDetoured = false; __try { - // If the caller did not request the process information then create a temporary - // one for ourselves - LPPROCESS_INFORMATION lpInternalProcessInformation = lpProcessInformation; - PROCESS_INFORMATION privateProcessInformation; - if (lpInternalProcessInformation == nullptr) + if (IsWhiteListProcess(lpApplicationName)) { - lpInternalProcessInformation = &privateProcessInformation; + result = Cache::CreateProcessW( + lpApplicationName, + lpCommandLine, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + lpProcessInformation); } - - // Pass along the request with an initially suspended process - result = DetourCreateProcessWithDllExW( - lpApplicationName, - lpCommandLine, - lpProcessAttributes, - lpThreadAttributes, - bInheritHandles, - dwCreationFlags | CREATE_SUSPENDED, - lpEnvironment, - lpCurrentDirectory, - lpStartupInfo, - lpInternalProcessInformation, - s_szDllPath, - Cache::CreateProcessW); - - if (result) + else { - // Perform the detour setup - CreateProcessInternals(lpInternalProcessInformation->hProcess); + wasDetoured = true; - // If the caller did not create the process suspended then undo our override - if (!(dwCreationFlags & CREATE_SUSPENDED)) + // If the caller did not request the process information then create a temporary + // one for ourselves + LPPROCESS_INFORMATION lpInternalProcessInformation = lpProcessInformation; + PROCESS_INFORMATION privateProcessInformation; + if (lpInternalProcessInformation == nullptr) { - ResumeThread(lpInternalProcessInformation->hThread); + lpInternalProcessInformation = &privateProcessInformation; } - // Cleanup if we used the private information store - if (lpInternalProcessInformation == &privateProcessInformation) + // Pass along the request with an initially suspended process + result = DetourCreateProcessWithDllExW( + lpApplicationName, + lpCommandLine, + lpProcessAttributes, + lpThreadAttributes, + bInheritHandles, + dwCreationFlags | CREATE_SUSPENDED, + lpEnvironment, + lpCurrentDirectory, + lpStartupInfo, + lpInternalProcessInformation, + s_szDllPath, + Cache::CreateProcessW); + + if (result) { - CloseHandle(privateProcessInformation.hThread); - CloseHandle(privateProcessInformation.hProcess); + // Perform the detour setup + CreateProcessInternals(lpInternalProcessInformation->hProcess); + + // If the caller did not create the process suspended then undo our override + if (!(dwCreationFlags & CREATE_SUSPENDED)) + { + ResumeThread(lpInternalProcessInformation->hThread); + } + + // Cleanup if we used the private information store + if (lpInternalProcessInformation == &privateProcessInformation) + { + CloseHandle(privateProcessInformation.hThread); + CloseHandle(privateProcessInformation.hProcess); + } } } } @@ -135,6 +176,7 @@ namespace Functions::ProcessThreadsApi::Overrides { auto message = Monitor::DetourMessage(); message.Type = Monitor::DetourMessageType::CreateProcessW; + EventLogger::AppendValue(message, wasDetoured); EventLogger::AppendValue(message, lpApplicationName); EventLogger::AppendValue(message, result); EventLogger::WriteMessage(message); diff --git a/Source/Monitor/Detours/Helpers.h b/Source/Monitor/Detours/Helpers.h index 9cc41c565..42b51df11 100644 --- a/Source/Monitor/Detours/Helpers.h +++ b/Source/Monitor/Detours/Helpers.h @@ -28,3 +28,55 @@ bool CreateProcessInternals( return true; } + +bool EndsWithIgnoreCase(std::wstring_view lhs, std::wstring_view value) +{ + auto lhsLength = lhs.size(); + auto valueLength = value.size(); + if (lhsLength < valueLength) + return false; + + auto offset = lhsLength - valueLength; + auto lhsEnd = lhs.substr(offset, valueLength); + return std::equal( + lhsEnd.begin(), + lhsEnd.end(), + value.begin(), + value.end(), + [](wchar_t a, wchar_t b) + { + return std::tolower(a) == std::tolower(b); + }); +} + +bool EndsWithIgnoreCase(std::string_view lhs, std::string_view value) +{ + auto lhsLength = lhs.size(); + auto valueLength = value.size(); + if (lhsLength < valueLength) + return false; + + auto offset = lhsLength - valueLength; + auto lhsEnd = lhs.substr(offset, valueLength); + return std::equal( + lhsEnd.begin(), + lhsEnd.end(), + value.begin(), + value.end(), + [](char a, char b) + { + return std::tolower(a) == std::tolower(b); + }); +} + +bool IsWhiteListProcess(std::string_view applicationName) +{ + // VCTIP is the Visual Studio telemetry service, they really need to stop looking at user folders... + return EndsWithIgnoreCase(applicationName, "VCTIP.EXE"); +} + +bool IsWhiteListProcess(std::wstring_view applicationName) +{ + // VCTIP is the Visual Studio telemetry service, they really need to stop looking at user folders... + return EndsWithIgnoreCase(applicationName, L"VCTIP.EXE"); +} \ No newline at end of file diff --git a/Source/Monitor/Detours/Recipe.toml b/Source/Monitor/Detours/Recipe.toml index 1e64e1042..b8c60600a 100644 --- a/Source/Monitor/Detours/Recipe.toml +++ b/Source/Monitor/Detours/Recipe.toml @@ -7,5 +7,6 @@ Dependencies = [ ] Defines = [ # "ENABLE_MONITOR_DEBUG", + "TRACE_DETOUR_CLIENT", ] Public = "Module.cpp" diff --git a/Source/Monitor/Log/DetourCallbackLogger.h b/Source/Monitor/Log/DetourCallbackLogger.h index d2392f8a2..de464f3e2 100644 --- a/Source/Monitor/Log/DetourCallbackLogger.h +++ b/Source/Monitor/Log/DetourCallbackLogger.h @@ -531,12 +531,12 @@ namespace Monitor } // ProcessThreadsApi - void OnCreateProcessA(std::string_view applicationName, bool result) override final + void OnCreateProcessA(bool wasDetoured, std::string_view applicationName, bool result) override final { m_stream << "CreateProcessA: " << std::endl; } - void OnCreateProcessW(std::wstring_view applicationName, bool result) override final + void OnCreateProcessW(bool wasDetoured, std::wstring_view applicationName, bool result) override final { m_stream << "CreateProcessW: " << std::endl; } diff --git a/Source/Monitor/Shared/EventListener.h b/Source/Monitor/Shared/EventListener.h index 6b5c92d8e..ebc8778e2 100644 --- a/Source/Monitor/Shared/EventListener.h +++ b/Source/Monitor/Shared/EventListener.h @@ -711,16 +711,18 @@ namespace Monitor // ProcessThreadsApi case DetourMessageType::CreateProcessA: { + auto wasDetoured = ReadBoolValue(message, offset); auto applicationName = ReadStringValue(message, offset); auto result = ReadBoolValue(message, offset); - m_callback->OnCreateProcessA(applicationName, result); + m_callback->OnCreateProcessA(wasDetoured, applicationName, result); break; } case DetourMessageType::CreateProcessW: { + auto wasDetoured = ReadBoolValue(message, offset); auto applicationName = ReadWStringValue(message, offset); auto result = ReadBoolValue(message, offset); - m_callback->OnCreateProcessW(applicationName, result); + m_callback->OnCreateProcessW(wasDetoured, applicationName, result); break; } case DetourMessageType::CreateProcessAsUserA: diff --git a/Source/Monitor/Shared/IDetourCallback.h b/Source/Monitor/Shared/IDetourCallback.h index f8e876fe0..d9eb4f540 100644 --- a/Source/Monitor/Shared/IDetourCallback.h +++ b/Source/Monitor/Shared/IDetourCallback.h @@ -114,8 +114,8 @@ namespace Monitor virtual void OnSearchPathW(std::wstring_view path, std::wstring_view fileName, std::wstring_view extension, uint32_t result) = 0; // ProcessThreadsApi - virtual void OnCreateProcessA(std::string_view applicationName, bool result) = 0; - virtual void OnCreateProcessW(std::wstring_view applicationName, bool result) = 0; + virtual void OnCreateProcessA(bool wasDetoured, std::string_view applicationName, bool result) = 0; + virtual void OnCreateProcessW(bool wasDetoured, std::wstring_view applicationName, bool result) = 0; virtual void OnCreateProcessAsUserA(std::string_view applicationName, bool result) = 0; virtual void OnCreateProcessAsUserW(std::wstring_view applicationName, bool result) = 0; virtual void OnExitProcess(uint32_t exitCode) = 0; diff --git a/Source/Monitor/Shared/Module.cpp b/Source/Monitor/Shared/Module.cpp index 70efbfd97..8ec412a28 100644 --- a/Source/Monitor/Shared/Module.cpp +++ b/Source/Monitor/Shared/Module.cpp @@ -31,8 +31,8 @@ import Opal; using namespace Opal; -export constexpr const char* TBLOG_PIPE_NAMEA = "\\\\.\\pipe\\tracebuild"; -export constexpr const wchar_t* TBLOG_PIPE_NAMEW = L"\\\\.\\pipe\\tracebuild"; +export constexpr const char* TBLOG_PIPE_NAMEA = "\\\\.\\pipe\\monitor"; +export constexpr const wchar_t* TBLOG_PIPE_NAMEW = L"\\\\.\\pipe\\monitor"; #ifdef UNICODE export constexpr const char* TBLOG_PIPE_NAME = TBLOG_PIPE_NAMEW; #else @@ -281,4 +281,5 @@ export void ThrowIfFailed(int32_t result, std::string_view message) } #include "MockDetourProcessManager.h" -#include "WindowsDetourProcessManager.h" \ No newline at end of file +#include "WindowsDetourProcessManager.h" +#include "ScopedDetourProcessManagerRegister.h" \ No newline at end of file diff --git a/Source/Monitor/Shared/Recipe.toml b/Source/Monitor/Shared/Recipe.toml index a2655f50c..63231d277 100644 --- a/Source/Monitor/Shared/Recipe.toml +++ b/Source/Monitor/Shared/Recipe.toml @@ -4,4 +4,7 @@ Dependencies = [ "../../../Dependencies/Detours/src/", "../../../Dependencies/Opal/Source/", ] +Defines = [ + # "TRACE_DETOUR_SERVER", +] Public = "Module.cpp" diff --git a/Source/Monitor/Shared/ScopedDetourProcessManagerRegister.h b/Source/Monitor/Shared/ScopedDetourProcessManagerRegister.h new file mode 100644 index 000000000..58efedf00 --- /dev/null +++ b/Source/Monitor/Shared/ScopedDetourProcessManagerRegister.h @@ -0,0 +1,32 @@ +// +// Copyright (c) Soup. All rights reserved. +// + +#pragma once +#include "IDetourProcessManager.h" + +namespace Monitor +{ + /// + /// A scopped process manager registration helper + /// + export class ScopedDetourProcessManagerRegister + { + public: + /// + /// Initializes a new instance of the class. + /// + ScopedDetourProcessManagerRegister(std::shared_ptr listener) + { + IDetourProcessManager::Register(std::move(listener)); + } + + /// + /// Finalizes an instance of the class. + /// + ~ScopedDetourProcessManagerRegister() + { + IDetourProcessManager::Register(nullptr); + } + }; +} diff --git a/Source/Monitor/Shared/WindowsDetourProcess.h b/Source/Monitor/Shared/WindowsDetourProcess.h index 0ff161b15..389c95335 100644 --- a/Source/Monitor/Shared/WindowsDetourProcess.h +++ b/Source/Monitor/Shared/WindowsDetourProcess.h @@ -114,6 +114,9 @@ namespace Monitor if (!SetHandleInformation(m_stdInWriteHandle.Get(), HANDLE_FLAG_INHERIT, 0)) throw std::runtime_error("Execute SetHandleInformation Failed"); + // Initialize the pipes so they are ready for the process and the worker thread + InitializePipes(); + // Create the worker thread that will act as the pipe server m_processRunning = true; m_workerFailed = false; @@ -314,7 +317,6 @@ namespace Monitor try { DebugTrace("WorkerThread Start"); - InitializePipes(); // Read until we get a client and then all clients disconnect m_hasAnyClients = false; @@ -360,7 +362,7 @@ namespace Monitor } // Handle the event - DebugTrace(std::to_string(pipeIndex)); + DebugTrace("Handle Event ", pipeIndex); HandlePipeEvent(m_pipes[pipeIndex]); } } @@ -641,11 +643,18 @@ namespace Monitor } } + + void DebugTrace(std::string_view message, uint32_t value) + { +#ifdef TRACE_DETOUR_SERVER + std::cout << "DETOUR-SERVER: " << message << " " << value << std::endl; +#endif + } + void DebugTrace(std::string_view message) { -// #define TRACE_DETOUR_SERVER #ifdef TRACE_DETOUR_SERVER - std::cout << message << std::endl; + std::cout << "DETOUR-SERVER: " << message << std::endl; #endif }