Added testing
Preparing profiles and dependencies, but added testing to separate them from regular targets and avoid the need for a 'testing' profile which could negatively impact configuration size and readability.
This commit is contained in:
@@ -6,3 +6,10 @@ indent_style = tab
|
|||||||
indent_size = 2
|
indent_size = 2
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.yaml,chinookfile]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
+44
-5
@@ -20,14 +20,42 @@ def _build(args: Namespace) -> None:
|
|||||||
try:
|
try:
|
||||||
projpath = Path(args.project)
|
projpath = Path(args.project)
|
||||||
cfgpath = projpath / "chinookconfig"
|
cfgpath = projpath / "chinookconfig"
|
||||||
return pipeline.build(
|
appcfg = Config.create_default_or_load(cfgpath)
|
||||||
Config.create_default_or_load(cfgpath),
|
projcfg = management.load(projpath)
|
||||||
management.load(projpath)
|
result = pipeline.build(appcfg, projcfg, logger)
|
||||||
)
|
logger.info(f"[100%] Built {projcfg.cid}")
|
||||||
|
return result
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
logger.exception(str(err))
|
logger.exception(str(err))
|
||||||
logger.critical("Failed to build project")
|
logger.critical("Failed to build project")
|
||||||
|
|
||||||
|
def _test(args: Namespace) -> None:
|
||||||
|
from chinook import management
|
||||||
|
from chinook import pipeline
|
||||||
|
|
||||||
|
from chinook._config import Config
|
||||||
|
from chinook._logger import logger
|
||||||
|
|
||||||
|
try:
|
||||||
|
projpath = Path(args.project)
|
||||||
|
cfgpath = projpath / "chinookconfig"
|
||||||
|
appcfg = Config.create_default_or_load(cfgpath)
|
||||||
|
projcfg = management.load(projpath)
|
||||||
|
result = pipeline.test(appcfg, projcfg, logger)
|
||||||
|
|
||||||
|
passed = 0
|
||||||
|
count = 0
|
||||||
|
for item in result:
|
||||||
|
if item.action == "Testing":
|
||||||
|
count += 1
|
||||||
|
if item.retcode == 0:
|
||||||
|
passed += 1
|
||||||
|
|
||||||
|
logger.info(f"Tested {projcfg.cid} [{passed}/{count}]")
|
||||||
|
except Exception as err:
|
||||||
|
logger.exception(str(err))
|
||||||
|
logger.critical("Failed to test project")
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
print("\n".join([
|
print("\n".join([
|
||||||
@@ -50,7 +78,18 @@ def main() -> None:
|
|||||||
nargs="?",
|
nargs="?",
|
||||||
default=".",
|
default=".",
|
||||||
type=str,
|
type=str,
|
||||||
help="The path to the project to build",
|
help="The path to the project to build.",
|
||||||
|
metavar="PROJECT"
|
||||||
|
)
|
||||||
|
|
||||||
|
testcmd = commands.add_parser("test",help="Tests your Chinook project")
|
||||||
|
testcmd.set_defaults(func=_test)
|
||||||
|
testcmd.add_argument(
|
||||||
|
"project",
|
||||||
|
nargs="?",
|
||||||
|
default=".",
|
||||||
|
type=str,
|
||||||
|
help="The path to the project to test.",
|
||||||
metavar="PROJECT"
|
metavar="PROJECT"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from dataclasses import field
|
from dataclasses import field
|
||||||
|
from logging import getLogger
|
||||||
|
from logging import Logger
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from ..models import Abi
|
from ..models import Abi
|
||||||
@@ -13,6 +14,8 @@ from ._mingw import MingwDriver
|
|||||||
from ._msvc import MsvcDriver
|
from ._msvc import MsvcDriver
|
||||||
from ._options import CompilerOptions
|
from ._options import CompilerOptions
|
||||||
|
|
||||||
|
_logger = getLogger("chinook")
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Compiler:
|
class Compiler:
|
||||||
@@ -25,10 +28,11 @@ class Compiler:
|
|||||||
}
|
}
|
||||||
|
|
||||||
tooling: Tooling
|
tooling: Tooling
|
||||||
|
logger: Logger = field(default=_logger)
|
||||||
_driver: CompilerDriver = field(init=False)
|
_driver: CompilerDriver = field(init=False)
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
self._driver = Compiler._driver_for(self.tooling)
|
self._driver = Compiler._driver_for(self.tooling, self.logger)
|
||||||
|
|
||||||
def compile_program(
|
def compile_program(
|
||||||
self,
|
self,
|
||||||
@@ -60,8 +64,12 @@ class Compiler:
|
|||||||
return self._driver.compile_object(options, srcpath, dstPath)
|
return self._driver.compile_object(options, srcpath, dstPath)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _driver_for(cls, tooling: Tooling) -> CompilerDriver:
|
def _driver_for(
|
||||||
|
cls,
|
||||||
|
tooling: Tooling,
|
||||||
|
logger: Logger
|
||||||
|
) -> CompilerDriver:
|
||||||
from ..models import Triple
|
from ..models import Triple
|
||||||
triples = tooling.when or [Triple.current()]
|
triples = tooling.when or [Triple.current()]
|
||||||
driver = cls.__COMPILER_MAPPING.get(triples[0].abi, GnucDriver)
|
driver = cls.__COMPILER_MAPPING.get(triples[0].abi, GnucDriver)
|
||||||
return driver(tooling)
|
return driver(tooling, logger)
|
||||||
|
|||||||
@@ -1,18 +1,26 @@
|
|||||||
from abc import ABC
|
from abc import ABC
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
|
from logging import getLogger
|
||||||
|
from logging import Logger
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from ..models import Tooling
|
from ..models import Tooling
|
||||||
from ..models import Result
|
from ..models import Result
|
||||||
from ._options import CompilerOptions
|
from ._options import CompilerOptions
|
||||||
|
|
||||||
|
_logger = getLogger("chinook")
|
||||||
|
|
||||||
|
|
||||||
class CompilerDriver(ABC):
|
class CompilerDriver(ABC):
|
||||||
def __init__(self, tooling: Tooling) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
tooling: Tooling,
|
||||||
|
logger: Logger = _logger
|
||||||
|
) -> None:
|
||||||
if tooling is None:
|
if tooling is None:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
self.tooling = tooling
|
self.tooling = tooling
|
||||||
|
self.logger = logger
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def compile_program(
|
def compile_program(
|
||||||
|
|||||||
+86
-67
@@ -2,10 +2,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from ..models import Optimization
|
from ..models import Optimization
|
||||||
from ..models import Result
|
from ..models import Result
|
||||||
from ..models import StdC
|
|
||||||
from ..models import StdCpp
|
|
||||||
from ..shell import execute
|
from ..shell import execute
|
||||||
from .._logger import logger
|
|
||||||
from ._driver import CompilerDriver
|
from ._driver import CompilerDriver
|
||||||
from ._options import CompilerOptions
|
from ._options import CompilerOptions
|
||||||
|
|
||||||
@@ -48,17 +45,30 @@ class GnucDriver(CompilerDriver):
|
|||||||
for directory in options.libdirs: args.append(f"-L{str(directory)}")
|
for directory in options.libdirs: args.append(f"-L{str(directory)}")
|
||||||
for archive in options.archives: args.append(f"-l{archive}")
|
for archive in options.archives: args.append(f"-l{archive}")
|
||||||
cmdstr = ' '.join(args)
|
cmdstr = ' '.join(args)
|
||||||
# logger.info(cmdstr)
|
# self.logger.info(cmdstr)
|
||||||
|
|
||||||
result: list[Result] = [None]
|
result = Result(
|
||||||
execute(
|
success = False,
|
||||||
cmdstr,
|
action = "Linked",
|
||||||
lambda cmdstr, stdout, stderr:
|
target = str(dstpath),
|
||||||
self.handle_on_data(result, str(dstpath), cmdstr, stdout, stderr),
|
command = cmdstr,
|
||||||
lambda cmdstr, stdout, stderr:
|
retcode = -1
|
||||||
self.handle_on_error(result, str(dstpath), cmdstr, stdout, stderr)
|
|
||||||
)
|
)
|
||||||
return result[0]
|
|
||||||
|
def handle_success(retcode: int, cmdstr: str, stdout: str) -> None:
|
||||||
|
result.success = True
|
||||||
|
result.command = cmdstr
|
||||||
|
result.retcode = retcode
|
||||||
|
result.diagnostics.append(stdout)
|
||||||
|
|
||||||
|
def handle_failure(retcode: int, cmdstr: str, stderr: str) -> None:
|
||||||
|
result.success = False
|
||||||
|
result.command = cmdstr
|
||||||
|
result.retcode = retcode
|
||||||
|
result.diagnostics.append(stderr)
|
||||||
|
|
||||||
|
execute(cmdstr, handle_success, handle_failure)
|
||||||
|
return result
|
||||||
|
|
||||||
def compile_archive(
|
def compile_archive(
|
||||||
self,
|
self,
|
||||||
@@ -70,15 +80,28 @@ class GnucDriver(CompilerDriver):
|
|||||||
args.append(str(obj))
|
args.append(str(obj))
|
||||||
cmdstr = ' '.join(args)
|
cmdstr = ' '.join(args)
|
||||||
|
|
||||||
result: list[Result] = [None]
|
result = Result(
|
||||||
execute(
|
success = False,
|
||||||
cmdstr,
|
action = "Linked",
|
||||||
lambda cmdstr, stdout, stderr:
|
target = str(dstpath),
|
||||||
self.handle_on_data(result, str(dstpath), cmdstr, stdout, stderr),
|
command = cmdstr,
|
||||||
lambda cmdstr, stdout, stderr:
|
retcode = -1
|
||||||
self.handle_on_error(result, str(dstpath), cmdstr, stdout, stderr)
|
|
||||||
)
|
)
|
||||||
return result[0]
|
|
||||||
|
def handle_success(retcode: int, cmdstr: str, stdout: str) -> None:
|
||||||
|
result.success = True
|
||||||
|
result.command = cmdstr
|
||||||
|
result.retcode = retcode
|
||||||
|
result.diagnostics.append(stdout)
|
||||||
|
|
||||||
|
def handle_failure(retcode: int, cmdstr: str, stderr: str) -> None:
|
||||||
|
result.success = False
|
||||||
|
result.command = cmdstr
|
||||||
|
result.retcode = retcode
|
||||||
|
result.diagnostics.append(stderr)
|
||||||
|
|
||||||
|
execute(cmdstr, handle_success, handle_failure)
|
||||||
|
return result
|
||||||
|
|
||||||
def compile_library(
|
def compile_library(
|
||||||
self,
|
self,
|
||||||
@@ -106,17 +129,30 @@ class GnucDriver(CompilerDriver):
|
|||||||
for directory in options.libdirs: args.append(f"-L{str(directory)}")
|
for directory in options.libdirs: args.append(f"-L{str(directory)}")
|
||||||
for archive in options.archives: args.append(f"-l{archive}")
|
for archive in options.archives: args.append(f"-l{archive}")
|
||||||
cmdstr = ' '.join(args)
|
cmdstr = ' '.join(args)
|
||||||
# logger.info(cmdstr)
|
# self.logger.info(cmdstr)
|
||||||
|
|
||||||
result: list[Result] = [None]
|
result = Result(
|
||||||
execute(
|
success = False,
|
||||||
cmdstr,
|
action = "Linked",
|
||||||
lambda cmdstr, stdout, stderr:
|
target = str(dstpath),
|
||||||
self.handle_on_data(result, str(dstpath), cmdstr, stdout, stderr),
|
command = cmdstr,
|
||||||
lambda cmdstr, stdout, stderr:
|
retcode = -1
|
||||||
self.handle_on_error(result, str(dstpath), cmdstr, stdout, stderr)
|
|
||||||
)
|
)
|
||||||
return result[0]
|
|
||||||
|
def handle_success(retcode: int, cmdstr: str, stdout: str) -> None:
|
||||||
|
result.success = True
|
||||||
|
result.command = cmdstr
|
||||||
|
result.retcode = retcode
|
||||||
|
result.diagnostics.append(stdout)
|
||||||
|
|
||||||
|
def handle_failure(retcode: int, cmdstr: str, stderr: str) -> None:
|
||||||
|
result.success = False
|
||||||
|
result.command = cmdstr
|
||||||
|
result.retcode = retcode
|
||||||
|
result.diagnostics.append(stderr)
|
||||||
|
|
||||||
|
execute(cmdstr, handle_success, handle_failure)
|
||||||
|
return result
|
||||||
|
|
||||||
def compile_object(
|
def compile_object(
|
||||||
self,
|
self,
|
||||||
@@ -150,44 +186,27 @@ class GnucDriver(CompilerDriver):
|
|||||||
for archive in options.archives: args.append(f"-l{archive}")
|
for archive in options.archives: args.append(f"-l{archive}")
|
||||||
for define in options.defines: args.append(f"-D{define}")
|
for define in options.defines: args.append(f"-D{define}")
|
||||||
cmdstr = ' '.join(args)
|
cmdstr = ' '.join(args)
|
||||||
# logger.info(cmdstr)
|
# self.logger.info(cmdstr)
|
||||||
|
|
||||||
result: list[Result] = [None]
|
result = Result(
|
||||||
execute(
|
|
||||||
cmdstr,
|
|
||||||
lambda cmdstr, stdout, stderr:
|
|
||||||
self.handle_on_data(result, str(dstpath), cmdstr, stdout, stderr),
|
|
||||||
lambda cmdstr, stdout, stderr:
|
|
||||||
self.handle_on_error(result, str(dstpath), cmdstr, stdout, stderr)
|
|
||||||
)
|
|
||||||
return result[0]
|
|
||||||
|
|
||||||
def handle_on_data(
|
|
||||||
self,
|
|
||||||
result: list[Result],
|
|
||||||
target: str,
|
|
||||||
cmdstr: str,
|
|
||||||
stdout: str,
|
|
||||||
stderr: str
|
|
||||||
) -> None:
|
|
||||||
result[0] = Result(
|
|
||||||
success = True,
|
|
||||||
target = target,
|
|
||||||
command = cmdstr
|
|
||||||
)
|
|
||||||
result[0].diagnostics.append(stdout)
|
|
||||||
|
|
||||||
def handle_on_error(
|
|
||||||
self,
|
|
||||||
result: list[Result],
|
|
||||||
target: str,
|
|
||||||
cmdstr: str,
|
|
||||||
stdout: str,
|
|
||||||
stderr: str
|
|
||||||
) -> None:
|
|
||||||
result[0] = Result(
|
|
||||||
success = False,
|
success = False,
|
||||||
target = target,
|
action = "Compiled",
|
||||||
command = cmdstr
|
target = str(dstpath),
|
||||||
|
command = cmdstr,
|
||||||
|
retcode = -1
|
||||||
)
|
)
|
||||||
result[0].diagnostics.append(stderr)
|
|
||||||
|
def handle_success(retcode: int, cmdstr: str, stdout: str) -> None:
|
||||||
|
result.success = True
|
||||||
|
result.command = cmdstr
|
||||||
|
result.retcode = retcode
|
||||||
|
result.diagnostics.append(stdout)
|
||||||
|
|
||||||
|
def handle_failure(retcode: int, cmdstr: str, stderr: str) -> None:
|
||||||
|
result.success = False
|
||||||
|
result.command = cmdstr
|
||||||
|
result.retcode = retcode
|
||||||
|
result.diagnostics.append(stderr)
|
||||||
|
|
||||||
|
execute(cmdstr, handle_success, handle_failure)
|
||||||
|
return result
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from typing import Optional
|
|||||||
|
|
||||||
from ..models import Project
|
from ..models import Project
|
||||||
from ..models import Target
|
from ..models import Target
|
||||||
from ._clone import clone
|
|
||||||
from ._except import NoProjectFoundError
|
from ._except import NoProjectFoundError
|
||||||
from ._latest import latest
|
from ._latest import latest
|
||||||
from ._load import load
|
from ._load import load
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ def load(path: Path) -> Project:
|
|||||||
project = Project.from_dict(yamlobj)
|
project = Project.from_dict(yamlobj)
|
||||||
project.origination = Origination(path)
|
project.origination = Origination(path)
|
||||||
project.destination = Destination(path)
|
project.destination = Destination(path)
|
||||||
|
_collect_profiles(project)
|
||||||
_collect_required(project)
|
_collect_required(project)
|
||||||
_collect_targets(project)
|
_collect_targets(project)
|
||||||
_flatten_targets(project)
|
_flatten_targets(project)
|
||||||
@@ -39,6 +40,12 @@ def load(path: Path) -> Project:
|
|||||||
except Exception as rethrowme:
|
except Exception as rethrowme:
|
||||||
raise rethrowme
|
raise rethrowme
|
||||||
|
|
||||||
|
def _collect_profiles(project: Project) -> None:
|
||||||
|
# TODO: Determine what profile is supposed to be active and load the targets
|
||||||
|
# so that they will be compiled.
|
||||||
|
for profile in project.profiles:
|
||||||
|
...
|
||||||
|
|
||||||
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)
|
_clone_then_load_required(required)
|
||||||
|
|||||||
@@ -58,8 +58,12 @@ class Dependency(yaml.YAMLError):
|
|||||||
return cls(**data)
|
return cls(**data)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_list(cls, data: list[str]) -> list[Self]:
|
def from_list(cls, data: list[dict | Self]) -> list[Self]:
|
||||||
return [cls.from_dict(v) for v in data]
|
return [
|
||||||
|
v if isinstance(v, Dependency) \
|
||||||
|
else cls.from_dict(v)
|
||||||
|
for v in data
|
||||||
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_yaml(
|
def from_yaml(
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import yaml
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from dataclasses import field
|
||||||
|
|
||||||
|
from typing import ClassVar
|
||||||
|
from typing import Self
|
||||||
|
|
||||||
|
from ._triple import Triple
|
||||||
|
from ._target import Target
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Profile(yaml.YAMLObject):
|
||||||
|
yaml_dumper: ClassVar[type[yaml.SafeDumper]] = yaml.SafeDumper
|
||||||
|
yaml_loader: ClassVar[type[yaml.SafeLoader]] = yaml.SafeLoader
|
||||||
|
yaml_tag: ClassVar[str] = u"!profile"
|
||||||
|
|
||||||
|
name: str
|
||||||
|
when: list[Triple] = field(default_factory=list)
|
||||||
|
targets: list[Target] = field(default_factory=list)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def native(self) -> bool:
|
||||||
|
return not self.when or any(t.native for t in self.when)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_dict(cls, data: Self) -> dict:
|
||||||
|
return {
|
||||||
|
"name": data.name,
|
||||||
|
"when": Triple.to_list(data.when),
|
||||||
|
"targets": Target.to_list(data.targets)
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_list(cls, data: list[Self]) -> list[dict]:
|
||||||
|
return [cls.to_dict(v) for v in data]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_yaml(
|
||||||
|
cls,
|
||||||
|
dumper: yaml.SafeDumper,
|
||||||
|
data: yaml.YAMLObject
|
||||||
|
) -> yaml.Node:
|
||||||
|
if not isinstance(data, Profile):
|
||||||
|
raise ValueError
|
||||||
|
return dumper.represent_mapping(
|
||||||
|
"tag:yaml.org,2002:map",
|
||||||
|
cls.to_dict(data)
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict) -> Self:
|
||||||
|
def bad_attr(name: str, obj: dict) -> None:
|
||||||
|
raise ArithmeticError(name=name,obj=obj)
|
||||||
|
|
||||||
|
return cls(**{
|
||||||
|
"name": data.get("name") or bad_attr("name", data),
|
||||||
|
"when": Triple.from_list(data.get("when", [])),
|
||||||
|
"targets": Target.from_list(data.get("targets", []))
|
||||||
|
})
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_list(cls, data: list[dict | Self]) -> list[Self]:
|
||||||
|
return [
|
||||||
|
v if isinstance(v, Profile) \
|
||||||
|
else cls.from_dict(v)
|
||||||
|
for v in data
|
||||||
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_yaml(
|
||||||
|
cls,
|
||||||
|
loader: yaml.SafeLoader,
|
||||||
|
node: yaml.Node
|
||||||
|
) -> Self:
|
||||||
|
return cls.from_dict(loader.construct_mapping(node, deep=True))
|
||||||
|
|
||||||
|
|
||||||
|
Profile.yaml_dumper.add_representer(
|
||||||
|
Profile.yaml_tag,
|
||||||
|
Profile.to_yaml
|
||||||
|
)
|
||||||
|
|
||||||
|
Profile.yaml_loader.add_constructor(
|
||||||
|
Profile.yaml_tag,
|
||||||
|
Profile.from_yaml
|
||||||
|
)
|
||||||
@@ -9,6 +9,7 @@ from ._dependency import Dependency
|
|||||||
from ._destination import Destination
|
from ._destination import Destination
|
||||||
from ._identity import Identity
|
from ._identity import Identity
|
||||||
from ._origination import Origination
|
from ._origination import Origination
|
||||||
|
from ._profile import Profile
|
||||||
from ._target import Target
|
from ._target import Target
|
||||||
|
|
||||||
|
|
||||||
@@ -18,8 +19,10 @@ class Project(Identity):
|
|||||||
yaml_loader: ClassVar[type[yaml.SafeLoader]] = yaml.SafeLoader
|
yaml_loader: ClassVar[type[yaml.SafeLoader]] = yaml.SafeLoader
|
||||||
yaml_tag: ClassVar[str] = u"!project"
|
yaml_tag: ClassVar[str] = u"!project"
|
||||||
|
|
||||||
|
profiles: list[Profile] = field(default_factory=list)
|
||||||
requires: list[Dependency] = field(default_factory=list)
|
requires: list[Dependency] = field(default_factory=list)
|
||||||
targets: list[Target] = field(default_factory=list)
|
targets: list[Target] = field(default_factory=list)
|
||||||
|
tests: list[Target] = field(default_factory=list)
|
||||||
exports: list[str] = field(default_factory=list)
|
exports: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
origination: Origination = field(default=Origination.cwd())
|
origination: Origination = field(default=Origination.cwd())
|
||||||
@@ -28,8 +31,10 @@ class Project(Identity):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def to_dict(cls, data: Self) -> dict:
|
def to_dict(cls, data: Self) -> dict:
|
||||||
return {
|
return {
|
||||||
|
"profiles": Profile.to_list(data.profiles),
|
||||||
"requires": Dependency.to_list(data.requires),
|
"requires": Dependency.to_list(data.requires),
|
||||||
"targets": Target.to_list(data.targets),
|
"targets": Target.to_list(data.targets),
|
||||||
|
"tests": Target.to_list(data.targets),
|
||||||
"exports": data.exports
|
"exports": data.exports
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,8 +58,10 @@ class Project(Identity):
|
|||||||
"name": data.get("name") or bad_attr("name", data),
|
"name": data.get("name") or bad_attr("name", data),
|
||||||
"gpid": data.get("gpid") or bad_attr("gpid", data),
|
"gpid": data.get("gpid") or bad_attr("gpid", data),
|
||||||
"semv": data.get("semv") or bad_attr("semv", data),
|
"semv": data.get("semv") or bad_attr("semv", data),
|
||||||
|
"profiles": Profile.from_list(data.get("profiles", [])),
|
||||||
"requires": Dependency.from_list(data.get("requires", [])),
|
"requires": Dependency.from_list(data.get("requires", [])),
|
||||||
"targets": Target.from_list(data.get("targets", [])),
|
"targets": Target.from_list(data.get("targets", [])),
|
||||||
|
"tests": Target.from_list(data.get("tests", [])),
|
||||||
"exports": data.get("exports", [])
|
"exports": data.get("exports", [])
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -67,3 +74,13 @@ class Project(Identity):
|
|||||||
return cls.from_dict(
|
return cls.from_dict(
|
||||||
loader.construct_mapping(node, deep=True)
|
loader.construct_mapping(node, deep=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Project.yaml_dumper.add_representer(
|
||||||
|
Project.yaml_tag,
|
||||||
|
Project.to_yaml
|
||||||
|
)
|
||||||
|
|
||||||
|
Project.yaml_loader.add_constructor(
|
||||||
|
Project.yaml_tag,
|
||||||
|
Project.from_yaml
|
||||||
|
)
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ from dataclasses import field
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Result:
|
class Result:
|
||||||
success: bool
|
success: bool
|
||||||
|
action: str
|
||||||
target: str
|
target: str
|
||||||
command: str
|
command: str
|
||||||
|
retcode: int
|
||||||
diagnostics: list[str] = field(default_factory=list)
|
diagnostics: list[str] = field(default_factory=list)
|
||||||
|
|||||||
@@ -54,18 +54,14 @@ class Tooling(yaml.YAMLObject):
|
|||||||
def from_dict(cls, data: dict) -> Self:
|
def from_dict(cls, data: dict) -> Self:
|
||||||
return cls(**{
|
return cls(**{
|
||||||
**data,
|
**data,
|
||||||
"when": [
|
"when": Triple.from_list(data.get("when", []))
|
||||||
v if isinstance(v, Triple) \
|
|
||||||
else Triple.from_string(v)
|
|
||||||
for v in data.get("when", [])
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_list(cls, data: list[dict]) -> list[Self]:
|
def from_list(cls, data: list[dict | Self]) -> list[Self]:
|
||||||
return [
|
return [
|
||||||
v if isinstance(v, Tooling) \
|
v if isinstance(v, Tooling) \
|
||||||
else Tooling.from_dict(v)
|
else cls.from_dict(v)
|
||||||
for v in data
|
for v in data
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
+24
-14
@@ -24,7 +24,7 @@ class Arch(enum.StrEnum):
|
|||||||
S390X = "s390x"
|
S390X = "s390x"
|
||||||
WASM32 = "wasm32"
|
WASM32 = "wasm32"
|
||||||
WASM64 = "wasm64"
|
WASM64 = "wasm64"
|
||||||
UNKNOWN = "unknown"
|
ANY = "any"
|
||||||
|
|
||||||
class Sys(enum.StrEnum):
|
class Sys(enum.StrEnum):
|
||||||
LINUX = "linux"
|
LINUX = "linux"
|
||||||
@@ -36,8 +36,7 @@ class Sys(enum.StrEnum):
|
|||||||
ANDROID = "android"
|
ANDROID = "android"
|
||||||
IOS = "ios"
|
IOS = "ios"
|
||||||
WASI = "wasi"
|
WASI = "wasi"
|
||||||
NONE = "none"
|
ANY = "any"
|
||||||
UNKNOWN = "unknown"
|
|
||||||
|
|
||||||
class Abi(enum.StrEnum):
|
class Abi(enum.StrEnum):
|
||||||
GNU = "gnu"
|
GNU = "gnu"
|
||||||
@@ -45,8 +44,7 @@ class Abi(enum.StrEnum):
|
|||||||
MINGW = "mingw"
|
MINGW = "mingw"
|
||||||
ANDROID = "android"
|
ANDROID = "android"
|
||||||
ELF = "elf"
|
ELF = "elf"
|
||||||
NONE = "none"
|
ANY = "any"
|
||||||
UNKNOWN = "unknown"
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -91,9 +89,9 @@ class Triple(yaml.YAMLObject):
|
|||||||
def native(self) -> bool:
|
def native(self) -> bool:
|
||||||
host = Triple.current()
|
host = Triple.current()
|
||||||
return (
|
return (
|
||||||
(self.arch == Arch.UNKNOWN or self.arch == host.arch) and
|
(self.arch == Arch.ANY or self.arch == host.arch) and
|
||||||
(self.sys == Sys.UNKNOWN or self.sys == host.sys) and
|
(self.sys == Sys.ANY or self.sys == host.sys) and
|
||||||
(self.abi == Abi.UNKNOWN or self.abi == host.abi)
|
(self.abi == Abi.ANY or self.abi == host.abi)
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
@@ -111,6 +109,10 @@ class Triple(yaml.YAMLObject):
|
|||||||
def to_string(cls, triple: Self) -> str:
|
def to_string(cls, triple: Self) -> str:
|
||||||
return str(triple)
|
return str(triple)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_list(cls, data: list[Self]) -> list[str]:
|
||||||
|
return [cls.to_string(v) for v in data]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def to_yaml(
|
def to_yaml(
|
||||||
cls,
|
cls,
|
||||||
@@ -141,11 +143,19 @@ class Triple(yaml.YAMLObject):
|
|||||||
return fallback
|
return fallback
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
arch = _coerce(Arch, parts[0].lower(), Arch.UNKNOWN),
|
arch = _coerce(Arch, parts[0].lower(), Arch.ANY),
|
||||||
sys = _coerce(Sys, parts[1].lower(), Sys.UNKNOWN),
|
sys = _coerce(Sys, parts[1].lower(), Sys.ANY),
|
||||||
abi = _coerce(Abi, parts[2].lower(), Abi.UNKNOWN)
|
abi = _coerce(Abi, parts[2].lower(), Abi.ANY)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_list(cls, data: list[str | Self]) -> list[Self]:
|
||||||
|
return [
|
||||||
|
v if isinstance(v, Triple) \
|
||||||
|
else cls.from_string(v)
|
||||||
|
for v in data
|
||||||
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_yaml(
|
def from_yaml(
|
||||||
cls,
|
cls,
|
||||||
@@ -157,18 +167,18 @@ class Triple(yaml.YAMLObject):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def _select_arch(cls) -> Arch:
|
def _select_arch(cls) -> Arch:
|
||||||
curr = platform.machine().lower()
|
curr = platform.machine().lower()
|
||||||
return cls.__arch_map.get(curr, Arch.UNKNOWN)
|
return cls.__arch_map.get(curr, Arch.ANY)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _select_sys(cls) -> Sys:
|
def _select_sys(cls) -> Sys:
|
||||||
curr = platform.system().lower()
|
curr = platform.system().lower()
|
||||||
return cls.__sys_map.get(curr, Sys.UNKNOWN) \
|
return cls.__sys_map.get(curr, Sys.ANY) \
|
||||||
if system.platform != "android" \
|
if system.platform != "android" \
|
||||||
else Sys.ANDROID
|
else Sys.ANDROID
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _select_abi(cls, arch: Arch, sys: Sys) -> Abi:
|
def _select_abi(cls, arch: Arch, sys: Sys) -> Abi:
|
||||||
res = Abi.UNKNOWN
|
res = Abi.ANY
|
||||||
if sys == Sys.WINDOWS:
|
if sys == Sys.WINDOWS:
|
||||||
res = Abi.MSVC
|
res = Abi.MSVC
|
||||||
elif sys == Sys.ANDROID:
|
elif sys == Sys.ANDROID:
|
||||||
|
|||||||
@@ -1 +1,4 @@
|
|||||||
from ._build import build
|
from ._build import build
|
||||||
|
from ._commands import Command
|
||||||
|
from ._prepare import prepare
|
||||||
|
from ._test import test
|
||||||
|
|||||||
+13
-93
@@ -1,106 +1,26 @@
|
|||||||
from pathlib import Path
|
from logging import getLogger
|
||||||
|
from logging import Logger
|
||||||
|
|
||||||
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 ..models import Access
|
|
||||||
from ..models import Accessor
|
|
||||||
from ..models import Output
|
|
||||||
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 .._logger import logger
|
|
||||||
from ._commands import Command
|
from ._commands import Command
|
||||||
from ._commands import CompileArchive
|
|
||||||
from ._commands import CompileDynlib
|
|
||||||
from ._commands import CompileObject
|
|
||||||
from ._commands import CompileProgram
|
|
||||||
from ._except import NoTargetsFoundError
|
from ._except import NoTargetsFoundError
|
||||||
|
from ._execute import execute
|
||||||
|
from ._prepare import prepare
|
||||||
|
|
||||||
|
_logger = getLogger("chinook")
|
||||||
|
|
||||||
|
|
||||||
def build(appcfg: Config, project: Project) -> list[Result]:
|
def build(
|
||||||
|
appcfg: Config,
|
||||||
|
project: Project,
|
||||||
|
logger: Logger = _logger
|
||||||
|
) -> list[Result]:
|
||||||
if len(project.targets) == 0:
|
if len(project.targets) == 0:
|
||||||
raise NoTargetsFoundError(project)
|
raise NoTargetsFoundError(project)
|
||||||
|
|
||||||
# Put together list of commands and then execute them.
|
|
||||||
commands: list[Command] = []
|
commands: list[Command] = []
|
||||||
results: list[Result] = []
|
|
||||||
for target in project.targets:
|
for target in project.targets:
|
||||||
_prepare_build_commands(appcfg, project, target, commands)
|
commands.extend(prepare(appcfg, project, target, logger))
|
||||||
|
return execute(commands, "Building")
|
||||||
executed: int = 0
|
|
||||||
cmdcount: int = len(commands)
|
|
||||||
for command in commands:
|
|
||||||
result = command.execute()
|
|
||||||
results.append(result)
|
|
||||||
percentage = (executed / cmdcount)
|
|
||||||
if result.success:
|
|
||||||
logger.info(f"[{percentage:.0%}] Compiled {result.target}")
|
|
||||||
executed += 1
|
|
||||||
else:
|
|
||||||
logger.error(f"[{percentage:.0%}] Failed {result.target}")
|
|
||||||
for diagnostic in result.diagnostics:
|
|
||||||
logger.error(f"Because {diagnostic}")
|
|
||||||
return
|
|
||||||
logger.info(f"[100%] Built {project.cid}")
|
|
||||||
|
|
||||||
|
|
||||||
def _prepare_build_commands(
|
|
||||||
appcfg: Config,
|
|
||||||
project: Project,
|
|
||||||
target: Target,
|
|
||||||
commands: list[Command]
|
|
||||||
) -> None:
|
|
||||||
objects: list[Path] = []
|
|
||||||
|
|
||||||
# TODO: clean this up.
|
|
||||||
compiler = Compiler(appcfg.native_tooling)
|
|
||||||
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])
|
|
||||||
for file in target.srcs:
|
|
||||||
srcfile = project.origination.src(file.value)
|
|
||||||
dstfile = project.destination.obj(file.value)
|
|
||||||
if latest(srcfile, dstfile):
|
|
||||||
continue
|
|
||||||
commands.append(CompileObject(compiler, defopts, srcfile, dstfile))
|
|
||||||
objects.append(dstfile)
|
|
||||||
|
|
||||||
defopts.objfiles.update(objects)
|
|
||||||
del objects
|
|
||||||
|
|
||||||
defopts.pic = False
|
|
||||||
defopts.defines.clear()
|
|
||||||
if len(target.libs) > 0:
|
|
||||||
for library in target.libs:
|
|
||||||
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)
|
|
||||||
elif libtgt.type == Output.DLL:
|
|
||||||
defopts.objfiles.add(libprj.destination.dll(library.value))
|
|
||||||
|
|
||||||
if len(defopts.objfiles) == 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
command: Command
|
|
||||||
match target.type:
|
|
||||||
case Output.EXE:
|
|
||||||
outfile = project.destination.bin(target.name)
|
|
||||||
command = CompileProgram(compiler, defopts, outfile)
|
|
||||||
case Output.LIB:
|
|
||||||
outfile = project.destination.lib(target.name)
|
|
||||||
command = CompileArchive(compiler, defopts, outfile)
|
|
||||||
case Output.DLL:
|
|
||||||
outfile = project.destination.dll(target.name)
|
|
||||||
command = CompileDynlib(compiler, defopts, outfile)
|
|
||||||
case Output.OBJ:
|
|
||||||
assert False, "Not Implemented"
|
|
||||||
commands.append(command)
|
|
||||||
|
|||||||
@@ -2,33 +2,82 @@ from abc import ABC
|
|||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from logging import getLogger
|
||||||
|
from logging import Logger
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import override
|
from typing import override
|
||||||
|
|
||||||
from ..compiler import Compiler
|
from ..compiler import Compiler
|
||||||
from ..compiler import CompilerOptions
|
from ..compiler import CompilerOptions
|
||||||
from ..models import Result
|
from ..models import Result
|
||||||
|
from ..shell import test
|
||||||
|
|
||||||
|
_logger = getLogger("chinook")
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Command(ABC):
|
class Command(ABC):
|
||||||
compiler: Compiler
|
compiler: Compiler
|
||||||
options: CompilerOptions
|
options: CompilerOptions
|
||||||
|
logger: Logger
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
|
if self.logger == None:
|
||||||
|
self.logger = _logger
|
||||||
self.options = deepcopy(self.options)
|
self.options = deepcopy(self.options)
|
||||||
|
|
||||||
|
def execute(
|
||||||
|
self,
|
||||||
|
percentage: float,
|
||||||
|
reason: str
|
||||||
|
) -> Result | None:
|
||||||
|
if reason != "Testing":
|
||||||
|
return self._execute_building(percentage)
|
||||||
|
else:
|
||||||
|
return self._execute_testing()
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def execute(self) -> Result:
|
def _execute(self) -> Result:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
def _execute_building(self, percentage: float) -> Result | None:
|
||||||
|
message = f"[{percentage:.0%}] "
|
||||||
|
try:
|
||||||
|
result = self._execute()
|
||||||
|
if result.success:
|
||||||
|
message += f"{result.action} {result.target}"
|
||||||
|
self.logger.info(message)
|
||||||
|
else:
|
||||||
|
message += f"Failed {result.target} because "
|
||||||
|
for index in range(len(result.diagnostics) - 1):
|
||||||
|
message += f"{result.diagnostics[index]} "
|
||||||
|
message += result.diagnostics[-1]
|
||||||
|
self.logger.error(message)
|
||||||
|
return result
|
||||||
|
except Exception as error:
|
||||||
|
message += f"Failed {result.target} because {str(error)}"
|
||||||
|
self.logger.error(message)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _execute_testing(self) -> Result | None:
|
||||||
|
try:
|
||||||
|
result = self._execute()
|
||||||
|
message = f"{result.action} {result.target}"
|
||||||
|
message += " - Passed" if result.retcode == 0 else " - Failed"
|
||||||
|
self.logger.info(message)
|
||||||
|
return result
|
||||||
|
except Exception as error:
|
||||||
|
message = f"Failed {result.target} because {str(error)}"
|
||||||
|
self.logger.error(message)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class CompileProgram(Command):
|
class CompileProgram(Command):
|
||||||
exefile: Path
|
exefile: Path
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def execute(self) -> Result:
|
def _execute(self) -> Result:
|
||||||
return self.compiler.compile_program(
|
return self.compiler.compile_program(
|
||||||
self.options,
|
self.options,
|
||||||
self.exefile
|
self.exefile
|
||||||
@@ -39,7 +88,7 @@ class CompileArchive(Command):
|
|||||||
libfile: Path
|
libfile: Path
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def execute(self) -> Result:
|
def _execute(self) -> Result:
|
||||||
return self.compiler.compile_archive(
|
return self.compiler.compile_archive(
|
||||||
self.options,
|
self.options,
|
||||||
self.libfile
|
self.libfile
|
||||||
@@ -50,7 +99,7 @@ class CompileDynlib(Command):
|
|||||||
dynfile: Path
|
dynfile: Path
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def execute(self) -> Result:
|
def _execute(self) -> Result:
|
||||||
return self.compiler.compile_library(
|
return self.compiler.compile_library(
|
||||||
self.options,
|
self.options,
|
||||||
self.dynfile
|
self.dynfile
|
||||||
@@ -62,9 +111,39 @@ class CompileObject(Command):
|
|||||||
dstfile: Path
|
dstfile: Path
|
||||||
|
|
||||||
@override
|
@override
|
||||||
def execute(self) -> Result:
|
def _execute(self) -> Result:
|
||||||
return self.compiler.compile_object(
|
return self.compiler.compile_object(
|
||||||
self.options,
|
self.options,
|
||||||
self.srcfile,
|
self.srcfile,
|
||||||
self.dstfile
|
self.dstfile
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ExecuteTest(Command):
|
||||||
|
testfile: Path
|
||||||
|
|
||||||
|
@override
|
||||||
|
def _execute(self) -> Result:
|
||||||
|
abspath = Path.absolute(self.testfile)
|
||||||
|
result = Result(
|
||||||
|
success = False,
|
||||||
|
action = "Testing",
|
||||||
|
target = str(self.testfile),
|
||||||
|
command = "",
|
||||||
|
retcode = -1
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle_success(retcode: int, cmdstr: str, stdout: str) -> None:
|
||||||
|
result.success = True
|
||||||
|
result.command = cmdstr
|
||||||
|
result.retcode = retcode
|
||||||
|
result.diagnostics.append(stdout)
|
||||||
|
|
||||||
|
def handle_failure(retcode: int, cmdstr: str, stderr: str) -> None:
|
||||||
|
result.success = False
|
||||||
|
result.command = cmdstr
|
||||||
|
result.retcode = retcode
|
||||||
|
result.diagnostics.append(stderr)
|
||||||
|
|
||||||
|
test(abspath, handle_success, handle_failure)
|
||||||
|
return result
|
||||||
|
|||||||
@@ -9,3 +9,12 @@ class NoTargetsFoundError(Exception):
|
|||||||
def __init__(self, project: Project) -> None:
|
def __init__(self, project: Project) -> None:
|
||||||
super().__init__(f"No targets for project: {project.cid}")
|
super().__init__(f"No targets for project: {project.cid}")
|
||||||
self._project = project
|
self._project = project
|
||||||
|
|
||||||
|
class NoTestsFoundError(Exception):
|
||||||
|
@property
|
||||||
|
def project(self) -> Project:
|
||||||
|
return self._project
|
||||||
|
|
||||||
|
def __init__(self, project: Project) -> None:
|
||||||
|
super().__init__(f"No tests for project: {project.cid}")
|
||||||
|
self._project = project
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
from ..models import Result
|
||||||
|
from ._commands import Command
|
||||||
|
|
||||||
|
|
||||||
|
def execute(commands: list[Command], reason: str) -> list[Result]:
|
||||||
|
results: list[Result] = []
|
||||||
|
|
||||||
|
cmdcount: int = len(commands)
|
||||||
|
if cmdcount == 1:
|
||||||
|
results.append(commands[0].execute(1, reason))
|
||||||
|
return results
|
||||||
|
|
||||||
|
executed: int = 0
|
||||||
|
for command in commands:
|
||||||
|
results.append(command.execute(executed / cmdcount, reason))
|
||||||
|
executed += 1
|
||||||
|
return results
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
from logging import getLogger
|
||||||
|
from logging import Logger
|
||||||
|
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 ..models import Output
|
||||||
|
from ..models import Project
|
||||||
|
from ..models import Target
|
||||||
|
from .._config import Config
|
||||||
|
from ._commands import Command
|
||||||
|
from ._commands import CompileArchive
|
||||||
|
from ._commands import CompileDynlib
|
||||||
|
from ._commands import CompileObject
|
||||||
|
from ._commands import CompileProgram
|
||||||
|
|
||||||
|
_logger = getLogger("chinook")
|
||||||
|
|
||||||
|
|
||||||
|
def prepare(
|
||||||
|
appcfg: Config,
|
||||||
|
project: Project,
|
||||||
|
target: Target,
|
||||||
|
logger: Logger = _logger
|
||||||
|
) -> list[Command]:
|
||||||
|
objects: list[Path] = []
|
||||||
|
commands: list[Command] = []
|
||||||
|
|
||||||
|
# TODO: clean this up.
|
||||||
|
compiler = Compiler(appcfg.native_tooling, logger)
|
||||||
|
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])
|
||||||
|
for file in target.srcs:
|
||||||
|
srcfile = project.origination.src(file.value)
|
||||||
|
dstfile = project.destination.obj(file.value)
|
||||||
|
if latest(srcfile, dstfile):
|
||||||
|
continue
|
||||||
|
commands.append(CompileObject(compiler, defopts, logger, srcfile, dstfile))
|
||||||
|
objects.append(dstfile)
|
||||||
|
|
||||||
|
defopts.objfiles.update(objects)
|
||||||
|
del objects
|
||||||
|
|
||||||
|
defopts.pic = False
|
||||||
|
defopts.defines.clear()
|
||||||
|
if len(target.libs) > 0:
|
||||||
|
for library in target.libs:
|
||||||
|
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)
|
||||||
|
elif libtgt.type == Output.DLL:
|
||||||
|
defopts.objfiles.add(libprj.destination.dll(library.value))
|
||||||
|
|
||||||
|
if len(defopts.objfiles) == 0:
|
||||||
|
return commands
|
||||||
|
|
||||||
|
command: Command
|
||||||
|
match target.type:
|
||||||
|
case Output.EXE:
|
||||||
|
outfile = project.destination.bin(target.name)
|
||||||
|
command = CompileProgram(compiler, defopts, logger, outfile)
|
||||||
|
case Output.LIB:
|
||||||
|
outfile = project.destination.lib(target.name)
|
||||||
|
command = CompileArchive(compiler, defopts, logger, outfile)
|
||||||
|
case Output.DLL:
|
||||||
|
outfile = project.destination.dll(target.name)
|
||||||
|
command = CompileDynlib(compiler, defopts, logger, outfile)
|
||||||
|
case Output.OBJ:
|
||||||
|
assert False, "Not Implemented"
|
||||||
|
commands.append(command)
|
||||||
|
return commands
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
from logging import getLogger
|
||||||
|
from logging import Logger
|
||||||
|
|
||||||
|
from ..models import Project
|
||||||
|
from ..models import Result
|
||||||
|
from .._config import Config
|
||||||
|
from ._commands import Command
|
||||||
|
from ._commands import ExecuteTest
|
||||||
|
from ._except import NoTestsFoundError
|
||||||
|
from ._execute import execute
|
||||||
|
from ._prepare import prepare
|
||||||
|
|
||||||
|
_logger = getLogger("chinook")
|
||||||
|
|
||||||
|
|
||||||
|
def test(
|
||||||
|
appcfg: Config,
|
||||||
|
project: Project,
|
||||||
|
logger: Logger = _logger
|
||||||
|
) -> list[Result]:
|
||||||
|
if len(project.tests) == 0:
|
||||||
|
raise NoTestsFoundError(project)
|
||||||
|
|
||||||
|
commands: list[Command] = []
|
||||||
|
for target in project.tests:
|
||||||
|
commands.extend(prepare(appcfg, project, target, logger))
|
||||||
|
results = execute(commands, "Building")
|
||||||
|
|
||||||
|
commands.clear()
|
||||||
|
for target in project.tests:
|
||||||
|
testfile = project.destination.bin(target.name)
|
||||||
|
commands.append(ExecuteTest(None, None, logger, testfile))
|
||||||
|
results.extend(execute(commands, "Testing"))
|
||||||
|
return results
|
||||||
@@ -1 +1,2 @@
|
|||||||
from ._execute import execute
|
from ._execute import execute
|
||||||
|
from ._execute import test
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ from subprocess import run
|
|||||||
|
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
DataCallback = Callable[[str, str, str], None]
|
DataCallback = Callable[[int, str, str], None]
|
||||||
ErrorCallback = Callable[[str, str, str], None]
|
ErrorCallback = Callable[[int, str, str], None]
|
||||||
|
|
||||||
|
|
||||||
def execute(
|
def execute(
|
||||||
@@ -24,14 +24,42 @@ def execute(
|
|||||||
|
|
||||||
if on_data != None:
|
if on_data != None:
|
||||||
on_data(
|
on_data(
|
||||||
command,
|
result.returncode,
|
||||||
result.stdout.decode("utf-8"),
|
result.args,
|
||||||
result.stderr.decode("utf-8")
|
result.stdout.decode("utf-8")
|
||||||
)
|
)
|
||||||
except CalledProcessError as error:
|
except CalledProcessError as error:
|
||||||
if on_error != None:
|
if on_error != None:
|
||||||
on_error(
|
on_error(
|
||||||
|
error.returncode,
|
||||||
|
error.cmd,
|
||||||
|
error.stderr.decode("utf-8")
|
||||||
|
)
|
||||||
|
|
||||||
|
def test(
|
||||||
|
command: str,
|
||||||
|
on_data: Optional[DataCallback],
|
||||||
|
on_error: Optional[ErrorCallback]
|
||||||
|
) -> None:
|
||||||
|
try:
|
||||||
|
result = run(
|
||||||
|
command,
|
||||||
|
shell = False,
|
||||||
|
check = False,
|
||||||
|
stdout = PIPE,
|
||||||
|
stderr = PIPE
|
||||||
|
)
|
||||||
|
|
||||||
|
if on_data != None:
|
||||||
|
on_data(
|
||||||
|
result.returncode,
|
||||||
|
result.args,
|
||||||
|
result.stdout.decode("utf-8")
|
||||||
|
)
|
||||||
|
except CalledProcessError as error:
|
||||||
|
if on_error != None:
|
||||||
|
on_error(
|
||||||
|
error.returncode,
|
||||||
error.cmd,
|
error.cmd,
|
||||||
error.stdout.decode("utf-8"),
|
|
||||||
error.stderr.decode("utf-8")
|
error.stderr.decode("utf-8")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
name: testing
|
||||||
|
gpid: examples
|
||||||
|
semv: 1.0.0
|
||||||
|
|
||||||
|
tests:
|
||||||
|
- name: factorial_test
|
||||||
|
type: program
|
||||||
|
srcs:
|
||||||
|
- ./entry.cpp
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
int factorial(int start) {
|
||||||
|
if (start < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
auto result = 1;
|
||||||
|
for (auto index = 2; index < start; index++)
|
||||||
|
result *= index;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
return factorial(5) == 120;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user