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