Skip to content

Commit cd818be

Browse files
committed
build(postgres): add parallelization
1 parent f50df82 commit cd818be

File tree

1 file changed

+95
-52
lines changed

1 file changed

+95
-52
lines changed

python/yugabyte/build_postgres.py

Lines changed: 95 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
directories.
1818
"""
1919

20+
import concurrent.futures
2021
import logging
2122
import multiprocessing
2223
import os
@@ -780,21 +781,53 @@ def write_debug_scripts(self, env_script_content: str) -> None:
780781

781782
run_program(['chmod', 'u+x', make_script_path])
782783

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:
785785
complete_make_install_cmd = make_cmd + ['install'] + make_cmd_suffix
786786
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
798831

799832
def make_postgres(self) -> None:
800833
self.set_env_vars('make')
@@ -814,54 +847,64 @@ def make_postgres(self) -> None:
814847

815848
external_extension_dirs = [os.path.join(self.pg_build_root, d) for d
816849
in ('third-party-extensions', 'yb-extensions')]
850+
817851
work_dirs = [
818-
self.pg_build_root,
819852
os.path.join(self.pg_build_root, 'contrib'),
820853
os.path.join(self.pg_build_root, 'src/test/modules/dummy_seclabel'),
821854
os.path.join(self.pg_build_root, 'src/tools/pg_bsd_indent'),
822855
] + external_extension_dirs
823856

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:
833861
make_cmd_suffix = []
834-
if work_dir in external_extension_dirs:
835-
make_cmd_suffix = ['PG_CONFIG=' + self.pg_config_path]
836862

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)
840865

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}")
865908

866909
if self.export_compile_commands:
867910
self.write_compile_commands_files(pg_compile_commands_paths)

0 commit comments

Comments
 (0)