Lots of work
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
.chinook/
|
.chinook/
|
||||||
.environ/
|
.environ/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
|
.vscode/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
from ._logger import logger
|
||||||
|
|||||||
@@ -1,21 +1,38 @@
|
|||||||
import logging
|
import yaml
|
||||||
|
|
||||||
from chinook import pipeline
|
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
|
from argparse import Namespace
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from semver import Version
|
from semver import Version
|
||||||
|
|
||||||
|
|
||||||
|
# Load application configurations.
|
||||||
|
cfgdir = Path.home() / ".config" / "chinook" / "remotes.yaml"
|
||||||
|
remotes = yaml.full_load(cfgdir) if Path.exists(cfgdir) else []
|
||||||
|
|
||||||
|
|
||||||
|
def _build(args: Namespace) -> None:
|
||||||
|
from chinook import management
|
||||||
|
from chinook import pipeline
|
||||||
|
|
||||||
|
from chinook._logger import logger
|
||||||
|
from chinook.models import Destination
|
||||||
|
from chinook.models import Origination
|
||||||
|
|
||||||
|
try:
|
||||||
|
projpath = Path(args.project)
|
||||||
|
projcfg = management.load(projpath)
|
||||||
|
if projcfg != None:
|
||||||
|
root = projpath.parent
|
||||||
|
projcfg.origination = Origination(root)
|
||||||
|
projcfg.destination = Destination(root)
|
||||||
|
return pipeline.build(projcfg)
|
||||||
|
except Exception as err:
|
||||||
|
logger.exception(str(err))
|
||||||
|
logger.critical("Failed to build project")
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
|
|
||||||
console = logging.StreamHandler()
|
|
||||||
console.setLevel(logging.DEBUG)
|
|
||||||
console.setFormatter(formatter)
|
|
||||||
|
|
||||||
logger = logging.getLogger("chinook")
|
|
||||||
logger.addHandler(console)
|
|
||||||
logger.setLevel(logging.INFO)
|
|
||||||
|
|
||||||
# Parent parser to all subcommands.
|
# Parent parser to all subcommands.
|
||||||
parser = ArgumentParser(
|
parser = ArgumentParser(
|
||||||
prog = "chinook",
|
prog = "chinook",
|
||||||
@@ -24,12 +41,12 @@ def main() -> None:
|
|||||||
|
|
||||||
commands = parser.add_subparsers(required=True)
|
commands = parser.add_subparsers(required=True)
|
||||||
buildcmd = commands.add_parser("build",help="Builds your Chinook project")
|
buildcmd = commands.add_parser("build",help="Builds your Chinook project")
|
||||||
buildcmd.set_defaults(func=pipeline.build)
|
buildcmd.set_defaults(func=_build)
|
||||||
buildcmd.add_argument(
|
buildcmd.add_argument(
|
||||||
"project",
|
"project",
|
||||||
nargs="?",
|
nargs="?",
|
||||||
default=".",
|
default=".",
|
||||||
type=Path,
|
type=str,
|
||||||
help="The path to the project to build",
|
help="The path to the project to build",
|
||||||
metavar="PROJECT"
|
metavar="PROJECT"
|
||||||
)
|
)
|
||||||
|
|||||||
3
chinook/_config.py
Normal file
3
chinook/_config.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
|
config = None
|
||||||
10
chinook/_logger.py
Normal file
10
chinook/_logger.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
_formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
|
||||||
|
_console = logging.StreamHandler()
|
||||||
|
_console.setLevel(logging.DEBUG)
|
||||||
|
_console.setFormatter(_formatter)
|
||||||
|
|
||||||
|
logger = logging.getLogger("chinook")
|
||||||
|
logger.addHandler(_console)
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
6
chinook/compiler/gcc.py
Normal file
6
chinook/compiler/gcc.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from ..models import Project
|
||||||
|
from ..models import Target
|
||||||
|
|
||||||
|
|
||||||
|
def compile(target: Target, project: Project) -> None:
|
||||||
|
...
|
||||||
6
chinook/management/__init__.py
Normal file
6
chinook/management/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from ._except import NoProjectFoundError
|
||||||
|
from ._except import NoPackageFoundError
|
||||||
|
from ._except import NoRemotesFoundError
|
||||||
|
from ._load import load
|
||||||
|
from ._shared import projects
|
||||||
|
from ._shared import targets
|
||||||
30
chinook/management/_except.py
Normal file
30
chinook/management/_except.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from ..models import Project
|
||||||
|
|
||||||
|
class NoProjectFoundError(Exception):
|
||||||
|
@property
|
||||||
|
def path(self) -> Path:
|
||||||
|
return self._path
|
||||||
|
|
||||||
|
def __init__(self, path: Path) -> None:
|
||||||
|
super().__init__(f"Project could not be found: {path}")
|
||||||
|
self._path = path
|
||||||
|
|
||||||
|
class NoPackageFoundError(Exception):
|
||||||
|
@property
|
||||||
|
def path(self) -> Path:
|
||||||
|
return self._path
|
||||||
|
|
||||||
|
def __init__(self, path: Path) -> None:
|
||||||
|
super().__init__(f"Package could not be found: {path}")
|
||||||
|
self._path = path
|
||||||
|
|
||||||
|
class NoRemotesFoundError(Exception):
|
||||||
|
@property
|
||||||
|
def project(self) -> Project:
|
||||||
|
return self._project
|
||||||
|
|
||||||
|
def __init__(self, project: Project) -> None:
|
||||||
|
super().__init__(f"Project remotes not present: {project.cid}")
|
||||||
|
self._project = project
|
||||||
|
|
||||||
77
chinook/management/_load.py
Normal file
77
chinook/management/_load.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import yaml
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from ..models import Project
|
||||||
|
from ..remote import clone
|
||||||
|
from ..remote import RemoteDomainUnavailableError
|
||||||
|
from ..remote import RemoteAddressNonexistentError
|
||||||
|
from ..remote import PackageCloneFailedError
|
||||||
|
from .._config import config
|
||||||
|
from .._logger import logger
|
||||||
|
from ._except import NoProjectFoundError
|
||||||
|
from ._except import NoPackageFoundError
|
||||||
|
from ._except import NoRemotesFoundError
|
||||||
|
from ._shared import projects
|
||||||
|
from ._shared import targets
|
||||||
|
|
||||||
|
|
||||||
|
def load(path: Path) -> Project:
|
||||||
|
if path in projects:
|
||||||
|
return projects[path]
|
||||||
|
|
||||||
|
fullpath = path / "chinookfile"
|
||||||
|
if not Path.exists(fullpath):
|
||||||
|
raise NoProjectFoundError(fullpath)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(fullpath, 'r') as file:
|
||||||
|
project = yaml.full_load(file.read())
|
||||||
|
# projects[path] = config
|
||||||
|
_load_packages(project)
|
||||||
|
_collect_targets(project)
|
||||||
|
return project
|
||||||
|
except Exception as rethrowme:
|
||||||
|
raise rethrowme
|
||||||
|
|
||||||
|
|
||||||
|
def _load_packages(project: Project) -> None:
|
||||||
|
# Don't process packages if none are present.
|
||||||
|
if not hasattr(project, "packages"):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Packages present, but no remotes is not good :(
|
||||||
|
if len(project.packages) != 0 and \
|
||||||
|
len(config.remotes) == 0:
|
||||||
|
raise NoRemotesFoundError(project)
|
||||||
|
|
||||||
|
for pckg in project.packages:
|
||||||
|
try:
|
||||||
|
# Get available versions, find highest possible version supported.
|
||||||
|
|
||||||
|
path = pckg.diskpath
|
||||||
|
if not Path.exists(path):
|
||||||
|
clone(config.remotes, pckg, path)
|
||||||
|
load(path)
|
||||||
|
except RemoteDomainUnavailableError as err:
|
||||||
|
# We tried to ping the remote to make sure we could download from it, and
|
||||||
|
# we got no response from the remote.
|
||||||
|
logger.warning(str(err))
|
||||||
|
except RemoteAddressNonexistentError as err:
|
||||||
|
# We checked if the remote address existed and we found that it did not.
|
||||||
|
logger.warning(str(err))
|
||||||
|
except PackageCloneFailedError as err:
|
||||||
|
# Remotes and pacakge exist, clone was uncessuccessful.
|
||||||
|
# Either clone command failed, or path did not exist afterwards.
|
||||||
|
logger.error(str(err))
|
||||||
|
except NoProjectFoundError as err:
|
||||||
|
# Remotes and package exist, clone was successful, but chinookfile is not
|
||||||
|
# present in the package directory.
|
||||||
|
raise NoPackageFoundError(err.project)
|
||||||
|
except Exception as rethrowme:
|
||||||
|
raise rethrowme
|
||||||
|
|
||||||
|
def _collect_targets(project: Project) -> None:
|
||||||
|
for target in project.targets:
|
||||||
|
name = f"{project.cid}#{target.name}"
|
||||||
|
if name not in targets:
|
||||||
|
targets[name] = target
|
||||||
7
chinook/management/_shared.py
Normal file
7
chinook/management/_shared.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from ..models import Package
|
||||||
|
from ..models import Project
|
||||||
|
from ..models import Target
|
||||||
|
|
||||||
|
packages: dict[str, Package] = {}
|
||||||
|
projects: dict[str, Project] = {}
|
||||||
|
targets: dict[str, Target] = {}
|
||||||
@@ -1 +1,13 @@
|
|||||||
from ._chinookfile import ChinookFile
|
from ._accessor import Accessor
|
||||||
|
from ._accessor import PublicAccessor
|
||||||
|
from ._accessor import ProtectedAccessor
|
||||||
|
from ._accessor import PrivateAccessor
|
||||||
|
from ._branch import Branch
|
||||||
|
from ._destination import Destination
|
||||||
|
from ._identity import Identity
|
||||||
|
from ._level import Level
|
||||||
|
from ._origination import Origination
|
||||||
|
from ._output import Output
|
||||||
|
from ._project import Project
|
||||||
|
from ._package import Package
|
||||||
|
from ._target import Target
|
||||||
|
|||||||
63
chinook/models/_accessor.py
Normal file
63
chinook/models/_accessor.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import yaml
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from ._level import Level
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Accessor:
|
||||||
|
level: Level
|
||||||
|
value: str
|
||||||
|
|
||||||
|
def __hash__(self) -> int:
|
||||||
|
return hash((self.level, self.value))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_list(value: Optional[list]) -> list['Accessor']:
|
||||||
|
return [Accessor.from_value(v) for v in value] if value != None else []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_value(value: dict | str) -> 'Accessor':
|
||||||
|
if isinstance(value, str):
|
||||||
|
return Accessor(level = Level.PUBLIC, value = value)
|
||||||
|
if isinstance(value, dict):
|
||||||
|
return Accessor(**dict)
|
||||||
|
if isinstance(value, Accessor):
|
||||||
|
return value
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
class PublicAccessor(yaml.YAMLObject, Accessor):
|
||||||
|
yaml_tag = u"!public"
|
||||||
|
def __init__(self, value: str) -> None:
|
||||||
|
super().__init__(level = Level.PUBLIC, value = value)
|
||||||
|
|
||||||
|
class ProtectedAccessor(yaml.YAMLObject, Accessor):
|
||||||
|
yaml_tag = u"!protected"
|
||||||
|
def __init__(self, value: str) -> None:
|
||||||
|
super().__init__(level = Level.PROTECTED, value = value)
|
||||||
|
|
||||||
|
class PrivateAccessor(yaml.YAMLObject, Accessor):
|
||||||
|
yaml_tag = u"!private"
|
||||||
|
def __init__(self, value: str) -> None:
|
||||||
|
super().__init__(level = Level.PRIVATE, value = value)
|
||||||
|
|
||||||
|
|
||||||
|
yaml.add_constructor(
|
||||||
|
PublicAccessor.yaml_tag,
|
||||||
|
lambda loader, node:
|
||||||
|
PublicAccessor(loader.construct_scalar(node))
|
||||||
|
)
|
||||||
|
|
||||||
|
yaml.add_constructor(
|
||||||
|
ProtectedAccessor.yaml_tag,
|
||||||
|
lambda loader, node:
|
||||||
|
ProtectedAccessor(loader.construct_scalar(node))
|
||||||
|
)
|
||||||
|
|
||||||
|
yaml.add_constructor(
|
||||||
|
PrivateAccessor.yaml_tag,
|
||||||
|
lambda loader, node:
|
||||||
|
PrivateAccessor(loader.construct_scalar(node))
|
||||||
|
)
|
||||||
6
chinook/models/_activation.py
Normal file
6
chinook/models/_activation.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import yaml
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Activation(yaml.YAMLObject):
|
||||||
|
pass
|
||||||
34
chinook/models/_branch.py
Normal file
34
chinook/models/_branch.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import yaml
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from dataclasses import field
|
||||||
|
|
||||||
|
from semantic_version import Version
|
||||||
|
from semantic_version import NpmSpec
|
||||||
|
from semantic_version import validate
|
||||||
|
|
||||||
|
from typing import ClassVar
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Branch(yaml.YAMLObject):
|
||||||
|
yaml_tag: ClassVar[str] = u"!branch"
|
||||||
|
|
||||||
|
value: str
|
||||||
|
_valid: Optional[bool] = field(init=False,default=None)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def as_version(self) -> Version:
|
||||||
|
if not self.is_version:
|
||||||
|
return Version(0, 0, 0, self.value)
|
||||||
|
return Version.parse(self.value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_version(self) -> bool:
|
||||||
|
if self._valid == None:
|
||||||
|
self._valid = validate(self.value)
|
||||||
|
return self._valid
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.value
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ChinookFile:
|
|
||||||
pass
|
|
||||||
53
chinook/models/_destination.py
Normal file
53
chinook/models/_destination.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Support custom output file extension based on user
|
||||||
|
# or platform specification.
|
||||||
|
class Destination:
|
||||||
|
@property
|
||||||
|
def root(self) -> Path:
|
||||||
|
return self._root
|
||||||
|
|
||||||
|
@property
|
||||||
|
def binpath(self) -> Path:
|
||||||
|
return self._binpath
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dllpath(self) -> Path:
|
||||||
|
return self._binpath
|
||||||
|
|
||||||
|
@property
|
||||||
|
def libpath(self) -> Path:
|
||||||
|
return self._libpath
|
||||||
|
|
||||||
|
@property
|
||||||
|
def objpath(self) -> Path:
|
||||||
|
return self._objpath
|
||||||
|
|
||||||
|
def __init__(self, outpath: Path, mkdirs=True) -> 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)
|
||||||
|
|
||||||
|
def bin(self, file: str) -> Path:
|
||||||
|
return self.binpath / file
|
||||||
|
|
||||||
|
def dll(self, file: str) -> Path:
|
||||||
|
return self.dllpath / f"{file}.so"
|
||||||
|
|
||||||
|
def lib(self, file: str) -> Path:
|
||||||
|
return self.libpath / f"lib{file}.a"
|
||||||
|
|
||||||
|
def obj(self, file: Path) -> Path:
|
||||||
|
return self.objpath / f"{file}.o"
|
||||||
36
chinook/models/_identity.py
Normal file
36
chinook/models/_identity.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import re
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from ._branch import Branch
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Identity(yaml.YAMLObject):
|
||||||
|
name: str
|
||||||
|
gpid: str
|
||||||
|
semv: Branch
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cid(self) -> str:
|
||||||
|
return f"{self.gpid}/{self.name}@{self.semv}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_cid(cid: str) -> dict[str, str]:
|
||||||
|
pattern = r"^(?P<gpid>[^/]+)/(?P<name>[^@]+)@(?P<semv>.+)$"
|
||||||
|
match = re.match(pattern, cid)
|
||||||
|
return match.groupdict() if match else {}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_fields(fields: dict) -> 'Identity':
|
||||||
|
if name:=fields.get("name") == None:
|
||||||
|
raise AttributeError(name="name",obj=fields)
|
||||||
|
if gpid:=fields.get("gpid") == None:
|
||||||
|
raise AttributeError(name="gpid",obj=fields)
|
||||||
|
if semv:=fields.get("semv") == None:
|
||||||
|
raise AttributeError(name="semv",obj=fields)
|
||||||
|
return Identity(
|
||||||
|
name=name,
|
||||||
|
gpid=gpid,
|
||||||
|
semv=Branch(semv)
|
||||||
|
)
|
||||||
8
chinook/models/_level.py
Normal file
8
chinook/models/_level.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from enum import IntEnum
|
||||||
|
from enum import auto
|
||||||
|
|
||||||
|
|
||||||
|
class Level(IntEnum):
|
||||||
|
PUBLIC = auto()
|
||||||
|
PROTECTED = auto()
|
||||||
|
PRIVATE = auto()
|
||||||
19
chinook/models/_origination.py
Normal file
19
chinook/models/_origination.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
class Origination:
|
||||||
|
@property
|
||||||
|
def root(self) -> Path:
|
||||||
|
return self._root
|
||||||
|
|
||||||
|
def __init__(self, inpath: Path) -> None:
|
||||||
|
self._root = inpath
|
||||||
|
self._srcpath = self._root / "source"
|
||||||
|
self._incpath = self._root / "include"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def cwd() -> 'Origination':
|
||||||
|
return Origination(Path.cwd())
|
||||||
|
|
||||||
|
def src(self, file: Path) -> Path:
|
||||||
|
return self._root / file
|
||||||
9
chinook/models/_output.py
Normal file
9
chinook/models/_output.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from enum import StrEnum
|
||||||
|
|
||||||
|
|
||||||
|
class Output(StrEnum):
|
||||||
|
EXE = "executable"
|
||||||
|
LIB = "archive"
|
||||||
|
DLL = "dynlib"
|
||||||
|
OBJ = "objfile"
|
||||||
|
INT = "interface"
|
||||||
59
chinook/models/_package.py
Normal file
59
chinook/models/_package.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import yaml
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from dataclasses import field
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from semantic_version import NpmSpec
|
||||||
|
|
||||||
|
from typing import ClassVar
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from ._identity import Identity
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Package(yaml.YAMLObject):
|
||||||
|
yaml_tag: ClassVar[str] = u"!package"
|
||||||
|
|
||||||
|
name: str
|
||||||
|
gpid: str
|
||||||
|
spec: NpmSpec
|
||||||
|
tag: Optional[str] = field(init=False,default=None)
|
||||||
|
domain: Optional[str] = field(init=False,default=None)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def reponame(self) -> str:
|
||||||
|
return f"{self.gpid}/{self.name}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def chinookid(self) -> str:
|
||||||
|
if self.tag == None:
|
||||||
|
raise ValueError
|
||||||
|
return f"{self.reponame}@{self.tag}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def remote(self) -> str:
|
||||||
|
if self.domain == None:
|
||||||
|
raise ValueError
|
||||||
|
return f"https://{self.domain}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def repository(self) -> str:
|
||||||
|
return f"{self.remote}/{self.reponame}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dirname(self) -> Path:
|
||||||
|
return self.chinookid
|
||||||
|
|
||||||
|
@property
|
||||||
|
def diskpath(self) -> Path:
|
||||||
|
return Path.home() / ".chinook" / self.chinookid
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_list(items: list) -> list['Package']:
|
||||||
|
return [Package.from_cid(item) for item in items]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_chinookid(cid: str) -> 'Package':
|
||||||
|
return Package(**Identity.from_cpid(cid))
|
||||||
13
chinook/models/_profile.py
Normal file
13
chinook/models/_profile.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import yaml
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import ClassVar
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Profile(yaml.YAMLObject):
|
||||||
|
yaml_tag: ClassVar[str] = u"!profile"
|
||||||
|
|
||||||
|
name: str
|
||||||
|
when: Optional[Triple]
|
||||||
30
chinook/models/_project.py
Normal file
30
chinook/models/_project.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from dataclasses import field
|
||||||
|
from typing import ClassVar
|
||||||
|
from ._identity import Identity
|
||||||
|
from ._package import Package
|
||||||
|
from ._target import Target
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Project(Identity):
|
||||||
|
yaml_tag: ClassVar[str] = u"!project"
|
||||||
|
|
||||||
|
packages: list[Package] = field(default_factory=list)
|
||||||
|
targets: list[Target] = field(default_factory=list)
|
||||||
|
options: dict[str, str] = field(default_factory=dict)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_fields(fields: dict) -> 'Project':
|
||||||
|
identity = Identity.from_fields(fields)
|
||||||
|
packages = fields.get("packages") or []
|
||||||
|
if targets:=fields.get("targets") == None:
|
||||||
|
raise AttributeError(name="targets", obj=fields)
|
||||||
|
return Project(
|
||||||
|
name = identity.name,
|
||||||
|
gpid = identity.gpid,
|
||||||
|
semv = identity.semv,
|
||||||
|
packages = [Package.from_fields(item) for item in packages],
|
||||||
|
targets = [Target.from_fields(item) for item in targets],
|
||||||
|
options = {}
|
||||||
|
)
|
||||||
29
chinook/models/_target.py
Normal file
29
chinook/models/_target.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import yaml
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from dataclasses import field
|
||||||
|
from typing import ClassVar
|
||||||
|
|
||||||
|
from ._accessor import Accessor
|
||||||
|
from ._output import Output
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Target(yaml.YAMLObject):
|
||||||
|
yaml_tag: ClassVar[str] = u"!target"
|
||||||
|
|
||||||
|
name: str
|
||||||
|
type: Output
|
||||||
|
options: set[Accessor]
|
||||||
|
sources: set[Accessor]
|
||||||
|
inherits: set[Accessor]
|
||||||
|
|
||||||
|
archives: set[Accessor] = field(default=set)
|
||||||
|
dynlibs: set[Accessor] = field(default=set)
|
||||||
|
|
||||||
|
def consolidate(self, parent: 'Target') -> None:
|
||||||
|
# NOTE: Do not need to append parents of parent.
|
||||||
|
self.options.update(parent.options)
|
||||||
|
self.sources.update(parent.sources)
|
||||||
|
self.archives.update(parent.archives)
|
||||||
|
self.dynlibs.update(parent.dynlibs)
|
||||||
19
chinook/models/_toolchain.py
Normal file
19
chinook/models/_toolchain.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import yaml
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import ClassVar
|
||||||
|
|
||||||
|
from ._triple import Triple
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Toolchain(yaml.YAMLObject):
|
||||||
|
yaml_tag: ClassVar[str] = u"!toolchain"
|
||||||
|
|
||||||
|
name: str
|
||||||
|
when: list[Triple]
|
||||||
|
compiler: Path
|
||||||
|
linker: Path
|
||||||
|
archiver: Path
|
||||||
|
debugger: Path
|
||||||
16
chinook/models/_triple.py
Normal file
16
chinook/models/_triple.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
from typing import ClassVar
|
||||||
|
|
||||||
|
from ._activation import Activation
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Triple(Activation):
|
||||||
|
yaml_tag: ClassVar[str] = u"!triple"
|
||||||
|
|
||||||
|
arch: str
|
||||||
|
vend: str
|
||||||
|
sys: str
|
||||||
|
abi: str
|
||||||
|
|
||||||
@@ -1,3 +1 @@
|
|||||||
from ._build import build
|
from ._build import build
|
||||||
|
|
||||||
__all__ = [ build ]
|
|
||||||
|
|||||||
@@ -1,25 +1,11 @@
|
|||||||
from ..models import ChinookFile
|
from ..models import Project
|
||||||
|
from .._logger import logger
|
||||||
|
from ._compile import compile
|
||||||
|
from ._except import NoTargetsFoundError
|
||||||
|
|
||||||
from pathlib import Path
|
def build(project: Project) -> None:
|
||||||
from typing import overload
|
# TODO: Download, catalog, and build dependencies
|
||||||
|
# TODO: Build the current configuration
|
||||||
|
if len(project.targets) == 0:
|
||||||
@overload
|
raise NoTargetsFoundError(project)
|
||||||
def build(path: Path) -> None: ...
|
logger.info(f"Building {project.cid}")
|
||||||
|
|
||||||
@overload
|
|
||||||
def build(file: ChinookFile) -> None: ...
|
|
||||||
|
|
||||||
|
|
||||||
def build(value) -> None:
|
|
||||||
if isinstance(value, Path):
|
|
||||||
return _build_from_path(value)
|
|
||||||
elif isinstance(value, ChinookFile):
|
|
||||||
return _build_from_file(value)
|
|
||||||
|
|
||||||
|
|
||||||
def _build_from_path(path: Path) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _build_from_file(file: ChinookFile) -> None:
|
|
||||||
pass
|
|
||||||
|
|||||||
4
chinook/pipeline/_compile.py
Normal file
4
chinook/pipeline/_compile.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
from ..models import Target
|
||||||
|
|
||||||
|
def compile(target: Target) -> None:
|
||||||
|
...
|
||||||
2
chinook/pipeline/_except.py
Normal file
2
chinook/pipeline/_except.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
class NoTargetsFoundError(Exception):
|
||||||
|
pass
|
||||||
1
chinook/pipeline/_shared.py
Normal file
1
chinook/pipeline/_shared.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
6
chinook/remote/__init__.py
Normal file
6
chinook/remote/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from ._clone import clone
|
||||||
|
from ._exists import exists
|
||||||
|
from ._except import RemoteDomainUnavailableError
|
||||||
|
from ._except import RemoteAddressNonexistentError
|
||||||
|
from ._except import PackageCloneFailedError
|
||||||
|
from ._ping import ping
|
||||||
63
chinook/remote/_clone.py
Normal file
63
chinook/remote/_clone.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from typing import overload
|
||||||
|
from ..models import Package
|
||||||
|
from ..shell import execute
|
||||||
|
from ._except import RemoteDomainUnavailableError
|
||||||
|
from ._except import RemoteAddressNonexistentError
|
||||||
|
from ._except import PackageCloneFailedError
|
||||||
|
from ._exists import exists
|
||||||
|
from ._ping import ping
|
||||||
|
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def clone(
|
||||||
|
domains: list[str],
|
||||||
|
fspath: Path,
|
||||||
|
package: Package
|
||||||
|
) -> None: ...
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def clone(
|
||||||
|
domain: str,
|
||||||
|
fspath: Path,
|
||||||
|
pacakge: Package
|
||||||
|
) -> None: ...
|
||||||
|
|
||||||
|
|
||||||
|
def clone(
|
||||||
|
domain: str | list[str],
|
||||||
|
fspath: Path,
|
||||||
|
package: Package
|
||||||
|
) -> None:
|
||||||
|
if package.remote != None:
|
||||||
|
return _clone_from_domain(package.remote, fspath, package)
|
||||||
|
|
||||||
|
if isinstance(domain, str):
|
||||||
|
return _clone_from_domain(domain, fspath, package)
|
||||||
|
|
||||||
|
|
||||||
|
def _clone_from_domain(package: Package) -> None:
|
||||||
|
if package.tag == None:
|
||||||
|
raise ValueError("No valid tag for package.")
|
||||||
|
|
||||||
|
cloned = True
|
||||||
|
cmdstr = "git clone --branch {} {} {}"
|
||||||
|
def _handle_error(_: Exception) -> None:
|
||||||
|
nonlocal cloned
|
||||||
|
cloned = False
|
||||||
|
|
||||||
|
if not ping(package.remote):
|
||||||
|
raise RemoteDomainUnavailableError(package.remote)
|
||||||
|
|
||||||
|
if not exists(package.repository):
|
||||||
|
raise RemoteAddressNonexistentError(package.repository)
|
||||||
|
|
||||||
|
cmd = cmdstr.format(
|
||||||
|
package.tag,
|
||||||
|
package.repository,
|
||||||
|
package.diskpath
|
||||||
|
)
|
||||||
|
|
||||||
|
execute(command = cmd, on_error = _handle_error)
|
||||||
|
if not cloned or not Path.exists(package.diskpath):
|
||||||
|
raise PackageCloneFailedError(package.cid)
|
||||||
11
chinook/remote/_except.py
Normal file
11
chinook/remote/_except.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
class RemoteDomainUnavailableError(Exception):
|
||||||
|
def __init__(self, domain: str) -> None:
|
||||||
|
super().__init__(f"Domain is unavailable: {domain}")
|
||||||
|
|
||||||
|
class RemoteAddressNonexistentError(Exception):
|
||||||
|
def __init__(self, address: str) -> None:
|
||||||
|
super().__init__(f"Address does not exist: {address}")
|
||||||
|
|
||||||
|
class PackageCloneFailedError(Exception):
|
||||||
|
def __init__(self, pckgid: str) -> None:
|
||||||
|
super().__init__(f"Failed to clone package: {pckgid}")
|
||||||
11
chinook/remote/_exists.py
Normal file
11
chinook/remote/_exists.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from ..shell import execute
|
||||||
|
|
||||||
|
|
||||||
|
def exists(addr: str) -> bool:
|
||||||
|
repo_exists = True
|
||||||
|
command_str = "git ls-remote --exit-code -h {}"
|
||||||
|
def _handle_error(error: Exception) -> None:
|
||||||
|
nonlocal repo_exists
|
||||||
|
repo_exists = False
|
||||||
|
execute(command_str.format(addr), _handle_error)
|
||||||
|
return repo_exists
|
||||||
11
chinook/remote/_ping.py
Normal file
11
chinook/remote/_ping.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from ..shell import execute
|
||||||
|
|
||||||
|
|
||||||
|
def ping(addr: str) -> bool:
|
||||||
|
can_ping = True
|
||||||
|
cmd_str = "ping {} -W 1"
|
||||||
|
def _handle_error(error: Exception) -> None:
|
||||||
|
nonlocal can_ping
|
||||||
|
can_ping = False
|
||||||
|
execute(cmd_str.format(addr), _handle_error)
|
||||||
|
return can_ping
|
||||||
1
chinook/shell/__init__.py
Normal file
1
chinook/shell/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from ._execute import execute
|
||||||
29
chinook/shell/_execute.py
Normal file
29
chinook/shell/_execute.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from subprocess import CalledProcessError
|
||||||
|
from subprocess import PIPE
|
||||||
|
from subprocess import run
|
||||||
|
|
||||||
|
from typing import Callable
|
||||||
|
from typing import Optional
|
||||||
|
DataCallback = Callable[[str, str, str], None]
|
||||||
|
ErrorCallback = Callable[[Exception], None]
|
||||||
|
|
||||||
|
|
||||||
|
def execute(
|
||||||
|
command: str,
|
||||||
|
on_data: Optional[DataCallback],
|
||||||
|
on_error: Optional[ErrorCallback]
|
||||||
|
) -> None:
|
||||||
|
try:
|
||||||
|
result = run(
|
||||||
|
command,
|
||||||
|
shell = True,
|
||||||
|
check = True,
|
||||||
|
stdout = PIPE,
|
||||||
|
stderr = PIPE
|
||||||
|
)
|
||||||
|
|
||||||
|
if on_data != None:
|
||||||
|
on_data(command, result.stdout, result.stderr)
|
||||||
|
except CalledProcessError as error:
|
||||||
|
if on_error != None:
|
||||||
|
on_error(error)
|
||||||
5
chinook/triple/__init__.py
Normal file
5
chinook/triple/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from ._arch import Architecture
|
||||||
|
from ._environ import Environment
|
||||||
|
from ._system import System
|
||||||
|
from ._system import SystemData
|
||||||
|
from ._vendor import Vendor
|
||||||
49
chinook/triple/_arch.py
Normal file
49
chinook/triple/_arch.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
from enum import auto
|
||||||
|
from enum import unique
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ArchDataMixin:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Architecture(Enum):
|
||||||
|
UNKNOWN = auto()
|
||||||
|
ARM = None # CUSTOM
|
||||||
|
AMDGCN = auto()
|
||||||
|
AARCH64 = None # CUSTOM
|
||||||
|
ASMJS = auto()
|
||||||
|
AVR = auto()
|
||||||
|
BPFEB = auto()
|
||||||
|
BPFEL = auto()
|
||||||
|
HEXAGON = auto()
|
||||||
|
X86_32 = None # CUSTOM
|
||||||
|
M68K = auto()
|
||||||
|
LOONGARCH64 = auto()
|
||||||
|
MIPS32 = None # CUSTOM
|
||||||
|
MIPS64 = None # CUSTOM
|
||||||
|
MSP430 = auto()
|
||||||
|
NVPTX64 = auto()
|
||||||
|
PULLEY32 = auto()
|
||||||
|
PULLEY64 = auto()
|
||||||
|
PULLEY32BE = auto()
|
||||||
|
PULLEY64BE = auto()
|
||||||
|
POWERPC = auto()
|
||||||
|
POWERPC64LE = auto()
|
||||||
|
RISCV32 = None # CUSTOM
|
||||||
|
RISCV64 = None # CUSTOM
|
||||||
|
S390X = auto()
|
||||||
|
SPARC = auto()
|
||||||
|
SPARC64 = auto()
|
||||||
|
SPARCV9 = auto()
|
||||||
|
WASM32 = auto()
|
||||||
|
WASM64 = auto()
|
||||||
|
X86_64 = auto()
|
||||||
|
X86_64H = auto()
|
||||||
|
XTENSA = auto()
|
||||||
|
CLEVER = None # CUSTOM
|
||||||
|
ZKASM = auto()
|
||||||
|
Z80 = None # CUSTOM
|
||||||
39
chinook/triple/_environ.py
Normal file
39
chinook/triple/_environ.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
from enum import StrEnum
|
||||||
|
from enum import auto
|
||||||
|
|
||||||
|
|
||||||
|
class Environment(StrEnum):
|
||||||
|
UNKNOWN = auto()
|
||||||
|
AMDGIZ = auto()
|
||||||
|
ANDROID = auto()
|
||||||
|
ANDROIDEABI = auto()
|
||||||
|
EABI = auto()
|
||||||
|
EABIHF = auto()
|
||||||
|
GNU = auto()
|
||||||
|
GNUABI64 = auto()
|
||||||
|
GNUEABI = auto()
|
||||||
|
GNUSPE = auto()
|
||||||
|
GNUX32 = auto()
|
||||||
|
GNU_ILP32 = auto()
|
||||||
|
GNULLVM = auto()
|
||||||
|
HERMITKERNEL = auto()
|
||||||
|
HURDKERNEL = auto()
|
||||||
|
LINUXKERNEL = auto()
|
||||||
|
MACABI = auto()
|
||||||
|
MUSL = auto()
|
||||||
|
MUSLEABI = auto()
|
||||||
|
MUSLEABIHF = auto()
|
||||||
|
MUSLABI64 = auto()
|
||||||
|
MSVC = auto()
|
||||||
|
NEWLIB = auto()
|
||||||
|
NONE = auto()
|
||||||
|
KERNEL = auto()
|
||||||
|
UCLIBC = auto()
|
||||||
|
UCLIBCEABI = auto()
|
||||||
|
UCLIBCEABIHF = auto()
|
||||||
|
SGX = auto()
|
||||||
|
SIM = auto()
|
||||||
|
SOFTFLOAT = auto()
|
||||||
|
SPE = auto()
|
||||||
|
THREADS = auto()
|
||||||
|
OHOS = auto()
|
||||||
66
chinook/triple/_system.py
Normal file
66
chinook/triple/_system.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
from enum import auto
|
||||||
|
from semver import Version
|
||||||
|
from typing import Any
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SystemData:
|
||||||
|
name: str
|
||||||
|
semv: Optional[Version]
|
||||||
|
|
||||||
|
|
||||||
|
class _SystemDataMixin(Enum):
|
||||||
|
@staticmethod
|
||||||
|
def _generate_next_value_(
|
||||||
|
name: str,
|
||||||
|
start: int,
|
||||||
|
count: int,
|
||||||
|
prevs: list[Any]
|
||||||
|
) -> SystemData:
|
||||||
|
return SystemData(name, None)
|
||||||
|
|
||||||
|
|
||||||
|
class System(_SystemDataMixin):
|
||||||
|
UNKNOWN = auto()
|
||||||
|
AIX = auto()
|
||||||
|
AMDHSA = auto()
|
||||||
|
BITRIG = auto()
|
||||||
|
CLOUDABI = auto()
|
||||||
|
CUDA = auto()
|
||||||
|
CYGWIN = auto()
|
||||||
|
DARWIN = auto()
|
||||||
|
DRAGONFLY = auto()
|
||||||
|
EMSCRIPTEN = auto()
|
||||||
|
ESPIDF = auto()
|
||||||
|
FREEBSD = auto()
|
||||||
|
FUCHSIA = auto()
|
||||||
|
HAIKU = auto()
|
||||||
|
HERMIT = auto()
|
||||||
|
HORIZON = auto()
|
||||||
|
HURD = auto()
|
||||||
|
ILLUMOS = auto()
|
||||||
|
IOS = auto()
|
||||||
|
L4RE = auto()
|
||||||
|
LINUX = auto()
|
||||||
|
MACOSX = auto()
|
||||||
|
NEBULET = auto()
|
||||||
|
NETBSD = auto()
|
||||||
|
NONE = auto()
|
||||||
|
OPENBSD = auto()
|
||||||
|
PSP = auto()
|
||||||
|
REDOX = auto()
|
||||||
|
SOLARIS = auto()
|
||||||
|
SOLIDASP3 = auto()
|
||||||
|
TVOS = auto()
|
||||||
|
UEFI = auto()
|
||||||
|
VISIONOS = auto()
|
||||||
|
VXWORKS = auto()
|
||||||
|
WASI = auto()
|
||||||
|
WASIP1 = auto()
|
||||||
|
WASIP2 = auto()
|
||||||
|
WATCHOS = auto()
|
||||||
|
WINDOWS = auto()
|
||||||
|
XROS = auto()
|
||||||
19
chinook/triple/_vendor.py
Normal file
19
chinook/triple/_vendor.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from enum import StrEnum
|
||||||
|
from enum import auto
|
||||||
|
|
||||||
|
|
||||||
|
class Vendor(StrEnum):
|
||||||
|
UNKNOWN = auto()
|
||||||
|
AMD = auto()
|
||||||
|
APPLE = auto()
|
||||||
|
ESPRESSIF = auto()
|
||||||
|
FORTANIX = auto()
|
||||||
|
IBM = auto()
|
||||||
|
KMC = auto()
|
||||||
|
NINTENDO = auto()
|
||||||
|
NVIDIA = auto()
|
||||||
|
PC = auto()
|
||||||
|
RUMPRUN = auto()
|
||||||
|
SUN = auto()
|
||||||
|
UWP = auto()
|
||||||
|
WRS = auto()
|
||||||
9
example/executable/chinookfile
Normal file
9
example/executable/chinookfile
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
name: executable
|
||||||
|
gpid: examples
|
||||||
|
semv: 1.0.0
|
||||||
|
|
||||||
|
targets:
|
||||||
|
- name: example
|
||||||
|
type: executable
|
||||||
|
srcs:
|
||||||
|
- ./entry.cpp
|
||||||
11
example/executable/entry.cpp
Normal file
11
example/executable/entry.cpp
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#include <iostream>
|
||||||
|
#if __cplusplus >= 202302L
|
||||||
|
# define print(x) std::print(#x)
|
||||||
|
#else // Old C++ version.
|
||||||
|
# define print(x) std::cout << #x << std::endl
|
||||||
|
#endif // Check C++ version.
|
||||||
|
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
print("Hello, World!");
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
name = "chinook"
|
name = "chinook"
|
||||||
version = "1.0.0-alpha"
|
version = "1.0.0-alpha"
|
||||||
description = "Opinionated build tool for C/C++."
|
description = "Opinionated build tool for C/C++."
|
||||||
dependencies = ["PyYAML", "semver"]
|
dependencies = ["PyYAML", "semantic-version"]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
chinook = "chinook.__main__:main"
|
chinook = "chinook.__main__:main"
|
||||||
|
|||||||
Reference in New Issue
Block a user