Compare commits
2
Commits
3c3ec7c354
...
6b88f01a05
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b88f01a05
|
||
|
|
9c41dc36e3
|
@@ -174,7 +174,8 @@ class GnucDriver(CompilerDriver):
|
||||
args = [
|
||||
f"{compiler}",
|
||||
f"-c {str(srcpath)}",
|
||||
f"-o {str(dstpath)}"
|
||||
f"-o {str(dstpath)}",
|
||||
f"-MMD -MP -MF {str(dstpath.with_suffix('.d'))}"
|
||||
]
|
||||
if options.pic:
|
||||
args.append("-fPIC")
|
||||
|
||||
@@ -4,6 +4,8 @@ from ..models import Project
|
||||
from ..models import Target
|
||||
from ._except import NoProjectFoundError
|
||||
from ._latest import latest
|
||||
from ._latest import read_depfile
|
||||
from ._latest import stale
|
||||
from ._load import load
|
||||
|
||||
|
||||
|
||||
@@ -21,13 +21,14 @@ class _FlattenContext:
|
||||
_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)
|
||||
# Keyed by value so the same entry at different access levels isn't repeated.
|
||||
_incs_set: set[str] = field(init=False, default_factory=set)
|
||||
_dirs_set: set[str] = field(init=False, default_factory=set)
|
||||
_opts_set: set[str] = field(init=False, default_factory=set)
|
||||
_defs_set: set[str] = field(init=False, default_factory=set)
|
||||
_deps_set: set[str] = field(init=False, default_factory=set)
|
||||
_libs_set: set[str] = field(init=False, default_factory=set)
|
||||
_srcs_set: set[str] = field(init=False, default_factory=set)
|
||||
|
||||
@property
|
||||
def incs(self) -> list[Accessor]:
|
||||
@@ -68,45 +69,45 @@ class _FlattenContext:
|
||||
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:
|
||||
if inc.level == Access.PRIVATE or str(inc.value) in self._incs_set:
|
||||
return
|
||||
self._incs_set.add(inc)
|
||||
self._incs_set.add(str(inc.value))
|
||||
self._incs_list.insert(0, inc)
|
||||
|
||||
def append_dir(self, lib: Accessor) -> None:
|
||||
if lib.level == Access.PRIVATE or lib in self._dirs_set:
|
||||
if lib.level == Access.PRIVATE or str(lib.value) in self._dirs_set:
|
||||
return
|
||||
self._dirs_set.add(lib)
|
||||
self._dirs_set.add(str(lib.value))
|
||||
self._dirs_list.insert(0, lib)
|
||||
|
||||
def append_opt(self, opt: Accessor) -> None:
|
||||
if opt.level == Access.PRIVATE or opt in self._opts_set:
|
||||
if opt.level == Access.PRIVATE or str(opt.value) in self._opts_set:
|
||||
return
|
||||
self._opts_set.add(opt)
|
||||
self._opts_set.add(str(opt.value))
|
||||
self._opts_list.insert(0, opt)
|
||||
|
||||
def append_def(self, dfn: Accessor) -> None:
|
||||
if dfn.level == Access.PRIVATE or dfn in self._defs_set:
|
||||
if dfn.level == Access.PRIVATE or str(dfn.value) in self._defs_set:
|
||||
return
|
||||
self._defs_set.add(dfn)
|
||||
self._defs_set.add(str(dfn.value))
|
||||
self._defs_list.insert(0, dfn)
|
||||
|
||||
def append_dep(self, dep: Accessor) -> None:
|
||||
if dep.level == Access.PRIVATE or dep in self._deps_set:
|
||||
if dep.level == Access.PRIVATE or str(dep.value) in self._deps_set:
|
||||
return
|
||||
self._deps_set.add(dep)
|
||||
self._deps_set.add(str(dep.value))
|
||||
self._deps_list.insert(0, dep)
|
||||
|
||||
def append_lib(self, lib: Accessor) -> None:
|
||||
if lib.level == Access.PRIVATE or lib in self._libs_set:
|
||||
if lib.level == Access.PRIVATE or str(lib.value) in self._libs_set:
|
||||
return
|
||||
self._libs_set.add(lib)
|
||||
self._libs_set.add(str(lib.value))
|
||||
self._libs_list.insert(0, lib)
|
||||
|
||||
def append_src(self, src: Accessor) -> None:
|
||||
if src.level == Access.PRIVATE or src in self._srcs_set:
|
||||
if src.level == Access.PRIVATE or str(src.value) in self._srcs_set:
|
||||
return
|
||||
self._srcs_set.add(src)
|
||||
self._srcs_set.add(str(src.value))
|
||||
self._srcs_list.insert(0, src)
|
||||
|
||||
|
||||
@@ -114,18 +115,7 @@ 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)
|
||||
_flatten_target(_target_context(project, target), target)
|
||||
|
||||
def flatten_tests(project: Project) -> None:
|
||||
for target in project.tests:
|
||||
@@ -142,6 +132,20 @@ def flatten_tests(project: Project) -> None:
|
||||
))
|
||||
_flatten_test(context, target)
|
||||
|
||||
def _target_context(project: Project, target: Target) -> _FlattenContext:
|
||||
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
|
||||
))
|
||||
return context
|
||||
|
||||
def _flatten_target(context: _FlattenContext, target: Target) -> None:
|
||||
if target._flattend:
|
||||
return
|
||||
@@ -150,14 +154,7 @@ def _flatten_target(context: _FlattenContext, target: Target) -> None:
|
||||
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
|
||||
_extend_target(context, target)
|
||||
|
||||
def _flatten_test(context: _FlattenContext, target: Target) -> None:
|
||||
if target._flattend:
|
||||
@@ -167,15 +164,25 @@ def _flatten_test(context: _FlattenContext, target: Target) -> None:
|
||||
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)
|
||||
_extend_target(context, target)
|
||||
|
||||
def _extend_target(context: _FlattenContext, target: Target) -> None:
|
||||
_extend_unique(target.incs.includes, context.incs)
|
||||
_extend_unique(target.incs.libraries, context.dirs)
|
||||
_extend_unique(target.opts, context.opts)
|
||||
_extend_unique(target.defs, context.defs)
|
||||
_extend_unique(target.deps, context.deps)
|
||||
_extend_unique(target.libs, context.libs)
|
||||
_extend_unique(target.srcs, context.srcs)
|
||||
target._flattend = True
|
||||
|
||||
def _extend_unique(dst: list[Accessor], src: list[Accessor]) -> None:
|
||||
values = {str(v.value) for v in dst}
|
||||
for accessor in src:
|
||||
if str(accessor.value) not in values:
|
||||
values.add(str(accessor.value))
|
||||
dst.append(accessor)
|
||||
|
||||
def _target_inherit_target(context: _FlattenContext, parent: Target) -> None:
|
||||
if parent.name in tests_by_name_mapping:
|
||||
raise ValueError("Cannot inherit tests")
|
||||
@@ -188,7 +195,8 @@ 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)
|
||||
# Parents flatten in their own context so the child's entries don't leak into them.
|
||||
_flatten_target(_target_context(parent_project, parent), parent)
|
||||
context.inherit_target(parent)
|
||||
context.append_inc(Accessor(
|
||||
level = Access.PROTECTED,
|
||||
@@ -202,4 +210,3 @@ def _inherit_raw(context: _FlattenContext, parent: Target) -> None:
|
||||
level = Access.PROTECTED,
|
||||
value = parent.name
|
||||
))
|
||||
|
||||
|
||||
@@ -1,10 +1,37 @@
|
||||
import re
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Iterable
|
||||
|
||||
|
||||
def latest(src: Path, dst: Path) -> bool:
|
||||
return not stale(dst, [src])
|
||||
|
||||
def stale(dst: Path, inputs: Iterable[Path]) -> bool:
|
||||
if not Path.exists(dst):
|
||||
return True
|
||||
|
||||
dstmtime = dst.stat().st_mtime
|
||||
for src in inputs:
|
||||
if not Path.exists(src) or src.stat().st_mtime >= dstmtime:
|
||||
return True
|
||||
return False
|
||||
|
||||
srcmtime = src.stat().st_mtime
|
||||
dstmtime = dst.stat().st_mtime
|
||||
return srcmtime < dstmtime
|
||||
def read_depfile(path: Path) -> list[Path]:
|
||||
if not Path.exists(path):
|
||||
return []
|
||||
|
||||
with open(path, 'r') as file:
|
||||
text = file.read().replace("\\\n", " ")
|
||||
|
||||
# Only the first rule matters, the rest are phony rules from -MP.
|
||||
rule = text.split("\n", 1)[0]
|
||||
_, _, prereqs = rule.partition(": ")
|
||||
|
||||
paths: list[Path] = []
|
||||
for token in re.split(r"(?<!\\)\s+", prereqs.strip()):
|
||||
if len(token) == 0:
|
||||
continue
|
||||
token = re.sub(r"\\([ #])", r"\1", token).replace("$$", "$")
|
||||
paths.append(Path(token))
|
||||
return paths
|
||||
|
||||
@@ -45,3 +45,6 @@ class Destination:
|
||||
|
||||
def obj(self, file: Path) -> Path:
|
||||
return self.objpath / f"{file}.o"
|
||||
|
||||
def dep(self, file: Path) -> Path:
|
||||
return self.objpath / f"{file}.d"
|
||||
|
||||
@@ -30,6 +30,7 @@ class Target(yaml.YAMLObject):
|
||||
# Internal to the tool.
|
||||
_flattend: bool = field(init=False,default=False)
|
||||
_prepared: bool = field(init=False,default=False)
|
||||
_rebuilt: bool = field(init=False,default=False)
|
||||
|
||||
def __deepcopy__(self, memo) -> Self:
|
||||
return Target(
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
from logging import getLogger
|
||||
from logging import Logger
|
||||
|
||||
from ..models import Output
|
||||
from ..models import Project
|
||||
from ..models import Result
|
||||
from ..models import Target
|
||||
from .._config import Config
|
||||
from ._commands import Command
|
||||
from ._except import NoTargetsFoundError
|
||||
from ._execute import execute
|
||||
from ._prepare import prepare
|
||||
from ._prepare import prepare_dependencies
|
||||
|
||||
_logger = getLogger("chinook")
|
||||
|
||||
@@ -24,31 +23,6 @@ def build(
|
||||
|
||||
commands: list[Command] = []
|
||||
for target in project.targets:
|
||||
commands.extend(_prepare_dependencies(appcfg, project, target, logger))
|
||||
commands.extend(prepare_dependencies(appcfg, project, target, logger))
|
||||
commands.extend(prepare(appcfg, project, target, logger))
|
||||
return execute(commands, "Building")
|
||||
|
||||
def _prepare_dependencies(
|
||||
appcfg: Config,
|
||||
project: Project,
|
||||
target: Target,
|
||||
logger: Logger = _logger
|
||||
) -> list[Command]:
|
||||
from ..management import find_target_by_name
|
||||
from ..management import find_project_by_target
|
||||
|
||||
commands: list[Command] = []
|
||||
|
||||
for required in target.deps:
|
||||
dependency = find_target_by_name(required.value)
|
||||
if dependency.type == Output.IMP or \
|
||||
dependency.type == Output.INT or \
|
||||
dependency._prepared:
|
||||
continue
|
||||
|
||||
parent = find_project_by_target(required.value)
|
||||
if project.cid != parent.cid:
|
||||
if required.value not in parent.exports:
|
||||
raise Exception(f"Cannot depend on non-exported target: {required.value}")
|
||||
commands.extend(prepare(appcfg, parent, dependency, logger))
|
||||
return commands
|
||||
|
||||
@@ -4,9 +4,10 @@ from pathlib import Path
|
||||
|
||||
from ..compiler import Compiler
|
||||
from ..compiler import CompilerOptions
|
||||
from ..management import latest
|
||||
from ..management import find_target_by_name
|
||||
from ..management import find_project_by_target
|
||||
from ..management import read_depfile
|
||||
from ..management import stale
|
||||
from ..models import Output
|
||||
from ..models import Project
|
||||
from ..models import Target
|
||||
@@ -28,8 +29,10 @@ def prepare(
|
||||
) -> list[Command]:
|
||||
commands: list[Command] = []
|
||||
if target.type == Output.IMP or \
|
||||
target.type == Output.INT:
|
||||
target.type == Output.INT or \
|
||||
target._prepared:
|
||||
return commands
|
||||
target._prepared = True
|
||||
|
||||
match target.type:
|
||||
case Output.EXE: outfile = project.destination.bin(target.name)
|
||||
@@ -49,13 +52,14 @@ def prepare(
|
||||
for file in target.srcs:
|
||||
srcfile = project.origination.src(file.value)
|
||||
dstfile = project.destination.obj(file.value)
|
||||
if not latest(srcfile, dstfile):
|
||||
depfile = project.destination.dep(file.value)
|
||||
objects.append(dstfile)
|
||||
if not Path.exists(depfile) or \
|
||||
stale(dstfile, [srcfile, *read_depfile(depfile)]):
|
||||
dstfile.parent.mkdir(parents=True, exist_ok=True)
|
||||
commands.append(CompileObject(compiler, defopts, logger, srcfile, dstfile))
|
||||
if not latest(srcfile, outfile):
|
||||
outfile.parent.mkdir(parents=True, exist_ok=True)
|
||||
objects.append(dstfile)
|
||||
|
||||
relink = len(commands) > 0 or stale(outfile, objects)
|
||||
defopts.objfiles.extend(objects)
|
||||
del objects
|
||||
|
||||
@@ -66,12 +70,22 @@ def prepare(
|
||||
libtgt = find_target_by_name(library.value)
|
||||
libprj = find_project_by_target(libtgt.name)
|
||||
if libtgt.type == Output.LIB:
|
||||
libfile = libprj.destination.lib(library.value)
|
||||
if libprj.destination.libpath not in defopts.libdirs:
|
||||
defopts.libdirs.append(libprj.destination.libpath)
|
||||
defopts.archives.append(library.value)
|
||||
elif libtgt.type == Output.DLL:
|
||||
defopts.objfiles.append(libprj.destination.dll(library.value))
|
||||
libfile = libprj.destination.dll(library.value)
|
||||
defopts.objfiles.append(libfile)
|
||||
else:
|
||||
continue
|
||||
|
||||
if len(defopts.objfiles) == 0:
|
||||
# Archives don't embed their libraries, so only relink linked outputs.
|
||||
if target.type != Output.LIB and \
|
||||
(libtgt._rebuilt or stale(outfile, [libfile])):
|
||||
relink = True
|
||||
|
||||
if not relink or len(defopts.objfiles) == 0:
|
||||
return commands
|
||||
|
||||
command: Command
|
||||
@@ -81,6 +95,30 @@ def prepare(
|
||||
case Output.DLL: command = CompileDynlib(compiler, defopts, logger, outfile)
|
||||
case Output.OBJ: assert False, "Not Implemented"
|
||||
|
||||
target._prepared = True
|
||||
outfile.parent.mkdir(parents=True, exist_ok=True)
|
||||
target._rebuilt = True
|
||||
commands.append(command)
|
||||
return commands
|
||||
|
||||
def prepare_dependencies(
|
||||
appcfg: Config,
|
||||
project: Project,
|
||||
target: Target,
|
||||
logger: Logger = _logger
|
||||
) -> list[Command]:
|
||||
commands: list[Command] = []
|
||||
|
||||
for required in target.deps:
|
||||
dependency = find_target_by_name(required.value)
|
||||
if dependency.type == Output.IMP or \
|
||||
dependency.type == Output.INT or \
|
||||
dependency._prepared:
|
||||
continue
|
||||
|
||||
parent = find_project_by_target(required.value)
|
||||
if project.cid != parent.cid:
|
||||
if required.value not in parent.exports:
|
||||
raise Exception(f"Cannot depend on non-exported target: {required.value}")
|
||||
commands.extend(prepare_dependencies(appcfg, parent, dependency, logger))
|
||||
commands.extend(prepare(appcfg, parent, dependency, logger))
|
||||
return commands
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
from logging import getLogger
|
||||
from logging import Logger
|
||||
|
||||
from ..models import Output
|
||||
from ..models import Project
|
||||
from ..models import Result
|
||||
from ..models import Target
|
||||
from .._config import Config
|
||||
from ._commands import Command
|
||||
from ._commands import ExecuteTest
|
||||
from ._except import NoTestsFoundError
|
||||
from ._execute import execute
|
||||
from ._prepare import prepare
|
||||
from ._prepare import prepare_dependencies
|
||||
|
||||
_logger = getLogger("chinook")
|
||||
|
||||
@@ -25,7 +24,7 @@ def test(
|
||||
|
||||
commands: list[Command] = []
|
||||
for target in project.tests:
|
||||
commands.extend(_prepare_dependencies(appcfg, project, target, logger))
|
||||
commands.extend(prepare_dependencies(appcfg, project, target, logger))
|
||||
commands.extend(prepare(appcfg, project, target, logger))
|
||||
results = execute(commands, "Building")
|
||||
|
||||
@@ -39,28 +38,3 @@ def test(
|
||||
commands.append(ExecuteTest(None, None, logger, testfile))
|
||||
results.extend(execute(commands, "Testing"))
|
||||
return results
|
||||
|
||||
def _prepare_dependencies(
|
||||
appcfg: Config,
|
||||
project: Project,
|
||||
target: Target,
|
||||
logger: Logger = _logger
|
||||
) -> list[Command]:
|
||||
from ..management import find_target_by_name
|
||||
from ..management import find_project_by_target
|
||||
|
||||
commands: list[Command] = []
|
||||
|
||||
for required in target.deps:
|
||||
dependency = find_target_by_name(required.value)
|
||||
if dependency.type == Output.IMP or \
|
||||
dependency.type == Output.INT or \
|
||||
dependency._prepared:
|
||||
continue
|
||||
|
||||
parent = find_project_by_target(required.value)
|
||||
if project.cid != parent.cid:
|
||||
if required.value not in parent.exports:
|
||||
raise Exception(f"Cannot depend on non-exported target: {required.value}")
|
||||
commands.extend(prepare(appcfg, parent, dependency, logger))
|
||||
return commands
|
||||
|
||||
Reference in New Issue
Block a user