mirror of
https://github.com/robbert-vdh/yabridge.git
synced 2026-05-07 03:50:11 +02:00
Replace Boost.Process for the Wine plugin host
Now we use our own library for this, so we can drop the Boost.Filesystem dependency after a couple more changes.
This commit is contained in:
@@ -27,224 +27,8 @@
|
||||
#include <asio/read_until.hpp>
|
||||
#include <asio/streambuf.hpp>
|
||||
|
||||
// FIXME: Remove when we get rid of the patched_async_pipe
|
||||
#include <asio/io_context.hpp>
|
||||
#include <asio/posix/stream_descriptor.hpp>
|
||||
#include <asio/post.hpp>
|
||||
|
||||
// FIXME: Get rid of Boost.Process and all of the wrangling below
|
||||
#include <boost/process/async_pipe.hpp>
|
||||
#include <boost/process/detail/posix/pipe_out.hpp>
|
||||
#include <boost/process/posix.hpp>
|
||||
|
||||
#include "../utils.h"
|
||||
|
||||
/**
|
||||
* Boost 1.72 was released with a known breaking bug caused by a missing
|
||||
* typedef: https://github.com/boostorg/process/issues/116.
|
||||
*
|
||||
* Luckily this is easy to fix since it's not really possible to downgrade Boost
|
||||
* as it would break other applications.
|
||||
*
|
||||
* Check if this is still needed for other distros after Arch starts packaging
|
||||
* Boost 1.73.
|
||||
*
|
||||
* FIXME: This has been adopted to work with standalone Asio, we should replace
|
||||
* this when we replace Boost.Process
|
||||
*/
|
||||
class patched_async_pipe {
|
||||
::asio::posix::stream_descriptor _source;
|
||||
::asio::posix::stream_descriptor _sink;
|
||||
|
||||
public:
|
||||
typedef int native_handle_type;
|
||||
typedef ::asio::posix::stream_descriptor handle_type;
|
||||
typedef typename handle_type::executor_type executor_type;
|
||||
|
||||
executor_type get_executor() { return _source.get_executor(); }
|
||||
|
||||
inline patched_async_pipe(asio::io_context& ios)
|
||||
: patched_async_pipe(ios, ios) {}
|
||||
|
||||
// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
|
||||
inline patched_async_pipe(asio::io_context& ios_source,
|
||||
asio::io_context& ios_sink)
|
||||
: _source(ios_source), _sink(ios_sink) {
|
||||
int fds[2];
|
||||
if (pipe(fds) == -1)
|
||||
boost::process::detail::throw_last_error("pipe(2) failed");
|
||||
|
||||
_source.assign(fds[0]);
|
||||
_sink.assign(fds[1]);
|
||||
};
|
||||
|
||||
inline patched_async_pipe(const patched_async_pipe& lhs);
|
||||
patched_async_pipe(patched_async_pipe&& lhs)
|
||||
: _source(std::move(lhs._source)), _sink(std::move(lhs._sink)) {
|
||||
lhs._source =
|
||||
::asio::posix::stream_descriptor{lhs._source.get_executor()};
|
||||
lhs._sink = ::asio::posix::stream_descriptor{lhs._sink.get_executor()};
|
||||
}
|
||||
|
||||
template <class CharT, class Traits = std::char_traits<CharT>>
|
||||
explicit patched_async_pipe(
|
||||
::asio::io_context& ios_source,
|
||||
::asio::io_context& ios_sink,
|
||||
const boost::process::detail::posix::basic_pipe<CharT, Traits>& p)
|
||||
: _source(ios_source, p.native_source()),
|
||||
_sink(ios_sink, p.native_sink()) {}
|
||||
|
||||
template <class CharT, class Traits = std::char_traits<CharT>>
|
||||
explicit patched_async_pipe(
|
||||
asio::io_context& ios,
|
||||
const boost::process::detail::posix::basic_pipe<CharT, Traits>& p)
|
||||
: patched_async_pipe(ios, ios, p) {}
|
||||
|
||||
template <class CharT, class Traits = std::char_traits<CharT>>
|
||||
inline patched_async_pipe& operator=(
|
||||
const boost::process::detail::posix::basic_pipe<CharT, Traits>& p);
|
||||
inline patched_async_pipe& operator=(const patched_async_pipe& rhs);
|
||||
|
||||
inline patched_async_pipe& operator=(patched_async_pipe&& lhs);
|
||||
|
||||
~patched_async_pipe() {
|
||||
std::error_code ec;
|
||||
close(ec);
|
||||
}
|
||||
|
||||
template <class CharT, class Traits = std::char_traits<CharT>>
|
||||
inline explicit
|
||||
operator boost::process::detail::posix::basic_pipe<CharT, Traits>() const;
|
||||
|
||||
void cancel() {
|
||||
if (_sink.is_open())
|
||||
_sink.cancel();
|
||||
if (_source.is_open())
|
||||
_source.cancel();
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (_sink.is_open())
|
||||
_sink.close();
|
||||
if (_source.is_open())
|
||||
_source.close();
|
||||
}
|
||||
void close(std::error_code& ec) {
|
||||
if (_sink.is_open())
|
||||
_sink.close(ec);
|
||||
if (_source.is_open())
|
||||
_source.close(ec);
|
||||
}
|
||||
|
||||
bool is_open() const { return _sink.is_open() || _source.is_open(); }
|
||||
void async_close() {
|
||||
if (_sink.is_open())
|
||||
asio::post(_sink.get_executor(), [this] { _sink.close(); });
|
||||
if (_source.is_open())
|
||||
asio::post(_source.get_executor(), [this] { _source.close(); });
|
||||
}
|
||||
|
||||
template <typename MutableBufferSequence>
|
||||
std::size_t read_some(const MutableBufferSequence& buffers) {
|
||||
return _source.read_some(buffers);
|
||||
}
|
||||
template <typename MutableBufferSequence>
|
||||
std::size_t write_some(const MutableBufferSequence& buffers) {
|
||||
return _sink.write_some(buffers);
|
||||
}
|
||||
|
||||
template <typename MutableBufferSequence>
|
||||
std::size_t read_some(const MutableBufferSequence& buffers,
|
||||
std::error_code& ec) noexcept {
|
||||
return _source.read_some(buffers, ec);
|
||||
}
|
||||
template <typename MutableBufferSequence>
|
||||
std::size_t write_some(const MutableBufferSequence& buffers,
|
||||
std::error_code& ec) noexcept {
|
||||
return _sink.write_some(buffers, ec);
|
||||
}
|
||||
|
||||
native_handle_type native_source() const {
|
||||
return const_cast<asio::posix::stream_descriptor&>(_source)
|
||||
.native_handle();
|
||||
}
|
||||
native_handle_type native_sink() const {
|
||||
return const_cast<asio::posix::stream_descriptor&>(_sink)
|
||||
.native_handle();
|
||||
}
|
||||
|
||||
template <typename MutableBufferSequence, typename ReadHandler>
|
||||
ASIO_INITFN_RESULT_TYPE(ReadHandler, void(std::error_code, std::size_t))
|
||||
async_read_some(const MutableBufferSequence& buffers,
|
||||
ReadHandler&& handler) {
|
||||
return _source.async_read_some(buffers,
|
||||
std::forward<ReadHandler>(handler));
|
||||
}
|
||||
|
||||
template <typename ConstBufferSequence, typename WriteHandler>
|
||||
ASIO_INITFN_RESULT_TYPE(WriteHandler, void(std::error_code, std::size_t))
|
||||
async_write_some(const ConstBufferSequence& buffers,
|
||||
WriteHandler&& handler) {
|
||||
return _sink.async_write_some(buffers,
|
||||
std::forward<WriteHandler>(handler));
|
||||
}
|
||||
|
||||
const handle_type& sink() const& { return _sink; }
|
||||
const handle_type& source() const& { return _source; }
|
||||
|
||||
handle_type&& sink() && { return std::move(_sink); }
|
||||
handle_type&& source() && { return std::move(_source); }
|
||||
|
||||
handle_type source(::asio::io_context& ios) && {
|
||||
::asio::posix::stream_descriptor stolen(ios, _source.release());
|
||||
return stolen;
|
||||
}
|
||||
handle_type sink(::asio::io_context& ios) && {
|
||||
::asio::posix::stream_descriptor stolen(ios, _sink.release());
|
||||
return stolen;
|
||||
}
|
||||
|
||||
handle_type source(::asio::io_context& ios) const& {
|
||||
auto source_in = const_cast<::asio::posix::stream_descriptor&>(_source)
|
||||
.native_handle();
|
||||
return ::asio::posix::stream_descriptor(ios, ::dup(source_in));
|
||||
}
|
||||
handle_type sink(::asio::io_context& ios) const& {
|
||||
auto sink_in = const_cast<::asio::posix::stream_descriptor&>(_sink)
|
||||
.native_handle();
|
||||
return ::asio::posix::stream_descriptor(ios, ::dup(sink_in));
|
||||
}
|
||||
};
|
||||
|
||||
// Even more of a mess, we can't use the nice `bp::std_out = ...`/`bp::std_err =
|
||||
// ...` syntax anymore.
|
||||
template <int p1, int p2>
|
||||
struct patched_async_pipe_out
|
||||
: public boost::process::detail::posix::pipe_out<p1, p2> {
|
||||
patched_async_pipe& pipe;
|
||||
template <typename AsyncPipe>
|
||||
patched_async_pipe_out(AsyncPipe& p)
|
||||
: boost::process::detail::posix::pipe_out<p1, p2>(p.native_sink(),
|
||||
p.native_source()),
|
||||
pipe(p) {}
|
||||
|
||||
template <typename Pipe, typename Executor>
|
||||
static void close(Pipe& pipe, Executor&) {
|
||||
std::error_code ec;
|
||||
std::move(pipe).sink().close(ec);
|
||||
}
|
||||
|
||||
template <typename Executor>
|
||||
void on_error(Executor& exec, const std::error_code&) {
|
||||
close(pipe, exec);
|
||||
}
|
||||
|
||||
template <typename Executor>
|
||||
void on_success(Executor& exec) {
|
||||
close(pipe, exec);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Super basic logging facility meant for debugging malfunctioning VST
|
||||
* plugins. This is also used to redirect the output of the Wine process
|
||||
|
||||
+91
-45
@@ -17,24 +17,72 @@
|
||||
#include "host-process.h"
|
||||
|
||||
#include <asio/read_until.hpp>
|
||||
#include <boost/process/env.hpp>
|
||||
#include <boost/process/start_dir.hpp>
|
||||
|
||||
#include "../common/utils.h"
|
||||
|
||||
namespace bp = boost::process;
|
||||
namespace fs = ghc::filesystem;
|
||||
|
||||
HostProcess::HostProcess(asio::io_context& io_context,
|
||||
Logger& logger,
|
||||
const Configuration& config,
|
||||
Sockets& sockets)
|
||||
: stdout_pipe_(io_context),
|
||||
stderr_pipe_(io_context),
|
||||
config_(config),
|
||||
sockets_(sockets),
|
||||
logger_(logger) {
|
||||
// See the comment above the `on_exec_setup` in `launch_host()`
|
||||
HostProcess::HostProcess(asio::io_context& io_context, Sockets& sockets)
|
||||
: sockets_(sockets), stdout_pipe_(io_context), stderr_pipe_(io_context) {}
|
||||
|
||||
HostProcess::~HostProcess() noexcept {}
|
||||
|
||||
Process::Handle HostProcess::launch_host(
|
||||
const ghc::filesystem::path& host_path,
|
||||
std::initializer_list<std::string> args,
|
||||
Logger& logger,
|
||||
const Configuration& config,
|
||||
const PluginInfo& plugin_info) {
|
||||
#ifdef WITH_WINEDBG
|
||||
// This is set up for KDE Plasma. Other desktop environments and window
|
||||
// managers require some slight modifications to spawn a detached terminal
|
||||
// emulator. Alternatively, you can spawn `winedbg` with the `--no-start`
|
||||
// option to launch a gdb server and then connect to it from another
|
||||
// terminal.
|
||||
Process child("kstart5");
|
||||
child.arg("konsole").arg("--").arg("-e").arg("winedbg").arg("--gdb");
|
||||
#ifdef WINEDBG_LEGACY_ARGUMENT_QUOTING
|
||||
// Note the double quoting here. Old versions of winedbg didn't
|
||||
// respect `argv` and instead expected a pre-quoted Win32 command
|
||||
// line as its arguments.
|
||||
child.arg("\"" + host_path.string() + ".so\""),
|
||||
#else
|
||||
child.arg(host_path.string() + ".so");
|
||||
#endif // WINEDBG_LEGACY_ARGUMENT_QUOTING
|
||||
#else
|
||||
Process child(host_path);
|
||||
#endif // WITH_WINEDBG
|
||||
|
||||
// What's up with this indentation
|
||||
for (const auto& arg : args) {
|
||||
child.arg(arg);
|
||||
}
|
||||
|
||||
child.environment(plugin_info.create_host_env());
|
||||
Process::Handle child_handle = std::visit(
|
||||
overload{
|
||||
[](Process::Handle handle) -> Process::Handle { return handle; },
|
||||
[&host_path](const Process::CommandNotFound&) -> Process::Handle {
|
||||
throw std::runtime_error("Could not launch '" +
|
||||
host_path.string() +
|
||||
"', command not found");
|
||||
},
|
||||
[](const std::error_code& err) -> Process::Handle {
|
||||
throw std::runtime_error("Error spawning Wine process: " +
|
||||
err.message());
|
||||
},
|
||||
},
|
||||
// HACK: If the `disable_pipes` option is enabled, then we'll redirect
|
||||
// the plugin's output to a file instead of using pipes to blend
|
||||
// it in with the rest of yabridge's output. This is for some
|
||||
// reason necessary for ujam's plugins and all other plugins made
|
||||
// with Gorilla Engine to function. Otherwise they'll print a
|
||||
// nondescriptive `JS_EXEC_FAILED` error message.
|
||||
config.disable_pipes
|
||||
? child.spawn_child_redirected(*config.disable_pipes)
|
||||
: child.spawn_child_piped(stdout_pipe_, stderr_pipe_));
|
||||
|
||||
// See the above comment
|
||||
if (config.disable_pipes) {
|
||||
logger.log("");
|
||||
logger.log("WARNING: All Wine output will be written to");
|
||||
@@ -49,9 +97,9 @@ HostProcess::HostProcess(asio::io_context& io_context,
|
||||
logger.async_log_pipe_lines(stderr_pipe_, stderr_buffer_,
|
||||
"[Wine STDERR] ");
|
||||
}
|
||||
}
|
||||
|
||||
HostProcess::~HostProcess() noexcept {}
|
||||
return child_handle;
|
||||
}
|
||||
|
||||
IndividualHost::IndividualHost(asio::io_context& io_context,
|
||||
Logger& logger,
|
||||
@@ -59,28 +107,32 @@ IndividualHost::IndividualHost(asio::io_context& io_context,
|
||||
Sockets& sockets,
|
||||
const PluginInfo& plugin_info,
|
||||
const HostRequest& host_request)
|
||||
: HostProcess(io_context, logger, config, sockets),
|
||||
: HostProcess(io_context, sockets),
|
||||
plugin_info_(plugin_info),
|
||||
host_path_(find_vst_host(plugin_info.native_library_path_,
|
||||
plugin_info.plugin_arch_,
|
||||
false)),
|
||||
host_(
|
||||
launch_host(host_path_,
|
||||
plugin_type_to_string(host_request.plugin_type),
|
||||
handle_(launch_host(
|
||||
host_path_,
|
||||
{
|
||||
plugin_type_to_string(host_request.plugin_type),
|
||||
#if defined(WITH_WINEDBG) && defined(WINEDBG_LEGACY_ARGUMENT_QUOTING)
|
||||
// Old versions of winedbg flattened all command line
|
||||
// arguments to a single space separated Win32 command
|
||||
// line, so we had to do our own quoting
|
||||
"\"" + plugin_info.windows_plugin_path + "\"",
|
||||
// Old versions of winedbg flattened all command line
|
||||
// arguments to a single space separated Win32 command line,
|
||||
// so we had to do our own quoting
|
||||
"\"" + plugin_info.windows_plugin_path + "\"",
|
||||
#else
|
||||
host_request.plugin_path,
|
||||
host_request.plugin_path,
|
||||
#endif
|
||||
host_request.endpoint_base_dir,
|
||||
// We pass this process' process ID as an argument so we
|
||||
// can run a watchdog on the Wine plugin host process that
|
||||
// shuts down the sockets after this process shuts down
|
||||
std::to_string(getpid()),
|
||||
bp::env = plugin_info.create_host_env())) {
|
||||
host_request.endpoint_base_dir,
|
||||
// We pass this process' process ID as an argument so we can
|
||||
// run a watchdog on the Wine plugin host process that shuts
|
||||
// down the sockets after this process shuts down
|
||||
std::to_string(getpid())
|
||||
},
|
||||
logger,
|
||||
config,
|
||||
plugin_info)) {
|
||||
#ifdef WITH_WINEDBG
|
||||
if (plugin_info.windows_plugin_path_.string().find('"') !=
|
||||
std::string::npos) {
|
||||
@@ -96,9 +148,7 @@ fs::path IndividualHost::path() {
|
||||
}
|
||||
|
||||
bool IndividualHost::running() {
|
||||
// NOTE: `boost::process::child::running()` still considers zombies as
|
||||
// running, so it's useless for our purposes.
|
||||
return pid_running(host_.id());
|
||||
return handle_.running();
|
||||
}
|
||||
|
||||
void IndividualHost::terminate() {
|
||||
@@ -109,10 +159,8 @@ void IndividualHost::terminate() {
|
||||
// prevents us from joining our `std::jthread`s on the plugin side.
|
||||
sockets_.close();
|
||||
|
||||
host_.terminate();
|
||||
// NOTE: This leaves a zombie, because Boost.Process will actually not call
|
||||
// `wait()` after we have terminated the process.
|
||||
host_.wait();
|
||||
// This will also reap the terminated process
|
||||
handle_.terminate();
|
||||
}
|
||||
|
||||
GroupHost::GroupHost(asio::io_context& io_context,
|
||||
@@ -121,7 +169,7 @@ GroupHost::GroupHost(asio::io_context& io_context,
|
||||
Sockets& sockets,
|
||||
const PluginInfo& plugin_info,
|
||||
const HostRequest& host_request)
|
||||
: HostProcess(io_context, logger, config, sockets),
|
||||
: HostProcess(io_context, sockets),
|
||||
plugin_info_(plugin_info),
|
||||
host_path_(find_vst_host(plugin_info.native_library_path_,
|
||||
plugin_info.plugin_arch_,
|
||||
@@ -156,15 +204,13 @@ GroupHost::GroupHost(asio::io_context& io_context,
|
||||
// new group host process. This process is detached immediately
|
||||
// because it should run independently of this yabridge instance as
|
||||
// it will likely outlive it.
|
||||
bp::child group_host =
|
||||
// FIXME: Boost.Filesystem conversion
|
||||
launch_host(host_path_, group_socket_path.string(),
|
||||
bp::env = plugin_info.create_host_env());
|
||||
Process::Handle group_host =
|
||||
launch_host(host_path_, {group_socket_path.string()}, logger,
|
||||
config, plugin_info);
|
||||
group_host.detach();
|
||||
|
||||
const pid_t group_host_pid = group_host.id();
|
||||
group_host_connect_handler_ =
|
||||
std::jthread([this, connect, group_host_pid]() {
|
||||
std::jthread([this, connect, group_host = std::move(group_host)]() {
|
||||
set_realtime_priority(true);
|
||||
pthread_setname_np(pthread_self(), "group-connect");
|
||||
|
||||
@@ -172,7 +218,7 @@ GroupHost::GroupHost(asio::io_context& io_context,
|
||||
|
||||
// We'll first try to connect to the group host we just spawned
|
||||
// TODO: Replace this polling with inotify
|
||||
while (pid_running(group_host_pid)) {
|
||||
while (group_host.running()) {
|
||||
std::this_thread::sleep_for(20ms);
|
||||
|
||||
try {
|
||||
|
||||
+34
-98
@@ -19,10 +19,8 @@
|
||||
#include <thread>
|
||||
|
||||
#include <asio/local/stream_protocol.hpp>
|
||||
#include <asio/posix/stream_descriptor.hpp>
|
||||
#include <asio/streambuf.hpp>
|
||||
#include <boost/process/child.hpp>
|
||||
#include <boost/process/extend.hpp>
|
||||
#include <boost/process/io.hpp>
|
||||
#include <ghc/filesystem.hpp>
|
||||
|
||||
#include "../common/communication/common.h"
|
||||
@@ -59,108 +57,42 @@ class HostProcess {
|
||||
*/
|
||||
virtual void terminate() = 0;
|
||||
|
||||
/**
|
||||
* Simple helper function around `boost::process::child` that launches the
|
||||
* host application (`*.exe`) with some basic setup. This includes setting
|
||||
* up the asynchronous pipes for STDIO redirection, closing file descriptors
|
||||
* to prevent leaks, and wrapping everything in winedbg if we're compiling
|
||||
* with `-Dwith-winedbg=true`. Keep in mind that winedbg does not handle
|
||||
* arguments containing spaces, so most Windows paths will be split up into
|
||||
* multiple arugments.
|
||||
*/
|
||||
template <typename... Args>
|
||||
boost::process::child launch_host(ghc::filesystem::path host_path,
|
||||
Args&&... args) {
|
||||
return boost::process::child(
|
||||
#ifdef WITH_WINEDBG
|
||||
// This is set up for KDE Plasma. Other desktop environments and
|
||||
// window managers require some slight modifications to spawn a
|
||||
// detached terminal emulator. Alternatively, you can spawn
|
||||
// `/usr/bin/winedbg` with the `--no-start` option to launch a gdb
|
||||
// server and then connect to it from another terminal.
|
||||
"/usr/bin/kstart5", "konsole", "--", "-e", "winedbg", "--gdb",
|
||||
#ifdef WINEDBG_LEGACY_ARGUMENT_QUOTING
|
||||
// Note the double quoting here. Old versions of winedbg didn't
|
||||
// respect `argv` and instead expected a pre-quoted Win32 command
|
||||
// line as its arguments.
|
||||
"\"" + host_path.string() + ".so\"",
|
||||
#else
|
||||
host_path.string() + ".so",
|
||||
#endif // WINEDBG_LEGACY_ARGUMENT_QUOTING
|
||||
#else
|
||||
// FIXME: Replace Boost.Filesystem
|
||||
host_path.string(),
|
||||
#endif // WITH_WINEDBG
|
||||
// FIXME: This won't work with our patched async_pipe version
|
||||
// boost::process::std_out = stdout_pipe_,
|
||||
// boost::process::std_err = stderr_pipe_,
|
||||
patched_async_pipe_out<1, -1>(stdout_pipe_),
|
||||
patched_async_pipe_out<2, -1>(stderr_pipe_),
|
||||
// NOTE: If the Wine process outlives the host, then it may cause
|
||||
// issues if our process is still keeping the host's file
|
||||
// descriptors alive that. This can prevent Ardour from
|
||||
// restarting after an unexpected shutdown. Because of this we
|
||||
// won't use `vfork()`, but instead we'll just manually close
|
||||
// all non-STDIO file descriptors.
|
||||
// HACK: If the `disable_pipes` option is enabled, then we'll
|
||||
// redirect the plugin's output to a file instead of using
|
||||
// pipes to blend it in with the rest of yabridge's output.
|
||||
// This is for some reason necessary for ujam's plugins and
|
||||
// all other plugins made with Gorilla Engine to function.
|
||||
// Otherwise they'll print a nondescriptive `JS_EXEC_FAILED`
|
||||
// error message.
|
||||
boost::process::extend::on_exec_setup =
|
||||
[this](auto& /*executor*/) {
|
||||
const int max_fds = static_cast<int>(sysconf(_SC_OPEN_MAX));
|
||||
for (int fd = STDERR_FILENO + 1; fd < max_fds; fd++) {
|
||||
close(fd);
|
||||
}
|
||||
|
||||
// See above
|
||||
if (config_.disable_pipes) {
|
||||
const int redirect_fd =
|
||||
open(config_.disable_pipes->c_str(),
|
||||
O_CREAT | O_APPEND | O_WRONLY, 0640);
|
||||
|
||||
assert(redirect_fd != -1);
|
||||
dup2(redirect_fd, STDOUT_FILENO);
|
||||
dup2(redirect_fd, STDERR_FILENO);
|
||||
close(redirect_fd);
|
||||
}
|
||||
},
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Initialize the host process by setting up the STDIO redirection.
|
||||
* The actual process initialization and everything involved in that process
|
||||
* is done in `launch_host()` since a new process may not be required
|
||||
* when using plugin groups.
|
||||
*
|
||||
* @param io_context The IO context that the STDIO redurection will be
|
||||
* handled on.
|
||||
* @param logger The `Logger` instance the redirected STDIO streams will be
|
||||
* written to.
|
||||
* @param io_context The IO context that the STDIO redirection pipes will be
|
||||
* bound to. The logging for these pipes is set up in `launch_host()`.
|
||||
* @param sockets The socket endpoints that will be used for communication
|
||||
* with the plugin. When the plugin shuts down, we'll close all of the
|
||||
* sockets used by the plugin.
|
||||
*/
|
||||
HostProcess(asio::io_context& io_context,
|
||||
Logger& logger,
|
||||
const Configuration& config,
|
||||
Sockets& sockets);
|
||||
HostProcess(asio::io_context& io_context, Sockets& sockets);
|
||||
|
||||
/**
|
||||
* The STDOUT stream of the Wine process we can forward to the logger.
|
||||
* Helper function that launches the Wine host application (`*.exe`) with
|
||||
* all of the correct environment setup. This includes setting the correct
|
||||
* environment variables for the Wine prefix the plugin is in, setting up
|
||||
* pipes or files for STDIO redirection, closing file descriptors to prevent
|
||||
* leaks, and wrapping all of that in a terminal process running winedbg if
|
||||
* we're compiling with `-Dwith-winedbg=true`. Keep in mind that winedbg
|
||||
* does not handle arguments containing spaces, so most Windows paths will
|
||||
* be split up into multiple arguments.
|
||||
*
|
||||
* @param logger The `Logger` instance the redirected STDIO streams will be
|
||||
* written to.
|
||||
* @param config The plugin's configuration, used to determine whether to
|
||||
* use pipes or to redirect the output to a file instead.
|
||||
* @param plugin_info Information about the plugin, used to determine the
|
||||
* plugin's Wine prefix.
|
||||
*/
|
||||
patched_async_pipe stdout_pipe_;
|
||||
/**
|
||||
* The STDERR stream of the Wine process we can forward to the logger.
|
||||
*/
|
||||
patched_async_pipe stderr_pipe_;
|
||||
|
||||
/**
|
||||
* The current plugin instance's configuration.
|
||||
*/
|
||||
const Configuration& config_;
|
||||
Process::Handle launch_host(const ghc::filesystem::path& host_path,
|
||||
std::initializer_list<std::string> args,
|
||||
Logger& logger,
|
||||
const Configuration& config,
|
||||
const PluginInfo& plugin_info);
|
||||
|
||||
/**
|
||||
* The associated sockets for the plugin we're hosting. This is used to
|
||||
@@ -170,9 +102,13 @@ class HostProcess {
|
||||
|
||||
private:
|
||||
/**
|
||||
* The logger the Wine output will be written to.
|
||||
* The STDOUT stream of the Wine process we can forward to the logger.
|
||||
*/
|
||||
Logger& logger_;
|
||||
asio::posix::stream_descriptor stdout_pipe_;
|
||||
/**
|
||||
* The STDERR stream of the Wine process we can forward to the logger.
|
||||
*/
|
||||
asio::posix::stream_descriptor stderr_pipe_;
|
||||
|
||||
asio::streambuf stdout_buffer_;
|
||||
asio::streambuf stderr_buffer_;
|
||||
@@ -218,7 +154,7 @@ class IndividualHost : public HostProcess {
|
||||
private:
|
||||
const PluginInfo& plugin_info_;
|
||||
ghc::filesystem::path host_path_;
|
||||
boost::process::child host_;
|
||||
Process::Handle handle_;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+20
-46
@@ -20,23 +20,14 @@
|
||||
|
||||
#include <unistd.h>
|
||||
#include <boost/dll/runtime_symbol_info.hpp>
|
||||
#include <boost/process/io.hpp>
|
||||
#include <boost/process/pipe.hpp>
|
||||
#include <boost/process/search_path.hpp>
|
||||
#include <boost/process/system.hpp>
|
||||
#include <sstream>
|
||||
|
||||
// XXX: With Boost 1.75 at least, this header cannot be included in alphabetical
|
||||
// order because it's missing some includes
|
||||
#include <boost/process/env.hpp>
|
||||
|
||||
// Generated inside of the build directory
|
||||
#include <config.h>
|
||||
|
||||
#include "../common/configuration.h"
|
||||
#include "../common/utils.h"
|
||||
|
||||
namespace bp = boost::process;
|
||||
namespace fs = ghc::filesystem;
|
||||
|
||||
// These functions are used to populate the fields in `PluginInfo`. See the
|
||||
@@ -65,25 +56,7 @@ PluginInfo::PluginInfo(PluginType plugin_type, bool prefer_32bit_vst3)
|
||||
normalize_plugin_path(windows_library_path_, plugin_type)),
|
||||
wine_prefix_(find_wine_prefix(windows_plugin_path_)) {}
|
||||
|
||||
bp::environment PluginInfo::create_host_env() const {
|
||||
bp::environment env = boost::this_process::environment();
|
||||
|
||||
// Only set the prefix when could auto detect it and it's not being
|
||||
// overridden (this entire `std::visit` instead of `std::has_alternative` is
|
||||
// just for clarity's sake)
|
||||
std::visit(overload{
|
||||
[](const OverridenWinePrefix&) {},
|
||||
[&](const ghc::filesystem::path& prefix) {
|
||||
env["WINEPREFIX"] = prefix.string();
|
||||
},
|
||||
[](const DefaultWinePrefix&) {},
|
||||
},
|
||||
wine_prefix_);
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
ProcessEnvironment PluginInfo::create_host_env_2() const {
|
||||
ProcessEnvironment PluginInfo::create_host_env() const {
|
||||
ProcessEnvironment env(environ);
|
||||
|
||||
// Only set the prefix when could auto detect it and it's not being
|
||||
@@ -130,7 +103,7 @@ std::string PluginInfo::wine_version() const {
|
||||
|
||||
Process process(wine_path);
|
||||
process.arg("--version");
|
||||
process.environment(create_host_env_2());
|
||||
process.environment(create_host_env());
|
||||
|
||||
const auto result = process.spawn_get_stdout_line();
|
||||
return std::visit(
|
||||
@@ -345,7 +318,6 @@ std::string create_logger_prefix(const fs::path& endpoint_base_dir) {
|
||||
fs::path find_vst_host(const ghc::filesystem::path& this_plugin_path,
|
||||
LibArchitecture plugin_arch,
|
||||
bool use_plugin_groups) {
|
||||
// FIXME: Anything using `this_plugin_path` and similar needs to be changed
|
||||
auto host_name = use_plugin_groups ? yabridge_group_host_name
|
||||
: yabridge_individual_host_name;
|
||||
if (plugin_arch == LibArchitecture::dll_32) {
|
||||
@@ -361,17 +333,13 @@ fs::path find_vst_host(const ghc::filesystem::path& this_plugin_path,
|
||||
return host_path;
|
||||
}
|
||||
|
||||
// Boost will return an empty path if the file could not be found in the
|
||||
// search path
|
||||
const boost::filesystem::path vst_host_path =
|
||||
bp::search_path(host_name, get_augmented_search_path());
|
||||
if (vst_host_path == "") {
|
||||
if (const std::optional<fs::path> vst_host_path =
|
||||
search_in_path(get_augmented_search_path(), host_name)) {
|
||||
return *vst_host_path;
|
||||
} else {
|
||||
throw std::runtime_error("Could not locate '" + std::string(host_name) +
|
||||
"'");
|
||||
}
|
||||
|
||||
// FIXME: Replace Boost.Filesystem usage requiring this conversion
|
||||
return vst_host_path.string();
|
||||
}
|
||||
|
||||
ghc::filesystem::path generate_group_endpoint(
|
||||
@@ -396,8 +364,7 @@ ghc::filesystem::path generate_group_endpoint(
|
||||
return get_temporary_directory() / socket_name.str();
|
||||
}
|
||||
|
||||
// FIXME: Replace Boost.Filesystem
|
||||
std::vector<boost::filesystem::path> get_augmented_search_path() {
|
||||
std::vector<fs::path> get_augmented_search_path() {
|
||||
// HACK: `std::locale("")` would return the current locale, but this
|
||||
// overload is implementation specific, and libstdc++ returns an error
|
||||
// when this happens and one of the locale variables (or `LANG`) is
|
||||
@@ -411,6 +378,11 @@ std::vector<boost::filesystem::path> get_augmented_search_path() {
|
||||
// https://svn.boost.org/trac10/changeset/72855
|
||||
//
|
||||
// https://github.com/boostorg/process/pull/179
|
||||
// FIXME: As mentioned above, we did this in the past to work around a
|
||||
// Boost.Process bug. Since we no longer use Boost.Process, we can
|
||||
// technically get rid of this, but we could also leave it in place
|
||||
// since this may still cause other crashes for the user if we don't
|
||||
// do it.
|
||||
try {
|
||||
std::locale("");
|
||||
} catch (const std::runtime_error&) {
|
||||
@@ -431,17 +403,19 @@ std::vector<boost::filesystem::path> get_augmented_search_path() {
|
||||
setenv("LC_ALL", "C", true); // NOLINT(concurrency-mt-unsafe)
|
||||
}
|
||||
|
||||
std::vector<boost::filesystem::path> search_path =
|
||||
boost::this_process::path();
|
||||
// NOLINTNEXTLINE(concurrency-mt-unsafe)
|
||||
const char* path_env = getenv("PATH");
|
||||
assert(path_env);
|
||||
|
||||
std::vector<fs::path> search_path = split_path(path_env);
|
||||
|
||||
// NOLINTNEXTLINE(concurrency-mt-unsafe)
|
||||
if (const char* xdg_data_home = getenv("XDG_DATA_HOME")) {
|
||||
search_path.push_back(boost::filesystem::path(xdg_data_home) /
|
||||
"yabridge");
|
||||
search_path.push_back(fs::path(xdg_data_home) / "yabridge");
|
||||
// NOLINTNEXTLINE(concurrency-mt-unsafe)
|
||||
} else if (const char* home_directory = getenv("HOME")) {
|
||||
search_path.push_back(boost::filesystem::path(home_directory) /
|
||||
".local" / "share" / "yabridge");
|
||||
search_path.push_back(fs::path(home_directory) / ".local" / "share" /
|
||||
"yabridge");
|
||||
}
|
||||
|
||||
return search_path;
|
||||
|
||||
+2
-8
@@ -18,8 +18,6 @@
|
||||
|
||||
#include <variant>
|
||||
|
||||
#include <boost/process/environment.hpp>
|
||||
|
||||
#include "../common/configuration.h"
|
||||
#include "../common/plugins.h"
|
||||
#include "../common/process.h"
|
||||
@@ -76,9 +74,7 @@ struct PluginInfo {
|
||||
* we'll set `WINEPREFIX` to the detected Wine prefix, or it will be left
|
||||
* unset if we could not detect a prefix.
|
||||
*/
|
||||
boost::process::environment create_host_env() const;
|
||||
// FIXME: Replace create_host_env with this one
|
||||
ProcessEnvironment create_host_env_2() const;
|
||||
ProcessEnvironment create_host_env() const;
|
||||
|
||||
/**
|
||||
* Return the path to the actual Wine prefix in use, taking into account
|
||||
@@ -236,10 +232,8 @@ ghc::filesystem::path generate_group_endpoint(
|
||||
* environment variable can be a big hurdle if you've never done anything like
|
||||
* that before. And since this is the recommended installation location, it
|
||||
* makes sense to also search there by default.
|
||||
*
|
||||
* FIXME: Replace Boost.Filesystem
|
||||
*/
|
||||
std::vector<boost::filesystem::path> get_augmented_search_path();
|
||||
std::vector<ghc::filesystem::path> get_augmented_search_path();
|
||||
|
||||
/**
|
||||
* Return a path to this `.so` file. This can be used to find out from where
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "../asio-fix.h"
|
||||
|
||||
#include <asio/local/stream_protocol.hpp>
|
||||
#include <asio/posix/stream_descriptor.hpp>
|
||||
|
||||
#include "../common/logging/common.h"
|
||||
#include "../utils.h"
|
||||
|
||||
Reference in New Issue
Block a user