Lots of work
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
.chinook/
|
||||
.environ/
|
||||
.pytest_cache/
|
||||
.vscode/
|
||||
*.egg-info/
|
||||
__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 Namespace
|
||||
from pathlib import Path
|
||||
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:
|
||||
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.
|
||||
parser = ArgumentParser(
|
||||
prog = "chinook",
|
||||
@@ -24,12 +41,12 @@ def main() -> None:
|
||||
|
||||
commands = parser.add_subparsers(required=True)
|
||||
buildcmd = commands.add_parser("build",help="Builds your Chinook project")
|
||||
buildcmd.set_defaults(func=pipeline.build)
|
||||
buildcmd.set_defaults(func=_build)
|
||||
buildcmd.add_argument(
|
||||
"project",
|
||||
nargs="?",
|
||||
default=".",
|
||||
type=Path,
|
||||
type=str,
|
||||
help="The path to the project to build",
|
||||
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
|
||||
|
||||
__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
|
||||
from typing import overload
|
||||
|
||||
|
||||
@overload
|
||||
def build(path: Path) -> None: ...
|
||||
|
||||
@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
|
||||
def build(project: Project) -> None:
|
||||
# TODO: Download, catalog, and build dependencies
|
||||
# TODO: Build the current configuration
|
||||
if len(project.targets) == 0:
|
||||
raise NoTargetsFoundError(project)
|
||||
logger.info(f"Building {project.cid}")
|
||||
|
||||
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"
|
||||
version = "1.0.0-alpha"
|
||||
description = "Opinionated build tool for C/C++."
|
||||
dependencies = ["PyYAML", "semver"]
|
||||
dependencies = ["PyYAML", "semantic-version"]
|
||||
|
||||
[project.scripts]
|
||||
chinook = "chinook.__main__:main"
|
||||
|
||||
Reference in New Issue
Block a user