Dependency/archive ordering causes compilation failure

This commit is contained in:
2026-07-02 22:58:59 -05:00
parent dd62839d5d
commit c7fba4144b
8 changed files with 267 additions and 108 deletions
+6 -6
View File
@@ -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,
+12 -12
View File
@@ -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)
)
+203
View File
@@ -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
))
+5 -69
View File
@@ -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:
+4 -4
View File
@@ -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
+26 -10
View File
@@ -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
+7 -7
View File
@@ -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
+4
View File
@@ -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)