from util.generic.multithreader import MultiprocessWorker, WorkerProcess, WorkerReturn, Spinners from util.generic.log import Log from util.builder.info import Build, BuildType, Platform from typing import List import subprocess import os import json class CppBuilder: def __init__(self, builds: List[Build] = [], log: Log = None, build_dir: str = "build/", source_dir: str = ".", include_dir: str = "include/"): if log is None: log = Log(self.__class__.__name__, Log.Level.DEBUG) self.logger = log.create_logger() self._builds:List[Build] = builds self._build_dir: str = build_dir self._source_dir: str = source_dir self._include_dir: str = include_dir self.multiprocesser = MultiprocessWorker(spinner_set=Spinners.SPIN_OPEN_CUBE) def find_source_files(self, root_dir:str) -> List[str]: cpp_files = [] for root, _, files in os.walk(root_dir): for file in files: if file.endswith('.c') or file.endswith('.cpp'): relative_path = os.path.relpath(os.path.join(root, file), root_dir) cpp_files.append(relative_path) return cpp_files def build(self): if len(self._builds) > 1: self.logger.log(Log.Level.INFO, "Starting builds...") else: self.logger.log(Log.Level.INFO, "Starting build...") os.makedirs(self._build_dir, exist_ok=True) for build in self._builds: output_filename = ( build.definitive_name if build.definitive_name else f"{build.name}_{build.platform.value}" ) instruction = [ build.platform.value.compiler, "-o", self._build_dir + output_filename, "-I", self._include_dir, *self.find_source_files(self._source_dir) ] if build.platform.value.architecture: instruction.append(f"-march={build.platform.value.architecture}") instruction.append(build.platform.value.code_specification) instruction.extend(build.type.value) instruction.extend(build.additional_instructions) self.multiprocesser.add_task(WorkerProcess(CppBuilder._build_worker, instruction)) results: List[WorkerReturn] = self.multiprocesser.run() for res in results: res.output_result(self.logger, r'-o (\S+)', "SUCCESS: Compiled '{output}'") @staticmethod def _build_worker(instruction): try: process = subprocess.run(instruction, capture_output=True, text=True, check=True) return WorkerReturn(True, ' '.join(instruction), process.stdout, process.stderr, None) except subprocess.CalledProcessError as e: return WorkerReturn(False, ' '.join(instruction), e.stdout, e.stderr, str(e)) except Exception as e: return WorkerReturn(False, ' '.join(instruction), "", "", str(e)) def load_config(self, file_path: str): try: with open(file_path, "r") as file: config = json.load(file) except FileNotFoundError: self.logger.log(Log.Level.ERROR, f"File '{file_path}' not found.") return False except json.JSONDecodeError: self.logger.log(Log.Level.ERROR, f"File '{file_path}' does not contain valid JSON data.") return False except Exception as e: self.logger.log(Log.Level.ERROR, f"An unexpected error occurred: {e}") return False if config is None: return False configurations = config["configurations"] global_build_args = config["global_build_args"] for i in configurations: build_name = i["name"] try: build_definitive_name = i["definitive_name"] except KeyError: build_definitive_name = None build_type_str = i["build_type"] platform_str = i["platform"] if build_type_str not in BuildType.__members__: self.logger.log( Log.Level.ERROR, f"Invalid build_type '{build_type_str}' for build '{build_name}'. Skipping..." ) continue if platform_str not in Platform.__members__: self.logger.log( Log.Level.ERROR, f"Invalid platform '{platform_str}' for build '{build_name}'. Skipping..." ) continue build_type = BuildType[build_type_str] platform = Platform[platform_str] self._builds.append(Build( build_name, build_type, platform, build_definitive_name, i["args"] + global_build_args )) self.logger.log(Log.Level.DEBUG, f"'{build_name}' config loaded from file.") self.logger.log(Log.Level.INFO, f"Configurations successfully loaded from '{file_path}'.")