Skip to content

Commit 7ece076

Browse files
committed
handle cmake's cache reset after toolchain change
1 parent b790781 commit 7ece076

File tree

1 file changed

+125
-9
lines changed

1 file changed

+125
-9
lines changed

src/lib.rs

+125-9
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 {
@@ -482,6 +483,7 @@ impl Config {
482483
// Build up the first cmake command to build the build system.
483484
let executable = env::var("CMAKE").unwrap_or("cmake".to_owned());
484485
let mut conf_cmd = Command::new(&executable);
486+
conf_cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
485487

486488
if self.verbose_cmake {
487489
conf_cmd.arg("-Wdev");
@@ -738,11 +740,14 @@ impl Config {
738740
conf_cmd.env(k, v);
739741
}
740742

743+
conf_cmd.env("CMAKE_PREFIX_PATH", cmake_prefix_path);
744+
conf_cmd.args(&self.configure_args);
741745
if self.always_configure || !build.join(CMAKE_CACHE_FILE).exists() {
742-
conf_cmd.args(&self.configure_args);
743-
run(
744-
conf_cmd.env("CMAKE_PREFIX_PATH", cmake_prefix_path),
745-
"cmake",
746+
run_cmake_action(
747+
&build,
748+
CMakeAction::Configure {
749+
conf_cmd: &mut conf_cmd,
750+
},
746751
);
747752
} else {
748753
println!("CMake project was already configured. Skipping configuration step.");
@@ -790,6 +795,7 @@ impl Config {
790795
// And build!
791796
let target = self.cmake_target.clone().unwrap_or("install".to_string());
792797
let mut build_cmd = Command::new(&executable);
798+
build_cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
793799
for &(ref k, ref v) in c_compiler.env().iter().chain(&self.env) {
794800
build_cmd.env(k, v);
795801
}
@@ -815,7 +821,13 @@ impl Config {
815821
build_cmd.arg(flags);
816822
}
817823

818-
run(&mut build_cmd, "cmake");
824+
run_cmake_action(
825+
&build,
826+
CMakeAction::Build {
827+
build_cmd: &mut build_cmd,
828+
conf_cmd: &mut conf_cmd,
829+
},
830+
);
819831

820832
println!("cargo:root={}", dst.display());
821833
return dst;
@@ -896,9 +908,113 @@ impl Config {
896908
}
897909
}
898910

911+
enum CMakeAction<'a> {
912+
Configure {
913+
conf_cmd: &'a mut Command,
914+
},
915+
Build {
916+
conf_cmd: &'a mut Command,
917+
build_cmd: &'a mut Command,
918+
},
919+
}
920+
921+
fn run_cmake_action(build_dir: &Path, mut action: CMakeAction) {
922+
let program = "cmake";
923+
let cmd = match &mut action {
924+
CMakeAction::Configure { conf_cmd } => conf_cmd,
925+
CMakeAction::Build { build_cmd, .. } => build_cmd,
926+
};
927+
let need_rerun = match run_and_check_if_need_reconf(*cmd) {
928+
Ok(x) => x,
929+
Err(err) => {
930+
handle_cmake_exec_result(Err(err), program);
931+
return;
932+
}
933+
};
934+
if need_rerun {
935+
println!("Looks like toolchain was changed");
936+
//just in case some wrong value was cached
937+
let _ = fs::remove_file(&build_dir.join(CMAKE_CACHE_FILE));
938+
match action {
939+
CMakeAction::Configure { conf_cmd } => run(conf_cmd, program),
940+
CMakeAction::Build {
941+
conf_cmd,
942+
build_cmd,
943+
} => {
944+
run(conf_cmd, program);
945+
run(build_cmd, program);
946+
}
947+
}
948+
}
949+
}
950+
951+
// Acording to
952+
// https://gitlab.kitware.com/cmake/cmake/-/issues/18959
953+
// CMake does not support usage of the same build directory for different
954+
// compilers. The problem is that we can not make sure that we use the same compiler
955+
// before running of CMake without CMake's logic duplication (for example consider
956+
// usage of CMAKE_TOOLCHAIN_FILE). Fortunately for us, CMake can detect is
957+
// compiler changed by itself. This is done for interactive CMake's configuration,
958+
// like ccmake/cmake-gui. But after compiler change CMake resets all cached variables.
959+
fn run_and_check_if_need_reconf(cmd: &mut Command) -> Result<bool, io::Error> {
960+
println!("running: {:?}", cmd);
961+
let mut child = cmd.spawn()?;
962+
let mut child_stderr = child.stderr.take().expect("Internal error no stderr");
963+
let full_stderr = Arc::new(Mutex::new(Vec::<u8>::with_capacity(1024)));
964+
let full_stderr2 = full_stderr.clone();
965+
let stderr_thread = thread::spawn(move || {
966+
let mut full_stderr = full_stderr2
967+
.lock()
968+
.expect("Internal error: Lock of stderr buffer failed");
969+
log_and_copy_stream(&mut child_stderr, &mut io::stderr(), &mut full_stderr)
970+
});
971+
972+
let mut child_stdout = child.stdout.take().expect("Internal error no stdout");
973+
let mut full_stdout = Vec::with_capacity(1024);
974+
log_and_copy_stream(&mut child_stdout, &mut io::stdout(), &mut full_stdout)?;
975+
stderr_thread
976+
.join()
977+
.expect("Internal stderr thread join failed")?;
978+
979+
static RESET_MSG: &[u8] = b"Configure will be re-run and you may have to reset some variables";
980+
let full_stderr = full_stderr
981+
.lock()
982+
.expect("Internal error stderr lock failed");
983+
Ok(contains(&full_stderr, RESET_MSG) || contains(&full_stdout, RESET_MSG))
984+
}
985+
899986
fn run(cmd: &mut Command, program: &str) {
900987
println!("running: {:?}", cmd);
901-
let status = match cmd.status() {
988+
handle_cmake_exec_result(cmd.status(), program);
989+
}
990+
991+
fn contains(haystack: &[u8], needle: &[u8]) -> bool {
992+
haystack
993+
.windows(needle.len())
994+
.any(|window| window == needle)
995+
}
996+
997+
fn log_and_copy_stream<R: Read, W: Write>(
998+
reader: &mut R,
999+
writer: &mut W,
1000+
log: &mut Vec<u8>,
1001+
) -> io::Result<()> {
1002+
let mut buf = [0; 80];
1003+
loop {
1004+
let len = match reader.read(&mut buf) {
1005+
Ok(0) => break,
1006+
Ok(len) => len,
1007+
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
1008+
Err(e) => return Err(e),
1009+
};
1010+
log.extend_from_slice(&buf[0..len]);
1011+
writer.write_all(&buf[0..len])?;
1012+
}
1013+
Ok(())
1014+
}
1015+
1016+
fn handle_cmake_exec_result(r: Result<ExitStatus, io::Error>, program: &str) {
1017+
let status = match r {
9021018
Ok(status) => status,
9031019
Err(ref e) if e.kind() == ErrorKind::NotFound => {
9041020
fail(&format!(

0 commit comments

Comments
 (0)