Files
compile/util/builder/cpp_builder.py
2025-07-07 14:12:53 +12:00

122 lines
4.6 KiB
Python

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:
instruction = [
build.platform.value.compiler,
"-o", os.path.join(self._build_dir, f"{build.name}_{build.platform.value}"),
f"-march={build.platform.value.architecture}",
"-I", self._include_dir,
*self.find_source_files(self._source_dir)
]
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"]
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,
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}'.")