Multiprocess linking + better messages

This commit is contained in:
Corentin 2021-06-29 01:47:50 +09:00
commit 2abe6c7953

107
umake.py
View file

@ -8,7 +8,6 @@ from pathlib import Path
import subprocess import subprocess
import shutil import shutil
import sys import sys
from typing import List
class Config: class Config:
@ -31,6 +30,18 @@ class Config:
CPP_SOURCES = SOURCE_DIR.rglob('*.cpp') CPP_SOURCES = SOURCE_DIR.rglob('*.cpp')
class ConsoleColor:
"""Simple shortcut to use colors in console"""
HEADER = '\033[95m'
BLUE = '\033[94m'
GREEN = '\033[92m'
ORANGE = '\033[93m'
RED = '\033[91m'
ENDCOLOR = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
def get_hash(path: Path) -> str: def get_hash(path: Path) -> str:
with open(path, 'r') as hashing_file: with open(path, 'r') as hashing_file:
hash_obj = hashlib.md5() hash_obj = hashlib.md5()
@ -94,36 +105,39 @@ def make(config: Config):
# Running compilation processes # Running compilation processes
if not os.path.exists(config.OBJECT_DIR): if not os.path.exists(config.OBJECT_DIR):
os.makedirs(config.OBJECT_DIR) os.makedirs(config.OBJECT_DIR)
error_path = None error_paths: list[tuple[Path, str]] = []
if arguments.j > 1: # Multi-process if arguments.j > 1: # Multi-process
jobs: List[subprocess.Popen] = [] jobs: list[tuple[Path, Path, subprocess.Popen]] = []
for source_path, source_hash, cmd in todo_list: for source_path, source_hash, cmd in todo_list:
print(cmd) print(ConsoleColor.BLUE + cmd + ConsoleColor.ENDCOLOR)
jobs.insert(0, (source_path, source_hash, subprocess.Popen(cmd, shell=True))) # FIFO style (will be poped) jobs.insert(0, (source_path, source_hash, subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True,
shell=True))) # FIFO style (will be poped)
if len(jobs) >= arguments.j: # If jobs count is maxed we wait for the oldest one to finished if len(jobs) >= arguments.j: # If jobs count is maxed we wait for the oldest one to finished
job_path, job_hash, oldest_job = jobs.pop() job_path, job_hash, oldest_job = jobs.pop()
oldest_job.wait() oldest_job.wait()
if oldest_job.returncode != 0: if oldest_job.returncode != 0:
error_path = job_path error_paths.append((job_path, oldest_job.stdout.read() + oldest_job.stderr.read()))
break break
hash_dict[str(job_path)] = job_hash # Update hash if no error hash_dict[str(job_path)] = job_hash # Update hash if no error
if error_path is None: for job_path, job_hash, job in jobs: # Wait the last jobs to finish
for job_path, job_hash, job in jobs: # Wait the last jobs to finish job.wait()
job.wait() if job.returncode != 0:
if job.returncode != 0: error_paths.append((job_path, job.stdout.read() + job.stderr.read()))
error_path = job_path else:
break
hash_dict[str(job_path)] = job_hash # Update hash if no error hash_dict[str(job_path)] = job_hash # Update hash if no error
else: # Single-process else: # Single-process
for source_path, source_hash, cmd in todo_list: for source_path, source_hash, cmd in todo_list:
print(cmd) print(cmd)
complete = subprocess.run(cmd, check=False, shell=True) job = subprocess.run(cmd, check=False, stdin=subprocess.PIPE, stderr=subprocess.PIPE,
if complete.returncode != 0: universal_newlines=True, shell=True)
error_path = source_path if job.returncode != 0:
error_paths.append((source_path, job.stdout.read() + job.stderr.read()))
break break
hash_dict[str(source_path)] = source_hash # Update hash if no error hash_dict[str(source_path)] = source_hash # Update hash if no error
if error_path: if error_paths:
print(f'Error compiling {error_path}') for error_path, error_text in error_paths:
print(ConsoleColor.RED + f'Error compiling {error_path}:\n' + ConsoleColor.ENDCOLOR + error_text)
with open(config.OBJECT_DIR / 'hash.json', 'w') as hash_file: with open(config.OBJECT_DIR / 'hash.json', 'w') as hash_file:
hash_file.write(json.dumps(hash_dict, indent=1)) hash_file.write(json.dumps(hash_dict, indent=1))
sys.exit(1) sys.exit(1)
@ -133,23 +147,52 @@ def make(config: Config):
config.BIN_DIR.mkdir(parents=True) config.BIN_DIR.mkdir(parents=True)
all_app_objects = [config.OBJECT_DIR / Path(app_path).parent / (Path(app_path).stem + '.o') all_app_objects = [config.OBJECT_DIR / Path(app_path).parent / (Path(app_path).stem + '.o')
for app_path in config.APPS] for app_path in config.APPS]
for app_path, app_object_path in zip(config.APPS, all_app_objects): if arguments.j > 1: # Multi-process
if 'IGNORE_APPS' in config.__dict__ and app_path in config.IGNORE_APPS: jobs: list[tuple[Path, subprocess.Popen]] = []
continue for app_path, app_object_path in zip(config.APPS, all_app_objects):
bin_path = config.BIN_DIR / app_path if 'IGNORE_APPS' in config.__dict__ and app_path in config.IGNORE_APPS:
if not bin_path.parent.exists(): continue
bin_path.parent.mkdir(parents=True) bin_path = config.BIN_DIR / app_path
object_files = [str(object_path) for _, object_path in compile_list if not bin_path.parent.exists():
if object_path not in all_app_objects] bin_path.parent.mkdir(parents=True)
object_files.append(str(app_object_path)) object_files = [str(object_path) for _, object_path in compile_list
cmd = ' '.join([config.CC, config.COMMON_FLAGS, *object_files, '-o', str(bin_path), config.LINK_FLAGS]) if object_path not in all_app_objects]
complete = subprocess.run(cmd, check=False, shell=True) object_files.append(str(app_object_path))
if complete.returncode != 0: cmd = ' '.join([config.CC, config.COMMON_FLAGS, *object_files, '-o', str(bin_path), config.LINK_FLAGS])
error_path = app_path print(ConsoleColor.BLUE + cmd + ConsoleColor.ENDCOLOR)
if error_path: jobs.insert(0, (app_path, subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True,
shell=True))) # FIFO style (will be poped)
if len(jobs) >= arguments.j: # If jobs count is maxed we wait for the oldest one to finished
app_path, oldest_job = jobs.pop()
oldest_job.wait()
if oldest_job.returncode != 0:
error_paths.append((source_path, oldest_job.stdout.read() + oldest_job.stderr.read()))
break
for job_path, job in jobs: # Wait the last jobs to finish
job.wait()
if job.returncode != 0:
error_paths.append((source_path, job.stdout.read() + job.stderr.read()))
else: # Single-process
for app_path, app_object_path in zip(config.APPS, all_app_objects):
if 'IGNORE_APPS' in config.__dict__ and app_path in config.IGNORE_APPS:
continue
bin_path = config.BIN_DIR / app_path
if not bin_path.parent.exists():
bin_path.parent.mkdir(parents=True)
object_files = [str(object_path) for _, object_path in compile_list
if object_path not in all_app_objects]
object_files.append(str(app_object_path))
cmd = ' '.join([config.CC, config.COMMON_FLAGS, *object_files, '-o', str(bin_path), config.LINK_FLAGS])
complete = subprocess.run(cmd, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True, shell=True)
if complete.returncode != 0:
error_paths.append(app_path)
if error_paths:
for error_path, error_text in error_paths:
print(ConsoleColor.RED + f'Error linking {error_path}:\n' + ConsoleColor.ENDCOLOR + error_text)
with open(os.path.join(config.OBJECT_DIR, 'hash.json'), 'w') as hash_file: with open(os.path.join(config.OBJECT_DIR, 'hash.json'), 'w') as hash_file:
hash_file.write(json.dumps(hash_dict, indent=1)) hash_file.write(json.dumps(hash_dict, indent=1))
print(f'Error linking {error_path}')
sys.exit(1) sys.exit(1)
# Updating header hashes only if everything compiled and linked correctly # Updating header hashes only if everything compiled and linked correctly