@@ -49,10 +49,11 @@ extern crate cc;
4949use std:: env;
5050use std:: ffi:: { OsStr , OsString } ;
5151use std:: fs:: { self , File } ;
52- use std:: io:: prelude:: * ;
53- use std:: io:: ErrorKind ;
52+ use std:: io:: { self , prelude:: * , ErrorKind } ;
5453use 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.
5859pub 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+
867954fn 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