// 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 output; mod rustc; mod util; use std::fmt; use std::fs::{copy, OpenOptions}; use std::io; use std::process::{exit, Command, ExitStatus, Stdio}; use crate::options::options; use crate::output::{process_output, LineOutput}; #[cfg(windows)] fn status_code(status: ExitStatus, was_killed: bool) -> i32 { // On windows, there's no good way to know if the process was killed by a signal. // If we killed the process, we override the code to signal success. if was_killed { 0 } else { status.code().unwrap_or(1) } } #[cfg(not(windows))] fn status_code(status: ExitStatus, was_killed: bool) -> i32 { // On unix, if code is None it means that the process was killed by a signal. // https://doc.rust-lang.org/std/process/struct.ExitStatus.html#method.success match status.code() { Some(code) => code, // If we killed the process, we expect None here None if was_killed => 0, // Otherwise it's some unexpected signal None => 1, } } #[derive(Debug)] struct ProcessWrapperError(String); impl fmt::Display for ProcessWrapperError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "process wrapper error: {}", self.0) } } impl std::error::Error for ProcessWrapperError {} fn main() -> Result<(), ProcessWrapperError> { let opts = options().map_err(|e| ProcessWrapperError(e.to_string()))?; let mut child = Command::new(opts.executable) .args(opts.child_arguments) .env_clear() .envs(opts.child_environment) .stdout(if let Some(stdout_file) = opts.stdout_file { OpenOptions::new() .create(true) .truncate(true) .write(true) .open(stdout_file) .map_err(|e| ProcessWrapperError(format!("unable to open stdout file: {}", e)))? .into() } else { Stdio::inherit() }) .stderr(Stdio::piped()) .spawn() .map_err(|e| ProcessWrapperError(format!("failed to spawn child process: {}", e)))?; let mut stderr: Box = if let Some(stderr_file) = opts.stderr_file { Box::new( OpenOptions::new() .create(true) .truncate(true) .write(true) .open(stderr_file) .map_err(|e| ProcessWrapperError(format!("unable to open stderr file: {}", e)))?, ) } else { Box::new(io::stderr()) }; let mut child_stderr = child.stderr.take().ok_or(ProcessWrapperError( "unable to get child stderr".to_string(), ))?; let mut output_file: Option = if let Some(output_file_name) = opts.output_file { Some( OpenOptions::new() .create(true) .truncate(true) .write(true) .open(output_file_name) .map_err(|e| ProcessWrapperError(format!("Unable to open output_file: {}", e)))?, ) } else { None }; let mut was_killed = false; let result = if let Some(format) = opts.rustc_output_format { let quit_on_rmeta = opts.rustc_quit_on_rmeta; // Process json rustc output and kill the subprocess when we get a signal // that we emitted a metadata file. let mut me = false; let metadata_emitted = &mut me; let result = process_output( &mut child_stderr, stderr.as_mut(), output_file.as_mut(), move |line| { if quit_on_rmeta { rustc::stop_on_rmeta_completion(line, format, metadata_emitted) } else { rustc::process_json(line, format) } }, ); if me { // If recv returns Ok(), a signal was sent in this channel so we should terminate the child process. // We can safely ignore the Result from kill() as we don't care if the process already terminated. let _ = child.kill(); was_killed = true; } result } else { // Process output normally by forwarding stderr process_output( &mut child_stderr, stderr.as_mut(), output_file.as_mut(), move |line| Ok(LineOutput::Message(line)), ) }; result.map_err(|e| ProcessWrapperError(format!("failed to process stderr: {}", e)))?; let status = child .wait() .map_err(|e| ProcessWrapperError(format!("failed to wait for child process: {}", e)))?; // If the child process is rustc and is killed after metadata generation, that's also a success. let code = status_code(status, was_killed); let success = code == 0; if success { if let Some(tf) = opts.touch_file { OpenOptions::new() .create(true) .truncate(true) .write(true) .open(tf) .map_err(|e| ProcessWrapperError(format!("failed to create touch file: {}", e)))?; } if let Some((copy_source, copy_dest)) = opts.copy_output { copy(©_source, ©_dest).map_err(|e| { ProcessWrapperError(format!( "failed to copy {} into {}: {}", copy_source, copy_dest, e )) })?; } } exit(code) }