Skip to content

Commit b879aac

Browse files
committed
handle cmake's cache reset after toolchain change
1 parent a254337 commit b879aac

File tree

1 file changed

+124
-8
lines changed

1 file changed

+124
-8
lines changed

src/lib.rs

+124-8
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,11 @@ extern crate cc;
4949
use std::env;
5050
use std::ffi::{OsStr, OsString};
5151
use std::fs::{self, File};
52-
use std::io::prelude::*;
53-
use std::io::ErrorKind;
52+
use std::io::{self, prelude::*, ErrorKind};
5453
use std::path::{Path, PathBuf};
55-
use std::process::Command;
54+
use std::process::{Command, ExitStatus, Stdio};
55+
use std::sync::{Arc, Mutex};
56+
use std::thread::{self};
5657

5758
/// Builder style configuration for a pending CMake build.
5859
pub struct Config {
@@ -457,6 +458,7 @@ impl Config {
457458
// Build up the first cmake command to build the build system.
458459
let executable = env::var("CMAKE").unwrap_or("cmake".to_owned());
459460
let mut conf_cmd = Command::new(&executable);
461+
conf_cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
460462

461463
if self.verbose_cmake {
462464
conf_cmd.arg("-Wdev");
@@ -707,10 +709,13 @@ impl Config {
707709
conf_cmd.env(k, v);
708710
}
709711

712+
conf_cmd.env("CMAKE_PREFIX_PATH", cmake_prefix_path);
710713
if self.always_configure || !build.join(CMAKE_CACHE_FILE).exists() {
711-
run(
712-
conf_cmd.env("CMAKE_PREFIX_PATH", cmake_prefix_path),
713-
"cmake",
714+
run_cmake_action(
715+
&build,
716+
CMakeAction::Configure {
717+
conf_cmd: &mut conf_cmd,
718+
},
714719
);
715720
} else {
716721
println!("CMake project was already configured. Skipping configuration step.");
@@ -758,6 +763,7 @@ impl Config {
758763
// And build!
759764
let target = self.cmake_target.clone().unwrap_or("install".to_string());
760765
let mut build_cmd = Command::new(&executable);
766+
build_cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
761767
for &(ref k, ref v) in c_compiler.env().iter().chain(&self.env) {
762768
build_cmd.env(k, v);
763769
}
@@ -783,7 +789,13 @@ impl Config {
783789
build_cmd.arg(flags);
784790
}
785791

786-
run(&mut build_cmd, "cmake");
792+
run_cmake_action(
793+
&build,
794+
CMakeAction::Build {
795+
build_cmd: &mut build_cmd,
796+
conf_cmd: &mut conf_cmd,
797+
},
798+
);
787799

788800
println!("cargo:root={}", dst.display());
789801
return dst;
@@ -864,9 +876,113 @@ impl Config {
864876
}
865877
}
866878

879+
enum CMakeAction<'a> {
880+
Configure {
881+
conf_cmd: &'a mut Command,
882+
},
883+
Build {
884+
conf_cmd: &'a mut Command,
885+
build_cmd: &'a mut Command,
886+
},
887+
}
888+
889+
fn run_cmake_action(build_dir: &Path, mut action: CMakeAction) {
890+
let program = "cmake";
891+
let cmd = match &mut action {
892+
CMakeAction::Configure { conf_cmd } => conf_cmd,
893+
CMakeAction::Build { build_cmd, .. } => build_cmd,
894+
};
895+
let need_rerun = match run_and_check_if_need_reconf(*cmd) {
896+
Ok(x) => x,
897+
Err(err) => {
898+
handle_cmake_exec_result(Err(err), program);
899+
return;
900+
}
901+
};
902+
if need_rerun {
903+
println!("Looks like toolchain was changed");
904+
//just in case some wrong value was cached
905+
let _ = fs::remove_file(&build_dir.join(CMAKE_CACHE_FILE));
906+
match action {
907+
CMakeAction::Configure { conf_cmd } => run(conf_cmd, program),
908+
CMakeAction::Build {
909+
conf_cmd,
910+
build_cmd,
911+
} => {
912+
run(conf_cmd, program);
913+
run(build_cmd, program);
914+
}
915+
}
916+
}
917+
}
918+
919+
// Acording to
920+
// https://gitlab.kitware.com/cmake/cmake/-/issues/18959
921+
// CMake does not support usage of the same build directory for different
922+
// compilers. The problem is that we can not make sure that we use the same compiler
923+
// before running of CMake without CMake's logic duplication (for example consider
924+
// usage of CMAKE_TOOLCHAIN_FILE). Fortunately for us, CMake can detect is
925+
// compiler changed by itself. This is done for interactive CMake's configuration,
926+
// like ccmake/cmake-gui. But after compiler change CMake resets all cached variables.
927+
fn run_and_check_if_need_reconf(cmd: &mut Command) -> Result<bool, io::Error> {
928+
println!("running: {:?}", cmd);
929+
let mut child = cmd.spawn()?;
930+
let mut child_stderr = child.stderr.take().expect("Internal error no stderr");
931+
let full_stderr = Arc::new(Mutex::new(Vec::<u8>::with_capacity(1024)));
932+
let full_stderr2 = full_stderr.clone();
933+
let stderr_thread = thread::spawn(move || {
934+
let mut full_stderr = full_stderr2
935+
.lock()
936+
.expect("Internal error: Lock of stderr buffer failed");
937+
log_and_copy_stream(&mut child_stderr, &mut io::stderr(), &mut full_stderr)
938+
});
939+
940+
let mut child_stdout = child.stdout.take().expect("Internal error no stdout");
941+
let mut full_stdout = Vec::with_capacity(1024);
942+
log_and_copy_stream(&mut child_stdout, &mut io::stdout(), &mut full_stdout)?;
943+
stderr_thread
944+
.join()
945+
.expect("Internal stderr thread join failed")?;
946+
947+
static RESET_MSG: &[u8] = b"Configure will be re-run and you may have to reset some variables";
948+
let full_stderr = full_stderr
949+
.lock()
950+
.expect("Internal error stderr lock failed");
951+
Ok(contains(&full_stderr, RESET_MSG) || contains(&full_stdout, RESET_MSG))
952+
}
953+
867954
fn run(cmd: &mut Command, program: &str) {
868955
println!("running: {:?}", cmd);
869-
let status = match cmd.status() {
956+
handle_cmake_exec_result(cmd.status(), program);
957+
}
958+
959+
fn contains(haystack: &[u8], needle: &[u8]) -> bool {
960+
haystack
961+
.windows(needle.len())
962+
.any(|window| window == needle)
963+
}
964+
965+
fn log_and_copy_stream<R: Read, W: Write>(
966+
reader: &mut R,
967+
writer: &mut W,
968+
log: &mut Vec<u8>,
969+
) -> io::Result<()> {
970+
let mut buf = [0; 80];
971+
loop {
972+
let len = match reader.read(&mut buf) {
973+
Ok(0) => break,
974+
Ok(len) => len,
975+
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
976+
Err(e) => return Err(e),
977+
};
978+
log.extend_from_slice(&buf[0..len]);
979+
writer.write_all(&buf[0..len])?;
980+
}
981+
Ok(())
982+
}
983+
984+
fn handle_cmake_exec_result(r: Result<ExitStatus, io::Error>, program: &str) {
985+
let status = match r {
870986
Ok(status) => status,
871987
Err(ref e) if e.kind() == ErrorKind::NotFound => {
872988
fail(&format!(

0 commit comments

Comments
 (0)