Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore basic functionality of the C++ process wrapper #1728

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions rust/private/rust.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -1094,12 +1094,12 @@ rust_binary = rule(
"""),
)

def _common_attrs_for_binary_without_process_wrapper(attrs):
def _common_attrs_for_binary_with_basic_process_wrapper(attrs):
new_attr = dict(attrs)

# use a fake process wrapper
new_attr["_process_wrapper"] = attr.label(
default = None,
default = Label("//util/process_wrapper:basic_process_wrapper"),
executable = True,
allow_single_file = True,
cfg = "exec",
Expand All @@ -1108,7 +1108,7 @@ def _common_attrs_for_binary_without_process_wrapper(attrs):
# fix stamp = 0
new_attr["stamp"] = attr.int(
doc = dedent("""\
Fix `stamp = 0` as stamping is not supported when building without process_wrapper:
Fix `stamp = 0` as stamping is not supported when building with the basic process_wrapper:
https://github.com/bazelbuild/rules_rust/blob/8df4517d370b0c543a01ba38b63e1d5a4104b035/rust/private/rustc.bzl#L955
"""),
default = 0,
Expand All @@ -1120,10 +1120,10 @@ def _common_attrs_for_binary_without_process_wrapper(attrs):
# Provides an internal rust_{binary,library} to use that we can use to build the process
# wrapper, this breaks the dependency of rust_* on the process wrapper by
# setting it to None, which the functions in rustc detect and build accordingly.
rust_binary_without_process_wrapper = rule(
rust_binary_with_basic_process_wrapper = rule(
implementation = _rust_binary_impl,
provides = _common_providers,
attrs = _common_attrs_for_binary_without_process_wrapper(_common_attrs.items() + _rust_binary_attrs.items()),
attrs = _common_attrs_for_binary_with_basic_process_wrapper(_common_attrs.items() + _rust_binary_attrs.items()),
executable = True,
fragments = ["cpp"],
host_fragments = ["cpp"],
Expand All @@ -1134,10 +1134,10 @@ rust_binary_without_process_wrapper = rule(
incompatible_use_toolchain_transition = True,
)

rust_library_without_process_wrapper = rule(
rust_library_with_basic_process_wrapper = rule(
implementation = _rust_library_impl,
provides = _common_providers,
attrs = dict(_common_attrs_for_binary_without_process_wrapper(_common_attrs).items()),
attrs = dict(_common_attrs_for_binary_with_basic_process_wrapper(_common_attrs).items()),
fragments = ["cpp"],
host_fragments = ["cpp"],
toolchains = [
Expand Down
13 changes: 7 additions & 6 deletions rust/private/rustc.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ load(
"is_exec_configuration",
"make_static_lib_symlink",
"relativize",
"uses_process_wrapper",
)

BuildInfo = _BuildInfo
Expand Down Expand Up @@ -1207,7 +1208,7 @@ def rustc_compile_action(
dsym_folder = ctx.actions.declare_directory(crate_info.output.basename + ".dSYM", sibling = crate_info.output)
action_outputs.append(dsym_folder)

if ctx.executable._process_wrapper:
if uses_process_wrapper(ctx):
# Run as normal
ctx.actions.run(
executable = ctx.executable._process_wrapper,
Expand Down Expand Up @@ -1239,17 +1240,17 @@ def rustc_compile_action(
),
)
else:
# Run without process_wrapper
# Run with the basic process_wrapper
if build_env_files or build_flags_files or stamp or build_metadata:
fail("build_env_files, build_flags_files, stamp, build_metadata are not supported when building without process_wrapper")
fail("build_env_files, build_flags_files, stamp, build_metadata are not supported when building with the basic process wrapper")
ctx.actions.run(
executable = toolchain.rustc,
executable = ctx.executable._process_wrapper,
inputs = compile_inputs,
outputs = action_outputs,
env = env,
arguments = [args.rustc_flags],
arguments = [toolchain.rustc.path, args.rustc_flags],
mnemonic = "Rustc",
progress_message = "Compiling Rust (without process_wrapper) {} {}{} ({} files)".format(
progress_message = "Compiling Rust (for process wrapper) {} {}{} ({} files)".format(
crate_info.type,
ctx.label.name,
formatted_version,
Expand Down
14 changes: 13 additions & 1 deletion rust/private/utils.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,7 @@ def can_build_metadata(toolchain, ctx, crate_type):
# 4) the crate_type is rlib or lib.
return toolchain._pipelined_compilation and \
toolchain.exec_triple.system != "windows" and \
ctx.attr._process_wrapper and \
uses_process_wrapper(ctx) and \
crate_type in ("rlib", "lib")

def crate_root_src(name, srcs, crate_type):
Expand Down Expand Up @@ -708,6 +708,18 @@ def crate_root_src(name, srcs, crate_type):
fail("No {} source file found.".format(" or ".join(file_names)), "srcs")
return crate_root

def uses_process_wrapper(ctx):
"""Returns true if this action uses the full fledged process wrapper.

Args:
ctx (list): The rule's context object

Returns:
bool: True if the full flaged process wrapper is used, false otherwise.
"""

return "basic_process_wrapper" not in ctx.executable._process_wrapper.path

def _shortest_src_with_basename(srcs, basename):
"""Finds the shortest among the paths in srcs that match the desired basename.

Expand Down
28 changes: 26 additions & 2 deletions util/process_wrapper/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
load("@rules_cc//cc:defs.bzl", "cc_binary")
load("//rust:defs.bzl", "rust_test")

# buildifier: disable=bzl-visibility
load("//rust/private:rust.bzl", "rust_binary_without_process_wrapper")
load("//rust/private:rust.bzl", "rust_binary_with_basic_process_wrapper")

rust_binary_without_process_wrapper(
rust_binary_with_basic_process_wrapper(
name = "process_wrapper",
srcs = glob(["*.rs"]),
edition = "2018",
Expand All @@ -18,3 +19,26 @@ rust_test(
crate = ":process_wrapper",
edition = "2018",
)

cc_binary(
name = "basic_process_wrapper",
srcs = [
"basic_process_wrapper.cc",
"system.h",
] + select({
"@platforms//os:windows": [
"system_windows.cc",
],
"//conditions:default": [
"system_posix.cc",
],
}),
defines = [] + select({
"@platforms//os:windows": [
"UNICODE",
"_UNICODE",
],
"//conditions:default": [],
}),
visibility = ["//visibility:public"],
)
4 changes: 2 additions & 2 deletions util/process_wrapper/BUILD.tinyjson.bazel
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# buildifier: disable=bzl-visibility
load("@rules_rust//rust/private:rust.bzl", "rust_library_without_process_wrapper")
load("@rules_rust//rust/private:rust.bzl", "rust_library_with_basic_process_wrapper")

rust_library_without_process_wrapper(
rust_library_with_basic_process_wrapper(
name = "tinyjson",
srcs = glob(["src/*.rs"]),
edition = "2018",
Expand Down
37 changes: 37 additions & 0 deletions util/process_wrapper/basic_process_wrapper.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#include <algorithm>
#include <fstream>
#include <iostream>
#include <string>
#include <utility>

#include "util/process_wrapper/system.h"

using CharType = process_wrapper::System::StrType::value_type;

// A basic process wrapper whose only purpose is to preserve determinism when
// building the true process wrapper. We do this by passing --remap-path-prefix=$pwd=
// to the command line.
int PW_MAIN(int argc, const CharType* argv[], const CharType* envp[]) {
using namespace process_wrapper;

System::EnvironmentBlock environment_block;

// Taking all environment variables from the current process
// and sending them down to the child process
for (int i = 0; envp[i] != nullptr; ++i) {
environment_block.push_back(envp[i]);
}

System::StrType exec_path = argv[1];

System::Arguments arguments;

for (int i = 2; i < argc; ++i) {
arguments.push_back(argv[i]);
}
System::StrType pwd_prefix =
PW_SYS_STR("--remap-path-prefix=") + System::GetWorkingDirectory() + PW_SYS_STR("=");
arguments.push_back(pwd_prefix);

return System::Exec(exec_path, arguments, environment_block);
}
47 changes: 47 additions & 0 deletions util/process_wrapper/system.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#ifndef LIB_PROCESS_WRAPPER_SYSTEM_H_
#define LIB_PROCESS_WRAPPER_SYSTEM_H_

#include <string>
#include <vector>

#if defined(_WIN32) && defined(UNICODE)
#define PW_WIN_UNICODE
#endif // defined(_WIN32) && defined(UNICODE)

#if defined(PW_WIN_UNICODE)
#define PW_SYS_STR(str) L##str
#define PW_MAIN wmain
#else
#define PW_SYS_STR(str) str
#define PW_MAIN main
#endif

namespace process_wrapper {

class System {
public:
#if defined(PW_WIN_UNICODE)
using StrType = std::wstring;
#else
using StrType = std::string;
#endif // defined(PW_WIN_UNICODE)

using StrVecType = std::vector<StrType>;
using Arguments = StrVecType;
using EnvironmentBlock = StrVecType;

public:
// Gets the working directory of the current process
static StrType GetWorkingDirectory();

// Simple function to execute a process that inherits all the current
// process handles.
// Even if the function doesn't modify global state it is not reentrant
// It is meant to be called once during the lifetime of the parent process
static int Exec(const StrType& executable, const Arguments& arguments,
const EnvironmentBlock& environment_block);
};

} // namespace process_wrapper

#endif // LIB_PROCESS_WRAPPER_SYSTEM_H_
127 changes: 127 additions & 0 deletions util/process_wrapper/system_posix.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#include "util/process_wrapper/system.h"

// posix headers
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <cerrno>
#include <cstring>
#include <iostream>
#include <vector>

namespace process_wrapper {

namespace {

class OutputPipe {
public:
static constexpr size_t kReadEndDesc = 0;
static constexpr size_t kWriteEndDesc = 1;

~OutputPipe() {
CloseReadEnd();
CloseWriteEnd();
}

int CreateEnds() {
if (pipe(output_pipe_desc_) != 0) {
std::cerr << "process wrapper error: failed to open the stdout pipes.\n";
return false;
}
return true;
}
void DupWriteEnd(int newfd) {
dup2(output_pipe_desc_[kWriteEndDesc], newfd);
CloseReadEnd();
CloseWriteEnd();
}

void CloseReadEnd() { Close(kReadEndDesc); }
void CloseWriteEnd() { Close(kWriteEndDesc); }

int ReadEndDesc() const { return output_pipe_desc_[kReadEndDesc]; }
int WriteEndDesc() const { return output_pipe_desc_[kWriteEndDesc]; }

private:
void Close(size_t idx) {
if (output_pipe_desc_[idx] > 0) {
close(output_pipe_desc_[idx]);
}
output_pipe_desc_[idx] = -1;
}
int output_pipe_desc_[2] = {-1};
};

} // namespace

System::StrType System::GetWorkingDirectory() {
const size_t kMaxBufferLength = 4096;
char cwd[kMaxBufferLength];
if (getcwd(cwd, sizeof(cwd)) == NULL) {
return System::StrType{};
}
return System::StrType{cwd};
}

int System::Exec(const System::StrType &executable,
const System::Arguments &arguments,
const System::EnvironmentBlock &environment_block) {
OutputPipe stdout_pipe;
if (!stdout_pipe.CreateEnds()) {
return -1;
}
OutputPipe stderr_pipe;
if (!stderr_pipe.CreateEnds()) {
return -1;
}

pid_t child_pid = fork();
if (child_pid < 0) {
std::cerr << "process wrapper error: failed to fork the current process: "
<< std::strerror(errno) << ".\n";
return -1;
} else if (child_pid == 0) {
std::vector<char *> argv;
argv.push_back(const_cast<char *>(executable.c_str()));
for (const StrType &argument : arguments) {
argv.push_back(const_cast<char *>(argument.c_str()));
}
argv.push_back(nullptr);

std::vector<char *> envp;
for (const StrType &ev : environment_block) {
envp.push_back(const_cast<char *>(ev.c_str()));
}
envp.push_back(nullptr);

umask(022);
execve(executable.c_str(), argv.data(), envp.data());
std::cerr << "process wrapper error: failed to exec the new process: "
<< std::strerror(errno) << ".\n";
return -1;
}

int err, exit_status;
do {
err = waitpid(child_pid, &exit_status, 0);
} while (err == -1 && errno == EINTR);

if (WIFEXITED(exit_status)) {
return WEXITSTATUS(exit_status);
} else if (WIFSIGNALED(exit_status)) {
raise(WTERMSIG(exit_status));
} else if (WIFSTOPPED(exit_status)) {
raise(WSTOPSIG(exit_status));
} else {
std::cerr << "process wrapper error: failed to parse exit code of the "
"child process: "
<< exit_status << ".\n";
}
return -1;
}

} // namespace process_wrapper
Loading