Skip to content

Commit 6518357

Browse files
committed
handle cmake's cache reset after toolchain change
1 parent eca9940 commit 6518357

File tree

1 file changed

+109
-9
lines changed

1 file changed

+109
-9
lines changed

src/lib.rs

Lines changed: 109 additions & 9 deletions
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 {
@@ -386,6 +387,7 @@ impl Config {
386387
// Build up the first cmake command to build the build system.
387388
let executable = env::var("CMAKE").unwrap_or("cmake".to_owned());
388389
let mut conf_cmd = Command::new(&executable);
390+
conf_cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
389391

390392
if self.verbose_cmake {
391393
conf_cmd.arg("-Wdev");
@@ -698,11 +700,9 @@ impl Config {
698700
conf_cmd.env(k, v);
699701
}
700702

703+
conf_cmd.env("CMAKE_PREFIX_PATH", cmake_prefix_path);
701704
if self.always_configure || !build.join(CMAKE_CACHE_FILE).exists() {
702-
run(
703-
conf_cmd.env("CMAKE_PREFIX_PATH", cmake_prefix_path),
704-
"cmake",
705-
);
705+
run_configure(&build, &mut conf_cmd);
706706
} else {
707707
println!("CMake project was already configured. Skipping configuration step.");
708708
}
@@ -749,6 +749,7 @@ impl Config {
749749
// And build!
750750
let target = self.cmake_target.clone().unwrap_or("install".to_string());
751751
let mut build_cmd = Command::new(&executable);
752+
build_cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
752753
for &(ref k, ref v) in c_compiler.env().iter().chain(&self.env) {
753754
build_cmd.env(k, v);
754755
}
@@ -774,7 +775,7 @@ impl Config {
774775
build_cmd.arg(flags);
775776
}
776777

777-
run(&mut build_cmd, "cmake");
778+
run_build(&build, &mut build_cmd, &mut conf_cmd);
778779

779780
println!("cargo:root={}", dst.display());
780781
return dst;
@@ -855,9 +856,108 @@ impl Config {
855856
}
856857
}
857858

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

0 commit comments

Comments
 (0)