Compare commits

..
12 Commits
17 changed files with 407 additions and 170 deletions
+6 -5
View File
@@ -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")
+12 -12
View File
@@ -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)
) )
+2
View File
@@ -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
+212
View File
@@ -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
))
+31 -4
View File
@@ -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
View File
@@ -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,
+9 -2
View File
@@ -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:
+5 -8
View File
@@ -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"
+4 -4
View File
@@ -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
View File
@@ -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
+2 -20
View File
@@ -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
+53 -14
View File
@@ -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
+6 -20
View File
@@ -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
+3
View File
@@ -11,3 +11,6 @@ targets:
type: program type: program
deps: [example] deps: [example]
srcs: [./entry.cpp] srcs: [./entry.cpp]
exports:
- example
+13
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
extern "C" void print(const char* str);
int main() {
print("Hello, World!");
}
+1 -1
View File
@@ -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: