diff --git a/Code/Editor/CryEdit.cpp b/Code/Editor/CryEdit.cpp index d5f2305dfb..3890a9e59a 100644 --- a/Code/Editor/CryEdit.cpp +++ b/Code/Editor/CryEdit.cpp @@ -2810,14 +2810,11 @@ void CCryEditApp::OpenProjectManager(const AZStd::string& screen) { // provide the current project path for in case we want to update the project AZ::IO::FixedMaxPathString projectPath = AZ::Utils::GetProjectPath(); -#if !AZ_TRAIT_OS_PLATFORM_APPLE && !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS - const char* argumentQuoteString = R"(")"; -#else - const char* argumentQuoteString = R"(\")"; -#endif - const AZStd::string commandLineOptions = AZStd::string::format(R"( --screen %s --project-path %s%s%s)", - screen.c_str(), - argumentQuoteString, projectPath.c_str(), argumentQuoteString); + + const AZStd::vector commandLineOptions { + "--screen", screen, + "--project-path", AZStd::string::format(R"("%s")", projectPath.c_str()) }; + bool launchSuccess = AzFramework::ProjectManager::LaunchProjectManager(commandLineOptions); if (!launchSuccess) { diff --git a/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.cpp b/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.cpp index 798b1fe99f..d64c83a6db 100644 --- a/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.cpp +++ b/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include @@ -22,7 +21,7 @@ namespace AzFramework AZStd::scoped_ptr pWatcher(LaunchProcess(processLaunchInfo, communicationType)); if (!pWatcher) { - AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process '%s %s'\n", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str()); + AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process '%s %s'\n", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.GetCommandLineParametersAsString().c_str()); return false; } else @@ -31,7 +30,7 @@ namespace AzFramework ProcessCommunicator* pCommunicator = pWatcher->GetCommunicator(); if (!pCommunicator || !pCommunicator->IsValid()) { - AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: No communicator for watcher's process (%s %s)!\n", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str()); + AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: No communicator for watcher's process (%s %s)!\n", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.GetCommandLineParametersAsString().c_str()); return false; } else diff --git a/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.h b/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.h index 042f450db0..323136c1e0 100644 --- a/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.h +++ b/Code/Framework/AzFramework/AzFramework/Process/ProcessWatcher.h @@ -13,6 +13,7 @@ #include #include #include +#include namespace AzFramework { @@ -37,7 +38,7 @@ namespace AzFramework * On windows, the command line will be passed as-is to the shell (with quotes) * on UNIX/OSX, the command line will be converted as appropriate (quotes removed, but used to chop up parameters) */ - AZStd::string m_commandlineParameters; + AZStd::variant> m_commandlineParameters; /** * (optional) If you specify a working directory, the command will be executed with that directory as the current directory. @@ -50,6 +51,8 @@ namespace AzFramework //Not Supported On Mac bool m_showWindow = true; + + AZStd::string GetCommandLineParametersAsString() const; }; static const AZ::u32 INFINITE_TIMEOUT = (AZ::u32) -1; diff --git a/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.cpp b/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.cpp index 6bbb07ea74..ed3ff44d1c 100644 --- a/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.cpp +++ b/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.cpp @@ -83,7 +83,7 @@ namespace AzFramework::ProjectManager return ProjectPathCheckResult::ProjectManagerLaunchFailed; } - bool LaunchProjectManager([[maybe_unused]]const AZStd::string& commandLineArgs) + bool LaunchProjectManager([[maybe_unused]] const AZStd::vector& commandLineArgs) { bool launchSuccess = false; #if (AZ_TRAIT_AZFRAMEWORK_USE_PROJECT_MANAGER) @@ -105,7 +105,12 @@ namespace AzFramework::ProjectManager } AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; - processLaunchInfo.m_commandlineParameters = executablePath.String() + commandLineArgs; + + AZStd::vector launchCmd = { executablePath.String() }; + launchCmd.insert(launchCmd.end(), commandLineArgs.begin(), commandLineArgs.end()); + + processLaunchInfo.m_commandlineParameters = AZStd::move(launchCmd); + launchSuccess = AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo); } if (ownsSystemAllocator) diff --git a/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.h b/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.h index 323045886d..ca4e88ebe1 100644 --- a/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.h +++ b/Code/Framework/AzFramework/AzFramework/ProjectManager/ProjectManager.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include namespace AzFramework::ProjectManager @@ -29,5 +30,5 @@ namespace AzFramework::ProjectManager //! current executable. Requires the o3de cli and python. //! @param commandLineArgs additional command line arguments to provide to the project manager //! @return true on success, false if failed to find or launch the executable - bool LaunchProjectManager(const AZStd::string& commandLineArgs = ""); + bool LaunchProjectManager(const AZStd::vector& commandLineArgs = {}); } // AzFramework::ProjectManager diff --git a/Code/Framework/AzFramework/Platform/Android/AzFramework/Process/ProcessWatcher_Android.cpp b/Code/Framework/AzFramework/Platform/Android/AzFramework/Process/ProcessWatcher_Android.cpp index f87c51bdc5..d637da29fe 100644 --- a/Code/Framework/AzFramework/Platform/Android/AzFramework/Process/ProcessWatcher_Android.cpp +++ b/Code/Framework/AzFramework/Platform/Android/AzFramework/Process/ProcessWatcher_Android.cpp @@ -6,10 +6,10 @@ * */ +#include #include #include - namespace AzFramework { @@ -83,4 +83,23 @@ namespace AzFramework { } + + AZStd::string ProcessLauncher::ProcessLaunchInfo::GetCommandLineParametersAsString() const + { + struct CommandLineParametersVisitor + { + AZStd::string operator()(const AZStd::string& commandLine) const + { + return commandLine; + } + + AZStd::string operator()(const AZStd::vector& commandLineArray) const + { + AZStd::string commandLineResult; + AZ::StringFunc::Join(commandLineResult, commandLineArray.begin(), commandLineArray.end(), " "); + return commandLineResult; + } + }; + return AZStd::visit(CommandLineParametersVisitor{}, m_commandlineParameters); + } } //namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Process/ProcessWatcher_Linux.cpp b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Process/ProcessWatcher_Linux.cpp index 51a8545443..7c86e00fec 100644 --- a/Code/Framework/AzFramework/Platform/Linux/AzFramework/Process/ProcessWatcher_Linux.cpp +++ b/Code/Framework/AzFramework/Platform/Linux/AzFramework/Process/ProcessWatcher_Linux.cpp @@ -10,12 +10,11 @@ #include #include -#include - #include #include #include #include +#include #include #include @@ -220,36 +219,52 @@ namespace AzFramework // this is so that the callers (which could be numerous) do not have to worry about this and sprinkle ifdefs // all over their code. // We'll convert this to UNIX style command line parameters by counting and eliminating quotes: - - AZStd::vector commandTokens; - - AZStd::string outputString; - bool inQuotes = false; - for (const char currentChar : processLaunchInfo.m_commandlineParameters) - { - if (currentChar == '"') - { - inQuotes = !inQuotes; - } - else if ((currentChar == ' ') && (!inQuotes)) - { - // its a space outside of quotes, so it ends the current parameter - commandTokens.push_back(outputString); - outputString.clear(); - } - else - { - // Its a normal character, or its a space inside quotes - outputString.push_back(currentChar); - } - } - if (!outputString.empty()) + // Struct uses overloaded operator() to quote command line arguments based + // on whether a string or a vector was supplied + struct EscapeCommandArguments { - commandTokens.push_back(outputString); - outputString.clear(); - } - + void operator()(const AZStd::string& commandParameterString) + { + AZStd::string outputString; + bool inQuotes = false; + for (size_t pos = 0; pos < commandParameterString.size(); ++pos) + { + char currentChar = commandParameterString[pos]; + if (currentChar == '"') + { + inQuotes = !inQuotes; + } + else if ((currentChar == ' ') && (!inQuotes)) + { + // its a space outside of quotes, so it ends the current parameter + commandArray.push_back(outputString); + outputString.clear(); + } + else + { + // Its a normal character, or its a space inside quotes + outputString.push_back(currentChar); + } + } + + if (!outputString.empty()) + { + commandArray.push_back(outputString); + outputString.clear(); + } + } + + void operator()(const AZStd::vector& commandParameterArray) + { + commandArray = commandParameterArray; + } + AZStd::vector& commandArray; + }; + + AZStd::vector commandTokens; + AZStd::visit(EscapeCommandArguments{ commandTokens }, processLaunchInfo.m_commandlineParameters); + if (!processLaunchInfo.m_processExecutableString.empty()) { commandTokens.insert(commandTokens.begin(), processLaunchInfo.m_processExecutableString); @@ -452,4 +467,23 @@ namespace AzFramework kill(m_pWatcherData->m_childProcessId, SIGKILL); } + + AZStd::string ProcessLauncher::ProcessLaunchInfo::GetCommandLineParametersAsString() const + { + struct CommandLineParametersVisitor + { + AZStd::string operator()(const AZStd::string& commandLine) const + { + return commandLine; + } + + AZStd::string operator()(const AZStd::vector& commandLineArray) const + { + AZStd::string commandLineResult; + AZ::StringFunc::Join(commandLineResult, commandLineArray.begin(), commandLineArray.end(), " "); + return commandLineResult; + } + }; + return AZStd::visit(CommandLineParametersVisitor{}, m_commandlineParameters); + } } //namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/Mac/AzFramework/Process/ProcessWatcher_Mac.cpp b/Code/Framework/AzFramework/Platform/Mac/AzFramework/Process/ProcessWatcher_Mac.cpp index 1ba9ff3067..e131ab4a7b 100644 --- a/Code/Framework/AzFramework/Platform/Mac/AzFramework/Process/ProcessWatcher_Mac.cpp +++ b/Code/Framework/AzFramework/Platform/Mac/AzFramework/Process/ProcessWatcher_Mac.cpp @@ -11,13 +11,12 @@ #include #include -#include - #include #include #include #include #include +#include #include #include @@ -210,46 +209,51 @@ namespace AzFramework // this is so that the callers (which could be numerous) do not have to worry about this and sprinkle ifdefs // all over their code. // We'll convert this to UNIX style command line parameters by counting and eliminating quotes: - + + // Struct uses overloaded operator() to quote command line arguments based + // on whether a string or a vector was supplied + struct EscapeCommandArguments + { + void operator()(const AZStd::string& commandParameterString) + { + AZStd::string outputString; + bool inQuotes = false; + for (size_t pos = 0; pos < commandParameterString.size(); ++pos) + { + char currentChar = commandParameterString[pos]; + if (currentChar == '"') + { + inQuotes = !inQuotes; + } + else if ((currentChar == ' ') && (!inQuotes)) + { + // its a space outside of quotes, so it ends the current parameter + commandArray.push_back(outputString); + outputString.clear(); + } + else + { + // Its a normal character, or its a space inside quotes + outputString.push_back(currentChar); + } + } + + if (!outputString.empty()) + { + commandArray.push_back(outputString); + outputString.clear(); + } + } + + void operator()(const AZStd::vector& commandParameterArray) + { + commandArray = commandParameterArray; + } + AZStd::vector& commandArray; + }; + AZStd::vector commandTokens; - - AZStd::string outputString; - bool inQuotes = false; - for (size_t pos = 0; pos < processLaunchInfo.m_commandlineParameters.size(); ++pos) - { - char currentChar = processLaunchInfo.m_commandlineParameters[pos]; - if (currentChar == '"') - { - // Allow quote literals to go through as quotes which do NOT alter our "in quotes" bool below - // This is to conform with our PC parameter strings which will sometimes include path parameters which - // Can have spaces and commas and need to be output as paramname="\"Some pa,ram\"" in order to capture both correctly - if (outputString.length() && outputString.back() == '\\') - { - outputString.back() = currentChar; - } - else - { - inQuotes = !inQuotes; - } - } - else if ((currentChar == ' ') && (!inQuotes)) - { - // its a space outside of quotes, so it ends the current parameter - commandTokens.push_back(outputString); - outputString.clear(); - } - else - { - // Its a normal character, or its a space inside quotes - outputString.push_back(currentChar); - } - } - - if (!outputString.empty()) - { - commandTokens.push_back(outputString); - outputString.clear(); - } + AZStd::visit(EscapeCommandArguments{ commandTokens }, processLaunchInfo.m_commandlineParameters); if (!processLaunchInfo.m_processExecutableString.empty()) { @@ -417,5 +421,24 @@ namespace AzFramework kill(m_pWatcherData->m_childProcessId, SIGKILL); waitpid(m_pWatcherData->m_childProcessId, NULL, 0); } + + AZStd::string ProcessLauncher::ProcessLaunchInfo::GetCommandLineParametersAsString() const + { + struct CommandLineParametersVisitor + { + AZStd::string operator()(const AZStd::string& commandLine) const + { + return commandLine; + } + + AZStd::string operator()(const AZStd::vector& commandLineArray) const + { + AZStd::string commandLineResult; + AZ::StringFunc::Join(commandLineResult, commandLineArray.begin(), commandLineArray.end(), " "); + return commandLineResult; + } + }; + return AZStd::visit(CommandLineParametersVisitor{}, m_commandlineParameters); + } } //namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/Windows/AzFramework/Process/ProcessWatcher_Win.cpp b/Code/Framework/AzFramework/Platform/Windows/AzFramework/Process/ProcessWatcher_Win.cpp index 6e21ecc205..b0ef2f09a4 100644 --- a/Code/Framework/AzFramework/Platform/Windows/AzFramework/Process/ProcessWatcher_Win.cpp +++ b/Code/Framework/AzFramework/Platform/Windows/AzFramework/Process/ProcessWatcher_Win.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -99,7 +100,7 @@ namespace AzFramework AZStd::wstring editableCommandLine; AZStd::wstring processExecutableString; AZStd::wstring workingDirectory; - AZStd::to_wstring(editableCommandLine, processLaunchInfo.m_commandlineParameters); + AZStd::to_wstring(editableCommandLine, processLaunchInfo.GetCommandLineParametersAsString()); AZStd::to_wstring(processExecutableString, processLaunchInfo.m_processExecutableString); AZStd::to_wstring(workingDirectory, processLaunchInfo.m_workingDirectory); @@ -355,4 +356,41 @@ namespace AzFramework ::TerminateProcess(m_pWatcherData->processInformation.hProcess, exitCode); } } + + AZStd::string ProcessLauncher::ProcessLaunchInfo::GetCommandLineParametersAsString() const + { + struct CommandLineParametersVisitor + { + AZStd::string operator()(const AZStd::string& commandLine) const + { + return commandLine; + } + + AZStd::string operator()(const AZStd::vector& commandLineArray) const + { + AZStd::string commandLineResult; + + // When re-constructing a command line from an argument list (on windows), if an argument + // is double-quoted, then the double-quotes must be escaped properly otherwise + // it will be absorbed by the native argument parser and possibly evaluated as + // multiple values for arguments + AZStd::string_view escapedDoubleQuote = R"("\")"; + + AZStd::vector preprocessedCommandArray; + + for (const auto& commandArg : commandLineArray) + { + AZStd::string replacedArg = commandArg; + AZ::StringFunc::Replace(replacedArg, R"(")", R"("\")", false, true, true); + preprocessedCommandArray.emplace_back(replacedArg); + } + AZ::StringFunc::Join(commandLineResult, preprocessedCommandArray.begin(), preprocessedCommandArray.end(), " "); + + return commandLineResult; + } + }; + + return AZStd::visit(CommandLineParametersVisitor{}, m_commandlineParameters); + } + } // namespace AzFramework diff --git a/Code/Framework/AzFramework/Platform/iOS/AzFramework/Process/ProcessWatcher_iOS.cpp b/Code/Framework/AzFramework/Platform/iOS/AzFramework/Process/ProcessWatcher_iOS.cpp index f87c51bdc5..c7b7513064 100644 --- a/Code/Framework/AzFramework/Platform/iOS/AzFramework/Process/ProcessWatcher_iOS.cpp +++ b/Code/Framework/AzFramework/Platform/iOS/AzFramework/Process/ProcessWatcher_iOS.cpp @@ -6,10 +6,10 @@ * */ +#include #include #include - namespace AzFramework { @@ -83,4 +83,23 @@ namespace AzFramework { } + + AZStd::string ProcessLauncher::ProcessLaunchInfo::GetCommandLineParametersAsString() const + { + struct CommandLineParametersVisitor + { + AZStd::string operator()(const AZStd::string& commandLine) const + { + return commandLine; + } + + AZStd::string operator()(const AZStd::vector& commandLineArray) const + { + AZStd::string commandLineResult; + Az::StringFunc::Join(commandLineResult, commandLineArray.begin(), commandLineArray.end(), " "); + return commandLineResult; + } + }; + return AZStd::visit(CommandLineParametersVisitor{}, m_commandlineParameters); + } } //namespace AzFramework diff --git a/Code/Framework/AzFramework/Tests/ProcessLaunchParseTests.cpp b/Code/Framework/AzFramework/Tests/ProcessLaunchParseTests.cpp index 17dd04c587..0a7eb24df4 100644 --- a/Code/Framework/AzFramework/Tests/ProcessLaunchParseTests.cpp +++ b/Code/Framework/AzFramework/Tests/ProcessLaunchParseTests.cpp @@ -75,7 +75,8 @@ namespace UnitTest AzFramework::ProcessOutput processOutput; AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; - processLaunchInfo.m_commandlineParameters = AZ_TRAIT_TEST_ROOT_FOLDER "ProcessLaunchTest"; + processLaunchInfo.m_commandlineParameters.emplace(AZStd::string(AZ_TRAIT_TEST_ROOT_FOLDER "ProcessLaunchTest")); + processLaunchInfo.m_workingDirectory = AZ::Test::GetCurrentExecutablePath(); processLaunchInfo.m_showWindow = false; bool launchReturn = AzFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, processOutput); @@ -90,7 +91,9 @@ namespace UnitTest AzFramework::ProcessOutput processOutput; AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; - processLaunchInfo.m_commandlineParameters = AZ_TRAIT_TEST_ROOT_FOLDER "ProcessLaunchTest -param1 param1val -param2=param2val"; + processLaunchInfo.m_commandlineParameters.emplace>( + AZStd::vector{AZStd::string(AZ_TRAIT_TEST_ROOT_FOLDER "ProcessLaunchTest"), "-param1", "param1val","-param2", "param2val"}); + processLaunchInfo.m_workingDirectory = AZ::Test::GetCurrentExecutablePath(); processLaunchInfo.m_showWindow = false; bool launchReturn = AzFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, processOutput); @@ -117,14 +120,16 @@ namespace UnitTest #if AZ_TRAIT_DISABLE_FAILED_PROCESS_LAUNCHER_TESTS TEST_F(ProcessLaunchParseTests, DISABLED_ProcessLauncher_StringsWithCommas_Success) #else - TEST_F(ProcessLaunchParseTests, ProcessLauncher_StringsWithCommas_Success) + TEST_F(ProcessLaunchParseTests, ProcessLauncher_WithCommas_Success) #endif // AZ_TRAIT_DISABLE_FAILED_PROCESS_LAUNCHER_TESTS { ProcessLaunchParseTests::ParsedArgMap argMap; AzFramework::ProcessOutput processOutput; AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; - processLaunchInfo.m_commandlineParameters = AZ_TRAIT_TEST_ROOT_FOLDER R"(ProcessLaunchTest -param1 "\"param,1val\"" -param2="\"param2v,al\"")"; + processLaunchInfo.m_commandlineParameters.emplace>( + AZStd::vector{AZStd::string(AZ_TRAIT_TEST_ROOT_FOLDER "ProcessLaunchTest"), "-param1", "param,1val","-param2", "param2v,al"}); + processLaunchInfo.m_workingDirectory = AZ::Test::GetCurrentExecutablePath(); processLaunchInfo.m_showWindow = false; bool launchReturn = AzFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, processOutput); @@ -137,28 +142,32 @@ namespace UnitTest EXPECT_NE(param1itr, argMap.end()); AZStd::vector param1{ param1itr->second }; - EXPECT_EQ(param1.size(), 1); - EXPECT_EQ(param1[0], "param,1val"); + EXPECT_EQ(param1.size(), 2); + EXPECT_EQ(param1[0], "param"); + EXPECT_EQ(param1[1], "1val"); auto param2itr = argMap.find("param2"); EXPECT_NE(param2itr, argMap.end()); AZStd::vector param2{ param2itr->second }; - EXPECT_EQ(param2.size(), 1); - EXPECT_EQ(param2[0], "param2v,al"); + EXPECT_EQ(param2.size(), 2); + EXPECT_EQ(param2[0], "param2v"); + EXPECT_EQ(param2[1], "al"); } #if AZ_TRAIT_DISABLE_FAILED_PROCESS_LAUNCHER_TESTS TEST_F(ProcessLaunchParseTests, DISABLED_ProcessLauncher_StringsWithSpaces_Success) #else - TEST_F(ProcessLaunchParseTests, ProcessLauncher_StringsWithSpaces_Success) + TEST_F(ProcessLaunchParseTests, ProcessLauncher_WithSpaces_Success) #endif // AZ_TRAIT_DISABLE_FAILED_PROCESS_LAUNCHER_TESTS { ProcessLaunchParseTests::ParsedArgMap argMap; AzFramework::ProcessOutput processOutput; AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; - processLaunchInfo.m_commandlineParameters = AZ_TRAIT_TEST_ROOT_FOLDER R"(ProcessLaunchTest -param1 "\"param 1val\"" -param2="\"param2v al\"")"; + processLaunchInfo.m_commandlineParameters.emplace>(AZStd::vector{ + AZStd::string(AZ_TRAIT_TEST_ROOT_FOLDER "ProcessLaunchTest"), "-param1", R"("param 1val")", R"(-param2="param2v al")" }); + processLaunchInfo.m_workingDirectory = AZ::Test::GetCurrentExecutablePath(); processLaunchInfo.m_showWindow = false; bool launchReturn = AzFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, processOutput); @@ -185,14 +194,16 @@ namespace UnitTest #if AZ_TRAIT_DISABLE_FAILED_PROCESS_LAUNCHER_TESTS TEST_F(ProcessLaunchParseTests, DISABLED_ProcessLauncher_StringsWithSpacesAndComma_Success) #else - TEST_F(ProcessLaunchParseTests, ProcessLauncher_StringsWithSpacesAndComma_Success) + TEST_F(ProcessLaunchParseTests, ProcessLauncher_WithSpacesAndComma_Success) #endif // AZ_TRAIT_DISABLE_FAILED_PROCESS_LAUNCHER_TESTS { ProcessLaunchParseTests::ParsedArgMap argMap; AzFramework::ProcessOutput processOutput; AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; - processLaunchInfo.m_commandlineParameters = AZ_TRAIT_TEST_ROOT_FOLDER R"(ProcessLaunchTest -param1 "\"par,am 1val\"" -param2="\"param,2v al\"")"; + processLaunchInfo.m_commandlineParameters.emplace>(AZStd::vector{ + AZStd::string(AZ_TRAIT_TEST_ROOT_FOLDER "ProcessLaunchTest"), "-param1", R"("param, 1val")", R"(-param2="param,2v al")" }); + processLaunchInfo.m_workingDirectory = AZ::Test::GetCurrentExecutablePath(); processLaunchInfo.m_showWindow = false; bool launchReturn = AzFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, processOutput); @@ -206,7 +217,7 @@ namespace UnitTest AZStd::vector param1{ param1itr->second }; EXPECT_EQ(param1.size(), 1); - EXPECT_EQ(param1[0], "par,am 1val"); + EXPECT_EQ(param1[0], "param, 1val"); auto param2itr = argMap.find("param2"); EXPECT_NE(param2itr, argMap.end()); @@ -216,35 +227,4 @@ namespace UnitTest EXPECT_EQ(param2[0], "param,2v al"); } - TEST_F(ProcessLaunchParseTests, ProcessLauncher_CommaStringNoQuotes_Success) - { - ProcessLaunchParseTests::ParsedArgMap argMap; - AzFramework::ProcessOutput processOutput; - AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; - - processLaunchInfo.m_commandlineParameters = AZ_TRAIT_TEST_ROOT_FOLDER "ProcessLaunchTest -param1 param,1val -param2=param2v,al"; - processLaunchInfo.m_workingDirectory = AZ::Test::GetCurrentExecutablePath(); - processLaunchInfo.m_showWindow = false; - bool launchReturn = AzFramework::ProcessWatcher::LaunchProcessAndRetrieveOutput(processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT, processOutput); - - EXPECT_EQ(launchReturn, true); - - argMap = ProcessLaunchParseTests::ParseParameters(processOutput.outputResult); - - auto param1itr = argMap.find("param1"); - EXPECT_NE(param1itr, argMap.end()); - AZStd::vector param1{ param1itr->second }; - - EXPECT_EQ(param1.size(), 2); - EXPECT_EQ(param1[0], "param"); - EXPECT_EQ(param1[1], "1val"); - - auto param2itr = argMap.find("param2"); - EXPECT_NE(param2itr, argMap.end()); - AZStd::vector param2{ param2itr->second }; - - EXPECT_EQ(param2.size(), 2); - EXPECT_EQ(param2[0], "param2v"); - EXPECT_EQ(param2[1], "al"); - } } // namespace UnitTest diff --git a/Code/Framework/AzTest/AzTest/Platform/Linux/AzTest_Traits_Linux.h b/Code/Framework/AzTest/AzTest/Platform/Linux/AzTest_Traits_Linux.h index 94f3dd004a..20e67e582b 100644 --- a/Code/Framework/AzTest/AzTest/Platform/Linux/AzTest_Traits_Linux.h +++ b/Code/Framework/AzTest/AzTest/Platform/Linux/AzTest_Traits_Linux.h @@ -13,8 +13,6 @@ #define AZ_TRAIT_UNIT_TEST_ENTITY_ID_GEN_TEST_COUNT 10000 #define AZ_TRAIT_UNIT_TEST_DILLER_TRIGGER_EVENT_COUNT 100000 -#define AZ_TRAIT_DISABLE_FAILED_AP_CONNECTION_TESTS true - #define AZ_TRAIT_DISABLE_FAILED_ATOM_RPI_TESTS true #define AZ_TRAIT_DISABLE_FAILED_ARCHIVE_TESTS true @@ -23,7 +21,6 @@ #define AZ_TRAIT_DISABLE_FAILED_GRADIENT_SIGNAL_TESTS true #define AZ_TRAIT_DISABLE_FAILED_MULTIPLAYER_GRIDMATE_TESTS true #define AZ_TRAIT_DISABLE_FAILED_NATIVE_WINDOWS_TESTS true -#define AZ_TRAIT_DISABLE_FAILED_PROCESS_LAUNCHER_TESTS true #define AZ_TRAIT_DISABLE_FAILED_EMOTION_FX_TESTS true #define AZ_TRAIT_DISABLE_FAILED_DLL_TESTS true #define AZ_TRAIT_DISABLE_FAILED_MODULE_TESTS true diff --git a/Code/Tools/AssetProcessor/native/utilities/BuilderManager.cpp b/Code/Tools/AssetProcessor/native/utilities/BuilderManager.cpp index aa462f7590..ddaa5aa777 100644 --- a/Code/Tools/AssetProcessor/native/utilities/BuilderManager.cpp +++ b/Code/Tools/AssetProcessor/native/utilities/BuilderManager.cpp @@ -155,7 +155,7 @@ namespace AssetProcessor return false; } - const AZStd::string params = BuildParams("resident", buildersFolder.c_str(), UuidString(), "", ""); + const AZStd::vector params = BuildParams("resident", buildersFolder.c_str(), UuidString(), "", ""); m_processWatcher = LaunchProcess(fullExePathString.c_str(), params); @@ -179,7 +179,7 @@ namespace AssetProcessor return !m_processWatcher || (m_processWatcher && m_processWatcher->IsProcessRunning(exitCode)); } - AZStd::string Builder::BuildParams(const char* task, const char* moduleFilePath, const AZStd::string& builderGuid, const AZStd::string& jobDescriptionFile, const AZStd::string& jobResponseFile) const + AZStd::vector Builder::BuildParams(const char* task, const char* moduleFilePath, const AZStd::string& builderGuid, const AZStd::string& jobDescriptionFile, const AZStd::string& jobResponseFile) const { QDir projectCacheRoot; AssetUtilities::ComputeProjectCacheRoot(projectCacheRoot); @@ -191,35 +191,24 @@ namespace AssetProcessor int portNumber = 0; ApplicationServerBus::BroadcastResult(portNumber, &ApplicationServerBus::Events::GetServerListeningPort); - AZStd::string params; -#if !AZ_TRAIT_OS_PLATFORM_APPLE && !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS - params = AZStd::string::format( - R"(-task=%s -id="%s" -project-name="%s" -project-cache-path="%s" -project-path="%s" -engine-path="%s" -port %d)", - task, builderGuid.c_str(), projectName.c_str(), projectCacheRoot.absolutePath().toUtf8().constData(), - projectPath.c_str(), enginePath.c_str(), portNumber); -#else - params = AZStd::string::format( - R"(-task=%s -id="%s" -project-name="\"%s\"" -project-cache-path="\"%s\"" -project-path="\"%s\"" -engine-path="\"%s\"" -port %d)", - task, builderGuid.c_str(), projectName.c_str(), projectCacheRoot.absolutePath().toUtf8().constData(), - projectPath.c_str(), enginePath.c_str(), portNumber); -#endif // !AZ_TRAIT_OS_PLATFORM_APPLE && !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS + AZStd::vector params; + params.emplace_back(AZStd::string::format(R"(-task="%s")", task)); + params.emplace_back(AZStd::string::format(R"(-id="%s")", builderGuid.c_str())); + params.emplace_back(AZStd::string::format(R"(-project-name="%s")", projectName.c_str())); + params.emplace_back(AZStd::string::format(R"(-project-cache-path="%s")", projectCacheRoot.absolutePath().toUtf8().constData())); + params.emplace_back(AZStd::string::format(R"(-project-path="%s")", projectPath.c_str())); + params.emplace_back(AZStd::string::format(R"(-engine-path="%s")", enginePath.c_str())); + params.emplace_back(AZStd::string::format("-port=%d", portNumber)); if (moduleFilePath && moduleFilePath[0]) { - #if !AZ_TRAIT_OS_PLATFORM_APPLE && !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS - params.append(AZStd::string::format(R"( -module="%s")", moduleFilePath).c_str()); - #else - params.append(AZStd::string::format(R"( -module="\"%s\"")", moduleFilePath).c_str()); - #endif // !AZ_TRAIT_OS_PLATFORM_APPLE && !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS + params.emplace_back(AZStd::string::format(R"(-module="%s")", moduleFilePath)); } if (!jobDescriptionFile.empty() && !jobResponseFile.empty()) { - #if !AZ_TRAIT_OS_PLATFORM_APPLE && !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS - params = AZStd::string::format(R"(%s -input="%s" -output="%s")", params.c_str(), jobDescriptionFile.c_str(), jobResponseFile.c_str()); - #else - params = AZStd::string::format(R"(%s -input="\"%s\"" -output="\"%s\"")", params.c_str(), jobDescriptionFile.c_str(), jobResponseFile.c_str()); - #endif // !AZ_TRAIT_OS_PLATFORM_APPLE && !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS + params.emplace_back(AZStd::string::format(R"(-input="%s")", jobDescriptionFile.c_str())); + params.emplace_back(AZStd::string::format(R"(-output="%s")", jobResponseFile.c_str())); } auto settingsRegistry = AZ::SettingsRegistry::Get(); @@ -232,28 +221,25 @@ namespace AssetProcessor for (size_t optionIndex = 0; optionIndex < commandOptionCount; ++optionIndex) { const AZStd::string& optionValue = commandLine.GetSwitchValue(optionKey, optionIndex); - params.append(AZStd::string::format( -#if !AZ_TRAIT_OS_PLATFORM_APPLE && !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS - R"( --%s="%s")", -#else - R"( --%s="\"%s\"")", -#endif - optionKey, optionValue.c_str())); + params.emplace_back(AZStd::string::format(R"(--%s="%s")", optionKey, optionValue.c_str())); } } return params; } - AZStd::unique_ptr Builder::LaunchProcess(const char* fullExePath, const AZStd::string& params) const + AZStd::unique_ptr Builder::LaunchProcess(const char* fullExePath, const AZStd::vector& params) const { AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; processLaunchInfo.m_processExecutableString = fullExePath; - processLaunchInfo.m_commandlineParameters = AZStd::string::format("\"%s\" %s", fullExePath, params.c_str()); + + AZStd::vector commandLineArray{ fullExePath }; + commandLineArray.insert(commandLineArray.end(), params.begin(), params.end()); + processLaunchInfo.m_commandlineParameters = AZStd::move(commandLineArray); processLaunchInfo.m_showWindow = false; processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_IDLE; - AZ_TracePrintf(AssetProcessor::DebugChannel, "Executing AssetBuilder with parameters: %s\n", processLaunchInfo.m_commandlineParameters.c_str()); + AZ_TracePrintf(AssetProcessor::DebugChannel, "Executing AssetBuilder with parameters: %s\n", processLaunchInfo.GetCommandLineParametersAsString().c_str()); auto processWatcher = AZStd::unique_ptr(AzFramework::ProcessWatcher::LaunchProcess(processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT)); diff --git a/Code/Tools/AssetProcessor/native/utilities/BuilderManager.h b/Code/Tools/AssetProcessor/native/utilities/BuilderManager.h index 440f0b3709..1208345a39 100644 --- a/Code/Tools/AssetProcessor/native/utilities/BuilderManager.h +++ b/Code/Tools/AssetProcessor/native/utilities/BuilderManager.h @@ -103,8 +103,8 @@ namespace AssetProcessor //! Sets the connection id and signals that the builder has connected void SetConnection(AZ::u32 connId); - AZStd::string BuildParams(const char* task, const char* moduleFilePath, const AZStd::string& builderGuid, const AZStd::string& jobDescriptionFile, const AZStd::string& jobResponseFile) const; - AZStd::unique_ptr LaunchProcess(const char* fullExePath, const AZStd::string& params) const; + AZStd::vector BuildParams(const char* task, const char* moduleFilePath, const AZStd::string& builderGuid, const AZStd::string& jobDescriptionFile, const AZStd::string& jobResponseFile) const; + AZStd::unique_ptr LaunchProcess(const char* fullExePath, const AZStd::vector& params) const; //! Waits for the builder exe to send the job response and pumps stdout/err BuilderRunJobOutcome WaitForBuilderResponse(AssetBuilderSDK::JobCancelListener* jobCancelListener, AZ::u32 processTimeoutLimitInSeconds, AZStd::binary_semaphore* waitEvent) const; diff --git a/Code/Tools/AssetProcessor/native/utilities/BuilderManager.inl b/Code/Tools/AssetProcessor/native/utilities/BuilderManager.inl index 986791b501..9893f32f6a 100644 --- a/Code/Tools/AssetProcessor/native/utilities/BuilderManager.inl +++ b/Code/Tools/AssetProcessor/native/utilities/BuilderManager.inl @@ -7,6 +7,8 @@ */ #pragma once +#include + namespace AssetProcessor { //! Sends the job over to the builder and blocks until the response is received or the builder crashes/times out @@ -82,10 +84,12 @@ namespace AssetProcessor } auto params = BuildParams(task.c_str(), modulePath.c_str(), "", jobRequestFile, jobResponseFile); + AZStd::string paramString; + AZ::StringFunc::Join(paramString, params.begin(), params.end(), " "); AZ_TracePrintf(AssetProcessor::DebugChannel, "Job request written to %s\n", jobRequestFile.c_str()); AZ_TracePrintf(AssetProcessor::DebugChannel, "To re-run this request manually, run AssetBuilder with the following parameters:\n"); - AZ_TracePrintf(AssetProcessor::DebugChannel, "%s\n", params.c_str()); + AZ_TracePrintf(AssetProcessor::DebugChannel, "%s\n", paramString.c_str()); return true; } diff --git a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp index acd753a1cb..948736eed1 100644 --- a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp @@ -424,12 +424,12 @@ namespace O3DE::ProjectManager return; } - auto cmdPath = AZ::IO::FixedMaxPathString::format( - "%s --regset=\"/Amazon/AzCore/Bootstrap/project_path=%s\"", editorExecutablePath.c_str(), - fixedProjectPath.c_str()); - AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo; - processLaunchInfo.m_commandlineParameters = cmdPath; + processLaunchInfo.m_commandlineParameters = AZStd::vector{ + editorExecutablePath.String(), + AZStd::string::format(R"(--regset="/Amazon/AzCore/Bootstrap/project_path=%s")", fixedProjectPath.c_str()) + }; + ; bool launchSucceeded = AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo); if (!launchSucceeded) { diff --git a/Gems/Atom/RHI/Code/Source/RHI.Edit/Utils.cpp b/Gems/Atom/RHI/Code/Source/RHI.Edit/Utils.cpp index dc203ef731..6b5b9cc3a5 100644 --- a/Gems/Atom/RHI/Code/Source/RHI.Edit/Utils.cpp +++ b/Gems/Atom/RHI/Code/Source/RHI.Edit/Utils.cpp @@ -264,9 +264,9 @@ namespace AZ } { AZStd::string contextKey = toolNameForLog + AZStd::string(" Command Line"); - AZ_TraceContext(contextKey, processLaunchInfo.m_commandlineParameters); + AZ_TraceContext(contextKey, processLaunchInfo.GetCommandLineParametersAsString()); } - AZ_TracePrintf(ShaderPlatformInterfaceName, "Executing '%s' ...", processLaunchInfo.m_commandlineParameters.c_str()); + AZ_TracePrintf(ShaderPlatformInterfaceName, "Executing '%s' ...", processLaunchInfo.GetCommandLineParametersAsString().c_str()); AzFramework::ProcessWatcher* watcher = AzFramework::ProcessWatcher::LaunchProcess(processLaunchInfo, AzFramework::COMMUNICATOR_TYPE_STDINOUT); if (!watcher)