/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #include "cmExecuteProcessCommand.h" #include "cmSystemTools.h" #include #include /* isspace */ static bool cmExecuteProcessCommandIsWhitespace(char c) { return (isspace((int)c) || c == '\n' || c == '\r'); } void cmExecuteProcessCommandFixText(std::vector& output, bool strip_trailing_whitespace); void cmExecuteProcessCommandAppend(std::vector& output, const char* data, int length); // cmExecuteProcessCommand bool cmExecuteProcessCommand::InitialPass(std::vector const& args, cmExecutionStatus&) { if (args.empty()) { this->SetError("called with incorrect number of arguments"); return false; } std::vector > cmds; std::string arguments; bool doing_command = false; size_t command_index = 0; bool output_quiet = false; bool error_quiet = false; bool output_strip_trailing_whitespace = false; bool error_strip_trailing_whitespace = false; std::string timeout_string; std::string input_file; std::string output_file; std::string error_file; std::string output_variable; std::string error_variable; std::string result_variable; std::string working_directory; for (size_t i = 0; i < args.size(); ++i) { if (args[i] == "COMMAND") { doing_command = true; command_index = cmds.size(); cmds.push_back(std::vector()); } else if (args[i] == "OUTPUT_VARIABLE") { doing_command = false; if (++i < args.size()) { output_variable = args[i]; } else { this->SetError(" called with no value for OUTPUT_VARIABLE."); return false; } } else if (args[i] == "ERROR_VARIABLE") { doing_command = false; if (++i < args.size()) { error_variable = args[i]; } else { this->SetError(" called with no value for ERROR_VARIABLE."); return false; } } else if (args[i] == "RESULT_VARIABLE") { doing_command = false; if (++i < args.size()) { result_variable = args[i]; } else { this->SetError(" called with no value for RESULT_VARIABLE."); return false; } } else if (args[i] == "WORKING_DIRECTORY") { doing_command = false; if (++i < args.size()) { working_directory = args[i]; } else { this->SetError(" called with no value for WORKING_DIRECTORY."); return false; } } else if (args[i] == "INPUT_FILE") { doing_command = false; if (++i < args.size()) { input_file = args[i]; } else { this->SetError(" called with no value for INPUT_FILE."); return false; } } else if (args[i] == "OUTPUT_FILE") { doing_command = false; if (++i < args.size()) { output_file = args[i]; } else { this->SetError(" called with no value for OUTPUT_FILE."); return false; } } else if (args[i] == "ERROR_FILE") { doing_command = false; if (++i < args.size()) { error_file = args[i]; } else { this->SetError(" called with no value for ERROR_FILE."); return false; } } else if (args[i] == "TIMEOUT") { doing_command = false; if (++i < args.size()) { timeout_string = args[i]; } else { this->SetError(" called with no value for TIMEOUT."); return false; } } else if (args[i] == "OUTPUT_QUIET") { doing_command = false; output_quiet = true; } else if (args[i] == "ERROR_QUIET") { doing_command = false; error_quiet = true; } else if (args[i] == "OUTPUT_STRIP_TRAILING_WHITESPACE") { doing_command = false; output_strip_trailing_whitespace = true; } else if (args[i] == "ERROR_STRIP_TRAILING_WHITESPACE") { doing_command = false; error_strip_trailing_whitespace = true; } else if (doing_command) { cmds[command_index].push_back(args[i].c_str()); } else { std::ostringstream e; e << " given unknown argument \"" << args[i] << "\"."; this->SetError(e.str()); return false; } } if (!this->Makefile->CanIWriteThisFile(output_file.c_str())) { std::string e = "attempted to output into a file: " + output_file + " into a source directory."; this->SetError(e); cmSystemTools::SetFatalErrorOccured(); return false; } // Check for commands given. if (cmds.empty()) { this->SetError(" called with no COMMAND argument."); return false; } for (unsigned int i = 0; i < cmds.size(); ++i) { if (cmds[i].empty()) { this->SetError(" given COMMAND argument with no value."); return false; } // Add the null terminating pointer to the command argument list. cmds[i].push_back(CM_NULLPTR); } // Parse the timeout string. double timeout = -1; if (!timeout_string.empty()) { if (sscanf(timeout_string.c_str(), "%lg", &timeout) != 1) { this->SetError(" called with TIMEOUT value that could not be parsed."); return false; } } // Create a process instance. cmsysProcess* cp = cmsysProcess_New(); // Set the command sequence. for (unsigned int i = 0; i < cmds.size(); ++i) { cmsysProcess_AddCommand(cp, &*cmds[i].begin()); } // Set the process working directory. if (!working_directory.empty()) { cmsysProcess_SetWorkingDirectory(cp, working_directory.c_str()); } // Always hide the process window. cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1); // Check the output variables. bool merge_output = false; if (!input_file.empty()) { cmsysProcess_SetPipeFile(cp, cmsysProcess_Pipe_STDIN, input_file.c_str()); } if (!output_file.empty()) { cmsysProcess_SetPipeFile(cp, cmsysProcess_Pipe_STDOUT, output_file.c_str()); } if (!error_file.empty()) { if (error_file == output_file) { merge_output = true; } else { cmsysProcess_SetPipeFile(cp, cmsysProcess_Pipe_STDERR, error_file.c_str()); } } if (!output_variable.empty() && output_variable == error_variable) { merge_output = true; } if (merge_output) { cmsysProcess_SetOption(cp, cmsysProcess_Option_MergeOutput, 1); } // Set the timeout if any. if (timeout >= 0) { cmsysProcess_SetTimeout(cp, timeout); } // Start the process. cmsysProcess_Execute(cp); // Read the process output. std::vector tempOutput; std::vector tempError; int length; char* data; int p; while ((p = cmsysProcess_WaitForData(cp, &data, &length, CM_NULLPTR), p)) { // Put the output in the right place. if (p == cmsysProcess_Pipe_STDOUT && !output_quiet) { if (output_variable.empty()) { cmSystemTools::Stdout(data, length); } else { cmExecuteProcessCommandAppend(tempOutput, data, length); } } else if (p == cmsysProcess_Pipe_STDERR && !error_quiet) { if (error_variable.empty()) { cmSystemTools::Stderr(data, length); } else { cmExecuteProcessCommandAppend(tempError, data, length); } } } // All output has been read. Wait for the process to exit. cmsysProcess_WaitForExit(cp, CM_NULLPTR); // Fix the text in the output strings. cmExecuteProcessCommandFixText(tempOutput, output_strip_trailing_whitespace); cmExecuteProcessCommandFixText(tempError, error_strip_trailing_whitespace); // Store the output obtained. if (!output_variable.empty() && !tempOutput.empty()) { this->Makefile->AddDefinition(output_variable, &*tempOutput.begin()); } if (!merge_output && !error_variable.empty() && !tempError.empty()) { this->Makefile->AddDefinition(error_variable, &*tempError.begin()); } // Store the result of running the process. if (!result_variable.empty()) { switch (cmsysProcess_GetState(cp)) { case cmsysProcess_State_Exited: { int v = cmsysProcess_GetExitValue(cp); char buf[100]; sprintf(buf, "%d", v); this->Makefile->AddDefinition(result_variable, buf); } break; case cmsysProcess_State_Exception: this->Makefile->AddDefinition(result_variable, cmsysProcess_GetExceptionString(cp)); break; case cmsysProcess_State_Error: this->Makefile->AddDefinition(result_variable, cmsysProcess_GetErrorString(cp)); break; case cmsysProcess_State_Expired: this->Makefile->AddDefinition(result_variable, "Process terminated due to timeout"); break; } } // Delete the process instance. cmsysProcess_Delete(cp); return true; } void cmExecuteProcessCommandFixText(std::vector& output, bool strip_trailing_whitespace) { // Remove \0 characters and the \r part of \r\n pairs. unsigned int in_index = 0; unsigned int out_index = 0; while (in_index < output.size()) { char c = output[in_index++]; if ((c != '\r' || !(in_index < output.size() && output[in_index] == '\n')) && c != '\0') { output[out_index++] = c; } } // Remove trailing whitespace if requested. if (strip_trailing_whitespace) { while (out_index > 0 && cmExecuteProcessCommandIsWhitespace(output[out_index - 1])) { --out_index; } } // Shrink the vector to the size needed. output.resize(out_index); // Put a terminator on the text string. output.push_back('\0'); } void cmExecuteProcessCommandAppend(std::vector& output, const char* data, int length) { #if defined(__APPLE__) // HACK on Apple to work around bug with inserting at the // end of an empty vector. This resulted in random failures // that were hard to reproduce. if (output.empty() && length > 0) { output.push_back(data[0]); ++data; --length; } #endif output.insert(output.end(), data, data + length); }