diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index 61e1b46e6d..596c10da59 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -922,20 +922,41 @@ def rustc_compile_action( 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 f50216317d..76fbb782bc 100644 --- a/rust/private/transitions.bzl +++ b/rust/private/transitions.bzl @@ -64,3 +64,48 @@ with_import_macro_bootstrapping_mode = rule( ), }, ) + +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 8be7aabf53..474237a3dc 100644 --- a/rust/settings/BUILD.bazel +++ b/rust/settings/BUILD.bazel @@ -29,6 +29,13 @@ bool_flag( 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 4b600369ee..c26d9a6171 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 0000000000..a0b6869116 --- /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 0000000000..d3d6fe5bf5 --- /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, +} + +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>, + repeated: BTreeMap>>, +} + +#[derive(Debug)] +pub(crate) enum ParseOutcome { + Help(String), + Parsed(Vec), +} + +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, + help: impl Into, + output_storage: &'a mut Option, + ) { + 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, + help: impl Into, + output_storage: &'a mut Option>, + ) { + 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> { + 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 = 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) -> Result { + 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::::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>( + flag: &str, + argv_iter: &mut Peekable, +) -> Vec { + 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 { + ["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 0000000000..41140a37e2 --- /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 0000000000..24bba9fe80 --- /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, + // Contains environment variables for the child process fetched from files. + pub(crate) child_environment: HashMap, + // If set, create the specified file after the child process successfully + // terminated its execution. + pub(crate) touch_file: Option, + // 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, + // If set, redirects the child process stderr to this file. + pub(crate) stderr_file: Option, +} + +pub(crate) fn options() -> Result { + // 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::, 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) -> Result, 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) -> Result, 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, subst_mappings: &[(String, String)]) -> Vec { + 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, + stamp_mappings: &[(String, String)], + subst_mappings: &[(String, String)], +) -> HashMap { + // Taking all environment variables from the current process + // and sending them down to the child process + let mut environment_variables: HashMap = 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 4f07c0111d..0000000000 --- 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 -#include -#include -#include -#include - -#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 exec_path; - std::vector subst_mappings; - std::vector 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 0000000000..a0b6869116 --- /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 8abf744e5f..0000000000 --- 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 -#include - -#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; - 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 45ff9e34a2..0000000000 --- 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 -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -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 argv; - argv.push_back(const_cast(executable.c_str())); - for (const StrType &argument : arguments) { - argv.push_back(const_cast(argument.c_str())); - } - argv.push_back(nullptr); - - std::vector envp; - for (const StrType &ev : environment_block) { - envp.push_back(const_cast(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 6131102e23..0000000000 --- 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 - -#include "util/process_wrapper/system.h" - -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif - -#include - -#include - -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 0000000000..4b3d6bb17c --- /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, 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, 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, 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, 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 00ade0af8e..0000000000 --- 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 -#include -#include - -#if defined(PW_WIN_UNICODE) -#include -#include -#endif // defined(PW_WIN_UNICODE) - -namespace process_wrapper { - -System::StrType FromUtf8(const std::string& string) { -#if defined(PW_WIN_UNICODE) - return std::wstring_convert>().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>().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>& 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 62c3da6c0a..0000000000 --- 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 - -#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>& vec); - -} // namespace process_wrapper - -#endif // LIB_PROCESS_WRAPPER_UTILS_H_