17
17
directories.
18
18
"""
19
19
20
+ import concurrent .futures
20
21
import logging
21
22
import multiprocessing
22
23
import os
@@ -780,21 +781,53 @@ def write_debug_scripts(self, env_script_content: str) -> None:
780
781
781
782
run_program (['chmod' , 'u+x' , make_script_path ])
782
783
783
- def run_make_install (self , make_cmd : List [str ], make_cmd_suffix : List [str ]) -> None :
784
- work_dir = os .getcwd ()
784
+ def run_make_install (self , make_cmd : List [str ], make_cmd_suffix : List [str ], cwd : str ) -> None :
785
785
complete_make_install_cmd = make_cmd + ['install' ] + make_cmd_suffix
786
786
start_time_sec = time .time ()
787
- with TimestampSaver (self .pg_prefix , file_suffix = '.h' ) as _ :
788
- run_program (
789
- shlex_join (complete_make_install_cmd ),
790
- stdout_stderr_prefix = 'make_install' ,
791
- cwd = work_dir ,
792
- error_ok = True ,
793
- # TODO: get rid of shell=True.
794
- shell = True ,
795
- ).print_output_and_raise_error_if_failed ()
796
- logging .info ("Successfully ran 'make install' in the %s directory in %.2f sec" ,
797
- work_dir , time .time () - start_time_sec )
787
+ run_program (
788
+ shlex_join (complete_make_install_cmd ),
789
+ stdout_stderr_prefix = 'make_install' ,
790
+ cwd = cwd ,
791
+ error_ok = True ,
792
+ # TODO: get rid of shell=True.
793
+ shell = True ,
794
+ ).print_output_and_raise_error_if_failed ()
795
+ logging .info ("Successfully ran 'make install' in the %s directory in %.2f sec" ,
796
+ cwd , time .time () - start_time_sec )
797
+
798
+ def _build_directory_with_make (
799
+ self ,
800
+ work_dir : str ,
801
+ make_cmd : List [str ],
802
+ make_cmd_suffix : List [str ]
803
+ ) -> Optional [str ]:
804
+ if is_verbose_mode ():
805
+ logging .info ("Running make in the %s directory" , work_dir )
806
+
807
+ complete_make_cmd = make_cmd + make_cmd_suffix
808
+ complete_make_cmd_str = shlex_join (complete_make_cmd )
809
+ self .run_make_with_retries (work_dir , complete_make_cmd_str )
810
+
811
+ if self .build_type != 'compilecmds' or work_dir == self .pg_build_root :
812
+ self .run_make_install (make_cmd , make_cmd_suffix , work_dir )
813
+ else :
814
+ logging .info (
815
+ "Not running 'make install' in the %s directory since we are only "
816
+ "generating the compilation database" , work_dir )
817
+
818
+ if self .export_compile_commands and not self .skip_pg_compile_commands :
819
+ logging .info ("Generating the compilation database in directory '%s'" , work_dir )
820
+
821
+ compile_commands_path = os .path .join (work_dir , 'compile_commands.json' )
822
+ if not os .path .exists (compile_commands_path ):
823
+ run_program (
824
+ ['compiledb' , 'make' , '-n' ] + make_cmd_suffix , cwd = work_dir , capture_output = False )
825
+
826
+ if not os .path .exists (compile_commands_path ):
827
+ raise RuntimeError ("Failed to generate compilation database at: %s" %
828
+ compile_commands_path )
829
+ return compile_commands_path
830
+ return None
798
831
799
832
def make_postgres (self ) -> None :
800
833
self .set_env_vars ('make' )
@@ -814,54 +847,64 @@ def make_postgres(self) -> None:
814
847
815
848
external_extension_dirs = [os .path .join (self .pg_build_root , d ) for d
816
849
in ('third-party-extensions' , 'yb-extensions' )]
850
+
817
851
work_dirs = [
818
- self .pg_build_root ,
819
852
os .path .join (self .pg_build_root , 'contrib' ),
820
853
os .path .join (self .pg_build_root , 'src/test/modules/dummy_seclabel' ),
821
854
os .path .join (self .pg_build_root , 'src/tools/pg_bsd_indent' ),
822
855
] + external_extension_dirs
823
856
824
- # TODO(#27196): parallelize this for loop.
825
- for work_dir in work_dirs :
826
- # Postgresql requires MAKELEVEL to be 0 or non-set when calling its make.
827
- # But in the case where the YB project is built with make,
828
- # MAKELEVEL is not 0 at this point. We temporarily unset MAKELEVEL to
829
- # deal with this.
830
- with WorkDirContext (work_dir ), SavedEnviron ('MAKELEVEL' ):
831
- self .write_debug_scripts (env_script_content )
832
-
857
+ def build_directory_worker (work_dir : str ) -> Optional [str ]:
858
+ if work_dir in external_extension_dirs :
859
+ make_cmd_suffix = ['PG_CONFIG=' + self .pg_config_path ]
860
+ else :
833
861
make_cmd_suffix = []
834
- if work_dir in external_extension_dirs :
835
- make_cmd_suffix = ['PG_CONFIG=' + self .pg_config_path ]
836
862
837
- # Actually run Make.
838
- if is_verbose_mode ():
839
- logging .info ("Running make in the %s directory" , work_dir )
863
+ return self ._build_directory_with_make (
864
+ work_dir , make_cmd , make_cmd_suffix )
840
865
841
- complete_make_cmd = make_cmd + make_cmd_suffix
842
- complete_make_cmd_str = shlex_join (complete_make_cmd )
843
- self .run_make_with_retries (work_dir , complete_make_cmd_str )
844
-
845
- if self .build_type != 'compilecmds' or work_dir == self .pg_build_root :
846
- self .run_make_install (make_cmd , make_cmd_suffix )
847
- else :
848
- logging .info (
849
- "Not running 'make install' in the %s directory since we are only "
850
- "generating the compilation database" , work_dir )
851
-
852
- if self .export_compile_commands and not self .skip_pg_compile_commands :
853
- logging .info ("Generating the compilation database in directory '%s'" , work_dir )
854
-
855
- compile_commands_path = os .path .join (work_dir , 'compile_commands.json' )
856
- with SavedEnviron (YB_PG_SKIP_CONFIG_STATUS = '1' ):
857
- if not os .path .exists (compile_commands_path ):
858
- run_program (
859
- ['compiledb' , 'make' , '-n' ] + make_cmd_suffix , capture_output = False )
860
-
861
- if not os .path .exists (compile_commands_path ):
862
- raise RuntimeError ("Failed to generate compilation database at: %s" %
863
- compile_commands_path )
864
- pg_compile_commands_paths .append (compile_commands_path )
866
+ with TimestampSaver (self .pg_prefix , file_suffix = '.h' ) as _ :
867
+ # Postgresql requires MAKELEVEL to be 0 or non-set when calling its make.
868
+ # But in the case where the YB project is built with make,
869
+ # MAKELEVEL is not 0 at this point. We temporarily unset MAKELEVEL to
870
+ # deal with this
871
+ with SavedEnviron ('MAKELEVEL' , YB_PG_SKIP_CONFIG_STATUS = '1' ):
872
+ # Build main postgres directory first as it's a dependency for other directories.
873
+ main_compile_commands_path = self ._build_directory_with_make (
874
+ self .pg_build_root , make_cmd , [])
875
+ if main_compile_commands_path :
876
+ pg_compile_commands_paths .append (main_compile_commands_path )
877
+
878
+ self .write_debug_scripts (env_script_content )
879
+
880
+ pg_config_dir = os .path .dirname (self .pg_config_path )
881
+
882
+ # Temporarily add the directory with pg_config to PATH so that
883
+ # contrib/ and other makefiles can find it.
884
+ # necessary for MacOs arm64 since pg_config is in /opt/homebrew/bin
885
+ with SavedEnviron ("MAKELEVEL" , PATH = f"{ pg_config_dir } :{ os .environ .get ('PATH' , '' )} " , YB_PG_SKIP_CONFIG_STATUS = '1' ):
886
+ with concurrent .futures .ThreadPoolExecutor (max_workers = min (make_parallelism , len (work_dirs ))) as executor :
887
+ future_to_dir = {
888
+ executor .submit (build_directory_worker , work_dir ): work_dir
889
+ for work_dir in work_dirs
890
+ }
891
+ errors = []
892
+
893
+ for future in concurrent .futures .as_completed (future_to_dir ):
894
+ work_dir = future_to_dir [future ]
895
+ try :
896
+ compile_commands_path = future .result ()
897
+ if compile_commands_path :
898
+ pg_compile_commands_paths .append (compile_commands_path )
899
+ logging .info ("Successfully completed build for %s" , work_dir )
900
+ except Exception as exc :
901
+ logging .exception ("Build failed for directory %s" , work_dir )
902
+ errors .append ((work_dir , exc ))
903
+ if errors :
904
+ error_msgs = "\n " .join (
905
+ f"Directory { work_dir } failed with error: { exc } "
906
+ for work_dir , exc in errors )
907
+ raise RuntimeError (f"Some directories failed to build:\n { error_msgs } " )
865
908
866
909
if self .export_compile_commands :
867
910
self .write_compile_commands_files (pg_compile_commands_paths )
0 commit comments