| 
 | 1 | +#include <sourcemeta/core/process.h>  | 
 | 2 | + | 
 | 3 | +#include <cassert>          // assert  | 
 | 4 | +#include <filesystem>       // std::filesystem  | 
 | 5 | +#include <initializer_list> // std::initializer_list  | 
 | 6 | +#include <span>             // std::span  | 
 | 7 | +#include <vector>           // std::vector  | 
 | 8 | + | 
 | 9 | +#if defined(_WIN32) && !defined(__MSYS__) && !defined(__CYGWIN__) &&           \  | 
 | 10 | +    !defined(__MINGW32__) && !defined(__MINGW64__)  | 
 | 11 | +#define WIN32_LEAN_AND_MEAN  | 
 | 12 | +#include <sstream>   // std::ostringstream  | 
 | 13 | +#include <windows.h> // CreateProcess, PROCESS_INFORMATION, STARTUPINFO, WaitForSingleObject, GetExitCodeProcess  | 
 | 14 | +#else  | 
 | 15 | +#include <spawn.h> // posix_spawnp, posix_spawnattr_t, posix_spawnattr_init, posix_spawnattr_destroy, posix_spawn_file_actions_t, posix_spawn_file_actions_init, posix_spawn_file_actions_destroy, pid_t  | 
 | 16 | +#include <sys/wait.h> // waitpid, WIFEXITED, WEXITSTATUS  | 
 | 17 | + | 
 | 18 | +#if defined(__MSYS__) || defined(__CYGWIN__) || defined(__MINGW32__) ||        \  | 
 | 19 | +    defined(__MINGW64__)  | 
 | 20 | +#include <unistd.h> // chdir  | 
 | 21 | +#endif  | 
 | 22 | + | 
 | 23 | +extern char **environ;  | 
 | 24 | +#endif  | 
 | 25 | + | 
 | 26 | +namespace sourcemeta::core {  | 
 | 27 | + | 
 | 28 | +auto spawn(const std::string &program,  | 
 | 29 | +           std::span<const std::string_view> arguments,  | 
 | 30 | +           const std::filesystem::path &directory) -> int {  | 
 | 31 | +  assert(directory.is_absolute());  | 
 | 32 | +  assert(std::filesystem::exists(directory));  | 
 | 33 | +  assert(std::filesystem::is_directory(directory));  | 
 | 34 | + | 
 | 35 | +#if defined(_WIN32) && !defined(__MSYS__) && !defined(__CYGWIN__) &&           \  | 
 | 36 | +    !defined(__MINGW32__) && !defined(__MINGW64__)  | 
 | 37 | +  std::ostringstream command_line;  | 
 | 38 | +  command_line << program;  | 
 | 39 | + | 
 | 40 | +  for (const auto &argument : arguments) {  | 
 | 41 | +    command_line << " ";  | 
 | 42 | +    // Quote arguments that contain spaces  | 
 | 43 | +    const std::string arg_str{argument};  | 
 | 44 | +    if (arg_str.find(' ') != std::string::npos) {  | 
 | 45 | +      command_line << "\"" << arg_str << "\"";  | 
 | 46 | +    } else {  | 
 | 47 | +      command_line << arg_str;  | 
 | 48 | +    }  | 
 | 49 | +  }  | 
 | 50 | + | 
 | 51 | +  std::string cmd_line_str = command_line.str();  | 
 | 52 | +  std::vector<char> cmd_line(cmd_line_str.begin(), cmd_line_str.end());  | 
 | 53 | +  cmd_line.push_back('\0');  | 
 | 54 | + | 
 | 55 | +  STARTUPINFOA startup_info{};  | 
 | 56 | +  startup_info.cb = sizeof(startup_info);  | 
 | 57 | +  PROCESS_INFORMATION process_info{};  | 
 | 58 | +  const std::string working_dir = directory.string();  | 
 | 59 | +  const BOOL success =  | 
 | 60 | +      CreateProcessA(nullptr,             // lpApplicationName  | 
 | 61 | +                     cmd_line.data(),     // lpCommandLine (modifiable)  | 
 | 62 | +                     nullptr,             // lpProcessAttributes  | 
 | 63 | +                     nullptr,             // lpThreadAttributes  | 
 | 64 | +                     TRUE,                // bInheritHandles  | 
 | 65 | +                     0,                   // dwCreationFlags  | 
 | 66 | +                     nullptr,             // lpEnvironment  | 
 | 67 | +                     working_dir.c_str(), // lpCurrentDirectory  | 
 | 68 | +                     &startup_info,       // lpStartupInfo  | 
 | 69 | +                     &process_info        // lpProcessInformation  | 
 | 70 | +      );  | 
 | 71 | + | 
 | 72 | +  if (!success) {  | 
 | 73 | +    throw ProcessProgramNotNotFoundError{program};  | 
 | 74 | +  }  | 
 | 75 | + | 
 | 76 | +  WaitForSingleObject(process_info.hProcess, INFINITE);  | 
 | 77 | + | 
 | 78 | +  DWORD exit_code;  | 
 | 79 | +  if (!GetExitCodeProcess(process_info.hProcess, &exit_code)) {  | 
 | 80 | +    CloseHandle(process_info.hProcess);  | 
 | 81 | +    CloseHandle(process_info.hThread);  | 
 | 82 | +    throw ProcessSpawnError{program, arguments};  | 
 | 83 | +  }  | 
 | 84 | + | 
 | 85 | +  CloseHandle(process_info.hProcess);  | 
 | 86 | +  CloseHandle(process_info.hThread);  | 
 | 87 | + | 
 | 88 | +  return static_cast<int>(exit_code);  | 
 | 89 | +#else  | 
 | 90 | +  std::vector<const char *> argv;  | 
 | 91 | +  argv.reserve(arguments.size() + 2);  | 
 | 92 | +  argv.push_back(program.c_str());  | 
 | 93 | + | 
 | 94 | +  for (const auto &argument : arguments) {  | 
 | 95 | +    argv.push_back(argument.data());  | 
 | 96 | +  }  | 
 | 97 | + | 
 | 98 | +  argv.push_back(nullptr);  | 
 | 99 | + | 
 | 100 | +  posix_spawnattr_t attributes;  | 
 | 101 | +  posix_spawnattr_init(&attributes);  | 
 | 102 | + | 
 | 103 | +  posix_spawn_file_actions_t file_actions;  | 
 | 104 | +  posix_spawn_file_actions_init(&file_actions);  | 
 | 105 | + | 
 | 106 | +#if defined(__MSYS__) || defined(__CYGWIN__) || defined(__MINGW32__) ||        \  | 
 | 107 | +    defined(__MINGW64__)  | 
 | 108 | +  const std::filesystem::path original_directory{  | 
 | 109 | +      std::filesystem::current_path()};  | 
 | 110 | +  std::filesystem::current_path(directory);  | 
 | 111 | +#else  | 
 | 112 | +  posix_spawn_file_actions_addchdir_np(&file_actions, directory.c_str());  | 
 | 113 | +#endif  | 
 | 114 | + | 
 | 115 | +  pid_t process_id;  | 
 | 116 | +  const int spawn_result{  | 
 | 117 | +      posix_spawnp(&process_id, program.c_str(), &file_actions, &attributes,  | 
 | 118 | +                   const_cast<char *const *>(argv.data()), environ)};  | 
 | 119 | + | 
 | 120 | +  posix_spawn_file_actions_destroy(&file_actions);  | 
 | 121 | +  posix_spawnattr_destroy(&attributes);  | 
 | 122 | + | 
 | 123 | +#if defined(__MSYS__) || defined(__CYGWIN__) || defined(__MINGW32__) ||        \  | 
 | 124 | +    defined(__MINGW64__)  | 
 | 125 | +  std::filesystem::current_path(original_directory);  | 
 | 126 | +#endif  | 
 | 127 | + | 
 | 128 | +  if (spawn_result != 0) {  | 
 | 129 | +    throw ProcessProgramNotNotFoundError{program};  | 
 | 130 | +  }  | 
 | 131 | + | 
 | 132 | +  int status;  | 
 | 133 | +  waitpid(process_id, &status, 0);  | 
 | 134 | + | 
 | 135 | +  if (WIFEXITED(status)) {  | 
 | 136 | +    return WEXITSTATUS(status);  | 
 | 137 | +  }  | 
 | 138 | + | 
 | 139 | +  throw ProcessSpawnError{program, arguments};  | 
 | 140 | +#endif  | 
 | 141 | +}  | 
 | 142 | + | 
 | 143 | +auto spawn(const std::string &program,  | 
 | 144 | +           std::initializer_list<std::string_view> arguments,  | 
 | 145 | +           const std::filesystem::path &directory) -> int {  | 
 | 146 | +  return spawn(  | 
 | 147 | +      program,  | 
 | 148 | +      std::span<const std::string_view>{arguments.begin(), arguments.size()},  | 
 | 149 | +      directory);  | 
 | 150 | +}  | 
 | 151 | + | 
 | 152 | +} // namespace sourcemeta::core  | 
0 commit comments