Squashed 'third_party/rules_rust/' content from commit bf59038ca

git-subtree-dir: third_party/rules_rust
git-subtree-split: bf59038cac11798cbaef9f3bf965bad8182b97fa
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I5a20e403203d670df467ea97dde9a4ac40339a8d
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
+}