Compare commits

..
10 Commits
15 changed files with 342 additions and 132 deletions
+4 -4
View File
@@ -42,8 +42,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)
@@ -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)
+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)
)
+205
View File
@@ -0,0 +1,205 @@
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 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 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
))
+15 -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
@@ -51,7 +50,7 @@ def _collect_profiles(project: Project) -> None:
def _collect_required(project: Project) -> None:
for required in project.requires:
_clone_then_load_required(required)
_load_required(required)
def _collect_targets(project: Project) -> None:
for target in project.targets:
@@ -64,68 +63,12 @@ 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:
load(Path(required.remote))
return
_clone_then_load_required(required)
def _clone_then_load_required(required: Dependency) -> None:
reqpath = Config.config_directory / required.cid
@@ -146,6 +89,9 @@ def _clone_required(
global errstr
errstr = stderr
from .._logger import logger
logger.info(f"Cloning {required.remote}...")
execute(
f"git clone -b {required.branch} --depth 1 {required.remote} {reqpath}",
handle_success,
+9 -2
View File
@@ -1,6 +1,7 @@
import yaml
from dataclasses import dataclass
from dataclasses import field
from typing import ClassVar
from typing import Self
@@ -15,7 +16,13 @@ class Dependency(yaml.YAMLError):
yaml_tag: ClassVar[str] = u"!dependency"
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
def cid(self) -> str:
@@ -27,7 +34,7 @@ class Dependency(yaml.YAMLError):
path = parsed.path.strip("/")
if path.endswith(".git"):
path = path[:-4]
return path + str(self.branch)
return path + "@" + str(self.branch)
@classmethod
def to_dict(cls, data: Self) -> dict:
+2 -8
View File
@@ -24,21 +24,15 @@ class Destination:
def objpath(self) -> Path:
return self._objpath
def __init__(self, outpath: Path, mkdirs=True) -> None:
def __init__(self, outpath: Path) -> None:
self._root = outpath / ".chinook"
self._binpath = self._root / "bin"
self._libpath = self._root / "lib"
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
def cwd() -> 'Destination':
return Destination(Path.cwd(), mkdirs=False)
return Destination(Path.cwd())
def bin(self, file: str) -> Path:
return self.binpath / file
+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
+15 -7
View File
@@ -1,6 +1,7 @@
from logging import getLogger
from logging import Logger
from ..models import Output
from ..models import Project
from ..models import Result
from ..models import Target
@@ -23,14 +24,15 @@ def build(
commands: list[Command] = []
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))
return execute(commands, "Building")
def _prepare_dependencies(
appcfg: Config,
target: Target,
logger: Logger = _logger
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
@@ -39,8 +41,14 @@ def _prepare_dependencies(
for required in target.deps:
dependency = find_target_by_name(required.value)
if dependency._prepared:
if dependency.type == Output.IMP or \
dependency.type == Output.INT or \
dependency._prepared:
continue
project = find_project_by_target(required.value)
commands.extend(prepare(appcfg, project, target, logger))
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
+8 -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)
@@ -53,9 +53,10 @@ def prepare(
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)
defopts.objfiles.update(objects)
defopts.objfiles.extend(objects)
del objects
defopts.pic = False
@@ -65,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
+19 -7
View File
@@ -1,6 +1,7 @@
from logging import getLogger
from logging import Logger
from ..models import Output
from ..models import Project
from ..models import Result
from ..models import Target
@@ -24,10 +25,14 @@ def test(
commands: list[Command] = []
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))
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)
@@ -36,9 +41,10 @@ def test(
return results
def _prepare_dependencies(
appcfg: Config,
target: Target,
logger: Logger = _logger
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
@@ -47,8 +53,14 @@ def _prepare_dependencies(
for required in target.deps:
dependency = find_target_by_name(required.value)
if dependency._prepared:
if dependency.type == Output.IMP or \
dependency.type == Output.INT or \
dependency._prepared:
continue
project = find_project_by_target(required.value)
commands.extend(prepare(appcfg, project, dependency, logger))
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
+3
View File
@@ -11,3 +11,6 @@ targets:
type: program
deps: [example]
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
tests:
- name: factorial_test
- name: testing
type: program
deps: [fibonacci]
srcs: