Lots of work

This commit is contained in:
2026-03-21 09:58:39 -05:00
parent a6690b4382
commit 88b6424bf6
49 changed files with 947 additions and 67 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
.chinook/ .chinook/
.environ/ .environ/
.pytest_cache/ .pytest_cache/
.vscode/
*.egg-info/ *.egg-info/
__pycache__/ __pycache__/

View File

@@ -1,13 +1,13 @@
# Changelog # Changelog
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.0.1] - <DATE> ## [0.0.1] - <DATE>
### Added ### Added
### Changed ### Changed
### Fixed ### Fixed
### Removed ### Removed

View File

@@ -1,7 +1,7 @@
Copyright 2026 John Christman Copyright 2026 John Christman
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1 +1 @@
# Chinook # Chinook

View File

@@ -0,0 +1 @@
from ._logger import logger

View File

@@ -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
View File

@@ -0,0 +1,3 @@
config = None

10
chinook/_logger.py Normal file
View 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
View File

@@ -0,0 +1,6 @@
from ..models import Project
from ..models import Target
def compile(target: Target, project: Project) -> None:
...

View 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

View 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

View 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

View 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] = {}

View File

@@ -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

View 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))
)

View File

@@ -0,0 +1,6 @@
import yaml
class Activation(yaml.YAMLObject):
pass

34
chinook/models/_branch.py Normal file
View 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

View File

@@ -1,5 +0,0 @@
from dataclasses import dataclass
@dataclass
class ChinookFile:
pass

View 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"

View 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
View File

@@ -0,0 +1,8 @@
from enum import IntEnum
from enum import auto
class Level(IntEnum):
PUBLIC = auto()
PROTECTED = auto()
PRIVATE = auto()

View 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

View File

@@ -0,0 +1,9 @@
from enum import StrEnum
class Output(StrEnum):
EXE = "executable"
LIB = "archive"
DLL = "dynlib"
OBJ = "objfile"
INT = "interface"

View 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))

View 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]

View 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
View 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)

View 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
View 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

View File

@@ -1,3 +1 @@
from ._build import build from ._build import build
__all__ = [ build ]

View File

@@ -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

View File

@@ -0,0 +1,4 @@
from ..models import Target
def compile(target: Target) -> None:
...

View File

@@ -0,0 +1,2 @@
class NoTargetsFoundError(Exception):
pass

View File

@@ -0,0 +1 @@

View 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
View 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
View 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
View 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
View 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

View File

@@ -0,0 +1 @@
from ._execute import execute

29
chinook/shell/_execute.py Normal file
View 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)

View 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
View 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

View 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
View 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
View 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()

View File

@@ -0,0 +1,9 @@
name: executable
gpid: examples
semv: 1.0.0
targets:
- name: example
type: executable
srcs:
- ./entry.cpp

View 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!");
}

View File

@@ -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"