Skip to content

Commit a97e526

Browse files
committed
handle cmake's cache reset after toolchain change
1 parent 497433f commit a97e526

File tree

1 file changed

+130
-9
lines changed

1 file changed

+130
-9
lines changed

src/lib.rs

+130-9
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,11 @@ use std::collections::HashMap;
5050
use std::env;
5151
use std::ffi::{OsStr, OsString};
5252
use std::fs::{self, File};
53-
use std::io::prelude::*;
54-
use std::io::ErrorKind;
53+
use std::io::{self, prelude::*, ErrorKind};
5554
use std::path::{Path, PathBuf};
56-
use std::process::Command;
55+
use std::process::{Command, ExitStatus, Stdio};
56+
use std::sync::{Arc, Mutex};
57+
use std::thread::{self};
5758

5859
/// Builder style configuration for a pending CMake build.
5960
pub struct Config {
@@ -510,6 +511,7 @@ impl Config {
510511
.getenv_target_os("CMAKE")
511512
.unwrap_or(OsString::from("cmake"));
512513
let mut conf_cmd = Command::new(&executable);
514+
conf_cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
513515

514516
if self.verbose_cmake {
515517
conf_cmd.arg("-Wdev");
@@ -779,11 +781,14 @@ impl Config {
779781
conf_cmd.env(k, v);
780782
}
781783

784+
conf_cmd.env("CMAKE_PREFIX_PATH", cmake_prefix_path);
785+
conf_cmd.args(&self.configure_args);
782786
if self.always_configure || !build.join(CMAKE_CACHE_FILE).exists() {
783-
conf_cmd.args(&self.configure_args);
784-
run(
785-
conf_cmd.env("CMAKE_PREFIX_PATH", cmake_prefix_path),
786-
"cmake",
787+
run_cmake_action(
788+
&build,
789+
CMakeAction::Configure {
790+
conf_cmd: &mut conf_cmd,
791+
},
787792
);
788793
} else {
789794
println!("CMake project was already configured. Skipping configuration step.");
@@ -793,6 +798,7 @@ impl Config {
793798
let target = self.cmake_target.clone().unwrap_or("install".to_string());
794799
let mut build_cmd = Command::new(&executable);
795800
build_cmd.current_dir(&build);
801+
build_cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
796802

797803
for &(ref k, ref v) in c_compiler.env().iter().chain(&self.env) {
798804
build_cmd.env(k, v);
@@ -836,7 +842,13 @@ impl Config {
836842
build_cmd.arg("--").args(&self.build_args);
837843
}
838844

839-
run(&mut build_cmd, "cmake");
845+
run_cmake_action(
846+
&build,
847+
CMakeAction::Build {
848+
build_cmd: &mut build_cmd,
849+
conf_cmd: &mut conf_cmd,
850+
},
851+
);
840852

841853
println!("cargo:root={}", dst.display());
842854
return dst;
@@ -943,9 +955,118 @@ impl Config {
943955
}
944956
}
945957

958+
enum CMakeAction<'a> {
959+
Configure {
960+
conf_cmd: &'a mut Command,
961+
},
962+
Build {
963+
conf_cmd: &'a mut Command,
964+
build_cmd: &'a mut Command,
965+
},
966+
}
967+
968+
fn run_cmake_action(build_dir: &Path, mut action: CMakeAction) {
969+
let program = "cmake";
970+
let cmd = match &mut action {
971+
CMakeAction::Configure { conf_cmd } => conf_cmd,
972+
CMakeAction::Build { build_cmd, .. } => build_cmd,
973+
};
974+
let need_rerun = match run_and_check_if_need_reconf(*cmd, program) {
975+
Ok(x) => x,
976+
Err(err) => {
977+
handle_cmake_exec_result(Err(err), program);
978+
return;
979+
}
980+
};
981+
if need_rerun {
982+
println!("Looks like toolchain was changed");
983+
//just in case some wrong value was cached
984+
let _ = fs::remove_file(&build_dir.join(CMAKE_CACHE_FILE));
985+
match action {
986+
CMakeAction::Configure { conf_cmd } => run(conf_cmd, program),
987+
CMakeAction::Build {
988+
conf_cmd,
989+
build_cmd,
990+
} => {
991+
run(conf_cmd, program);
992+
run(build_cmd, program);
993+
}
994+
}
995+
}
996+
}
997+
998+
// Acording to
999+
// https://gitlab.kitware.com/cmake/cmake/-/issues/18959
1000+
// CMake does not support usage of the same build directory for different
1001+
// compilers. The problem is that we can not make sure that we use the same compiler
1002+
// before running of CMake without CMake's logic duplication (for example consider
1003+
// usage of CMAKE_TOOLCHAIN_FILE). Fortunately for us, CMake can detect is
1004+
// compiler changed by itself. This is done for interactive CMake's configuration,
1005+
// like ccmake/cmake-gui. But after compiler change CMake resets all cached variables.
1006+
fn run_and_check_if_need_reconf(cmd: &mut Command, program: &str) -> Result<bool, io::Error> {
1007+
println!("running: {:?}", cmd);
1008+
let mut child = cmd.spawn()?;
1009+
let mut child_stderr = child.stderr.take().expect("Internal error no stderr");
1010+
let full_stderr = Arc::new(Mutex::new(Vec::<u8>::with_capacity(1024)));
1011+
let full_stderr2 = full_stderr.clone();
1012+
let stderr_thread = thread::spawn(move || {
1013+
let mut full_stderr = full_stderr2
1014+
.lock()
1015+
.expect("Internal error: Lock of stderr buffer failed");
1016+
log_and_copy_stream(&mut child_stderr, &mut io::stderr(), &mut full_stderr)
1017+
});
1018+
1019+
let mut child_stdout = child.stdout.take().expect("Internal error no stdout");
1020+
let mut full_stdout = Vec::with_capacity(1024);
1021+
log_and_copy_stream(&mut child_stdout, &mut io::stdout(), &mut full_stdout)?;
1022+
stderr_thread
1023+
.join()
1024+
.expect("Internal stderr thread join failed")?;
1025+
1026+
static RESET_MSG: &[u8] = b"Configure will be re-run and you may have to reset some variables";
1027+
let full_stderr = full_stderr
1028+
.lock()
1029+
.expect("Internal error stderr lock failed");
1030+
if contains(&full_stderr, RESET_MSG) || contains(&full_stdout, RESET_MSG) {
1031+
return Ok(true);
1032+
} else {
1033+
handle_cmake_exec_result(child.wait(), program);
1034+
return Ok(false);
1035+
}
1036+
}
1037+
9461038
fn run(cmd: &mut Command, program: &str) {
9471039
println!("running: {:?}", cmd);
948-
let status = match cmd.status() {
1040+
handle_cmake_exec_result(cmd.status(), program);
1041+
}
1042+
1043+
fn contains(haystack: &[u8], needle: &[u8]) -> bool {
1044+
haystack
1045+
.windows(needle.len())
1046+
.any(|window| window == needle)
1047+
}
1048+
1049+
fn log_and_copy_stream<R: Read, W: Write>(
1050+
reader: &mut R,
1051+
writer: &mut W,
1052+
log: &mut Vec<u8>,
1053+
) -> io::Result<()> {
1054+
let mut buf = [0; 80];
1055+
loop {
1056+
let len = match reader.read(&mut buf) {
1057+
Ok(0) => break,
1058+
Ok(len) => len,
1059+
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
1060+
Err(e) => return Err(e),
1061+
};
1062+
log.extend_from_slice(&buf[0..len]);
1063+
writer.write_all(&buf[0..len])?;
1064+
}
1065+
Ok(())
1066+
}
1067+
1068+
fn handle_cmake_exec_result(r: Result<ExitStatus, io::Error>, program: &str) {
1069+
let status = match r {
9491070
Ok(status) => status,
9501071
Err(ref e) if e.kind() == ErrorKind::NotFound => {
9511072
fail(&format!(

0 commit comments

Comments
 (0)