shuriken: Refactor merged target configs

This commit is contained in:
Lexi / Zoe 2026-05-19 17:47:32 +02:00
parent d5d480d854
commit acd53e17d1
Signed by: binaryDiv
GPG key ID: F8D4956E224DA232

View file

@ -10,7 +10,6 @@
############################################
import argparse
import copy
import json
import shlex
import subprocess
@ -18,7 +17,7 @@ import sys
from collections import namedtuple
from dataclasses import asdict, dataclass, field
from pathlib import Path
from typing import Any
from typing import Any, Self
import yaml
@ -60,6 +59,38 @@ class TargetConfig:
return f"{self.linker_args or ''} {self.linker_args_extra or ''}".strip()
@dataclass(kw_only=True)
class MergedTargetConfig(TargetConfig):
cpp_compiler: str = ''
cpp_standard: str = ''
cpp_flags: str = ''
cpp_flags_extra: str = ''
linker_flags: str = ''
linker_flags_extra: str = ''
linker_args: str = ''
linker_args_extra: str = ''
output_file: str = ''
run_command: str = ''
@classmethod
def create_from(cls, target_defaults: TargetConfig, target_config: TargetConfig) -> Self:
merged_config = cls()
merged_config.update(asdict(target_defaults))
merged_config.update(asdict(target_config))
return merged_config
def validate(self) -> None:
# Validate that all required fields are set
if not self.cpp_compiler:
raise ConfigValidationException('cpp_compiler must be set')
if not self.cpp_standard:
raise ConfigValidationException('cpp_standard must be set')
if not self.output_file:
raise ConfigValidationException('output_file must be set')
@dataclass(kw_only=True)
class ShurikenConfig:
__path_fields = ['source_dir', 'build_dir']
@ -72,9 +103,6 @@ class ShurikenConfig:
defaults: TargetConfig = field(default_factory=lambda: TargetConfig(
cpp_compiler='clang++',
cpp_standard='c++20',
cpp_flags='',
linker_flags='',
linker_args='',
))
enable_import_std: bool = False
@ -82,6 +110,9 @@ class ShurikenConfig:
default_target: str = ''
targets: dict[str, TargetConfig] = field(default_factory=dict)
# Merged target configs, generated from defaults and targets
_merged_target_configs = {}
def update(self, data: dict) -> None:
for key, value in data.items():
# Ignore unknown config keys
@ -116,6 +147,10 @@ class ShurikenConfig:
else:
setattr(self, key, str(value))
# Generate merged target configs (we generate all of them so that we can validate the full config)
for target_name, target_config in self.targets.items():
self._merged_target_configs[target_name] = MergedTargetConfig.create_from(self.defaults, target_config)
def update_from_yaml(self, file_path: Path) -> None:
with file_path.open('r') as file:
self.update(yaml.safe_load(file))
@ -137,23 +172,18 @@ class ShurikenConfig:
raise ConfigValidationException('default_target is not defined in targets')
# Validate individual build targets
for target_name, target_config in self.targets.items():
if not target_config.cpp_compiler and not self.defaults.cpp_compiler:
raise ConfigValidationException(f'Target "{target_name}": cpp_compiler must be set')
if not target_config.cpp_standard and not self.defaults.cpp_standard:
raise ConfigValidationException(f'Target "{target_name}": cpp_standard must be set')
if not target_config.output_file and not self.defaults.output_file:
raise ConfigValidationException(f'Target "{target_name}": output_file must be set')
for target_name, merged_target_config in self._merged_target_configs.items():
try:
merged_target_config.validate()
except ConfigValidationException as exc:
raise ConfigValidationException(f'Target "{target_name}": {str(exc)}')
def get_ninja_file_path(self, target: str) -> Path:
return self.build_dir / f'build.{target}.ninja'
def get_merged_target_config(self, target: str) -> TargetConfig:
def get_merged_target_config(self, target: str) -> MergedTargetConfig:
assert target in self.targets, f'Target "{target}" not defined!'
merged_config = copy.deepcopy(self.defaults)
merged_config.update(asdict(self.targets[target]))
return merged_config
return self._merged_target_configs[target]
class ShurikenArgumentParser: