diff --git a/src/lib.rs b/src/lib.rs index 4b84de7..ea996a1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,10 +49,11 @@ extern crate cc; use std::env; use std::ffi::{OsStr, OsString}; use std::fs::{self, File}; -use std::io::prelude::*; -use std::io::ErrorKind; +use std::io::{self, prelude::*, ErrorKind}; use std::path::{Path, PathBuf}; -use std::process::Command; +use std::process::{Command, ExitStatus, Stdio}; +use std::sync::{Arc, Mutex}; +use std::thread::{self}; /// Builder style configuration for a pending CMake build. pub struct Config { @@ -386,6 +387,7 @@ impl Config { // Build up the first cmake command to build the build system. let executable = env::var("CMAKE").unwrap_or("cmake".to_owned()); let mut conf_cmd = Command::new(&executable); + conf_cmd.stdout(Stdio::piped()).stderr(Stdio::piped()); if self.verbose_cmake { conf_cmd.arg("-Wdev"); @@ -698,11 +700,9 @@ impl Config { conf_cmd.env(k, v); } + conf_cmd.env("CMAKE_PREFIX_PATH", cmake_prefix_path); if self.always_configure || !build.join(CMAKE_CACHE_FILE).exists() { - run( - conf_cmd.env("CMAKE_PREFIX_PATH", cmake_prefix_path), - "cmake", - ); + run_configure(&build, &mut conf_cmd); } else { println!("CMake project was already configured. Skipping configuration step."); } @@ -749,6 +749,7 @@ impl Config { // And build! let target = self.cmake_target.clone().unwrap_or("install".to_string()); let mut build_cmd = Command::new(&executable); + build_cmd.stdout(Stdio::piped()).stderr(Stdio::piped()); for &(ref k, ref v) in c_compiler.env().iter().chain(&self.env) { build_cmd.env(k, v); } @@ -774,7 +775,7 @@ impl Config { build_cmd.arg(flags); } - run(&mut build_cmd, "cmake"); + run_build(&build, &mut build_cmd, &mut conf_cmd); println!("cargo:root={}", dst.display()); return dst; @@ -855,9 +856,108 @@ impl Config { } } +fn run_configure(build_dir: &Path, conf_cmd: &mut Command) { + let program = "cmake"; + let need_rerun = match run_and_check_if_need_reconf(conf_cmd) { + Ok(x) => x, + Err(err) => { + handle_cmake_exec_result(Err(err), program); + return; + } + }; + if need_rerun { + println!("Looks like toolchain was changed"); + //just in case some wrong value was cached + let _ = fs::remove_file(&build_dir.join(CMAKE_CACHE_FILE)); + run(conf_cmd, program); + } +} +fn run_build(build_dir: &Path, build_cmd: &mut Command, conf_cmd: &mut Command) { + let program = "cmake"; + let need_rerun = match run_and_check_if_need_reconf(build_cmd) { + Ok(x) => x, + Err(err) => { + handle_cmake_exec_result(Err(err), program); + return; + } + }; + if need_rerun { + println!("Looks like toolchain was changed"); + //just in case some wrong value was cached + let _ = fs::remove_file(&build_dir.join(CMAKE_CACHE_FILE)); + run(conf_cmd, program); + run(build_cmd, program); + } +} + +// Acording to +// https://gitlab.kitware.com/cmake/cmake/-/issues/18959 +// CMake does not support usage of the same build directory for different +// compilers. The problem is that we can not make sure that we use the same compiler +// before running of CMake without CMake's logic duplication (for example consider +// usage of CMAKE_TOOLCHAIN_FILE). Fortunately for us, CMake can detect is +// compiler changed by itself. This is done for interactive CMake's configuration, +// like ccmake/cmake-gui. But after compiler change CMake resets all cached variables. +// So +fn run_and_check_if_need_reconf(cmd: &mut Command) -> Result { + println!("running: {:?}", cmd); + let mut child = cmd.spawn()?; + let mut child_stderr = child.stderr.take().expect("Internal error no stderr"); + let full_stderr = Arc::new(Mutex::new(Vec::::with_capacity(1024))); + let full_stderr2 = full_stderr.clone(); + let stderr_thread = thread::spawn(move || { + let mut full_stderr = full_stderr2 + .lock() + .expect("Internal error: Lock of stderr buffer failed"); + log_and_copy_stream(&mut child_stderr, &mut io::stderr(), &mut full_stderr) + }); + + let mut child_stdout = child.stdout.take().expect("Internal error no stdout"); + let mut full_stdout = Vec::with_capacity(1024); + log_and_copy_stream(&mut child_stdout, &mut io::stdout(), &mut full_stdout)?; + stderr_thread + .join() + .expect("Internal stderr thread join failed")?; + + static RESET_MSG: &[u8] = b"Configure will be re-run and you may have to reset some variables"; + let full_stderr = full_stderr + .lock() + .expect("Internal error stderr lock failed"); + Ok(contains(&full_stderr, RESET_MSG) || contains(&full_stdout, RESET_MSG)) +} + fn run(cmd: &mut Command, program: &str) { println!("running: {:?}", cmd); - let status = match cmd.status() { + handle_cmake_exec_result(cmd.status(), program); +} + +fn contains(haystack: &[u8], needle: &[u8]) -> bool { + haystack + .windows(needle.len()) + .any(|window| window == needle) +} + +fn log_and_copy_stream( + reader: &mut R, + writer: &mut W, + log: &mut Vec, +) -> io::Result<()> { + let mut buf = [0; 80]; + loop { + let len = match reader.read(&mut buf) { + Ok(0) => break, + Ok(len) => len, + Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, + Err(e) => return Err(e), + }; + log.extend_from_slice(&buf[0..len]); + writer.write_all(&buf[0..len])?; + } + Ok(()) +} + +fn handle_cmake_exec_result(r: Result, program: &str) { + let status = match r { Ok(status) => status, Err(ref e) if e.kind() == ErrorKind::NotFound => { fail(&format!(