From c7fba4144b37654bc53d3a16e3a8fcd180f13ff3 Mon Sep 17 00:00:00 2001 From: SoraKatadzuma Date: Thu, 2 Jul 2026 22:58:59 -0500 Subject: [PATCH] Dependency/archive ordering causes compilation failure --- chinook/compiler/_gnuc.py | 12 +- chinook/compiler/_options.py | 24 ++-- chinook/management/_flatten.py | 203 +++++++++++++++++++++++++++++++++ chinook/management/_load.py | 74 +----------- chinook/models/_includes.py | 8 +- chinook/models/_target.py | 36 ++++-- chinook/pipeline/_prepare.py | 14 +-- chinook/pipeline/_test.py | 4 + 8 files changed, 267 insertions(+), 108 deletions(-) create mode 100644 chinook/management/_flatten.py diff --git a/chinook/compiler/_gnuc.py b/chinook/compiler/_gnuc.py index 2258c4e..7652a51 100644 --- a/chinook/compiler/_gnuc.py +++ b/chinook/compiler/_gnuc.py @@ -42,10 +42,10 @@ class GnucDriver(CompilerDriver): args.extend(options.flags) if len(options.archives) > 0: - for directory in options.libdirs: args.append(f"-L{str(directory)}") - for archive in options.archives: args.append(f"-l{archive}") + args.extend([f"-L{str(directory)}" for directory in options.libdirs]) + args.extend([f"-l{str(archive)}" for archive in options.archives]) cmdstr = ' '.join(args) - self.logger.info(cmdstr) + # self.logger.info(cmdstr) result = Result( success = False, @@ -126,8 +126,8 @@ class GnucDriver(CompilerDriver): args.extend(options.flags) if len(options.archives) > 0: - for directory in options.libdirs: args.append(f"-L{str(directory)}") - for archive in options.archives: args.append(f"-l{archive}") + args.extend([f"-L{str(directory)}" for directory in options.libdirs]) + args.extend([f"-l{str(archive)}" for archive in options.archives]) cmdstr = ' '.join(args) # self.logger.info(cmdstr) @@ -186,7 +186,7 @@ class GnucDriver(CompilerDriver): for archive in options.archives: args.append(f"-l{archive}") for define in options.defines: args.append(f"-D{define}") cmdstr = ' '.join(args) - self.logger.info(cmdstr) + # self.logger.info(cmdstr) result = Result( success = False, diff --git a/chinook/compiler/_options.py b/chinook/compiler/_options.py index 4bd4860..19fd38f 100644 --- a/chinook/compiler/_options.py +++ b/chinook/compiler/_options.py @@ -16,12 +16,12 @@ class CompilerOptions: stdcpp: StdCpp = StdCpp.C14 optlevel: Optimization = Optimization.NONE - incdirs: set[Path] = field(default_factory=set) - libdirs: set[Path] = field(default_factory=set) - objfiles: set[Path] = field(default_factory=set) - archives: set[str] = field(default_factory=set) - defines: set[str] = field(default_factory=set) - flags: set[str] = field(default_factory=set) + incdirs: list[Path] = field(default_factory=list) + libdirs: list[Path] = field(default_factory=list) + objfiles: list[Path] = field(default_factory=list) + archives: list[str] = field(default_factory=list) + defines: list[str] = field(default_factory=list) + flags: list[str] = field(default_factory=list) def __deepcopy__(self, memo) -> Self: return CompilerOptions( @@ -30,10 +30,10 @@ class CompilerOptions: self.stdc, self.stdcpp, self.optlevel, - set(self.incdirs), - set(self.libdirs), - set(self.objfiles), - set(self.archives), - set(self.defines), - set(self.flags) + list(self.incdirs), + list(self.libdirs), + list(self.objfiles), + list(self.archives), + list(self.defines), + list(self.flags) ) diff --git a/chinook/management/_flatten.py b/chinook/management/_flatten.py new file mode 100644 index 0000000..7974205 --- /dev/null +++ b/chinook/management/_flatten.py @@ -0,0 +1,203 @@ +from dataclasses import dataclass +from dataclasses import field + +from ..models import Access +from ..models import Accessor +from ..models import Output +from ..models import Project +from ..models import Target +from ._shared import targets_by_name_mapping +from ._shared import tests_by_name_mapping +from ._shared import project_by_targets_mapping + + +@dataclass +class _FlattenContext: + _incs_list: list[Accessor] = field(init=False, default_factory=list) + _dirs_list: list[Accessor] = field(init=False, default_factory=list) + _opts_list: list[Accessor] = field(init=False, default_factory=list) + _defs_list: list[Accessor] = field(init=False, default_factory=list) + _deps_list: list[Accessor] = field(init=False, default_factory=list) + _libs_list: list[Accessor] = field(init=False, default_factory=list) + _srcs_list: list[Accessor] = field(init=False, default_factory=list) + + _incs_set: set[Accessor] = field(init=False, default_factory=set) + _dirs_set: set[Accessor] = field(init=False, default_factory=set) + _opts_set: set[Accessor] = field(init=False, default_factory=set) + _defs_set: set[Accessor] = field(init=False, default_factory=set) + _deps_set: set[Accessor] = field(init=False, default_factory=set) + _libs_set: set[Accessor] = field(init=False, default_factory=set) + _srcs_set: set[Accessor] = field(init=False, default_factory=set) + + @property + def incs(self) -> list[Accessor]: + return self._incs_list + + @property + def dirs(self) -> list[Accessor]: + return self._dirs_list + + @property + def opts(self) -> list[Accessor]: + return self._opts_list + + @property + def defs(self) -> list[Accessor]: + return self._defs_list + + @property + def deps(self) -> list[Accessor]: + return self._deps_list + + @property + def libs(self) -> list[Accessor]: + return self._libs_list + + @property + def srcs(self) -> list[Accessor]: + return self._srcs_list + + def inherit_target(self, target: Target) -> None: + for opt in target.opts: self.append_opt(opt) + for dfn in target.defs: self.append_def(dfn) + for dep in target.deps: self.append_dep(dep) + for lib in target.libs: self.append_lib(lib) + if target.type == Output.INT: + for src in target.srcs: self.append_src(src) + + def append_inc(self, inc: Accessor) -> None: + if inc.level == Access.PRIVATE or inc in self._incs_set: + return + self._incs_set.add(inc) + self._incs_list.insert(0, inc) + + def append_dir(self, lib: Accessor) -> None: + if lib.level == Access.PRIVATE or lib in self._dirs_set: + return + self._dirs_set.add(lib) + self._dirs_list.insert(0, lib) + + def append_opt(self, opt: Accessor) -> None: + if opt.level == Access.PRIVATE or opt in self._opts_set: + return + self._opts_set.add(opt) + self._opts_list.insert(0, opt) + + def append_def(self, dfn: Accessor) -> None: + if dfn.level == Access.PRIVATE or dfn in self._defs_set: + return + self._defs_set.add(dfn) + self._defs_list.insert(0, dfn) + + def append_dep(self, dep: Accessor) -> None: + if dep.level == Access.PRIVATE or dep in self._deps_set: + return + self._deps_set.add(dep) + self._deps_list.insert(0, dep) + + def append_lib(self, lib: Accessor) -> None: + if lib.level == Access.PRIVATE or lib in self._libs_set: + return + self._libs_set.add(lib) + self._libs_list.insert(0, lib) + + def append_src(self, src: Accessor) -> None: + if src.level == Access.PRIVATE or src in self._srcs_set: + return + self._srcs_set.add(src) + self._srcs_list.insert(0, src) + + +def flatten_targets(project: Project) -> None: + for target in project.targets: + if target._flattend: + continue + context = _FlattenContext() + context.append_inc(Accessor( + level = Access.PUBLIC \ + if target.name in project.exports \ + else Access.PROTECTED, + value = project.origination.src("include") + )) + context.append_dir(Accessor( + level = Access.PUBLIC, + value = project.destination.libpath + )) + _flatten_target(context, target) + +def flatten_tests(project: Project) -> None: + for target in project.tests: + if target._flattend: + continue + context = _FlattenContext() + context.append_inc(Accessor( + level = Access.PRIVATE, + value = project.origination.src("include") + )) + context.append_dir(Accessor( + level = Access.PRIVATE, + value = project.destination.libpath + )) + _flatten_test(context, target) + +def _flatten_target(context: _FlattenContext, target: Target) -> None: + if target._flattend: + return + + for dependency in target.deps: + if (parent:=targets_by_name_mapping.get(dependency.value)) == None: + raise ValueError(f"No such dependable target: {dependency.value}") + _target_inherit_target(context, parent) + target.incs.includes.extend(context.incs) + target.incs.libraries.extend(context.dirs) + target.opts.extend(context.opts) + target.defs.extend(context.defs) + target.deps.extend(context.deps) + target.libs.extend(context.libs) + target.srcs.extend(context.srcs) + target._flattend = True + +def _flatten_test(context: _FlattenContext, target: Target) -> None: + if target._flattend: + return + + for dependency in target.deps: + if (parent:=targets_by_name_mapping.get(dependency.value)) == None: + raise ValueError(f"No such dependable target: {dependency.value}") + _test_inherit_target(context, parent) + target.incs.includes.extend(context.incs) + target.incs.libraries.extend(context.dirs) + target.opts.extend(context.opts) + target.defs.extend(context.defs) + target.deps.extend(context.deps) + target.libs.extend(context.libs) + target.srcs.extend(context.srcs) + target._flattend = True + +def _target_inherit_target(context: _FlattenContext, parent: Target) -> None: + if parent.name in tests_by_name_mapping: + raise ValueError("Cannot inherit tests") + _inherit_raw(context, parent) + +def _test_inherit_target(context: _FlattenContext, parent: Target) -> None: + _inherit_raw(context, parent) + +def _inherit_raw(context: _FlattenContext, parent: Target) -> None: + parent_project = project_by_targets_mapping.get(parent.name) + assert parent_project is not None, "Parent project not mapped!" + + _flatten_target(context, parent) + context.inherit_target(parent) + context.append_inc(Accessor( + level = Access.PROTECTED, + value = parent_project.origination.src("include") + )) + context.append_dir(Accessor( + level = Access.PROTECTED, + value = parent_project.destination.libpath + )) + context.append_lib(Accessor( + level = Access.PROTECTED, + value = parent.name + )) + diff --git a/chinook/management/_load.py b/chinook/management/_load.py index d7eb3ec..4edfbf5 100644 --- a/chinook/management/_load.py +++ b/chinook/management/_load.py @@ -1,18 +1,17 @@ import yaml +from copy import deepcopy from pathlib import Path -from ..models import Access -from ..models import Accessor from ..models import Dependency from ..models import Destination from ..models import Origination -from ..models import Output from ..models import Project -from ..models import Target from ..shell import execute from .._config import Config from ._except import NoProjectFoundError +from ._flatten import flatten_targets +from ._flatten import flatten_tests from ._shared import projects_by_name_mapping from ._shared import targets_by_name_mapping from ._shared import tests_by_name_mapping @@ -37,8 +36,8 @@ def load(path: Path) -> Project: _collect_required(project) _collect_targets(project) _collect_tests(project) - _flatten_targets(project) - _flatten_tests(project) + flatten_targets(project) + flatten_tests(project) return project except Exception as rethrowme: raise rethrowme @@ -64,69 +63,6 @@ def _collect_tests(project: Project) -> None: if test.name not in tests_by_name_mapping: tests_by_name_mapping[test.name] = test -def _flatten_targets(project: Project) -> None: - for target in project.targets: - _flatten_target(target) - target.incs.includes.add(Accessor( - level = Access.PUBLIC \ - if target.name in project.exports \ - else Access.PRIVATE, - value = project.origination.src("include") - )) - target.incs.libraries.add(Accessor( - level = Access.PUBLIC, - value = project.destination.libpath - )) - -def _flatten_tests(project: Project) -> None: - for test in project.tests: - _flatten_target(test) - test.incs.includes.add(Accessor( - level = Access.PRIVATE, - value = project.origination.src("include") - )) - test.incs.libraries.add(Accessor( - level = Access.PRIVATE, - value = project.destination.libpath - )) - -def _flatten_target(target: Target) -> None: - for dependency in target.deps: - if (parent:=targets_by_name_mapping.get(dependency.value)) == None: - raise ValueError(f"No such dependable target: {dependency.value}") - _inherit_target(target, parent) - -def _inherit_target(target: Target, parent: Target) -> None: - if parent.name in tests_by_name_mapping: - raise ValueError("Cannot inherit tests") - - _flatten_target(parent) - target.incs.includes.update([v for v in parent.incs.includes if v.level != Access.PRIVATE]) - target.incs.libraries.update([v for v in parent.incs.libraries if v.level != Access.PRIVATE]) - - parent_project = project_by_targets_mapping.get(parent.name) - assert parent_project is not None, "Parent project not mapped!" - target.incs.includes.add(Accessor( - level = Access.PUBLIC, - value = parent_project.origination.src("include") - )) - target.incs.libraries.add(Accessor( - level = Access.PUBLIC, - value = parent_project.destination.libpath - )) - - target.opts.update([v for v in parent.opts if v.level != Access.PRIVATE]) - target.defs.update([v for v in parent.defs if v.level != Access.PRIVATE]) - target.deps.update([v for v in parent.deps if v.level != Access.PRIVATE]) - target.libs.update([v for v in parent.libs if v.level != Access.PRIVATE]) - target.libs.add(Accessor( - level = Access.PUBLIC, - value = parent.name - )) - - if parent.type == Output.INT: - target.srcs.update([v for v in parent.srcs if v.level != Access.PRIVATE]) - def _load_required(required: Dependency) -> None: # Determine if local then load or clone then load. if required.local: diff --git a/chinook/models/_includes.py b/chinook/models/_includes.py index 5d254df..ea93748 100644 --- a/chinook/models/_includes.py +++ b/chinook/models/_includes.py @@ -16,8 +16,8 @@ class Includes(yaml.YAMLObject): yaml_loader: ClassVar[Any] = yaml.SafeLoader yaml_tag: ClassVar[str] = u"!includes" - includes: set[Accessor] = field(default_factory=set) - libraries: set[Accessor] = field(default_factory=set) + includes: list[Accessor] = field(default_factory=list) + libraries: list[Accessor] = field(default_factory=list) @classmethod def to_dict(cls, data: Self) -> dict: @@ -38,8 +38,8 @@ class Includes(yaml.YAMLObject): @classmethod def from_dict(cls, data: dict) -> Self: return cls(**{ - "includes": set(Accessor.from_list(data.get("includes", []))), - "libraries": set(Accessor.from_list(data.get("libraries", []))) + "includes": Accessor.from_list(data.get("includes", [])), + "libraries": Accessor.from_list(data.get("libraries", [])) }) @classmethod diff --git a/chinook/models/_target.py b/chinook/models/_target.py index cdd2f6c..57763c1 100644 --- a/chinook/models/_target.py +++ b/chinook/models/_target.py @@ -21,15 +21,31 @@ class Target(yaml.YAMLObject): name: str type: Output incs: Includes - opts: set[Accessor] = field(default_factory=set) - defs: set[Accessor] = field(default_factory=set) - deps: set[Accessor] = field(default_factory=set) - libs: set[Accessor] = field(default_factory=set) - srcs: set[Accessor] = field(default_factory=set) + opts: list[Accessor] = field(default_factory=list) + defs: list[Accessor] = field(default_factory=list) + deps: list[Accessor] = field(default_factory=list) + libs: list[Accessor] = field(default_factory=list) + srcs: list[Accessor] = field(default_factory=list) # Internal to the tool. + _flattend: bool = field(init=False,default=False) _prepared: bool = field(init=False,default=False) + def __deepcopy__(self, memo) -> Self: + return Target( + self.name, + self.type, + Includes( + list(self.incs.includes), + list(self.incs.libraries) + ), + list(self.opts), + list(self.defs), + list(self.deps), + list(self.libs), + list(self.srcs) + ) + @classmethod def to_dict(cls, data: Self) -> dict: return { @@ -69,11 +85,11 @@ class Target(yaml.YAMLObject): "name": data.get("name") or bad_attr("name", data), "type": data.get("type") or bad_attr("type", data), "incs": Includes.from_dict(data.get("incs", {})), - "opts": set(Accessor.from_list(data.get("opts", []))), - "defs": set(Accessor.from_list(data.get("defs", []))), - "deps": set(Accessor.from_list(data.get("deps", []))), - "libs": set(Accessor.from_list(data.get("libs", []))), - "srcs": set(Accessor.from_list(data.get("srcs", []))) + "opts": Accessor.from_list(data.get("opts", [])), + "defs": Accessor.from_list(data.get("defs", [])), + "deps": Accessor.from_list(data.get("deps", [])), + "libs": Accessor.from_list(data.get("libs", [])), + "srcs": Accessor.from_list(data.get("srcs", [])) }) @classmethod diff --git a/chinook/pipeline/_prepare.py b/chinook/pipeline/_prepare.py index adc8ee6..19d9429 100644 --- a/chinook/pipeline/_prepare.py +++ b/chinook/pipeline/_prepare.py @@ -43,9 +43,9 @@ def prepare( defopts = CompilerOptions() defopts.pic = target.type == Output.DLL - defopts.incdirs.update([v.value for v in target.incs.includes]) - defopts.defines.update([v.value for v in target.defs]) - defopts.flags.update([v.value for v in target.opts]) + defopts.incdirs.extend([v.value for v in target.incs.includes]) + defopts.defines.extend([v.value for v in target.defs]) + defopts.flags.extend([v.value for v in target.opts]) for file in target.srcs: srcfile = project.origination.src(file.value) dstfile = project.destination.obj(file.value) @@ -56,7 +56,7 @@ def prepare( outfile.parent.mkdir(parents=True, exist_ok=True) objects.append(dstfile) - defopts.objfiles.update(objects) + defopts.objfiles.extend(objects) del objects defopts.pic = False @@ -66,10 +66,10 @@ def prepare( libtgt = find_target_by_name(library.value) libprj = find_project_by_target(libtgt.name) if libtgt.type == Output.LIB: - defopts.libdirs.add(libprj.destination.libpath) - defopts.archives.add(library.value) + defopts.libdirs.append(libprj.destination.libpath) + defopts.archives.append(library.value) elif libtgt.type == Output.DLL: - defopts.objfiles.add(libprj.destination.dll(library.value)) + defopts.objfiles.append(libprj.destination.dll(library.value)) if len(defopts.objfiles) == 0: return commands diff --git a/chinook/pipeline/_test.py b/chinook/pipeline/_test.py index d1dde38..7fafd82 100644 --- a/chinook/pipeline/_test.py +++ b/chinook/pipeline/_test.py @@ -29,6 +29,10 @@ def test( commands.extend(prepare(appcfg, project, target, logger)) results = execute(commands, "Building") + for result in results: + if not result.success: + return results + commands.clear() for target in project.tests: testfile = project.destination.bin(target.name)