Skip to content

Commit dfa2f23

Browse files
committed
handle cmake's cache reset after toolchain change
1 parent 2ac2fe8 commit dfa2f23

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 {
@@ -518,6 +519,7 @@ impl Config {
518519
.getenv_target_os("CMAKE")
519520
.unwrap_or(OsString::from("cmake"));
520521
let mut conf_cmd = Command::new(&executable);
522+
conf_cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
521523

522524
if self.verbose_cmake {
523525
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;
@@ -944,9 +956,118 @@ impl Config {
944956
}
945957
}
946958

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

0 commit comments

Comments
 (0)