137 lines
5.1 KiB
Python
137 lines
5.1 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:
|
|
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}'.") |