Compare commits
12
Commits
ccc69857bb
..
trunk
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b88f01a05
|
||
|
|
9c41dc36e3
|
||
|
|
3c3ec7c354 | ||
|
|
c7fba4144b | ||
|
|
dd62839d5d | ||
|
|
c5e0179edb | ||
|
|
3dee00a2f6 | ||
|
|
19a7fb9312 | ||
|
|
230b982d20 | ||
|
|
58aff1b2bb | ||
|
|
6c4da250c3 | ||
|
|
70b4517559 |
@@ -42,8 +42,8 @@ class GnucDriver(CompilerDriver):
|
|||||||
|
|
||||||
args.extend(options.flags)
|
args.extend(options.flags)
|
||||||
if len(options.archives) > 0:
|
if len(options.archives) > 0:
|
||||||
for directory in options.libdirs: args.append(f"-L{str(directory)}")
|
args.extend([f"-L{str(directory)}" for directory in options.libdirs])
|
||||||
for archive in options.archives: args.append(f"-l{archive}")
|
args.extend([f"-l{str(archive)}" for archive in options.archives])
|
||||||
cmdstr = ' '.join(args)
|
cmdstr = ' '.join(args)
|
||||||
# self.logger.info(cmdstr)
|
# self.logger.info(cmdstr)
|
||||||
|
|
||||||
@@ -126,8 +126,8 @@ class GnucDriver(CompilerDriver):
|
|||||||
|
|
||||||
args.extend(options.flags)
|
args.extend(options.flags)
|
||||||
if len(options.archives) > 0:
|
if len(options.archives) > 0:
|
||||||
for directory in options.libdirs: args.append(f"-L{str(directory)}")
|
args.extend([f"-L{str(directory)}" for directory in options.libdirs])
|
||||||
for archive in options.archives: args.append(f"-l{archive}")
|
args.extend([f"-l{str(archive)}" for archive in options.archives])
|
||||||
cmdstr = ' '.join(args)
|
cmdstr = ' '.join(args)
|
||||||
# self.logger.info(cmdstr)
|
# self.logger.info(cmdstr)
|
||||||
|
|
||||||
@@ -174,7 +174,8 @@ class GnucDriver(CompilerDriver):
|
|||||||
args = [
|
args = [
|
||||||
f"{compiler}",
|
f"{compiler}",
|
||||||
f"-c {str(srcpath)}",
|
f"-c {str(srcpath)}",
|
||||||
f"-o {str(dstpath)}"
|
f"-o {str(dstpath)}",
|
||||||
|
f"-MMD -MP -MF {str(dstpath.with_suffix('.d'))}"
|
||||||
]
|
]
|
||||||
if options.pic:
|
if options.pic:
|
||||||
args.append("-fPIC")
|
args.append("-fPIC")
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ class CompilerOptions:
|
|||||||
stdcpp: StdCpp = StdCpp.C14
|
stdcpp: StdCpp = StdCpp.C14
|
||||||
optlevel: Optimization = Optimization.NONE
|
optlevel: Optimization = Optimization.NONE
|
||||||
|
|
||||||
incdirs: set[Path] = field(default_factory=set)
|
incdirs: list[Path] = field(default_factory=list)
|
||||||
libdirs: set[Path] = field(default_factory=set)
|
libdirs: list[Path] = field(default_factory=list)
|
||||||
objfiles: set[Path] = field(default_factory=set)
|
objfiles: list[Path] = field(default_factory=list)
|
||||||
archives: set[str] = field(default_factory=set)
|
archives: list[str] = field(default_factory=list)
|
||||||
defines: set[str] = field(default_factory=set)
|
defines: list[str] = field(default_factory=list)
|
||||||
flags: set[str] = field(default_factory=set)
|
flags: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
def __deepcopy__(self, memo) -> Self:
|
def __deepcopy__(self, memo) -> Self:
|
||||||
return CompilerOptions(
|
return CompilerOptions(
|
||||||
@@ -30,10 +30,10 @@ class CompilerOptions:
|
|||||||
self.stdc,
|
self.stdc,
|
||||||
self.stdcpp,
|
self.stdcpp,
|
||||||
self.optlevel,
|
self.optlevel,
|
||||||
set(self.incdirs),
|
list(self.incdirs),
|
||||||
set(self.libdirs),
|
list(self.libdirs),
|
||||||
set(self.objfiles),
|
list(self.objfiles),
|
||||||
set(self.archives),
|
list(self.archives),
|
||||||
set(self.defines),
|
list(self.defines),
|
||||||
set(self.flags)
|
list(self.flags)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ from ..models import Project
|
|||||||
from ..models import Target
|
from ..models import Target
|
||||||
from ._except import NoProjectFoundError
|
from ._except import NoProjectFoundError
|
||||||
from ._latest import latest
|
from ._latest import latest
|
||||||
|
from ._latest import read_depfile
|
||||||
|
from ._latest import stale
|
||||||
from ._load import load
|
from ._load import load
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,212 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
# 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]:
|
||||||
|
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 inc in target.incs.includes: self.append_inc(inc)
|
||||||
|
for drn in target.incs.libraries: self.append_dir(drn)
|
||||||
|
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 str(inc.value) in self._incs_set:
|
||||||
|
return
|
||||||
|
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 str(lib.value) in self._dirs_set:
|
||||||
|
return
|
||||||
|
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 str(opt.value) in self._opts_set:
|
||||||
|
return
|
||||||
|
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 str(dfn.value) in self._defs_set:
|
||||||
|
return
|
||||||
|
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 str(dep.value) in self._deps_set:
|
||||||
|
return
|
||||||
|
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 str(lib.value) in self._libs_set:
|
||||||
|
return
|
||||||
|
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 str(src.value) in self._srcs_set:
|
||||||
|
return
|
||||||
|
self._srcs_set.add(str(src.value))
|
||||||
|
self._srcs_list.insert(0, src)
|
||||||
|
|
||||||
|
|
||||||
|
def flatten_targets(project: Project) -> None:
|
||||||
|
for target in project.targets:
|
||||||
|
if target._flattend:
|
||||||
|
continue
|
||||||
|
_flatten_target(_target_context(project, target), 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 _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
|
||||||
|
|
||||||
|
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)
|
||||||
|
_extend_target(context, target)
|
||||||
|
|
||||||
|
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)
|
||||||
|
_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")
|
||||||
|
_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!"
|
||||||
|
|
||||||
|
# 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,
|
||||||
|
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
|
||||||
|
))
|
||||||
@@ -1,10 +1,37 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
|
|
||||||
def latest(src: Path, dst: Path) -> bool:
|
def latest(src: Path, dst: Path) -> bool:
|
||||||
if not Path.exists(dst):
|
return not stale(dst, [src])
|
||||||
return False
|
|
||||||
|
def stale(dst: Path, inputs: Iterable[Path]) -> bool:
|
||||||
|
if not Path.exists(dst):
|
||||||
|
return True
|
||||||
|
|
||||||
srcmtime = src.stat().st_mtime
|
|
||||||
dstmtime = dst.stat().st_mtime
|
dstmtime = dst.stat().st_mtime
|
||||||
return srcmtime < dstmtime
|
for src in inputs:
|
||||||
|
if not Path.exists(src) or src.stat().st_mtime >= dstmtime:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
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
|
||||||
|
|||||||
+15
-69
@@ -1,18 +1,17 @@
|
|||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
from copy import deepcopy
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from ..models import Access
|
|
||||||
from ..models import Accessor
|
|
||||||
from ..models import Dependency
|
from ..models import Dependency
|
||||||
from ..models import Destination
|
from ..models import Destination
|
||||||
from ..models import Origination
|
from ..models import Origination
|
||||||
from ..models import Output
|
|
||||||
from ..models import Project
|
from ..models import Project
|
||||||
from ..models import Target
|
|
||||||
from ..shell import execute
|
from ..shell import execute
|
||||||
from .._config import Config
|
from .._config import Config
|
||||||
from ._except import NoProjectFoundError
|
from ._except import NoProjectFoundError
|
||||||
|
from ._flatten import flatten_targets
|
||||||
|
from ._flatten import flatten_tests
|
||||||
from ._shared import projects_by_name_mapping
|
from ._shared import projects_by_name_mapping
|
||||||
from ._shared import targets_by_name_mapping
|
from ._shared import targets_by_name_mapping
|
||||||
from ._shared import tests_by_name_mapping
|
from ._shared import tests_by_name_mapping
|
||||||
@@ -37,8 +36,8 @@ def load(path: Path) -> Project:
|
|||||||
_collect_required(project)
|
_collect_required(project)
|
||||||
_collect_targets(project)
|
_collect_targets(project)
|
||||||
_collect_tests(project)
|
_collect_tests(project)
|
||||||
_flatten_targets(project)
|
flatten_targets(project)
|
||||||
_flatten_tests(project)
|
flatten_tests(project)
|
||||||
return project
|
return project
|
||||||
except Exception as rethrowme:
|
except Exception as rethrowme:
|
||||||
raise rethrowme
|
raise rethrowme
|
||||||
@@ -51,7 +50,7 @@ def _collect_profiles(project: Project) -> None:
|
|||||||
|
|
||||||
def _collect_required(project: Project) -> None:
|
def _collect_required(project: Project) -> None:
|
||||||
for required in project.requires:
|
for required in project.requires:
|
||||||
_clone_then_load_required(required)
|
_load_required(required)
|
||||||
|
|
||||||
def _collect_targets(project: Project) -> None:
|
def _collect_targets(project: Project) -> None:
|
||||||
for target in project.targets:
|
for target in project.targets:
|
||||||
@@ -64,68 +63,12 @@ def _collect_tests(project: Project) -> None:
|
|||||||
if test.name not in tests_by_name_mapping:
|
if test.name not in tests_by_name_mapping:
|
||||||
tests_by_name_mapping[test.name] = test
|
tests_by_name_mapping[test.name] = test
|
||||||
|
|
||||||
def _flatten_targets(project: Project) -> None:
|
def _load_required(required: Dependency) -> None:
|
||||||
for target in project.targets:
|
# Determine if local then load or clone then load.
|
||||||
_flatten_target(target)
|
if required.local:
|
||||||
target.incs.includes.add(Accessor(
|
load(Path(required.remote))
|
||||||
level = Access.PUBLIC \
|
return
|
||||||
if target.name in project.exports \
|
_clone_then_load_required(required)
|
||||||
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 _clone_then_load_required(required: Dependency) -> None:
|
def _clone_then_load_required(required: Dependency) -> None:
|
||||||
reqpath = Config.config_directory / required.cid
|
reqpath = Config.config_directory / required.cid
|
||||||
@@ -146,6 +89,9 @@ def _clone_required(
|
|||||||
global errstr
|
global errstr
|
||||||
errstr = stderr
|
errstr = stderr
|
||||||
|
|
||||||
|
from .._logger import logger
|
||||||
|
logger.info(f"Cloning {required.remote}...")
|
||||||
|
|
||||||
execute(
|
execute(
|
||||||
f"git clone -b {required.branch} --depth 1 {required.remote} {reqpath}",
|
f"git clone -b {required.branch} --depth 1 {required.remote} {reqpath}",
|
||||||
handle_success,
|
handle_success,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from dataclasses import field
|
||||||
|
|
||||||
from typing import ClassVar
|
from typing import ClassVar
|
||||||
from typing import Self
|
from typing import Self
|
||||||
@@ -15,7 +16,13 @@ class Dependency(yaml.YAMLError):
|
|||||||
yaml_tag: ClassVar[str] = u"!dependency"
|
yaml_tag: ClassVar[str] = u"!dependency"
|
||||||
|
|
||||||
remote: str
|
remote: str
|
||||||
branch: Branch
|
branch: Branch = field(default="main")
|
||||||
|
local: bool = field(default=False)
|
||||||
|
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
url = urlparse(self.remote)
|
||||||
|
self.local = not self.remote.startswith("git@") and not url.scheme
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cid(self) -> str:
|
def cid(self) -> str:
|
||||||
@@ -27,7 +34,7 @@ class Dependency(yaml.YAMLError):
|
|||||||
path = parsed.path.strip("/")
|
path = parsed.path.strip("/")
|
||||||
if path.endswith(".git"):
|
if path.endswith(".git"):
|
||||||
path = path[:-4]
|
path = path[:-4]
|
||||||
return path + str(self.branch)
|
return path + "@" + str(self.branch)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def to_dict(cls, data: Self) -> dict:
|
def to_dict(cls, data: Self) -> dict:
|
||||||
|
|||||||
@@ -24,21 +24,15 @@ class Destination:
|
|||||||
def objpath(self) -> Path:
|
def objpath(self) -> Path:
|
||||||
return self._objpath
|
return self._objpath
|
||||||
|
|
||||||
def __init__(self, outpath: Path, mkdirs=True) -> None:
|
def __init__(self, outpath: Path) -> None:
|
||||||
self._root = outpath / ".chinook"
|
self._root = outpath / ".chinook"
|
||||||
self._binpath = self._root / "bin"
|
self._binpath = self._root / "bin"
|
||||||
self._libpath = self._root / "lib"
|
self._libpath = self._root / "lib"
|
||||||
self._objpath = self._root / "obj"
|
self._objpath = self._root / "obj"
|
||||||
|
|
||||||
if mkdirs:
|
|
||||||
self._root.mkdir(parents=True, exist_ok=True)
|
|
||||||
self._binpath.mkdir(parents=True, exist_ok=True)
|
|
||||||
self._libpath.mkdir(parents=True, exist_ok=True)
|
|
||||||
self._objpath.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def cwd() -> 'Destination':
|
def cwd() -> 'Destination':
|
||||||
return Destination(Path.cwd(), mkdirs=False)
|
return Destination(Path.cwd())
|
||||||
|
|
||||||
def bin(self, file: str) -> Path:
|
def bin(self, file: str) -> Path:
|
||||||
return self.binpath / file
|
return self.binpath / file
|
||||||
@@ -51,3 +45,6 @@ class Destination:
|
|||||||
|
|
||||||
def obj(self, file: Path) -> Path:
|
def obj(self, file: Path) -> Path:
|
||||||
return self.objpath / f"{file}.o"
|
return self.objpath / f"{file}.o"
|
||||||
|
|
||||||
|
def dep(self, file: Path) -> Path:
|
||||||
|
return self.objpath / f"{file}.d"
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ class Includes(yaml.YAMLObject):
|
|||||||
yaml_loader: ClassVar[Any] = yaml.SafeLoader
|
yaml_loader: ClassVar[Any] = yaml.SafeLoader
|
||||||
yaml_tag: ClassVar[str] = u"!includes"
|
yaml_tag: ClassVar[str] = u"!includes"
|
||||||
|
|
||||||
includes: set[Accessor] = field(default_factory=set)
|
includes: list[Accessor] = field(default_factory=list)
|
||||||
libraries: set[Accessor] = field(default_factory=set)
|
libraries: list[Accessor] = field(default_factory=list)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def to_dict(cls, data: Self) -> dict:
|
def to_dict(cls, data: Self) -> dict:
|
||||||
@@ -38,8 +38,8 @@ class Includes(yaml.YAMLObject):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: dict) -> Self:
|
def from_dict(cls, data: dict) -> Self:
|
||||||
return cls(**{
|
return cls(**{
|
||||||
"includes": set(Accessor.from_list(data.get("includes", []))),
|
"includes": Accessor.from_list(data.get("includes", [])),
|
||||||
"libraries": set(Accessor.from_list(data.get("libraries", [])))
|
"libraries": Accessor.from_list(data.get("libraries", []))
|
||||||
})
|
})
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
+27
-10
@@ -21,14 +21,31 @@ class Target(yaml.YAMLObject):
|
|||||||
name: str
|
name: str
|
||||||
type: Output
|
type: Output
|
||||||
incs: Includes
|
incs: Includes
|
||||||
opts: set[Accessor] = field(default_factory=set)
|
opts: list[Accessor] = field(default_factory=list)
|
||||||
defs: set[Accessor] = field(default_factory=set)
|
defs: list[Accessor] = field(default_factory=list)
|
||||||
deps: set[Accessor] = field(default_factory=set)
|
deps: list[Accessor] = field(default_factory=list)
|
||||||
libs: set[Accessor] = field(default_factory=set)
|
libs: list[Accessor] = field(default_factory=list)
|
||||||
srcs: set[Accessor] = field(default_factory=set)
|
srcs: list[Accessor] = field(default_factory=list)
|
||||||
|
|
||||||
# Internal to the tool.
|
# Internal to the tool.
|
||||||
|
_flattend: bool = field(init=False,default=False)
|
||||||
_prepared: 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(
|
||||||
|
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
|
@classmethod
|
||||||
def to_dict(cls, data: Self) -> dict:
|
def to_dict(cls, data: Self) -> dict:
|
||||||
@@ -69,11 +86,11 @@ class Target(yaml.YAMLObject):
|
|||||||
"name": data.get("name") or bad_attr("name", data),
|
"name": data.get("name") or bad_attr("name", data),
|
||||||
"type": data.get("type") or bad_attr("type", data),
|
"type": data.get("type") or bad_attr("type", data),
|
||||||
"incs": Includes.from_dict(data.get("incs", {})),
|
"incs": Includes.from_dict(data.get("incs", {})),
|
||||||
"opts": set(Accessor.from_list(data.get("opts", []))),
|
"opts": Accessor.from_list(data.get("opts", [])),
|
||||||
"defs": set(Accessor.from_list(data.get("defs", []))),
|
"defs": Accessor.from_list(data.get("defs", [])),
|
||||||
"deps": set(Accessor.from_list(data.get("deps", []))),
|
"deps": Accessor.from_list(data.get("deps", [])),
|
||||||
"libs": set(Accessor.from_list(data.get("libs", []))),
|
"libs": Accessor.from_list(data.get("libs", [])),
|
||||||
"srcs": set(Accessor.from_list(data.get("srcs", [])))
|
"srcs": Accessor.from_list(data.get("srcs", []))
|
||||||
})
|
})
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ from logging import Logger
|
|||||||
|
|
||||||
from ..models import Project
|
from ..models import Project
|
||||||
from ..models import Result
|
from ..models import Result
|
||||||
from ..models import Target
|
|
||||||
from .._config import Config
|
from .._config import Config
|
||||||
from ._commands import Command
|
from ._commands import Command
|
||||||
from ._except import NoTargetsFoundError
|
from ._except import NoTargetsFoundError
|
||||||
from ._execute import execute
|
from ._execute import execute
|
||||||
from ._prepare import prepare
|
from ._prepare import prepare
|
||||||
|
from ._prepare import prepare_dependencies
|
||||||
|
|
||||||
_logger = getLogger("chinook")
|
_logger = getLogger("chinook")
|
||||||
|
|
||||||
@@ -23,24 +23,6 @@ def build(
|
|||||||
|
|
||||||
commands: list[Command] = []
|
commands: list[Command] = []
|
||||||
for target in project.targets:
|
for target in project.targets:
|
||||||
commands.extend(_prepare_dependencies(appcfg, target, logger))
|
commands.extend(prepare_dependencies(appcfg, project, target, logger))
|
||||||
commands.extend(prepare(appcfg, project, target, logger))
|
commands.extend(prepare(appcfg, project, target, logger))
|
||||||
return execute(commands, "Building")
|
return execute(commands, "Building")
|
||||||
|
|
||||||
def _prepare_dependencies(
|
|
||||||
appcfg: Config,
|
|
||||||
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._prepared:
|
|
||||||
continue
|
|
||||||
project = find_project_by_target(required.value)
|
|
||||||
commands.extend(prepare(appcfg, project, target, logger))
|
|
||||||
return commands
|
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ from pathlib import Path
|
|||||||
|
|
||||||
from ..compiler import Compiler
|
from ..compiler import Compiler
|
||||||
from ..compiler import CompilerOptions
|
from ..compiler import CompilerOptions
|
||||||
from ..management import latest
|
|
||||||
from ..management import find_target_by_name
|
from ..management import find_target_by_name
|
||||||
from ..management import find_project_by_target
|
from ..management import find_project_by_target
|
||||||
|
from ..management import read_depfile
|
||||||
|
from ..management import stale
|
||||||
from ..models import Output
|
from ..models import Output
|
||||||
from ..models import Project
|
from ..models import Project
|
||||||
from ..models import Target
|
from ..models import Target
|
||||||
@@ -28,8 +29,10 @@ def prepare(
|
|||||||
) -> list[Command]:
|
) -> list[Command]:
|
||||||
commands: list[Command] = []
|
commands: list[Command] = []
|
||||||
if target.type == Output.IMP or \
|
if target.type == Output.IMP or \
|
||||||
target.type == Output.INT:
|
target.type == Output.INT or \
|
||||||
|
target._prepared:
|
||||||
return commands
|
return commands
|
||||||
|
target._prepared = True
|
||||||
|
|
||||||
match target.type:
|
match target.type:
|
||||||
case Output.EXE: outfile = project.destination.bin(target.name)
|
case Output.EXE: outfile = project.destination.bin(target.name)
|
||||||
@@ -43,19 +46,21 @@ def prepare(
|
|||||||
defopts = CompilerOptions()
|
defopts = CompilerOptions()
|
||||||
|
|
||||||
defopts.pic = target.type == Output.DLL
|
defopts.pic = target.type == Output.DLL
|
||||||
defopts.incdirs.update([v.value for v in target.incs.includes])
|
defopts.incdirs.extend([v.value for v in target.incs.includes])
|
||||||
defopts.defines.update([v.value for v in target.defs])
|
defopts.defines.extend([v.value for v in target.defs])
|
||||||
defopts.flags.update([v.value for v in target.opts])
|
defopts.flags.extend([v.value for v in target.opts])
|
||||||
for file in target.srcs:
|
for file in target.srcs:
|
||||||
srcfile = project.origination.src(file.value)
|
srcfile = project.origination.src(file.value)
|
||||||
dstfile = project.destination.obj(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)
|
dstfile.parent.mkdir(parents=True, exist_ok=True)
|
||||||
commands.append(CompileObject(compiler, defopts, logger, srcfile, dstfile))
|
commands.append(CompileObject(compiler, defopts, logger, srcfile, dstfile))
|
||||||
if not latest(srcfile, outfile):
|
|
||||||
objects.append(dstfile)
|
|
||||||
|
|
||||||
defopts.objfiles.update(objects)
|
relink = len(commands) > 0 or stale(outfile, objects)
|
||||||
|
defopts.objfiles.extend(objects)
|
||||||
del objects
|
del objects
|
||||||
|
|
||||||
defopts.pic = False
|
defopts.pic = False
|
||||||
@@ -65,12 +70,22 @@ def prepare(
|
|||||||
libtgt = find_target_by_name(library.value)
|
libtgt = find_target_by_name(library.value)
|
||||||
libprj = find_project_by_target(libtgt.name)
|
libprj = find_project_by_target(libtgt.name)
|
||||||
if libtgt.type == Output.LIB:
|
if libtgt.type == Output.LIB:
|
||||||
defopts.libdirs.add(libprj.destination.libpath)
|
libfile = libprj.destination.lib(library.value)
|
||||||
defopts.archives.add(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:
|
elif libtgt.type == Output.DLL:
|
||||||
defopts.objfiles.add(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
|
return commands
|
||||||
|
|
||||||
command: Command
|
command: Command
|
||||||
@@ -80,6 +95,30 @@ def prepare(
|
|||||||
case Output.DLL: command = CompileDynlib(compiler, defopts, logger, outfile)
|
case Output.DLL: command = CompileDynlib(compiler, defopts, logger, outfile)
|
||||||
case Output.OBJ: assert False, "Not Implemented"
|
case Output.OBJ: assert False, "Not Implemented"
|
||||||
|
|
||||||
target._prepared = True
|
outfile.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
target._rebuilt = True
|
||||||
commands.append(command)
|
commands.append(command)
|
||||||
return commands
|
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
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ from logging import Logger
|
|||||||
|
|
||||||
from ..models import Project
|
from ..models import Project
|
||||||
from ..models import Result
|
from ..models import Result
|
||||||
from ..models import Target
|
|
||||||
from .._config import Config
|
from .._config import Config
|
||||||
from ._commands import Command
|
from ._commands import Command
|
||||||
from ._commands import ExecuteTest
|
from ._commands import ExecuteTest
|
||||||
from ._except import NoTestsFoundError
|
from ._except import NoTestsFoundError
|
||||||
from ._execute import execute
|
from ._execute import execute
|
||||||
from ._prepare import prepare
|
from ._prepare import prepare
|
||||||
|
from ._prepare import prepare_dependencies
|
||||||
|
|
||||||
_logger = getLogger("chinook")
|
_logger = getLogger("chinook")
|
||||||
|
|
||||||
@@ -24,31 +24,17 @@ def test(
|
|||||||
|
|
||||||
commands: list[Command] = []
|
commands: list[Command] = []
|
||||||
for target in project.tests:
|
for target in project.tests:
|
||||||
commands.extend(_prepare_dependencies(appcfg, target, logger))
|
commands.extend(prepare_dependencies(appcfg, project, target, logger))
|
||||||
commands.extend(prepare(appcfg, project, target, logger))
|
commands.extend(prepare(appcfg, project, target, logger))
|
||||||
results = execute(commands, "Building")
|
results = execute(commands, "Building")
|
||||||
|
|
||||||
|
for result in results:
|
||||||
|
if not result.success:
|
||||||
|
return results
|
||||||
|
|
||||||
commands.clear()
|
commands.clear()
|
||||||
for target in project.tests:
|
for target in project.tests:
|
||||||
testfile = project.destination.bin(target.name)
|
testfile = project.destination.bin(target.name)
|
||||||
commands.append(ExecuteTest(None, None, logger, testfile))
|
commands.append(ExecuteTest(None, None, logger, testfile))
|
||||||
results.extend(execute(commands, "Testing"))
|
results.extend(execute(commands, "Testing"))
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def _prepare_dependencies(
|
|
||||||
appcfg: Config,
|
|
||||||
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._prepared:
|
|
||||||
continue
|
|
||||||
project = find_project_by_target(required.value)
|
|
||||||
commands.extend(prepare(appcfg, project, dependency, logger))
|
|
||||||
return commands
|
|
||||||
|
|||||||
@@ -10,4 +10,7 @@ targets:
|
|||||||
- name: hello
|
- name: hello
|
||||||
type: program
|
type: program
|
||||||
deps: [example]
|
deps: [example]
|
||||||
srcs: [./entry.cpp]
|
srcs: [./entry.cpp]
|
||||||
|
|
||||||
|
exports:
|
||||||
|
- example
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
name: local
|
||||||
|
gpid: examples
|
||||||
|
semv: 1.0.0
|
||||||
|
|
||||||
|
requires:
|
||||||
|
- remote: examples/archive
|
||||||
|
|
||||||
|
targets:
|
||||||
|
- name: hello
|
||||||
|
type: program
|
||||||
|
deps: [example] # Comes from local required
|
||||||
|
srcs:
|
||||||
|
- ./entry.cpp
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
extern "C" void print(const char* str);
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
print("Hello, World!");
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ targets:
|
|||||||
- ./fibonacci.cpp
|
- ./fibonacci.cpp
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
- name: factorial_test
|
- name: testing
|
||||||
type: program
|
type: program
|
||||||
deps: [fibonacci]
|
deps: [fibonacci]
|
||||||
srcs:
|
srcs:
|
||||||
|
|||||||
Reference in New Issue
Block a user