@@ -50,10 +50,11 @@ use std::collections::HashMap;
50
50
use std:: env;
51
51
use std:: ffi:: { OsStr , OsString } ;
52
52
use std:: fs:: { self , File } ;
53
- use std:: io:: prelude:: * ;
54
- use std:: io:: ErrorKind ;
53
+ use std:: io:: { self , prelude:: * , ErrorKind } ;
55
54
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 } ;
57
58
58
59
/// Builder style configuration for a pending CMake build.
59
60
pub struct Config {
@@ -510,6 +511,7 @@ impl Config {
510
511
. getenv_target_os ( "CMAKE" )
511
512
. unwrap_or ( OsString :: from ( "cmake" ) ) ;
512
513
let mut conf_cmd = Command :: new ( & executable) ;
514
+ conf_cmd. stdout ( Stdio :: piped ( ) ) . stderr ( Stdio :: piped ( ) ) ;
513
515
514
516
if self . verbose_cmake {
515
517
conf_cmd. arg ( "-Wdev" ) ;
@@ -779,11 +781,14 @@ impl Config {
779
781
conf_cmd. env ( k, v) ;
780
782
}
781
783
784
+ conf_cmd. env ( "CMAKE_PREFIX_PATH" , cmake_prefix_path) ;
785
+ conf_cmd. args ( & self . configure_args ) ;
782
786
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
+ } ,
787
792
) ;
788
793
} else {
789
794
println ! ( "CMake project was already configured. Skipping configuration step." ) ;
@@ -793,6 +798,7 @@ impl Config {
793
798
let target = self . cmake_target . clone ( ) . unwrap_or ( "install" . to_string ( ) ) ;
794
799
let mut build_cmd = Command :: new ( & executable) ;
795
800
build_cmd. current_dir ( & build) ;
801
+ build_cmd. stdout ( Stdio :: piped ( ) ) . stderr ( Stdio :: piped ( ) ) ;
796
802
797
803
for & ( ref k, ref v) in c_compiler. env ( ) . iter ( ) . chain ( & self . env ) {
798
804
build_cmd. env ( k, v) ;
@@ -836,7 +842,13 @@ impl Config {
836
842
build_cmd. arg ( "--" ) . args ( & self . build_args ) ;
837
843
}
838
844
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
+ ) ;
840
852
841
853
println ! ( "cargo:root={}" , dst. display( ) ) ;
842
854
return dst;
@@ -943,9 +955,118 @@ impl Config {
943
955
}
944
956
}
945
957
958
+ enum CMakeAction < ' a > {
959
+ Configure {
960
+ conf_cmd : & ' a mut Command ,
961
+ } ,
962
+ Build {
963
+ conf_cmd : & ' a mut Command ,
964
+ build_cmd : & ' a mut Command ,
965
+ } ,
966
+ }
967
+
968
+ fn run_cmake_action ( build_dir : & Path , mut action : CMakeAction ) {
969
+ let program = "cmake" ;
970
+ let cmd = match & mut action {
971
+ CMakeAction :: Configure { conf_cmd } => conf_cmd,
972
+ CMakeAction :: Build { build_cmd, .. } => build_cmd,
973
+ } ;
974
+ let need_rerun = match run_and_check_if_need_reconf ( * cmd, program) {
975
+ Ok ( x) => x,
976
+ Err ( err) => {
977
+ handle_cmake_exec_result ( Err ( err) , program) ;
978
+ return ;
979
+ }
980
+ } ;
981
+ if need_rerun {
982
+ println ! ( "Looks like toolchain was changed" ) ;
983
+ //just in case some wrong value was cached
984
+ let _ = fs:: remove_file ( & build_dir. join ( CMAKE_CACHE_FILE ) ) ;
985
+ match action {
986
+ CMakeAction :: Configure { conf_cmd } => run ( conf_cmd, program) ,
987
+ CMakeAction :: Build {
988
+ conf_cmd,
989
+ build_cmd,
990
+ } => {
991
+ run ( conf_cmd, program) ;
992
+ run ( build_cmd, program) ;
993
+ }
994
+ }
995
+ }
996
+ }
997
+
998
+ // Acording to
999
+ // https://gitlab.kitware.com/cmake/cmake/-/issues/18959
1000
+ // CMake does not support usage of the same build directory for different
1001
+ // compilers. The problem is that we can not make sure that we use the same compiler
1002
+ // before running of CMake without CMake's logic duplication (for example consider
1003
+ // usage of CMAKE_TOOLCHAIN_FILE). Fortunately for us, CMake can detect is
1004
+ // compiler changed by itself. This is done for interactive CMake's configuration,
1005
+ // like ccmake/cmake-gui. But after compiler change CMake resets all cached variables.
1006
+ fn run_and_check_if_need_reconf ( cmd : & mut Command , program : & str ) -> Result < bool , io:: Error > {
1007
+ println ! ( "running: {:?}" , cmd) ;
1008
+ let mut child = cmd. spawn ( ) ?;
1009
+ let mut child_stderr = child. stderr . take ( ) . expect ( "Internal error no stderr" ) ;
1010
+ let full_stderr = Arc :: new ( Mutex :: new ( Vec :: < u8 > :: with_capacity ( 1024 ) ) ) ;
1011
+ let full_stderr2 = full_stderr. clone ( ) ;
1012
+ let stderr_thread = thread:: spawn ( move || {
1013
+ let mut full_stderr = full_stderr2
1014
+ . lock ( )
1015
+ . expect ( "Internal error: Lock of stderr buffer failed" ) ;
1016
+ log_and_copy_stream ( & mut child_stderr, & mut io:: stderr ( ) , & mut full_stderr)
1017
+ } ) ;
1018
+
1019
+ let mut child_stdout = child. stdout . take ( ) . expect ( "Internal error no stdout" ) ;
1020
+ let mut full_stdout = Vec :: with_capacity ( 1024 ) ;
1021
+ log_and_copy_stream ( & mut child_stdout, & mut io:: stdout ( ) , & mut full_stdout) ?;
1022
+ stderr_thread
1023
+ . join ( )
1024
+ . expect ( "Internal stderr thread join failed" ) ?;
1025
+
1026
+ static RESET_MSG : & [ u8 ] = b"Configure will be re-run and you may have to reset some variables" ;
1027
+ let full_stderr = full_stderr
1028
+ . lock ( )
1029
+ . expect ( "Internal error stderr lock failed" ) ;
1030
+ if contains ( & full_stderr, RESET_MSG ) || contains ( & full_stdout, RESET_MSG ) {
1031
+ return Ok ( true ) ;
1032
+ } else {
1033
+ handle_cmake_exec_result ( child. wait ( ) , program) ;
1034
+ return Ok ( false ) ;
1035
+ }
1036
+ }
1037
+
946
1038
fn run ( cmd : & mut Command , program : & str ) {
947
1039
println ! ( "running: {:?}" , cmd) ;
948
- let status = match cmd. status ( ) {
1040
+ handle_cmake_exec_result ( cmd. status ( ) , program) ;
1041
+ }
1042
+
1043
+ fn contains ( haystack : & [ u8 ] , needle : & [ u8 ] ) -> bool {
1044
+ haystack
1045
+ . windows ( needle. len ( ) )
1046
+ . any ( |window| window == needle)
1047
+ }
1048
+
1049
+ fn log_and_copy_stream < R : Read , W : Write > (
1050
+ reader : & mut R ,
1051
+ writer : & mut W ,
1052
+ log : & mut Vec < u8 > ,
1053
+ ) -> io:: Result < ( ) > {
1054
+ let mut buf = [ 0 ; 80 ] ;
1055
+ loop {
1056
+ let len = match reader. read ( & mut buf) {
1057
+ Ok ( 0 ) => break ,
1058
+ Ok ( len) => len,
1059
+ Err ( ref e) if e. kind ( ) == ErrorKind :: Interrupted => continue ,
1060
+ Err ( e) => return Err ( e) ,
1061
+ } ;
1062
+ log. extend_from_slice ( & buf[ 0 ..len] ) ;
1063
+ writer. write_all ( & buf[ 0 ..len] ) ?;
1064
+ }
1065
+ Ok ( ( ) )
1066
+ }
1067
+
1068
+ fn handle_cmake_exec_result ( r : Result < ExitStatus , io:: Error > , program : & str ) {
1069
+ let status = match r {
949
1070
Ok ( status) => status,
950
1071
Err ( ref e) if e. kind ( ) == ErrorKind :: NotFound => {
951
1072
fail ( & format ! (
0 commit comments