@@ -50,10 +50,11 @@ use std::collections::HashMap;
5050use std:: env;
5151use std:: ffi:: { OsStr , OsString } ;
5252use std:: fs:: { self , File } ;
53- use std:: io:: prelude:: * ;
54- use std:: io:: ErrorKind ;
53+ use std:: io:: { self , prelude:: * , ErrorKind } ;
5554use 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.
5960pub 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+
9471039fn 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