Skip to content

Commit d9ba374

Browse files
authoredMar 9, 2022
process_wrapper: replace C++ implementation with rust. (#1159)
1 parent e5fefdc commit d9ba374

16 files changed

+818
-987
lines changed
 

‎rust/private/rustc.bzl

+35-14
Original file line numberDiff line numberDiff line change
@@ -922,20 +922,41 @@ def rustc_compile_action(
922922
dsym_folder = ctx.actions.declare_directory(crate_info.output.basename + ".dSYM")
923923
action_outputs.append(dsym_folder)
924924

925-
ctx.actions.run(
926-
executable = ctx.executable._process_wrapper,
927-
inputs = compile_inputs,
928-
outputs = action_outputs,
929-
env = env,
930-
arguments = args.all,
931-
mnemonic = "Rustc",
932-
progress_message = "Compiling Rust {} {}{} ({} files)".format(
933-
crate_info.type,
934-
ctx.label.name,
935-
formatted_version,
936-
len(crate_info.srcs.to_list()),
937-
),
938-
)
925+
# This uses startswith as on windows the basename will be process_wrapper_fake.exe.
926+
if not ctx.executable._process_wrapper.basename.startswith("process_wrapper_fake"):
927+
# Run as normal
928+
ctx.actions.run(
929+
executable = ctx.executable._process_wrapper,
930+
inputs = compile_inputs,
931+
outputs = action_outputs,
932+
env = env,
933+
arguments = args.all,
934+
mnemonic = "Rustc",
935+
progress_message = "Compiling Rust {} {}{} ({} files)".format(
936+
crate_info.type,
937+
ctx.label.name,
938+
formatted_version,
939+
len(crate_info.srcs.to_list()),
940+
),
941+
)
942+
else:
943+
# Run without process_wrapper
944+
if build_env_files or build_flags_files or stamp:
945+
fail("build_env_files, build_flags_files, stamp are not supported if use_process_wrapper is False")
946+
ctx.actions.run(
947+
executable = toolchain.rustc,
948+
inputs = compile_inputs,
949+
outputs = action_outputs,
950+
env = env,
951+
arguments = [args.rustc_flags],
952+
mnemonic = "Rustc",
953+
progress_message = "Compiling Rust (without process_wrapper) {} {}{} ({} files)".format(
954+
crate_info.type,
955+
ctx.label.name,
956+
formatted_version,
957+
len(crate_info.srcs.to_list()),
958+
),
959+
)
939960

940961
runfiles = ctx.runfiles(
941962
files = getattr(ctx.files, "data", []),

‎rust/private/transitions.bzl

+45
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,48 @@ with_import_macro_bootstrapping_mode = rule(
6464
),
6565
},
6666
)
67+
68+
def _without_process_wrapper_transition_impl(_settings, _attr):
69+
"""This transition allows rust_* rules to invoke rustc without process_wrapper."""
70+
return {
71+
"//rust/settings:use_process_wrapper": False,
72+
}
73+
74+
without_process_wrapper_transition = transition(
75+
implementation = _without_process_wrapper_transition_impl,
76+
inputs = [],
77+
outputs = ["//rust/settings:use_process_wrapper"],
78+
)
79+
80+
def _without_process_wrapper_impl(ctx):
81+
executable = ctx.executable.target
82+
link_name = ctx.label.name
83+
84+
# Append .exe if on windows
85+
if executable.extension:
86+
link_name = link_name + "." + executable.extension
87+
link = ctx.actions.declare_file(link_name)
88+
ctx.actions.symlink(
89+
output = link,
90+
target_file = executable,
91+
)
92+
return [
93+
DefaultInfo(
94+
executable = link,
95+
),
96+
]
97+
98+
without_process_wrapper = rule(
99+
implementation = _without_process_wrapper_impl,
100+
attrs = {
101+
"target": attr.label(
102+
cfg = without_process_wrapper_transition,
103+
allow_single_file = True,
104+
mandatory = True,
105+
executable = True,
106+
),
107+
"_allowlist_function_transition": attr.label(
108+
default = Label("@bazel_tools//tools/allowlists/function_transition_allowlist"),
109+
),
110+
},
111+
)

‎rust/settings/BUILD.bazel

+7
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ bool_flag(
2929
build_setting_default = False,
3030
)
3131

32+
# A flag that enables/disables the use of process_wrapper when invoking rustc.
33+
# This is useful for building process_wrapper itself without causing dependency cycles.
34+
bool_flag(
35+
name = "use_process_wrapper",
36+
build_setting_default = True,
37+
)
38+
3239
bzl_library(
3340
name = "bzl_lib",
3441
srcs = glob(["**/*.bzl"]),

‎util/process_wrapper/BUILD.bazel

+39-20
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,45 @@
11
load("@rules_cc//cc:defs.bzl", "cc_binary")
2+
load("//rust:defs.bzl", "rust_binary", "rust_test")
23

3-
cc_binary(
4+
# buildifier: disable=bzl-visibility
5+
load("//rust/private:transitions.bzl", "without_process_wrapper")
6+
7+
alias(
48
name = "process_wrapper",
5-
srcs = [
6-
"process_wrapper.cc",
7-
"system.h",
8-
"utils.h",
9-
"utils.cc",
10-
] + select({
11-
"@bazel_tools//src/conditions:host_windows": [
12-
"system_windows.cc",
13-
],
14-
"//conditions:default": [
15-
"system_posix.cc",
16-
],
17-
}),
18-
defines = [] + select({
19-
"@bazel_tools//src/conditions:host_windows": [
20-
"UNICODE",
21-
"_UNICODE",
22-
],
23-
"//conditions:default": [],
9+
actual = select({
10+
# This will never get used, it's only here to break the circular dependency to allow building process_wrapper
11+
":use_fake_process_wrapper": ":process_wrapper_fake",
12+
"//conditions:default": ":process_wrapper_impl",
2413
}),
2514
visibility = ["//visibility:public"],
2615
)
16+
17+
cc_binary(
18+
name = "process_wrapper_fake",
19+
srcs = ["fake.cc"],
20+
)
21+
22+
config_setting(
23+
name = "use_fake_process_wrapper",
24+
flag_values = {
25+
"//rust/settings:use_process_wrapper": "False",
26+
},
27+
)
28+
29+
# Changing the name of this rule requires a corresponding
30+
# change in //rust/private/rustc.bzl:925
31+
without_process_wrapper(
32+
name = "process_wrapper_impl",
33+
target = ":process_wrapper_bin",
34+
visibility = ["//visibility:public"],
35+
)
36+
37+
rust_binary(
38+
name = "process_wrapper_bin",
39+
srcs = glob(["*.rs"]),
40+
)
41+
42+
rust_test(
43+
name = "process_wrapper_test",
44+
crate = ":process_wrapper_bin",
45+
)

‎util/process_wrapper/fake.cc

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
int main() {
2+
// Noop on purpose.
3+
return 0;
4+
}

‎util/process_wrapper/flags.rs

+276
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
// Copyright 2020 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use std::collections::{BTreeMap, HashSet};
16+
use std::error::Error;
17+
use std::fmt;
18+
use std::fmt::Write;
19+
use std::iter::Peekable;
20+
use std::mem::take;
21+
22+
#[derive(Debug, Clone)]
23+
pub(crate) enum FlagParseError {
24+
UnknownFlag(String),
25+
ValueMissing(String),
26+
ProvidedMultipleTimes(String),
27+
ProgramNameMissing,
28+
}
29+
30+
impl fmt::Display for FlagParseError {
31+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32+
match self {
33+
Self::UnknownFlag(ref flag) => write!(f, "unknown flag \"{}\"", flag),
34+
Self::ValueMissing(ref flag) => write!(f, "flag \"{}\" missing parameter(s)", flag),
35+
Self::ProvidedMultipleTimes(ref flag) => {
36+
write!(f, "flag \"{}\" can only appear once", flag)
37+
}
38+
Self::ProgramNameMissing => {
39+
write!(f, "program name (argv[0]) missing")
40+
}
41+
}
42+
}
43+
}
44+
impl Error for FlagParseError {}
45+
46+
struct FlagDef<'a, T> {
47+
name: String,
48+
help: String,
49+
output_storage: &'a mut Option<T>,
50+
}
51+
52+
impl<'a, T> fmt::Display for FlagDef<'a, T> {
53+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54+
write!(f, "{}\t{}", self.name, self.help)
55+
}
56+
}
57+
58+
impl<'a, T> fmt::Debug for FlagDef<'a, T> {
59+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60+
f.debug_struct("FlagDef")
61+
.field("name", &self.name)
62+
.field("help", &self.help)
63+
.finish()
64+
}
65+
}
66+
67+
#[derive(Debug)]
68+
pub(crate) struct Flags<'a> {
69+
single: BTreeMap<String, FlagDef<'a, String>>,
70+
repeated: BTreeMap<String, FlagDef<'a, Vec<String>>>,
71+
}
72+
73+
#[derive(Debug)]
74+
pub(crate) enum ParseOutcome {
75+
Help(String),
76+
Parsed(Vec<String>),
77+
}
78+
79+
impl<'a> Flags<'a> {
80+
pub(crate) fn new() -> Flags<'a> {
81+
Flags {
82+
single: BTreeMap::new(),
83+
repeated: BTreeMap::new(),
84+
}
85+
}
86+
87+
pub(crate) fn define_flag(
88+
&mut self,
89+
name: impl Into<String>,
90+
help: impl Into<String>,
91+
output_storage: &'a mut Option<String>,
92+
) {
93+
let name = name.into();
94+
if self.repeated.contains_key(&name) {
95+
panic!("argument \"{}\" already defined as repeated flag", name)
96+
}
97+
self.single.insert(
98+
name.clone(),
99+
FlagDef::<'a, String> {
100+
name,
101+
help: help.into(),
102+
output_storage,
103+
},
104+
);
105+
}
106+
107+
pub(crate) fn define_repeated_flag(
108+
&mut self,
109+
name: impl Into<String>,
110+
help: impl Into<String>,
111+
output_storage: &'a mut Option<Vec<String>>,
112+
) {
113+
let name = name.into();
114+
if self.single.contains_key(&name) {
115+
panic!("argument \"{}\" already defined as flag", name)
116+
}
117+
self.repeated.insert(
118+
name.clone(),
119+
FlagDef::<'a, Vec<String>> {
120+
name,
121+
help: help.into(),
122+
output_storage,
123+
},
124+
);
125+
}
126+
127+
fn help(&self, program_name: String) -> String {
128+
let single = self.single.values().map(|fd| fd.to_string());
129+
let repeated = self.repeated.values().map(|fd| fd.to_string());
130+
let mut all: Vec<String> = single.chain(repeated).collect();
131+
all.sort();
132+
133+
let mut help_text = String::new();
134+
writeln!(
135+
&mut help_text,
136+
"Help for {}: [options] -- [extra arguments]",
137+
program_name
138+
)
139+
.unwrap();
140+
for line in all {
141+
writeln!(&mut help_text, "\t{}", line).unwrap();
142+
}
143+
help_text
144+
}
145+
146+
pub(crate) fn parse(mut self, argv: Vec<String>) -> Result<ParseOutcome, FlagParseError> {
147+
let mut argv_iter = argv.into_iter().peekable();
148+
let program_name = argv_iter.next().ok_or(FlagParseError::ProgramNameMissing)?;
149+
150+
// To check if a non-repeated flag has been set already.
151+
let mut seen_single_flags = HashSet::<String>::new();
152+
153+
while let Some(flag) = argv_iter.next() {
154+
if flag == "--help" {
155+
return Ok(ParseOutcome::Help(self.help(program_name)));
156+
}
157+
if !flag.starts_with("--") {
158+
return Err(FlagParseError::UnknownFlag(flag));
159+
}
160+
let mut args = consume_args(&flag, &mut argv_iter);
161+
if flag == "--" {
162+
return Ok(ParseOutcome::Parsed(args));
163+
}
164+
if args.is_empty() {
165+
return Err(FlagParseError::ValueMissing(flag.clone()));
166+
}
167+
if let Some(flag_def) = self.single.get_mut(&flag) {
168+
if args.len() > 1 || seen_single_flags.contains(&flag) {
169+
return Err(FlagParseError::ProvidedMultipleTimes(flag.clone()));
170+
}
171+
let arg = args.first_mut().unwrap();
172+
seen_single_flags.insert(flag);
173+
*flag_def.output_storage = Some(take(arg));
174+
continue;
175+
}
176+
if let Some(flag_def) = self.repeated.get_mut(&flag) {
177+
flag_def
178+
.output_storage
179+
.get_or_insert_with(Vec::new)
180+
.append(&mut args);
181+
continue;
182+
}
183+
return Err(FlagParseError::UnknownFlag(flag));
184+
}
185+
Ok(ParseOutcome::Parsed(vec![]))
186+
}
187+
}
188+
189+
fn consume_args<I: Iterator<Item = String>>(
190+
flag: &str,
191+
argv_iter: &mut Peekable<I>,
192+
) -> Vec<String> {
193+
if flag == "--" {
194+
// If we have found --, the rest of the iterator is just returned as-is.
195+
argv_iter.collect()
196+
} else {
197+
let mut args = vec![];
198+
while let Some(arg) = argv_iter.next_if(|s| !s.starts_with("--")) {
199+
args.push(arg);
200+
}
201+
args
202+
}
203+
}
204+
205+
#[cfg(test)]
206+
mod test {
207+
use super::*;
208+
209+
fn args(args: &[&str]) -> Vec<String> {
210+
["foo"].iter().chain(args).map(|&s| s.to_owned()).collect()
211+
}
212+
213+
#[test]
214+
fn test_flag_help() {
215+
let mut bar = None;
216+
let mut parser = Flags::new();
217+
parser.define_flag("--bar", "bar help", &mut bar);
218+
let result = parser.parse(args(&["--help"])).unwrap();
219+
if let ParseOutcome::Help(h) = result {
220+
assert!(h.contains("Help for foo"));
221+
assert!(h.contains("--bar\tbar help"));
222+
} else {
223+
panic!("expected that --help would invoke help, instead parsed arguments")
224+
}
225+
}
226+
227+
#[test]
228+
fn test_flag_single_repeated() {
229+
let mut bar = None;
230+
let mut parser = Flags::new();
231+
parser.define_flag("--bar", "bar help", &mut bar);
232+
let result = parser.parse(args(&["--bar", "aa", "bb"]));
233+
if let Err(FlagParseError::ProvidedMultipleTimes(f)) = result {
234+
assert_eq!(f, "--bar");
235+
} else {
236+
panic!("expected error, got {:?}", result)
237+
}
238+
let mut parser = Flags::new();
239+
parser.define_flag("--bar", "bar help", &mut bar);
240+
let result = parser.parse(args(&["--bar", "aa", "--bar", "bb"]));
241+
if let Err(FlagParseError::ProvidedMultipleTimes(f)) = result {
242+
assert_eq!(f, "--bar");
243+
} else {
244+
panic!("expected error, got {:?}", result)
245+
}
246+
}
247+
248+
#[test]
249+
fn test_repeated_flags() {
250+
// Test case 1) --bar something something_else should work as a repeated flag.
251+
let mut bar = None;
252+
let mut parser = Flags::new();
253+
parser.define_repeated_flag("--bar", "bar help", &mut bar);
254+
let result = parser.parse(args(&["--bar", "aa", "bb"])).unwrap();
255+
assert!(matches!(result, ParseOutcome::Parsed(_)));
256+
assert_eq!(bar, Some(vec!["aa".to_owned(), "bb".to_owned()]));
257+
// Test case 2) --bar something --bar something_else should also work as a repeated flag.
258+
bar = None;
259+
let mut parser = Flags::new();
260+
parser.define_repeated_flag("--bar", "bar help", &mut bar);
261+
let result = parser.parse(args(&["--bar", "aa", "--bar", "bb"])).unwrap();
262+
assert!(matches!(result, ParseOutcome::Parsed(_)));
263+
assert_eq!(bar, Some(vec!["aa".to_owned(), "bb".to_owned()]));
264+
}
265+
266+
#[test]
267+
fn test_extra_args() {
268+
let parser = Flags::new();
269+
let result = parser.parse(args(&["--", "bb"])).unwrap();
270+
if let ParseOutcome::Parsed(got) = result {
271+
assert_eq!(got, vec!["bb".to_owned()])
272+
} else {
273+
panic!("expected correct parsing, got {:?}", result)
274+
}
275+
}
276+
}

‎util/process_wrapper/main.rs

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright 2020 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
mod flags;
16+
mod options;
17+
mod util;
18+
19+
use std::fs::{copy, OpenOptions};
20+
use std::process::{exit, Command, Stdio};
21+
22+
use crate::options::options;
23+
24+
fn main() {
25+
let opts = match options() {
26+
Err(err) => panic!("process wrapper error: {}", err),
27+
Ok(v) => v,
28+
};
29+
let stdout = if let Some(stdout_file) = opts.stdout_file {
30+
OpenOptions::new()
31+
.create(true)
32+
.truncate(true)
33+
.write(true)
34+
.open(stdout_file)
35+
.expect("process wrapper error: unable to open stdout file")
36+
.into()
37+
} else {
38+
Stdio::inherit()
39+
};
40+
let stderr = if let Some(stderr_file) = opts.stderr_file {
41+
OpenOptions::new()
42+
.create(true)
43+
.truncate(true)
44+
.write(true)
45+
.open(stderr_file)
46+
.expect("process wrapper error: unable to open stderr file")
47+
.into()
48+
} else {
49+
Stdio::inherit()
50+
};
51+
let status = Command::new(opts.executable)
52+
.args(opts.child_arguments)
53+
.env_clear()
54+
.envs(opts.child_environment)
55+
.stdout(stdout)
56+
.stderr(stderr)
57+
.status()
58+
.expect("process wrapper error: failed to spawn child process");
59+
60+
if status.success() {
61+
if let Some(tf) = opts.touch_file {
62+
OpenOptions::new()
63+
.create(true)
64+
.write(true)
65+
.open(tf)
66+
.expect("process wrapper error: failed to create touch file");
67+
}
68+
if let Some((copy_source, copy_dest)) = opts.copy_output {
69+
copy(&copy_source, &copy_dest).unwrap_or_else(|_| {
70+
panic!(
71+
"process wrapper error: failed to copy {} into {}",
72+
copy_source, copy_dest
73+
)
74+
});
75+
}
76+
}
77+
78+
exit(status.code().unwrap())
79+
}

‎util/process_wrapper/options.rs

+226
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
use std::collections::HashMap;
2+
use std::env;
3+
use std::fmt;
4+
use std::process::exit;
5+
6+
use crate::flags::{FlagParseError, Flags, ParseOutcome};
7+
use crate::util::*;
8+
9+
#[derive(Debug)]
10+
pub(crate) enum OptionError {
11+
FlagError(FlagParseError),
12+
Generic(String),
13+
}
14+
15+
impl fmt::Display for OptionError {
16+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
17+
match self {
18+
Self::FlagError(e) => write!(f, "error parsing flags: {}", e),
19+
Self::Generic(s) => write!(f, "{}", s),
20+
}
21+
}
22+
}
23+
24+
#[derive(Debug)]
25+
pub(crate) struct Options {
26+
// Contains the path to the child executable
27+
pub(crate) executable: String,
28+
// Contains arguments for the child process fetched from files.
29+
pub(crate) child_arguments: Vec<String>,
30+
// Contains environment variables for the child process fetched from files.
31+
pub(crate) child_environment: HashMap<String, String>,
32+
// If set, create the specified file after the child process successfully
33+
// terminated its execution.
34+
pub(crate) touch_file: Option<String>,
35+
// If set to (source, dest) copies the source file to dest.
36+
pub(crate) copy_output: Option<(String, String)>,
37+
// If set, redirects the child process stdout to this file.
38+
pub(crate) stdout_file: Option<String>,
39+
// If set, redirects the child process stderr to this file.
40+
pub(crate) stderr_file: Option<String>,
41+
}
42+
43+
pub(crate) fn options() -> Result<Options, OptionError> {
44+
// Process argument list until -- is encountered.
45+
// Everything after is sent to the child process.
46+
let mut subst_mapping_raw = None;
47+
let mut volatile_status_file_raw = None;
48+
let mut env_file_raw = None;
49+
let mut arg_file_raw = None;
50+
let mut touch_file = None;
51+
let mut copy_output_raw = None;
52+
let mut stdout_file = None;
53+
let mut stderr_file = None;
54+
let mut flags = Flags::new();
55+
flags.define_repeated_flag("--subst", "", &mut subst_mapping_raw);
56+
flags.define_flag("--volatile-status-file", "", &mut volatile_status_file_raw);
57+
flags.define_repeated_flag(
58+
"--env-file",
59+
"File(s) containing environment variables to pass to the child process.",
60+
&mut env_file_raw,
61+
);
62+
flags.define_repeated_flag(
63+
"--arg-file",
64+
"File(s) containing command line arguments to pass to the child process.",
65+
&mut arg_file_raw,
66+
);
67+
flags.define_flag(
68+
"--touch-file",
69+
"Create this file after the child process runs successfully.",
70+
&mut touch_file,
71+
);
72+
flags.define_repeated_flag("--copy-output", "", &mut copy_output_raw);
73+
flags.define_flag(
74+
"--stdout-file",
75+
"Redirect subprocess stdout in this file.",
76+
&mut stdout_file,
77+
);
78+
flags.define_flag(
79+
"--stderr-file",
80+
"Redirect subprocess stderr in this file.",
81+
&mut stderr_file,
82+
);
83+
84+
let mut child_args = match flags
85+
.parse(env::args().collect())
86+
.map_err(OptionError::FlagError)?
87+
{
88+
ParseOutcome::Help(help) => {
89+
eprintln!("{}", help);
90+
exit(0);
91+
}
92+
ParseOutcome::Parsed(p) => p,
93+
};
94+
let current_dir = std::env::current_dir()
95+
.map_err(|e| OptionError::Generic(format!("failed to get current directory: {}", e)))?
96+
.to_str()
97+
.ok_or_else(|| OptionError::Generic("current directory not utf-8".to_owned()))?
98+
.to_owned();
99+
let subst_mappings = subst_mapping_raw
100+
.unwrap_or_default()
101+
.into_iter()
102+
.map(|arg| {
103+
let (key, val) = arg.split_once('=').ok_or_else(|| {
104+
OptionError::Generic(format!("empty key for substitution '{}'", arg))
105+
})?;
106+
let v = if val == "${pwd}" {
107+
current_dir.as_str()
108+
} else {
109+
val
110+
}
111+
.to_owned();
112+
Ok((key.to_owned(), v))
113+
})
114+
.collect::<Result<Vec<(String, String)>, OptionError>>()?;
115+
let stamp_mappings =
116+
volatile_status_file_raw.map_or_else(Vec::new, |s| read_stamp_status_to_array(s).unwrap());
117+
118+
let environment_file_block = env_from_files(env_file_raw.unwrap_or_default())?;
119+
let mut file_arguments = args_from_file(arg_file_raw.unwrap_or_default())?;
120+
// Process --copy-output
121+
let copy_output = copy_output_raw
122+
.map(|co| {
123+
if co.len() != 2 {
124+
return Err(OptionError::Generic(format!(
125+
"\"--copy-output\" needs exactly 2 parameters, {} provided",
126+
co.len()
127+
)));
128+
}
129+
let copy_source = &co[0];
130+
let copy_dest = &co[1];
131+
if copy_source == copy_dest {
132+
return Err(OptionError::Generic(format!(
133+
"\"--copy-output\" source ({}) and dest ({}) need to be different.",
134+
copy_source, copy_dest
135+
)));
136+
}
137+
Ok((copy_source.to_owned(), copy_dest.to_owned()))
138+
})
139+
.transpose()?;
140+
141+
// Prepare the environment variables, unifying those read from files with the ones
142+
// of the current process.
143+
let vars = environment_block(environment_file_block, &stamp_mappings, &subst_mappings);
144+
// Append all the arguments fetched from files to those provided via command line.
145+
child_args.append(&mut file_arguments);
146+
let child_args = prepare_args(child_args, &subst_mappings);
147+
// Split the executable path from the rest of the arguments.
148+
let (exec_path, args) = child_args.split_first().ok_or_else(|| {
149+
OptionError::Generic(
150+
"at least one argument after -- is required (the child process path)".to_owned(),
151+
)
152+
})?;
153+
154+
Ok(Options {
155+
executable: exec_path.to_owned(),
156+
child_arguments: args.to_vec(),
157+
child_environment: vars,
158+
touch_file,
159+
copy_output,
160+
stdout_file,
161+
stderr_file,
162+
})
163+
}
164+
165+
fn args_from_file(paths: Vec<String>) -> Result<Vec<String>, OptionError> {
166+
let mut args = vec![];
167+
for path in paths.into_iter() {
168+
let mut lines = read_file_to_array(path).map_err(OptionError::Generic)?;
169+
args.append(&mut lines);
170+
}
171+
Ok(args)
172+
}
173+
174+
fn env_from_files(paths: Vec<String>) -> Result<HashMap<String, String>, OptionError> {
175+
let mut env_vars = HashMap::new();
176+
for path in paths.into_iter() {
177+
let lines = read_file_to_array(path).map_err(OptionError::Generic)?;
178+
for line in lines.into_iter() {
179+
let (k, v) = line
180+
.split_once('=')
181+
.ok_or_else(|| OptionError::Generic("environment file invalid".to_owned()))?;
182+
env_vars.insert(k.to_owned(), v.to_owned());
183+
}
184+
}
185+
Ok(env_vars)
186+
}
187+
188+
fn prepare_args(mut args: Vec<String>, subst_mappings: &[(String, String)]) -> Vec<String> {
189+
for (f, replace_with) in subst_mappings {
190+
for arg in args.iter_mut() {
191+
let from = format!("${{{}}}", f);
192+
let new = arg.replace(from.as_str(), replace_with);
193+
*arg = new;
194+
}
195+
}
196+
args
197+
}
198+
199+
fn environment_block(
200+
environment_file_block: HashMap<String, String>,
201+
stamp_mappings: &[(String, String)],
202+
subst_mappings: &[(String, String)],
203+
) -> HashMap<String, String> {
204+
// Taking all environment variables from the current process
205+
// and sending them down to the child process
206+
let mut environment_variables: HashMap<String, String> = std::env::vars().collect();
207+
// Have the last values added take precedence over the first.
208+
// This is simpler than needing to track duplicates and explicitly override
209+
// them.
210+
environment_variables.extend(environment_file_block.into_iter());
211+
for (f, replace_with) in stamp_mappings {
212+
for value in environment_variables.values_mut() {
213+
let from = format!("{{{}}}", f);
214+
let new = value.replace(from.as_str(), replace_with);
215+
*value = new;
216+
}
217+
}
218+
for (f, replace_with) in subst_mappings {
219+
for value in environment_variables.values_mut() {
220+
let from = format!("${{{}}}", f);
221+
let new = value.replace(from.as_str(), replace_with);
222+
*value = new;
223+
}
224+
}
225+
environment_variables
226+
}

‎util/process_wrapper/process_wrapper.cc

-225
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
int main() {
2+
// Noop on purpose.
3+
return 0;
4+
}

‎util/process_wrapper/system.h

-62
This file was deleted.

‎util/process_wrapper/system_posix.cc

-197
This file was deleted.

‎util/process_wrapper/system_windows.cc

-297
This file was deleted.

‎util/process_wrapper/util.rs

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright 2020 The Bazel Authors. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
use std::fs::File;
16+
use std::io::{BufRead, BufReader, Read};
17+
18+
pub(crate) fn read_file_to_array(path: String) -> Result<Vec<String>, String> {
19+
let file = File::open(path).map_err(|e| e.to_string())?;
20+
read_to_array(file)
21+
}
22+
23+
pub(crate) fn read_stamp_status_to_array(path: String) -> Result<Vec<(String, String)>, String> {
24+
let file = File::open(path).map_err(|e| e.to_string())?;
25+
stamp_status_to_array(file)
26+
}
27+
28+
fn read_to_array(reader: impl Read) -> Result<Vec<String>, String> {
29+
let reader = BufReader::new(reader);
30+
let mut ret = vec![];
31+
let mut escaped_line = String::new();
32+
for l in reader.lines() {
33+
let line = l.map_err(|e| e.to_string())?;
34+
if line.is_empty() {
35+
continue;
36+
}
37+
// a \ at the end of a line allows us to escape the new line break,
38+
// \\ yields a single \, so \\\ translates to a single \ and a new line
39+
// escape
40+
let end_backslash_count = line.chars().rev().take_while(|&c| c == '\\').count();
41+
// a 0 or even number of backslashes do not lead to a new line escape
42+
let escape = end_backslash_count % 2 == 1;
43+
// remove backslashes and add back two for every one
44+
let l = line.trim_end_matches('\\');
45+
escaped_line.push_str(l);
46+
for _ in 0..end_backslash_count / 2 {
47+
escaped_line.push('\\');
48+
}
49+
if escape {
50+
// we add a newline as we expect a line after this
51+
escaped_line.push('\n');
52+
} else {
53+
ret.push(escaped_line);
54+
escaped_line = String::new();
55+
}
56+
}
57+
Ok(ret)
58+
}
59+
60+
fn stamp_status_to_array(reader: impl Read) -> Result<Vec<(String, String)>, String> {
61+
let escaped_lines = read_to_array(reader)?;
62+
escaped_lines
63+
.into_iter()
64+
.map(|l| {
65+
let (s1, s2) = l
66+
.split_once(' ')
67+
.ok_or_else(|| format!("wrong workspace status file format for \"{}\"", l))?;
68+
Ok((s1.to_owned(), s2.to_owned()))
69+
})
70+
.collect()
71+
}
72+
73+
#[cfg(test)]
74+
mod test {
75+
use super::*;
76+
77+
#[test]
78+
fn test_read_to_array() {
79+
let input = r#"some escaped \\\
80+
string
81+
with other lines"#
82+
.to_owned();
83+
let expected = vec![
84+
r#"some escaped \
85+
string"#,
86+
"with other lines",
87+
];
88+
let got = read_to_array(input.as_bytes()).unwrap();
89+
assert_eq!(expected, got);
90+
}
91+
92+
#[test]
93+
fn test_stamp_status_to_array() {
94+
let lines = "aaa bbb\\\nvvv\nccc ddd\neee fff";
95+
let got = stamp_status_to_array(lines.as_bytes()).unwrap();
96+
let expected = vec![
97+
("aaa".to_owned(), "bbb\nvvv".to_owned()),
98+
("ccc".to_owned(), "ddd".to_owned()),
99+
("eee".to_owned(), "fff".to_owned()),
100+
];
101+
assert_eq!(expected, got);
102+
}
103+
}

‎util/process_wrapper/utils.cc

-130
This file was deleted.

‎util/process_wrapper/utils.h

-42
This file was deleted.

4 commit comments

Comments
 (4)

korDen commented on Mar 10, 2022

@korDen
Contributor

I think this change broke Windows build. With previous revision (e5fefdc), my local project builds successfully but when I update to this particular revision, I'm getting linking errors:

LINK : fatal error LNK1104: cannot open file 'C:\Users\Denis_bazel_Denis\iakn3uvv\execroot\vrst\bazel-out\x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7\bin\external\rust_windows_x86_64\toolchain_for_x86_64-pc-windows-msvc_impl\lib\rustlib\x86_64-pc-windows-msvc\lib\librustc_demangle-4e228cbffbe5e3cd.rlib'

I made sure to run bazel clean --expunge before and after just to make sure. The file DOES exist! Is it possible that the slashes are wrong? Windows is weird that way.

korDen commented on Mar 10, 2022

@korDen
Contributor

Apparently, process_wrapper itself fails to link, here is the failing step:

Compiling Rust (without process_wrapper) bin process_wrapper_bin (4 files) failed:

 bazel-out\x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7\bin\external\rust_windows_x86_64\toolchain_for_x86_64-pc-windows-msvc_impl\bin\rustc.exe external/rules_rust/util/process_wrapper/main.rs --crate-name=process_wrapper_bin --crate-type=bin --error-format=human --codegen=metadata= --out-dir=bazel-out/x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7/bin/external/rules_rust/util/process_wrapper --codegen=extra-filename= --codegen=opt-level=3 --codegen=debuginfo=0 --remap-path-prefix=${pwd}=. --emit=dep-info,link --color=always --target=x86_64-pc-windows-msvc -L bazel-out/x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7/bin/external/rust_windows_x86_64/toolchain_for_x86_64-pc-windows-msvc_impl/lib/rustlib/x86_64-pc-windows-msvc/lib --edition=2018 --codegen=linker=C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.27.29110/bin/HostX64/x64/link.exe --codegen link-args=/nologo /SUBSYSTEM:CONSOLE /MACHINE:X64 /DEFAULTLIB:msvcrt.lib /OPT:ICF /OPT:REF
# Configuration: 83ce4ebe25ba4221dba6fe8d6e1dc7a830b6f8ca93db5d0261ebdbf5f8ef99fb
# Execution platform: @local_config_platform//:host
error: linking with `C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.27.29110/bin/HostX64/x64/link.exe` failed: exit code: 1104
  |
  = note: "C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Tools/MSVC/14.27.29110/bin/HostX64/x64/link.exe" "/NOLOGO" "bazel-out/x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7/bin/external/rules_rust/util/process_wrapper\\process_wrapper_bin.process_wrapper_bin.07985259-cgu.0.rcgu.o" "bazel-out/x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7/bin/external/rules_rust/util/process_wrapper\\process_wrapper_bin.process_wrapper_bin.07985259-cgu.1.rcgu.o" "bazel-out/x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7/bin/external/rules_rust/util/process_wrapper\\process_wrapper_bin.process_wrapper_bin.07985259-cgu.10.rcgu.o" "bazel-out/x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7/bin/external/rules_rust/util/process_wrapper\\process_wrapper_bin.process_wrapper_bin.07985259-cgu.11.rcgu.o" "bazel-out/x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7/bin/external/rules_rust/util/process_wrapper\\process_wrapper_bin.process_wrapper_bin.07985259-cgu.12.rcgu.o" "bazel-out/x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7/bin/external/rules_rust/util/process_wrapper\\process_wrapper_bin.process_wrapper_bin.07985259-cgu.13.rcgu.o" "bazel-out/x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7/bin/external/rules_rust/util/process_wrapper\\process_wrapper_bin.process_wrapper_bin.07985259-cgu.14.rcgu.o" "bazel-out/x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7/bin/external/rules_rust/util/process_wrapper\\process_wrapper_bin.process_wrapper_bin.07985259-cgu.15.rcgu.o" "bazel-out/x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7/bin/external/rules_rust/util/process_wrapper\\process_wrapper_bin.process_wrapper_bin.07985259-cgu.2.rcgu.o" "bazel-out/x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7/bin/external/rules_rust/util/process_wrapper\\process_wrapper_bin.process_wrapper_bin.07985259-cgu.3.rcgu.o" "bazel-out/x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7/bin/external/rules_rust/util/process_wrapper\\process_wrapper_bin.process_wrapper_bin.07985259-cgu.4.rcgu.o" "bazel-out/x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7/bin/external/rules_rust/util/process_wrapper\\process_wrapper_bin.process_wrapper_bin.07985259-cgu.5.rcgu.o" "bazel-out/x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7/bin/external/rules_rust/util/process_wrapper\\process_wrapper_bin.process_wrapper_bin.07985259-cgu.6.rcgu.o" "bazel-out/x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7/bin/external/rules_rust/util/process_wrapper\\process_wrapper_bin.process_wrapper_bin.07985259-cgu.7.rcgu.o" "bazel-out/x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7/bin/external/rules_rust/util/process_wrapper\\process_wrapper_bin.process_wrapper_bin.07985259-cgu.8.rcgu.o" "bazel-out/x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7/bin/external/rules_rust/util/process_wrapper\\process_wrapper_bin.process_wrapper_bin.07985259-cgu.9.rcgu.o" "bazel-out/x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7/bin/external/rules_rust/util/process_wrapper\\process_wrapper_bin.18q0a9eercjcv5ck.rcgu.o" "/LIBPATH:bazel-out/x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7/bin/external/rust_windows_x86_64/toolchain_for_x86_64-pc-windows-msvc_impl/lib/rustlib/x86_64-pc-windows-msvc/lib" "/LIBPATH:C:\\tmp\\bazel\\output\\iakn3uvv\\execroot\\vrst\\bazel-out\\x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7\\bin\\external\\rust_windows_x86_64\\toolchain_for_x86_64-pc-windows-msvc_impl\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib" "C:\\tmp\\bazel\\output\\iakn3uvv\\execroot\\vrst\\bazel-out\\x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7\\bin\\external\\rust_windows_x86_64\\toolchain_for_x86_64-pc-windows-msvc_impl\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib\\libstd-ca7b0c28ec762872.rlib" "C:\\tmp\\bazel\\output\\iakn3uvv\\execroot\\vrst\\bazel-out\\x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7\\bin\\external\\rust_windows_x86_64\\toolchain_for_x86_64-pc-windows-msvc_impl\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib\\libpanic_unwind-1b050a71ed5c4477.rlib" "C:\\tmp\\bazel\\output\\iakn3uvv\\execroot\\vrst\\bazel-out\\x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7\\bin\\external\\rust_windows_x86_64\\toolchain_for_x86_64-pc-windows-msvc_impl\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib\\libstd_detect-ba9f9c006950f110.rlib" "C:\\tmp\\bazel\\output\\iakn3uvv\\execroot\\vrst\\bazel-out\\x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7\\bin\\external\\rust_windows_x86_64\\toolchain_for_x86_64-pc-windows-msvc_impl\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib\\librustc_demangle-4e228cbffbe5e3cd.rlib" "C:\\tmp\\bazel\\output\\iakn3uvv\\execroot\\vrst\\bazel-out\\x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7\\bin\\external\\rust_windows_x86_64\\toolchain_for_x86_64-pc-windows-msvc_impl\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib\\libhashbrown-d3d7c65121bb0d35.rlib" "C:\\tmp\\bazel\\output\\iakn3uvv\\execroot\\vrst\\bazel-out\\x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7\\bin\\external\\rust_windows_x86_64\\toolchain_for_x86_64-pc-windows-msvc_impl\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib\\librustc_std_workspace_alloc-2cedaf2947cb8622.rlib" "C:\\tmp\\bazel\\output\\iakn3uvv\\execroot\\vrst\\bazel-out\\x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7\\bin\\external\\rust_windows_x86_64\\toolchain_for_x86_64-pc-windows-msvc_impl\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib\\libunwind-7d6cff0c7b8f0c2b.rlib" "C:\\tmp\\bazel\\output\\iakn3uvv\\execroot\\vrst\\bazel-out\\x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7\\bin\\external\\rust_windows_x86_64\\toolchain_for_x86_64-pc-windows-msvc_impl\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib\\libcfg_if-63bdfcda4a65748c.rlib" "C:\\tmp\\bazel\\output\\iakn3uvv\\execroot\\vrst\\bazel-out\\x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7\\bin\\external\\rust_windows_x86_64\\toolchain_for_x86_64-pc-windows-msvc_impl\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib\\liblibc-c331c9d260094b22.rlib" "C:\\tmp\\bazel\\output\\iakn3uvv\\execroot\\vrst\\bazel-out\\x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7\\bin\\external\\rust_windows_x86_64\\toolchain_for_x86_64-pc-windows-msvc_impl\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib\\liballoc-34d0a2dd4a5dbc91.rlib" "C:\\tmp\\bazel\\output\\iakn3uvv\\execroot\\vrst\\bazel-out\\x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7\\bin\\external\\rust_windows_x86_64\\toolchain_for_x86_64-pc-windows-msvc_impl\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib\\librustc_std_workspace_core-e7b8421abede5598.rlib" "C:\\tmp\\bazel\\output\\iakn3uvv\\execroot\\vrst\\bazel-out\\x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7\\bin\\external\\rust_windows_x86_64\\toolchain_for_x86_64-pc-windows-msvc_impl\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib\\libcore-08b052fa5e861ac2.rlib" "C:\\tmp\\bazel\\output\\iakn3uvv\\execroot\\vrst\\bazel-out\\x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7\\bin\\external\\rust_windows_x86_64\\toolchain_for_x86_64-pc-windows-msvc_impl\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib\\libcompiler_builtins-18761c3bc8f2e6ea.rlib" "kernel32.lib" "ws2_32.lib" "bcrypt.lib" "advapi32.lib" "userenv.lib" "kernel32.lib" "msvcrt.lib" "/NXCOMPAT" "/LIBPATH:C:\\tmp\\bazel\\output\\iakn3uvv\\execroot\\vrst\\bazel-out\\x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7\\bin\\external\\rust_windows_x86_64\\toolchain_for_x86_64-pc-windows-msvc_impl\\lib\\rustlib\\x86_64-pc-windows-msvc\\lib" "/OUT:bazel-out/x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7/bin/external/rules_rust/util/process_wrapper\\process_wrapper_bin.exe" "/OPT:REF,ICF" "/DEBUG" "/nologo" "/SUBSYSTEM:CONSOLE" "/MACHINE:X64" "/DEFAULTLIB:msvcrt.lib" "/OPT:ICF" "/OPT:REF"

korDen commented on Mar 10, 2022

@korDen
Contributor

Not sure if it's related but there are a few directories in bazel-out/:

x64_windows-opt-exec-2B5CBBC6-ST-04e0165bf6a7/ -- doesn't appear to have functional rustc... Yet this is what is being used to compile new process_wrapper, and fails.
x64_windows-opt-exec-2B5CBBC6/ - does appear to contain functional rustc.
x64_windows-fastbuild/ - does appear to contain functional rustc. This is used what was used to compile rust code before the patch.

So possibly a sysroot issue.

hlopko commented on Mar 18, 2022

@hlopko
Member

(just to not leave these comments seemingly ignored - I assume all of this was fixed by #1187)

Please sign in to comment.