Skip to content

Commit

Permalink
apacheGH-44096: [C++] Don't use Boost.Process with Emscripten
Browse files Browse the repository at this point in the history
Because Boost.Process doesn't work with Emscripten.
  • Loading branch information
kou committed Sep 13, 2024
1 parent 0bdb5be commit ad112ad
Showing 1 changed file with 105 additions and 64 deletions.
169 changes: 105 additions & 64 deletions cpp/src/arrow/testing/process.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,76 +18,83 @@
#include "arrow/testing/process.h"
#include "arrow/result.h"

#define BOOST_PROCESS_AVAILABLE
#ifdef __EMSCRIPTEN__
# undef BOOST_PROCESS_AVAILABLE
#endif

#ifdef BOOST_PROCESS_AVAILABLE
// This boost/asio/io_context.hpp include is needless for no MinGW
// build.
//
// This is for including boost/asio/detail/socket_types.hpp before any
// "#include <windows.h>". boost/asio/detail/socket_types.hpp doesn't
// work if windows.h is already included.
#include <boost/asio/io_context.hpp>
# include <boost/asio/io_context.hpp>

#ifdef BOOST_PROCESS_HAVE_V2
# ifdef BOOST_PROCESS_HAVE_V2
// We can't use v2 API on Windows because v2 API doesn't support
// process group [1] and GCS testbench uses multiple processes [2].
//
// [1] https://github.com/boostorg/process/issues/259
// [2] https://github.com/googleapis/storage-testbench/issues/669
# ifndef _WIN32
# define BOOST_PROCESS_USE_V2
# ifndef _WIN32
# define BOOST_PROCESS_USE_V2
# endif
# endif
#endif

#ifdef BOOST_PROCESS_USE_V2
# ifdef BOOST_PROCESS_NEED_SOURCE
# ifdef BOOST_PROCESS_USE_V2
# ifdef BOOST_PROCESS_NEED_SOURCE
// Workaround for https://github.com/boostorg/process/issues/312
# define BOOST_PROCESS_V2_SEPARATE_COMPILATION
# ifdef __APPLE__
# include <sys/sysctl.h>
# define BOOST_PROCESS_V2_SEPARATE_COMPILATION
# ifdef __APPLE__
# include <sys/sysctl.h>
# endif
# include <boost/process/v2.hpp>
# include <boost/process/v2/src.hpp>
# else
# include <boost/process/v2.hpp>
# endif
# include <boost/process/v2.hpp>
# include <boost/process/v2/src.hpp>
# include <unordered_map>
# else
# include <boost/process/v2.hpp>
# endif
# include <unordered_map>
#else
// We need BOOST_USE_WINDOWS_H definition with MinGW when we use
// boost/process.hpp. boost/process/detail/windows/handle_workaround.hpp
// doesn't work without BOOST_USE_WINDOWS_H with MinGW because MinGW
// doesn't provide __kernel_entry without winternl.h.
//
// See also:
// https://github.com/boostorg/process/blob/develop/include/boost/process/detail/windows/handle_workaround.hpp
# ifdef __MINGW32__
# define BOOST_USE_WINDOWS_H = 1
# endif
# ifdef BOOST_PROCESS_HAVE_V1
# include <boost/process/v1.hpp>
# else
# include <boost/process.hpp>
# ifdef __MINGW32__
# define BOOST_USE_WINDOWS_H = 1
# endif
# ifdef BOOST_PROCESS_HAVE_V1
# include <boost/process/v1.hpp>
# else
# include <boost/process.hpp>
# endif
# endif
#endif

#ifdef __APPLE__
# include <limits.h>
# include <mach-o/dyld.h>
#endif
# ifdef __APPLE__
# include <limits.h>
# include <mach-o/dyld.h>
# endif

#include <chrono>
#include <iostream>
#include <sstream>
#include <thread>
# include <chrono>
# include <iostream>
# include <sstream>
# include <thread>

#ifdef BOOST_PROCESS_USE_V2
# ifdef BOOST_PROCESS_USE_V2
namespace asio = BOOST_PROCESS_V2_ASIO_NAMESPACE;
namespace process = BOOST_PROCESS_V2_NAMESPACE;
namespace filesystem = process::filesystem;
#elif defined(BOOST_PROCESS_HAVE_V1)
# elif defined(BOOST_PROCESS_HAVE_V1)
namespace process = boost::process::v1;
namespace filesystem = boost::process::v1::filesystem;
#else
# else
namespace process = boost::process;
namespace filesystem = boost::filesystem;
# endif
#endif

namespace arrow::util {
Expand All @@ -96,17 +103,20 @@ class Process::Impl {
public:
Impl() {
// Get a copy of the current environment.
#ifdef BOOST_PROCESS_USE_V2
#ifdef BOOST_PROCESS_AVAILABLE
# ifdef BOOST_PROCESS_USE_V2
for (const auto& kv : process::environment::current()) {
env_[kv.key()] = process::environment::value(kv.value());
}
#else
# else
env_ = process::environment(boost::this_process::environment());
# endif
#endif
}

~Impl() {
#ifdef BOOST_PROCESS_USE_V2
#ifdef BOOST_PROCESS_AVAILABLE
# ifdef BOOST_PROCESS_USE_V2
// V2 doesn't provide process group support yet:
// https://github.com/boostorg/process/issues/259
//
Expand All @@ -126,120 +136,150 @@ class Process::Impl {
}
}
}
#else
# else
process_group_ = nullptr;
#endif
# endif
process_ = nullptr;
#endif
}

Status SetExecutable(const std::string& name) {
#ifdef BOOST_PROCESS_USE_V2
#ifdef BOOST_PROCESS_AVAILABLE
# ifdef BOOST_PROCESS_USE_V2
executable_ = process::environment::find_executable(name);
#else
# else
executable_ = process::search_path(name);
#endif
# endif
if (executable_.empty()) {
// Search the current executable directory as fallback.
ARROW_ASSIGN_OR_RAISE(auto current_exe, ResolveCurrentExecutable());
#ifdef BOOST_PROCESS_USE_V2
# ifdef BOOST_PROCESS_USE_V2
std::unordered_map<process::environment::key, process::environment::value> env;
for (const auto& kv : process::environment::current()) {
env[kv.key()] = process::environment::value(kv.value());
}
env["PATH"] = process::environment::value(current_exe.parent_path());
executable_ = process::environment::find_executable(name, env);
#else
# else
executable_ = process::search_path(name, {current_exe.parent_path()});
#endif
# endif
}
if (executable_.empty()) {
return Status::IOError("Failed to find '", name, "' in PATH");
}
return Status::OK();
#else
return Status::NotImplemented("Boost.Process isn't available on this system");
#endif
}

void SetArgs(const std::vector<std::string>& args) { args_ = args; }
void SetArgs(const std::vector<std::string>& args) {
#ifdef BOOST_PROCESS_AVAILABLE
args_ = args;
#endif
}

void SetEnv(const std::string& name, const std::string& value) {
#ifdef BOOST_PROCESS_USE_V2
#ifdef BOOST_PROCESS_AVAILABLE
# ifdef BOOST_PROCESS_USE_V2
env_[name] = process::environment::value(value);
#else
# else
env_[name] = value;
# endif
#endif
}

void IgnoreStderr() { keep_stderr_ = false; }
void IgnoreStderr() {
#ifdef BOOST_PROCESS_AVAILABLE
keep_stderr_ = false;
#endif
}

Status Execute() {
#ifdef BOOST_PROCESS_AVAILABLE
try {
#ifdef BOOST_PROCESS_USE_V2
# ifdef BOOST_PROCESS_USE_V2
return ExecuteV2();
#else
# else
return ExecuteV1();
#endif
# endif
} catch (const std::exception& e) {
return Status::IOError("Failed to launch '", executable_, "': ", e.what());
}
#else
return Status::NotImplemented("Boost.Process isn't available on this system");
#endif
}

bool IsRunning() {
#ifdef BOOST_PROCESS_USE_V2
#ifdef BOOST_PROCESS_AVAILABLE
# ifdef BOOST_PROCESS_USE_V2
boost::system::error_code error_code;
return process_ && process_->running(error_code);
#else
# else
return process_ && process_->running();
# endif
#else
return false;
#endif
}

uint64_t pid() {
#ifdef BOOST_PROCESS_AVAILABLE
if (!process_) {
return 0;
}
return process_->id();
#else
return 0;
#endif
}

private:
#ifdef BOOST_PROCESS_AVAILABLE
filesystem::path executable_;
std::vector<std::string> args_;
bool keep_stderr_ = true;
#ifdef BOOST_PROCESS_USE_V2
# ifdef BOOST_PROCESS_USE_V2
std::unordered_map<process::environment::key, process::environment::value> env_;
std::unique_ptr<process::process> process_;
asio::io_context ctx_;
// boost/process/v2/ doesn't support process group yet:
// https://github.com/boostorg/process/issues/259
#else
# else
process::environment env_;
std::unique_ptr<process::child> process_;
std::unique_ptr<process::group> process_group_;
# endif
#endif

#ifdef BOOST_PROCESS_AVAILABLE
Result<filesystem::path> ResolveCurrentExecutable() {
// See https://stackoverflow.com/a/1024937/10194 for various
// platform-specific recipes.

filesystem::path path;
boost::system::error_code error_code;

#if defined(__linux__)
# if defined(__linux__)
path = filesystem::canonical("/proc/self/exe", error_code);
#elif defined(__APPLE__)
# elif defined(__APPLE__)
char buf[PATH_MAX + 1];
uint32_t bufsize = sizeof(buf);
if (_NSGetExecutablePath(buf, &bufsize) < 0) {
return Status::Invalid("Can't resolve current exe: path too large");
}
path = filesystem::canonical(buf, error_code);
#elif defined(_WIN32)
# elif defined(_WIN32)
char buf[MAX_PATH + 1];
if (!GetModuleFileNameA(NULL, buf, sizeof(buf))) {
return Status::Invalid("Can't get executable file path");
}
path = filesystem::canonical(buf, error_code);
#else
# else
ARROW_UNUSED(error_code);
return Status::NotImplemented("Not available on this system");
#endif
# endif
if (error_code) {
// XXX fold this into the Status class?
return Status::IOError("Can't resolve current exe: ", error_code.message());
Expand All @@ -248,7 +288,7 @@ class Process::Impl {
}
}

#ifdef BOOST_PROCESS_USE_V2
# ifdef BOOST_PROCESS_USE_V2
Status ExecuteV2() {
process::process_environment env(env_);
// We can't use std::make_unique<process::process>.
Expand All @@ -258,7 +298,7 @@ class Process::Impl {
: process::process_stdio{{}, {}, nullptr}));
return Status::OK();
}
#else
# else
Status ExecuteV1() {
process_group_ = std::make_unique<process::group>();
if (keep_stderr_) {
Expand All @@ -271,6 +311,7 @@ class Process::Impl {
}
return Status::OK();
}
# endif
#endif
};

Expand Down

0 comments on commit ad112ad

Please sign in to comment.