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

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 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
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
__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
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}")

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