process_wrapper: replace C++ implementation with rust. (#1159)
diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl
index 61e1b46..596c10d 100644
--- a/rust/private/rustc.bzl
+++ b/rust/private/rustc.bzl
@@ -922,20 +922,41 @@
dsym_folder = ctx.actions.declare_directory(crate_info.output.basename + ".dSYM")
action_outputs.append(dsym_folder)
- ctx.actions.run(
- executable = ctx.executable._process_wrapper,
- inputs = compile_inputs,
- outputs = action_outputs,
- env = env,
- arguments = args.all,
- mnemonic = "Rustc",
- progress_message = "Compiling Rust {} {}{} ({} files)".format(
- crate_info.type,
- ctx.label.name,
- formatted_version,
- len(crate_info.srcs.to_list()),
- ),
- )
+ # This uses startswith as on windows the basename will be process_wrapper_fake.exe.
+ if not ctx.executable._process_wrapper.basename.startswith("process_wrapper_fake"):
+ # Run as normal
+ ctx.actions.run(
+ executable = ctx.executable._process_wrapper,
+ inputs = compile_inputs,
+ outputs = action_outputs,
+ env = env,
+ arguments = args.all,
+ mnemonic = "Rustc",
+ progress_message = "Compiling Rust {} {}{} ({} files)".format(
+ crate_info.type,
+ ctx.label.name,
+ formatted_version,
+ len(crate_info.srcs.to_list()),
+ ),
+ )
+ else:
+ # Run without process_wrapper
+ if build_env_files or build_flags_files or stamp:
+ fail("build_env_files, build_flags_files, stamp are not supported if use_process_wrapper is False")
+ ctx.actions.run(
+ executable = toolchain.rustc,
+ inputs = compile_inputs,
+ outputs = action_outputs,
+ env = env,
+ arguments = [args.rustc_flags],
+ mnemonic = "Rustc",
+ progress_message = "Compiling Rust (without process_wrapper) {} {}{} ({} files)".format(
+ crate_info.type,
+ ctx.label.name,
+ formatted_version,
+ len(crate_info.srcs.to_list()),
+ ),
+ )
runfiles = ctx.runfiles(
files = getattr(ctx.files, "data", []),
diff --git a/rust/private/transitions.bzl b/rust/private/transitions.bzl
index f502163..76fbb78 100644
--- a/rust/private/transitions.bzl
+++ b/rust/private/transitions.bzl
@@ -64,3 +64,48 @@
),
},
)
+
+def _without_process_wrapper_transition_impl(_settings, _attr):
+ """This transition allows rust_* rules to invoke rustc without process_wrapper."""
+ return {
+ "//rust/settings:use_process_wrapper": False,
+ }
+
+without_process_wrapper_transition = transition(
+ implementation = _without_process_wrapper_transition_impl,
+ inputs = [],
+ outputs = ["//rust/settings:use_process_wrapper"],
+)
+
+def _without_process_wrapper_impl(ctx):
+ executable = ctx.executable.target
+ link_name = ctx.label.name
+
+ # Append .exe if on windows
+ if executable.extension:
+ link_name = link_name + "." + executable.extension
+ link = ctx.actions.declare_file(link_name)
+ ctx.actions.symlink(
+ output = link,
+ target_file = executable,
+ )
+ return [
+ DefaultInfo(
+ executable = link,
+ ),
+ ]
+
+without_process_wrapper = rule(
+ implementation = _without_process_wrapper_impl,
+ attrs = {
+ "target": attr.label(
+ cfg = without_process_wrapper_transition,
+ allow_single_file = True,
+ mandatory = True,
+ executable = True,
+ ),
+ "_allowlist_function_transition": attr.label(
+ default = Label("@bazel_tools//tools/allowlists/function_transition_allowlist"),
+ ),
+ },
+)
diff --git a/rust/settings/BUILD.bazel b/rust/settings/BUILD.bazel
index 8be7aab..474237a 100644
--- a/rust/settings/BUILD.bazel
+++ b/rust/settings/BUILD.bazel
@@ -29,6 +29,13 @@
build_setting_default = False,
)
+# A flag that enables/disables the use of process_wrapper when invoking rustc.
+# This is useful for building process_wrapper itself without causing dependency cycles.
+bool_flag(
+ name = "use_process_wrapper",
+ build_setting_default = True,
+)
+
bzl_library(
name = "bzl_lib",
srcs = glob(["**/*.bzl"]),
diff --git a/util/process_wrapper/BUILD.bazel b/util/process_wrapper/BUILD.bazel
index 4b60036..c26d9a6 100644
--- a/util/process_wrapper/BUILD.bazel
+++ b/util/process_wrapper/BUILD.bazel
@@ -1,26 +1,45 @@
load("@rules_cc//cc:defs.bzl", "cc_binary")
+load("//rust:defs.bzl", "rust_binary", "rust_test")
-cc_binary(
+# buildifier: disable=bzl-visibility
+load("//rust/private:transitions.bzl", "without_process_wrapper")
+
+alias(
name = "process_wrapper",
- srcs = [
- "process_wrapper.cc",
- "system.h",
- "utils.h",
- "utils.cc",
- ] + select({
- "@bazel_tools//src/conditions:host_windows": [
- "system_windows.cc",
- ],
- "//conditions:default": [
- "system_posix.cc",
- ],
- }),
- defines = [] + select({
- "@bazel_tools//src/conditions:host_windows": [
- "UNICODE",
- "_UNICODE",
- ],
- "//conditions:default": [],
+ actual = select({
+ # This will never get used, it's only here to break the circular dependency to allow building process_wrapper
+ ":use_fake_process_wrapper": ":process_wrapper_fake",
+ "//conditions:default": ":process_wrapper_impl",
}),
visibility = ["//visibility:public"],
)
+
+cc_binary(
+ name = "process_wrapper_fake",
+ srcs = ["fake.cc"],
+)
+
+config_setting(
+ name = "use_fake_process_wrapper",
+ flag_values = {
+ "//rust/settings:use_process_wrapper": "False",
+ },
+)
+
+# Changing the name of this rule requires a corresponding
+# change in //rust/private/rustc.bzl:925
+without_process_wrapper(
+ name = "process_wrapper_impl",
+ target = ":process_wrapper_bin",
+ visibility = ["//visibility:public"],
+)
+
+rust_binary(
+ name = "process_wrapper_bin",
+ srcs = glob(["*.rs"]),
+)
+
+rust_test(
+ name = "process_wrapper_test",
+ crate = ":process_wrapper_bin",
+)
diff --git a/util/process_wrapper/fake.cc b/util/process_wrapper/fake.cc
new file mode 100644
index 0000000..a0b6869
--- /dev/null
+++ b/util/process_wrapper/fake.cc
@@ -0,0 +1,4 @@
+int main() {
+ // Noop on purpose.
+ return 0;
+}
\ No newline at end of file
diff --git a/util/process_wrapper/flags.rs b/util/process_wrapper/flags.rs
new file mode 100644
index 0000000..d3d6fe5
--- /dev/null
+++ b/util/process_wrapper/flags.rs
@@ -0,0 +1,276 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::collections::{BTreeMap, HashSet};
+use std::error::Error;
+use std::fmt;
+use std::fmt::Write;
+use std::iter::Peekable;
+use std::mem::take;
+
+#[derive(Debug, Clone)]
+pub(crate) enum FlagParseError {
+ UnknownFlag(String),
+ ValueMissing(String),
+ ProvidedMultipleTimes(String),
+ ProgramNameMissing,
+}
+
+impl fmt::Display for FlagParseError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::UnknownFlag(ref flag) => write!(f, "unknown flag \"{}\"", flag),
+ Self::ValueMissing(ref flag) => write!(f, "flag \"{}\" missing parameter(s)", flag),
+ Self::ProvidedMultipleTimes(ref flag) => {
+ write!(f, "flag \"{}\" can only appear once", flag)
+ }
+ Self::ProgramNameMissing => {
+ write!(f, "program name (argv[0]) missing")
+ }
+ }
+ }
+}
+impl Error for FlagParseError {}
+
+struct FlagDef<'a, T> {
+ name: String,
+ help: String,
+ output_storage: &'a mut Option<T>,
+}
+
+impl<'a, T> fmt::Display for FlagDef<'a, T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}\t{}", self.name, self.help)
+ }
+}
+
+impl<'a, T> fmt::Debug for FlagDef<'a, T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("FlagDef")
+ .field("name", &self.name)
+ .field("help", &self.help)
+ .finish()
+ }
+}
+
+#[derive(Debug)]
+pub(crate) struct Flags<'a> {
+ single: BTreeMap<String, FlagDef<'a, String>>,
+ repeated: BTreeMap<String, FlagDef<'a, Vec<String>>>,
+}
+
+#[derive(Debug)]
+pub(crate) enum ParseOutcome {
+ Help(String),
+ Parsed(Vec<String>),
+}
+
+impl<'a> Flags<'a> {
+ pub(crate) fn new() -> Flags<'a> {
+ Flags {
+ single: BTreeMap::new(),
+ repeated: BTreeMap::new(),
+ }
+ }
+
+ pub(crate) fn define_flag(
+ &mut self,
+ name: impl Into<String>,
+ help: impl Into<String>,
+ output_storage: &'a mut Option<String>,
+ ) {
+ let name = name.into();
+ if self.repeated.contains_key(&name) {
+ panic!("argument \"{}\" already defined as repeated flag", name)
+ }
+ self.single.insert(
+ name.clone(),
+ FlagDef::<'a, String> {
+ name,
+ help: help.into(),
+ output_storage,
+ },
+ );
+ }
+
+ pub(crate) fn define_repeated_flag(
+ &mut self,
+ name: impl Into<String>,
+ help: impl Into<String>,
+ output_storage: &'a mut Option<Vec<String>>,
+ ) {
+ let name = name.into();
+ if self.single.contains_key(&name) {
+ panic!("argument \"{}\" already defined as flag", name)
+ }
+ self.repeated.insert(
+ name.clone(),
+ FlagDef::<'a, Vec<String>> {
+ name,
+ help: help.into(),
+ output_storage,
+ },
+ );
+ }
+
+ fn help(&self, program_name: String) -> String {
+ let single = self.single.values().map(|fd| fd.to_string());
+ let repeated = self.repeated.values().map(|fd| fd.to_string());
+ let mut all: Vec<String> = single.chain(repeated).collect();
+ all.sort();
+
+ let mut help_text = String::new();
+ writeln!(
+ &mut help_text,
+ "Help for {}: [options] -- [extra arguments]",
+ program_name
+ )
+ .unwrap();
+ for line in all {
+ writeln!(&mut help_text, "\t{}", line).unwrap();
+ }
+ help_text
+ }
+
+ pub(crate) fn parse(mut self, argv: Vec<String>) -> Result<ParseOutcome, FlagParseError> {
+ let mut argv_iter = argv.into_iter().peekable();
+ let program_name = argv_iter.next().ok_or(FlagParseError::ProgramNameMissing)?;
+
+ // To check if a non-repeated flag has been set already.
+ let mut seen_single_flags = HashSet::<String>::new();
+
+ while let Some(flag) = argv_iter.next() {
+ if flag == "--help" {
+ return Ok(ParseOutcome::Help(self.help(program_name)));
+ }
+ if !flag.starts_with("--") {
+ return Err(FlagParseError::UnknownFlag(flag));
+ }
+ let mut args = consume_args(&flag, &mut argv_iter);
+ if flag == "--" {
+ return Ok(ParseOutcome::Parsed(args));
+ }
+ if args.is_empty() {
+ return Err(FlagParseError::ValueMissing(flag.clone()));
+ }
+ if let Some(flag_def) = self.single.get_mut(&flag) {
+ if args.len() > 1 || seen_single_flags.contains(&flag) {
+ return Err(FlagParseError::ProvidedMultipleTimes(flag.clone()));
+ }
+ let arg = args.first_mut().unwrap();
+ seen_single_flags.insert(flag);
+ *flag_def.output_storage = Some(take(arg));
+ continue;
+ }
+ if let Some(flag_def) = self.repeated.get_mut(&flag) {
+ flag_def
+ .output_storage
+ .get_or_insert_with(Vec::new)
+ .append(&mut args);
+ continue;
+ }
+ return Err(FlagParseError::UnknownFlag(flag));
+ }
+ Ok(ParseOutcome::Parsed(vec![]))
+ }
+}
+
+fn consume_args<I: Iterator<Item = String>>(
+ flag: &str,
+ argv_iter: &mut Peekable<I>,
+) -> Vec<String> {
+ if flag == "--" {
+ // If we have found --, the rest of the iterator is just returned as-is.
+ argv_iter.collect()
+ } else {
+ let mut args = vec![];
+ while let Some(arg) = argv_iter.next_if(|s| !s.starts_with("--")) {
+ args.push(arg);
+ }
+ args
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ fn args(args: &[&str]) -> Vec<String> {
+ ["foo"].iter().chain(args).map(|&s| s.to_owned()).collect()
+ }
+
+ #[test]
+ fn test_flag_help() {
+ let mut bar = None;
+ let mut parser = Flags::new();
+ parser.define_flag("--bar", "bar help", &mut bar);
+ let result = parser.parse(args(&["--help"])).unwrap();
+ if let ParseOutcome::Help(h) = result {
+ assert!(h.contains("Help for foo"));
+ assert!(h.contains("--bar\tbar help"));
+ } else {
+ panic!("expected that --help would invoke help, instead parsed arguments")
+ }
+ }
+
+ #[test]
+ fn test_flag_single_repeated() {
+ let mut bar = None;
+ let mut parser = Flags::new();
+ parser.define_flag("--bar", "bar help", &mut bar);
+ let result = parser.parse(args(&["--bar", "aa", "bb"]));
+ if let Err(FlagParseError::ProvidedMultipleTimes(f)) = result {
+ assert_eq!(f, "--bar");
+ } else {
+ panic!("expected error, got {:?}", result)
+ }
+ let mut parser = Flags::new();
+ parser.define_flag("--bar", "bar help", &mut bar);
+ let result = parser.parse(args(&["--bar", "aa", "--bar", "bb"]));
+ if let Err(FlagParseError::ProvidedMultipleTimes(f)) = result {
+ assert_eq!(f, "--bar");
+ } else {
+ panic!("expected error, got {:?}", result)
+ }
+ }
+
+ #[test]
+ fn test_repeated_flags() {
+ // Test case 1) --bar something something_else should work as a repeated flag.
+ let mut bar = None;
+ let mut parser = Flags::new();
+ parser.define_repeated_flag("--bar", "bar help", &mut bar);
+ let result = parser.parse(args(&["--bar", "aa", "bb"])).unwrap();
+ assert!(matches!(result, ParseOutcome::Parsed(_)));
+ assert_eq!(bar, Some(vec!["aa".to_owned(), "bb".to_owned()]));
+ // Test case 2) --bar something --bar something_else should also work as a repeated flag.
+ bar = None;
+ let mut parser = Flags::new();
+ parser.define_repeated_flag("--bar", "bar help", &mut bar);
+ let result = parser.parse(args(&["--bar", "aa", "--bar", "bb"])).unwrap();
+ assert!(matches!(result, ParseOutcome::Parsed(_)));
+ assert_eq!(bar, Some(vec!["aa".to_owned(), "bb".to_owned()]));
+ }
+
+ #[test]
+ fn test_extra_args() {
+ let parser = Flags::new();
+ let result = parser.parse(args(&["--", "bb"])).unwrap();
+ if let ParseOutcome::Parsed(got) = result {
+ assert_eq!(got, vec!["bb".to_owned()])
+ } else {
+ panic!("expected correct parsing, got {:?}", result)
+ }
+ }
+}
diff --git a/util/process_wrapper/main.rs b/util/process_wrapper/main.rs
new file mode 100644
index 0000000..41140a3
--- /dev/null
+++ b/util/process_wrapper/main.rs
@@ -0,0 +1,79 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+mod flags;
+mod options;
+mod util;
+
+use std::fs::{copy, OpenOptions};
+use std::process::{exit, Command, Stdio};
+
+use crate::options::options;
+
+fn main() {
+ let opts = match options() {
+ Err(err) => panic!("process wrapper error: {}", err),
+ Ok(v) => v,
+ };
+ let stdout = if let Some(stdout_file) = opts.stdout_file {
+ OpenOptions::new()
+ .create(true)
+ .truncate(true)
+ .write(true)
+ .open(stdout_file)
+ .expect("process wrapper error: unable to open stdout file")
+ .into()
+ } else {
+ Stdio::inherit()
+ };
+ let stderr = if let Some(stderr_file) = opts.stderr_file {
+ OpenOptions::new()
+ .create(true)
+ .truncate(true)
+ .write(true)
+ .open(stderr_file)
+ .expect("process wrapper error: unable to open stderr file")
+ .into()
+ } else {
+ Stdio::inherit()
+ };
+ let status = Command::new(opts.executable)
+ .args(opts.child_arguments)
+ .env_clear()
+ .envs(opts.child_environment)
+ .stdout(stdout)
+ .stderr(stderr)
+ .status()
+ .expect("process wrapper error: failed to spawn child process");
+
+ if status.success() {
+ if let Some(tf) = opts.touch_file {
+ OpenOptions::new()
+ .create(true)
+ .write(true)
+ .open(tf)
+ .expect("process wrapper error: failed to create touch file");
+ }
+ if let Some((copy_source, copy_dest)) = opts.copy_output {
+ copy(©_source, ©_dest).unwrap_or_else(|_| {
+ panic!(
+ "process wrapper error: failed to copy {} into {}",
+ copy_source, copy_dest
+ )
+ });
+ }
+ }
+
+ exit(status.code().unwrap())
+}
diff --git a/util/process_wrapper/options.rs b/util/process_wrapper/options.rs
new file mode 100644
index 0000000..24bba9f
--- /dev/null
+++ b/util/process_wrapper/options.rs
@@ -0,0 +1,226 @@
+use std::collections::HashMap;
+use std::env;
+use std::fmt;
+use std::process::exit;
+
+use crate::flags::{FlagParseError, Flags, ParseOutcome};
+use crate::util::*;
+
+#[derive(Debug)]
+pub(crate) enum OptionError {
+ FlagError(FlagParseError),
+ Generic(String),
+}
+
+impl fmt::Display for OptionError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Self::FlagError(e) => write!(f, "error parsing flags: {}", e),
+ Self::Generic(s) => write!(f, "{}", s),
+ }
+ }
+}
+
+#[derive(Debug)]
+pub(crate) struct Options {
+ // Contains the path to the child executable
+ pub(crate) executable: String,
+ // Contains arguments for the child process fetched from files.
+ pub(crate) child_arguments: Vec<String>,
+ // Contains environment variables for the child process fetched from files.
+ pub(crate) child_environment: HashMap<String, String>,
+ // If set, create the specified file after the child process successfully
+ // terminated its execution.
+ pub(crate) touch_file: Option<String>,
+ // If set to (source, dest) copies the source file to dest.
+ pub(crate) copy_output: Option<(String, String)>,
+ // If set, redirects the child process stdout to this file.
+ pub(crate) stdout_file: Option<String>,
+ // If set, redirects the child process stderr to this file.
+ pub(crate) stderr_file: Option<String>,
+}
+
+pub(crate) fn options() -> Result<Options, OptionError> {
+ // Process argument list until -- is encountered.
+ // Everything after is sent to the child process.
+ let mut subst_mapping_raw = None;
+ let mut volatile_status_file_raw = None;
+ let mut env_file_raw = None;
+ let mut arg_file_raw = None;
+ let mut touch_file = None;
+ let mut copy_output_raw = None;
+ let mut stdout_file = None;
+ let mut stderr_file = None;
+ let mut flags = Flags::new();
+ flags.define_repeated_flag("--subst", "", &mut subst_mapping_raw);
+ flags.define_flag("--volatile-status-file", "", &mut volatile_status_file_raw);
+ flags.define_repeated_flag(
+ "--env-file",
+ "File(s) containing environment variables to pass to the child process.",
+ &mut env_file_raw,
+ );
+ flags.define_repeated_flag(
+ "--arg-file",
+ "File(s) containing command line arguments to pass to the child process.",
+ &mut arg_file_raw,
+ );
+ flags.define_flag(
+ "--touch-file",
+ "Create this file after the child process runs successfully.",
+ &mut touch_file,
+ );
+ flags.define_repeated_flag("--copy-output", "", &mut copy_output_raw);
+ flags.define_flag(
+ "--stdout-file",
+ "Redirect subprocess stdout in this file.",
+ &mut stdout_file,
+ );
+ flags.define_flag(
+ "--stderr-file",
+ "Redirect subprocess stderr in this file.",
+ &mut stderr_file,
+ );
+
+ let mut child_args = match flags
+ .parse(env::args().collect())
+ .map_err(OptionError::FlagError)?
+ {
+ ParseOutcome::Help(help) => {
+ eprintln!("{}", help);
+ exit(0);
+ }
+ ParseOutcome::Parsed(p) => p,
+ };
+ let current_dir = std::env::current_dir()
+ .map_err(|e| OptionError::Generic(format!("failed to get current directory: {}", e)))?
+ .to_str()
+ .ok_or_else(|| OptionError::Generic("current directory not utf-8".to_owned()))?
+ .to_owned();
+ let subst_mappings = subst_mapping_raw
+ .unwrap_or_default()
+ .into_iter()
+ .map(|arg| {
+ let (key, val) = arg.split_once('=').ok_or_else(|| {
+ OptionError::Generic(format!("empty key for substitution '{}'", arg))
+ })?;
+ let v = if val == "${pwd}" {
+ current_dir.as_str()
+ } else {
+ val
+ }
+ .to_owned();
+ Ok((key.to_owned(), v))
+ })
+ .collect::<Result<Vec<(String, String)>, OptionError>>()?;
+ let stamp_mappings =
+ volatile_status_file_raw.map_or_else(Vec::new, |s| read_stamp_status_to_array(s).unwrap());
+
+ let environment_file_block = env_from_files(env_file_raw.unwrap_or_default())?;
+ let mut file_arguments = args_from_file(arg_file_raw.unwrap_or_default())?;
+ // Process --copy-output
+ let copy_output = copy_output_raw
+ .map(|co| {
+ if co.len() != 2 {
+ return Err(OptionError::Generic(format!(
+ "\"--copy-output\" needs exactly 2 parameters, {} provided",
+ co.len()
+ )));
+ }
+ let copy_source = &co[0];
+ let copy_dest = &co[1];
+ if copy_source == copy_dest {
+ return Err(OptionError::Generic(format!(
+ "\"--copy-output\" source ({}) and dest ({}) need to be different.",
+ copy_source, copy_dest
+ )));
+ }
+ Ok((copy_source.to_owned(), copy_dest.to_owned()))
+ })
+ .transpose()?;
+
+ // Prepare the environment variables, unifying those read from files with the ones
+ // of the current process.
+ let vars = environment_block(environment_file_block, &stamp_mappings, &subst_mappings);
+ // Append all the arguments fetched from files to those provided via command line.
+ child_args.append(&mut file_arguments);
+ let child_args = prepare_args(child_args, &subst_mappings);
+ // Split the executable path from the rest of the arguments.
+ let (exec_path, args) = child_args.split_first().ok_or_else(|| {
+ OptionError::Generic(
+ "at least one argument after -- is required (the child process path)".to_owned(),
+ )
+ })?;
+
+ Ok(Options {
+ executable: exec_path.to_owned(),
+ child_arguments: args.to_vec(),
+ child_environment: vars,
+ touch_file,
+ copy_output,
+ stdout_file,
+ stderr_file,
+ })
+}
+
+fn args_from_file(paths: Vec<String>) -> Result<Vec<String>, OptionError> {
+ let mut args = vec![];
+ for path in paths.into_iter() {
+ let mut lines = read_file_to_array(path).map_err(OptionError::Generic)?;
+ args.append(&mut lines);
+ }
+ Ok(args)
+}
+
+fn env_from_files(paths: Vec<String>) -> Result<HashMap<String, String>, OptionError> {
+ let mut env_vars = HashMap::new();
+ for path in paths.into_iter() {
+ let lines = read_file_to_array(path).map_err(OptionError::Generic)?;
+ for line in lines.into_iter() {
+ let (k, v) = line
+ .split_once('=')
+ .ok_or_else(|| OptionError::Generic("environment file invalid".to_owned()))?;
+ env_vars.insert(k.to_owned(), v.to_owned());
+ }
+ }
+ Ok(env_vars)
+}
+
+fn prepare_args(mut args: Vec<String>, subst_mappings: &[(String, String)]) -> Vec<String> {
+ for (f, replace_with) in subst_mappings {
+ for arg in args.iter_mut() {
+ let from = format!("${{{}}}", f);
+ let new = arg.replace(from.as_str(), replace_with);
+ *arg = new;
+ }
+ }
+ args
+}
+
+fn environment_block(
+ environment_file_block: HashMap<String, String>,
+ stamp_mappings: &[(String, String)],
+ subst_mappings: &[(String, String)],
+) -> HashMap<String, String> {
+ // Taking all environment variables from the current process
+ // and sending them down to the child process
+ let mut environment_variables: HashMap<String, String> = std::env::vars().collect();
+ // Have the last values added take precedence over the first.
+ // This is simpler than needing to track duplicates and explicitly override
+ // them.
+ environment_variables.extend(environment_file_block.into_iter());
+ for (f, replace_with) in stamp_mappings {
+ for value in environment_variables.values_mut() {
+ let from = format!("{{{}}}", f);
+ let new = value.replace(from.as_str(), replace_with);
+ *value = new;
+ }
+ }
+ for (f, replace_with) in subst_mappings {
+ for value in environment_variables.values_mut() {
+ let from = format!("${{{}}}", f);
+ let new = value.replace(from.as_str(), replace_with);
+ *value = new;
+ }
+ }
+ environment_variables
+}
diff --git a/util/process_wrapper/process_wrapper.cc b/util/process_wrapper/process_wrapper.cc
deleted file mode 100644
index 4f07c01..0000000
--- a/util/process_wrapper/process_wrapper.cc
+++ /dev/null
@@ -1,225 +0,0 @@
-// Copyright 2020 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <algorithm>
-#include <fstream>
-#include <iostream>
-#include <string>
-#include <utility>
-
-#include "util/process_wrapper/system.h"
-#include "util/process_wrapper/utils.h"
-
-using CharType = process_wrapper::System::StrType::value_type;
-
-// Simple process wrapper allowing us to not depend on the shell to run a
-// process to perform basic operations like capturing the output or having
-// the $pwd used in command line arguments or environment variables
-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::EnvironmentBlock environment_file_block;
-
- using Subst = std::pair<System::StrType, System::StrType>;
-
- System::StrType exec_path;
- std::vector<Subst> subst_mappings;
- std::vector<Subst> stamp_mappings;
- System::StrType volatile_status_file;
- System::StrType stdout_file;
- System::StrType stderr_file;
- System::StrType touch_file;
- System::StrType copy_source;
- System::StrType copy_dest;
- System::Arguments arguments;
- System::Arguments file_arguments;
-
- // Processing current process argument list until -- is encountered
- // everthing after gets sent down to the child process
- for (int i = 1; i < argc; ++i) {
- System::StrType arg = argv[i];
- if (++i == argc) {
- std::cerr << "process wrapper error: argument \"" << ToUtf8(arg)
- << "\" missing parameter.\n";
- return -1;
- }
- if (arg == PW_SYS_STR("--subst")) {
- System::StrType subst = argv[i];
- size_t equal_pos = subst.find('=');
- if (equal_pos == std::string::npos) {
- std::cerr << "process wrapper error: wrong substituion format for \""
- << ToUtf8(subst) << "\".\n";
- return -1;
- }
- System::StrType key = subst.substr(0, equal_pos);
- if (key.empty()) {
- std::cerr << "process wrapper error: empty key for substituion \""
- << ToUtf8(subst) << "\".\n";
- return -1;
- }
- System::StrType value = subst.substr(equal_pos + 1, subst.size());
- if (value == PW_SYS_STR("${pwd}")) {
- value = System::GetWorkingDirectory();
- }
- subst_mappings.push_back({std::move(key), std::move(value)});
- } else if (arg == PW_SYS_STR("--volatile-status-file")) {
- if (!volatile_status_file.empty()) {
- std::cerr << "process wrapper error: \"--volatile-status-file\" can "
- "only appear once.\n";
- return -1;
- }
- if (!ReadStampStatusToArray(argv[i], stamp_mappings)) {
- return -1;
- }
- } else if (arg == PW_SYS_STR("--env-file")) {
- if (!ReadFileToArray(argv[i], environment_file_block)) {
- return -1;
- }
- } else if (arg == PW_SYS_STR("--arg-file")) {
- if (!ReadFileToArray(argv[i], file_arguments)) {
- return -1;
- }
- } else if (arg == PW_SYS_STR("--touch-file")) {
- if (!touch_file.empty()) {
- std::cerr << "process wrapper error: \"--touch-file\" can only appear "
- "once.\n";
- return -1;
- }
- touch_file = argv[i];
- } else if (arg == PW_SYS_STR("--copy-output")) {
- // i is already at the first arg position, accountint we need another arg
- // and then -- executable_name.
- if (i + 1 > argc) {
- std::cerr
- << "process wrapper error: \"--copy-output\" needs 2 parameters.\n";
- return -1;
- }
- if (!copy_source.empty() || !copy_dest.empty()) {
- std::cerr << "process wrapper error: \"--copy-output\" can only appear "
- "once.\n";
- return -1;
- }
- copy_source = argv[i];
- copy_dest = argv[++i];
- if (copy_source == copy_dest) {
- std::cerr << "process wrapper error: \"--copy-output\" source and dest "
- "need to be different.\n";
- return -1;
- }
- } else if (arg == PW_SYS_STR("--stdout-file")) {
- if (!stdout_file.empty()) {
- std::cerr << "process wrapper error: \"--stdout-file\" can only appear "
- "once.\n";
- return -1;
- }
- stdout_file = argv[i];
- } else if (arg == PW_SYS_STR("--stderr-file")) {
- if (!stderr_file.empty()) {
- std::cerr << "process wrapper error: \"--stderr-file\" can only appear "
- "once.\n";
- return -1;
- }
- stderr_file = argv[i];
- } else if (arg == PW_SYS_STR("--")) {
- exec_path = argv[i];
- for (++i; i < argc; ++i) {
- arguments.push_back(argv[i]);
- }
- // after we consume all arguments we append the files arguments
- for (const System::StrType& file_arg : file_arguments) {
- arguments.push_back(file_arg);
- }
- } else {
- std::cerr << "process wrapper error: unknow argument \"" << ToUtf8(arg)
- << "\"." << '\n';
- return -1;
- }
- }
-
- // Stamp any format string in an environment variable block
- for (const Subst& stamp : stamp_mappings) {
- System::StrType token = PW_SYS_STR("{");
- token += stamp.first;
- token.push_back('}');
- for (System::StrType& env : environment_file_block) {
- ReplaceToken(env, token, stamp.second);
- }
- }
-
- // Join environment variables arrays
- environment_block.reserve(environment_block.size() +
- environment_file_block.size());
- environment_block.insert(environment_block.end(),
- environment_file_block.begin(),
- environment_file_block.end());
-
- if (subst_mappings.size()) {
- for (const Subst& subst : subst_mappings) {
- System::StrType token = PW_SYS_STR("${");
- token += subst.first;
- token.push_back('}');
- for (System::StrType& arg : arguments) {
- ReplaceToken(arg, token, subst.second);
- }
-
- for (System::StrType& env : environment_block) {
- ReplaceToken(env, token, subst.second);
- }
- }
- }
-
- // Have the last values added take precedence over the first.
- // This is simpler than needing to track duplicates and explicitly override
- // them.
- std::reverse(environment_block.begin(), environment_block.end());
-
- int exit_code = System::Exec(exec_path, arguments, environment_block,
- stdout_file, stderr_file);
- if (exit_code == 0) {
- if (!touch_file.empty()) {
- std::ofstream file(touch_file);
- if (file.fail()) {
- std::cerr << "process wrapper error: failed to create touch file: \""
- << ToUtf8(touch_file) << "\"\n";
- return -1;
- }
- file.close();
- }
-
- // we perform a copy of the output if necessary
- if (!copy_source.empty() && !copy_dest.empty()) {
- std::ifstream source(copy_source, std::ios::binary);
- if (source.fail()) {
- std::cerr << "process wrapper error: failed to open copy source: \""
- << ToUtf8(copy_source) << "\"\n";
- return -1;
- }
- std::ofstream dest(copy_dest, std::ios::binary);
- if (dest.fail()) {
- std::cerr << "process wrapper error: failed to open copy dest: \""
- << ToUtf8(copy_dest) << "\"\n";
- return -1;
- }
- dest << source.rdbuf();
- }
- }
- return exit_code;
-}
diff --git a/util/process_wrapper/process_wrapper_fake.cc b/util/process_wrapper/process_wrapper_fake.cc
new file mode 100644
index 0000000..a0b6869
--- /dev/null
+++ b/util/process_wrapper/process_wrapper_fake.cc
@@ -0,0 +1,4 @@
+int main() {
+ // Noop on purpose.
+ return 0;
+}
\ No newline at end of file
diff --git a/util/process_wrapper/system.h b/util/process_wrapper/system.h
deleted file mode 100644
index 8abf744..0000000
--- a/util/process_wrapper/system.h
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2020 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#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,
- const StrType& stdout_file, const StrType& stderr_file);
-};
-
-} // namespace process_wrapper
-
-#endif // LIB_PROCESS_WRAPPER_SYSTEM_H_
diff --git a/util/process_wrapper/system_posix.cc b/util/process_wrapper/system_posix.cc
deleted file mode 100644
index 45ff9e3..0000000
--- a/util/process_wrapper/system_posix.cc
+++ /dev/null
@@ -1,197 +0,0 @@
-// Copyright 2020 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "util/process_wrapper/system.h"
-#include "util/process_wrapper/utils.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]; }
-
- bool WriteToFile(const System::StrType &stdout_file) {
- CloseWriteEnd();
-
- constexpr size_t kBufferSize = 4096;
- char buffer[kBufferSize];
- int output_file_desc =
- open(stdout_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC,
- S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
- if (output_file_desc == -1) {
- std::cerr << "process wrapper error: failed to open redirection file: "
- << std::strerror(errno) << ".\n";
- return false;
- }
- while (1) {
- ssize_t read_bytes = read(ReadEndDesc(), buffer, kBufferSize);
- if (read_bytes < 0) {
- std::cerr
- << "process wrapper error: failed to read child process output: "
- << std::strerror(errno) << ".\n";
- return false;
- } else if (read_bytes == 0) {
- break;
- }
- ssize_t written_bytes = write(output_file_desc, buffer, read_bytes);
- if (written_bytes < 0 || written_bytes != read_bytes) {
- std::cerr << "process wrapper error: failed to write to ouput file: "
- << std::strerror(errno) << ".\n";
- return false;
- }
- }
-
- CloseReadEnd();
- close(output_file_desc);
- return true;
- }
-
- 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,
- const StrType &stdout_file, const StrType &stderr_file) {
- OutputPipe stdout_pipe;
- if (!stdout_file.empty() && !stdout_pipe.CreateEnds()) {
- return -1;
- }
- OutputPipe stderr_pipe;
- if (!stderr_file.empty() && !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) {
- if (!stdout_file.empty()) {
- stdout_pipe.DupWriteEnd(STDOUT_FILENO);
- }
- if (!stderr_file.empty()) {
- stderr_pipe.DupWriteEnd(STDERR_FILENO);
- }
- 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;
- }
-
- if (!stdout_file.empty()) {
- if (!stdout_pipe.WriteToFile(stdout_file)) {
- return -1;
- }
- }
- if (!stderr_file.empty()) {
- if (!stderr_pipe.WriteToFile(stderr_file)) {
- 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
diff --git a/util/process_wrapper/system_windows.cc b/util/process_wrapper/system_windows.cc
deleted file mode 100644
index 6131102..0000000
--- a/util/process_wrapper/system_windows.cc
+++ /dev/null
@@ -1,297 +0,0 @@
-// Copyright 2020 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <cstddef>
-
-#include "util/process_wrapper/system.h"
-
-#ifndef WIN32_LEAN_AND_MEAN
-#define WIN32_LEAN_AND_MEAN
-#endif
-
-#include <windows.h>
-
-#include <iostream>
-
-namespace process_wrapper {
-
-namespace {
-
-// We need to follow specific quoting rules for maximum compatibility as
-// explained here:
-// https://docs.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way
-void ArgumentQuote(const System::StrType& argument,
- System::StrType& command_line) {
- if (argument.empty() == false &&
- argument.find_first_of(PW_SYS_STR(" \t\n\v\"")) == argument.npos) {
- command_line.append(argument);
- } else {
- command_line.push_back(PW_SYS_STR('"'));
-
- for (auto it = argument.begin();; ++it) {
- unsigned number_backslashes = 0;
-
- while (it != argument.end() && *it == PW_SYS_STR('\\')) {
- ++it;
- ++number_backslashes;
- }
-
- if (it == argument.end()) {
- command_line.append(number_backslashes * 2, PW_SYS_STR('\\'));
- break;
- } else if (*it == L'"') {
- command_line.append(number_backslashes * 2 + 1, PW_SYS_STR('\\'));
- command_line.push_back(*it);
- } else {
- command_line.append(number_backslashes, PW_SYS_STR('\\'));
- command_line.push_back(*it);
- }
- }
- command_line.push_back(PW_SYS_STR('"'));
- }
-}
-
-// Arguments needs to be quoted and space separated
-void MakeCommandLine(const System::Arguments& arguments,
- System::StrType& command_line) {
- for (const System::StrType& argument : arguments) {
- command_line.push_back(PW_SYS_STR(' '));
- ArgumentQuote(argument, command_line);
- }
-}
-
-// Environment variables are \0 separated
-void MakeEnvironmentBlock(const System::EnvironmentBlock& environment_block,
- System::StrType& environment_block_win) {
- for (const System::StrType& ev : environment_block) {
- environment_block_win += ev;
- environment_block_win.push_back(PW_SYS_STR('\0'));
- }
- environment_block_win.push_back(PW_SYS_STR('\0'));
-}
-
-std::string GetLastErrorAsStr() {
- LPVOID msg_buffer = nullptr;
- size_t size = ::FormatMessageA(
- FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
- FORMAT_MESSAGE_IGNORE_INSERTS,
- NULL, ::GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
- (LPSTR)&msg_buffer, 0, NULL);
- std::string error((LPSTR)msg_buffer, size);
- LocalFree(msg_buffer);
- return error;
-}
-
-class OutputPipe {
- public:
- static constexpr size_t kReadEndHandle = 0;
- static constexpr size_t kWriteEndHandle = 1;
-
- ~OutputPipe() {
- CloseReadEnd();
- CloseWriteEnd();
- }
-
- bool CreateEnds(SECURITY_ATTRIBUTES& saAttr) {
- if (!::CreatePipe(&output_pipe_handles_[kReadEndHandle],
- &output_pipe_handles_[kWriteEndHandle], &saAttr, 0)) {
- return false;
- }
-
- if (!::SetHandleInformation(output_pipe_handles_[kReadEndHandle],
- HANDLE_FLAG_INHERIT, 0)) {
- return false;
- }
-
- return true;
- }
-
- void CloseReadEnd() { Close(kReadEndHandle); }
- void CloseWriteEnd() { Close(kWriteEndHandle); }
-
- HANDLE ReadEndHandle() const { return output_pipe_handles_[kReadEndHandle]; }
- HANDLE WriteEndHandle() const {
- return output_pipe_handles_[kWriteEndHandle];
- }
-
- bool WriteToFile(const System::StrType& stdout_file) {
- CloseWriteEnd();
- HANDLE output_file_handle = CreateFile(
- /*lpFileName*/ stdout_file.c_str(),
- /*dwDesiredAccess*/ GENERIC_WRITE,
- /*dwShareMode*/ FILE_SHARE_WRITE,
- /*lpSecurityAttributes*/ NULL,
- /*dwCreationDisposition*/ CREATE_ALWAYS,
- /*dwFlagsAndAttributes*/ FILE_ATTRIBUTE_NORMAL,
- /*hTemplateFile*/ NULL);
-
- if (output_file_handle == INVALID_HANDLE_VALUE) {
- std::cerr << "process wrapper error: failed to open the output file: "
- << GetLastErrorAsStr();
- return false;
- }
-
- constexpr DWORD kBufferSize = 4096;
- CHAR buffer[kBufferSize];
- bool ret = true;
- while (1) {
- DWORD read;
- bool success =
- ReadFile(ReadEndHandle(), buffer, kBufferSize, &read, NULL);
- if (read == 0) {
- break;
- } else if (!success) {
- std::cerr
- << "process wrapper error: failed to read child process output: "
- << GetLastErrorAsStr();
- ret = false;
- break;
- }
-
- DWORD written;
- success = WriteFile(output_file_handle, buffer, read, &written, NULL);
- if (!success) {
- std::cerr << "process wrapper error: failed to write to output capture "
- "file: "
- << GetLastErrorAsStr();
- ret = false;
- break;
- }
- }
- CloseHandle(output_file_handle);
- return ret;
- }
-
- private:
- void Close(size_t idx) {
- if (output_pipe_handles_[idx] != nullptr) {
- ::CloseHandle(output_pipe_handles_[idx]);
- }
- output_pipe_handles_[idx] = nullptr;
- }
- HANDLE output_pipe_handles_[2] = {nullptr};
-};
-
-} // namespace
-
-System::StrType System::GetWorkingDirectory() {
- constexpr DWORD kMaxBufferLength = 4096;
- TCHAR buffer[kMaxBufferLength];
- if (::GetCurrentDirectory(kMaxBufferLength, buffer) == 0) {
- return System::StrType{};
- }
- return System::StrType{buffer};
-}
-
-int System::Exec(const System::StrType& executable,
- const System::Arguments& arguments,
- const System::EnvironmentBlock& environment_block,
- const StrType& stdout_file, const StrType& stderr_file) {
- STARTUPINFO startup_info;
- ZeroMemory(&startup_info, sizeof(STARTUPINFO));
- startup_info.cb = sizeof(STARTUPINFO);
-
- OutputPipe stdout_pipe;
- OutputPipe stderr_pipe;
-
- if (!stdout_file.empty() || !stderr_file.empty()) {
- // We will be setting our own stdout/stderr handles. Note that when setting `STARTF_USESTDHANDLES`
- // it is critical to set *all* handles or the child process might get a null handle (or garbage).
- startup_info.dwFlags |= STARTF_USESTDHANDLES;
- startup_info.hStdInput = INVALID_HANDLE_VALUE;
-
- SECURITY_ATTRIBUTES saAttr;
- ZeroMemory(&saAttr, sizeof(SECURITY_ATTRIBUTES));
- saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
- saAttr.bInheritHandle = TRUE;
- saAttr.lpSecurityDescriptor = NULL;
-
- if (!stdout_file.empty()) {
- if (!stdout_pipe.CreateEnds(saAttr)) {
- std::cerr << "process wrapper error: failed to create stdout pipe: "
- << GetLastErrorAsStr();
- return -1;
- }
- startup_info.hStdOutput = stdout_pipe.WriteEndHandle();
- } else {
- startup_info.hStdOutput = INVALID_HANDLE_VALUE;
- }
-
- if (!stderr_file.empty()) {
- if (!stderr_pipe.CreateEnds(saAttr)) {
- std::cerr << "process wrapper error: failed to create stderr pipe: "
- << GetLastErrorAsStr();
- return -1;
- }
- startup_info.hStdError = stderr_pipe.WriteEndHandle();
- } else {
- startup_info.hStdError = INVALID_HANDLE_VALUE;
- }
- }
-
- System::StrType command_line;
- ArgumentQuote(executable, command_line);
- MakeCommandLine(arguments, command_line);
-
- System::StrType environment_block_win;
- MakeEnvironmentBlock(environment_block, environment_block_win);
-
- PROCESS_INFORMATION process_info;
- ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION));
-
- BOOL success = ::CreateProcess(
- /*lpApplicationName*/ nullptr,
- /*lpCommandLine*/ command_line.empty() ? nullptr : &command_line[0],
- /*lpProcessAttributes*/ nullptr,
- /*lpThreadAttributes*/ nullptr,
- /*bInheritHandles*/ TRUE,
- /*dwCreationFlags*/ 0
-#if defined(UNICODE)
- | CREATE_UNICODE_ENVIRONMENT
-#endif // defined(UNICODE)
- ,
- /*lpEnvironment*/ environment_block_win.empty()
- ? nullptr
- : &environment_block_win[0],
- /*lpCurrentDirectory*/ nullptr,
- /*lpStartupInfo*/ &startup_info,
- /*lpProcessInformation*/ &process_info);
-
- if (success == FALSE) {
- std::cerr << "process wrapper error: failed to launch a new process: "
- << GetLastErrorAsStr();
- return -1;
- }
-
- if (!stdout_file.empty()) {
- if (!stdout_pipe.WriteToFile(stdout_file)) {
- return -1;
- }
- }
- if (!stderr_file.empty()) {
- if (!stderr_pipe.WriteToFile(stderr_file)) {
- return -1;
- }
- }
-
- DWORD exit_status;
- WaitForSingleObject(process_info.hProcess, INFINITE);
- if (GetExitCodeProcess(process_info.hProcess, &exit_status) == FALSE)
- exit_status = -1;
- CloseHandle(process_info.hThread);
- CloseHandle(process_info.hProcess);
- return exit_status;
-}
-
-} // namespace process_wrapper
diff --git a/util/process_wrapper/util.rs b/util/process_wrapper/util.rs
new file mode 100644
index 0000000..4b3d6bb
--- /dev/null
+++ b/util/process_wrapper/util.rs
@@ -0,0 +1,103 @@
+// Copyright 2020 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::fs::File;
+use std::io::{BufRead, BufReader, Read};
+
+pub(crate) fn read_file_to_array(path: String) -> Result<Vec<String>, String> {
+ let file = File::open(path).map_err(|e| e.to_string())?;
+ read_to_array(file)
+}
+
+pub(crate) fn read_stamp_status_to_array(path: String) -> Result<Vec<(String, String)>, String> {
+ let file = File::open(path).map_err(|e| e.to_string())?;
+ stamp_status_to_array(file)
+}
+
+fn read_to_array(reader: impl Read) -> Result<Vec<String>, String> {
+ let reader = BufReader::new(reader);
+ let mut ret = vec![];
+ let mut escaped_line = String::new();
+ for l in reader.lines() {
+ let line = l.map_err(|e| e.to_string())?;
+ if line.is_empty() {
+ continue;
+ }
+ // a \ at the end of a line allows us to escape the new line break,
+ // \\ yields a single \, so \\\ translates to a single \ and a new line
+ // escape
+ let end_backslash_count = line.chars().rev().take_while(|&c| c == '\\').count();
+ // a 0 or even number of backslashes do not lead to a new line escape
+ let escape = end_backslash_count % 2 == 1;
+ // remove backslashes and add back two for every one
+ let l = line.trim_end_matches('\\');
+ escaped_line.push_str(l);
+ for _ in 0..end_backslash_count / 2 {
+ escaped_line.push('\\');
+ }
+ if escape {
+ // we add a newline as we expect a line after this
+ escaped_line.push('\n');
+ } else {
+ ret.push(escaped_line);
+ escaped_line = String::new();
+ }
+ }
+ Ok(ret)
+}
+
+fn stamp_status_to_array(reader: impl Read) -> Result<Vec<(String, String)>, String> {
+ let escaped_lines = read_to_array(reader)?;
+ escaped_lines
+ .into_iter()
+ .map(|l| {
+ let (s1, s2) = l
+ .split_once(' ')
+ .ok_or_else(|| format!("wrong workspace status file format for \"{}\"", l))?;
+ Ok((s1.to_owned(), s2.to_owned()))
+ })
+ .collect()
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_read_to_array() {
+ let input = r#"some escaped \\\
+string
+with other lines"#
+ .to_owned();
+ let expected = vec![
+ r#"some escaped \
+string"#,
+ "with other lines",
+ ];
+ let got = read_to_array(input.as_bytes()).unwrap();
+ assert_eq!(expected, got);
+ }
+
+ #[test]
+ fn test_stamp_status_to_array() {
+ let lines = "aaa bbb\\\nvvv\nccc ddd\neee fff";
+ let got = stamp_status_to_array(lines.as_bytes()).unwrap();
+ let expected = vec![
+ ("aaa".to_owned(), "bbb\nvvv".to_owned()),
+ ("ccc".to_owned(), "ddd".to_owned()),
+ ("eee".to_owned(), "fff".to_owned()),
+ ];
+ assert_eq!(expected, got);
+ }
+}
diff --git a/util/process_wrapper/utils.cc b/util/process_wrapper/utils.cc
deleted file mode 100644
index 00ade0a..0000000
--- a/util/process_wrapper/utils.cc
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright 2020 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "util/process_wrapper/utils.h"
-
-#include <fstream>
-#include <iostream>
-#include <streambuf>
-
-#if defined(PW_WIN_UNICODE)
-#include <codecvt>
-#include <locale>
-#endif // defined(PW_WIN_UNICODE)
-
-namespace process_wrapper {
-
-System::StrType FromUtf8(const std::string& string) {
-#if defined(PW_WIN_UNICODE)
- return std::wstring_convert<std::codecvt_utf8<wchar_t>>().from_bytes(string);
-#else
- return string;
-#endif // defined(PW_WIN_UNICODE)
-}
-
-std::string ToUtf8(const System::StrType& string) {
-#if defined(PW_WIN_UNICODE)
- return std::wstring_convert<std::codecvt_utf8<wchar_t>>().to_bytes(string);
-#else
- return string;
-#endif // defined(PW_WIN_UNICODE)
-}
-
-void ReplaceToken(System::StrType& str, const System::StrType& token,
- const System::StrType& replacement) {
- std::size_t pos = str.find(token);
- if (pos != std::string::npos) {
- str.replace(pos, token.size(), replacement);
- }
-}
-
-bool ReadFileToArray(const System::StrType& file_path,
- System::StrVecType& vec) {
- std::ifstream file(file_path);
- if (file.fail()) {
- std::cerr << "process wrapper error: failed to open file: "
- << ToUtf8(file_path) << '\n';
- return false;
- }
- std::string read_line, escaped_line;
- while (std::getline(file, read_line)) {
- // handle CRLF files when as they might be
- // written on windows and read from linux
- if (!read_line.empty() && read_line.back() == '\r') {
- read_line.pop_back();
- }
- // Skip empty lines if any
- if (read_line.empty()) {
- continue;
- }
-
- // a \ at the end of a line allows us to escape the new line break,
- // \\ yields a single \, so \\\ translates to a single \ and a new line
- // escape
- int end_backslash_count = 0;
- for (std::string::reverse_iterator rit = read_line.rbegin();
- rit != read_line.rend() && *rit == '\\'; ++rit) {
- end_backslash_count++;
- }
-
- // a 0 or pair number of backslashes do not lead to a new line escape
- bool escape = false;
- if (end_backslash_count & 1) {
- escape = true;
- }
-
- // remove backslashes
- while (end_backslash_count > 0) {
- end_backslash_count -= 2;
- read_line.pop_back();
- }
-
- if (escape) {
- read_line.push_back('\n');
- escaped_line += read_line;
- } else {
- vec.push_back(FromUtf8(escaped_line + read_line));
- escaped_line.clear();
- }
- }
- return true;
-}
-
-bool ReadStampStatusToArray(
- const System::StrType& stamp_path,
- std::vector<std::pair<System::StrType, System::StrType>>& vec) {
- // Read each line of the stamp file and split on the first space
- System::StrVecType stamp_block;
- if (!ReadFileToArray(stamp_path, stamp_block)) {
- return false;
- }
-
- for (System::StrVecType::size_type i = 0; i < stamp_block.size(); ++i) {
- size_t space_pos = stamp_block[i].find(' ');
- if (space_pos == std::string::npos) {
- std::cerr << "process wrapper error: wrong workspace status file "
- "format for \""
- << ToUtf8(stamp_block[i]) << "\".\n";
- return false;
- }
- System::StrType key = stamp_block[i].substr(0, space_pos);
- System::StrType value =
- stamp_block[i].substr(space_pos + 1, stamp_block[i].size());
- vec.push_back({std::move(key), std::move(value)});
- }
-
- return true;
-}
-
-} // namespace process_wrapper
diff --git a/util/process_wrapper/utils.h b/util/process_wrapper/utils.h
deleted file mode 100644
index 62c3da6..0000000
--- a/util/process_wrapper/utils.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2020 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#ifndef LIB_PROCESS_WRAPPER_UTILS_H_
-#define LIB_PROCESS_WRAPPER_UTILS_H_
-
-#include <string>
-
-#include "util/process_wrapper/system.h"
-
-namespace process_wrapper {
-
-// Converts to and frin the system string format
-System::StrType FromUtf8(const std::string& string);
-std::string ToUtf8(const System::StrType& string);
-
-// Replaces a token in str by replacement
-void ReplaceToken(System::StrType& str, const System::StrType& token,
- const System::StrType& replacement);
-
-// Reads a file in text mode and feeds each line to item in the vec output
-bool ReadFileToArray(const System::StrType& file_path, System::StrVecType& vec);
-
-// Reads a workspace status stamp file to an array of key value pairs
-bool ReadStampStatusToArray(
- const System::StrType& stamp_path,
- std::vector<std::pair<System::StrType, System::StrType>>& vec);
-
-} // namespace process_wrapper
-
-#endif // LIB_PROCESS_WRAPPER_UTILS_H_