Files
releng/machine_spec.py
Ole André Vadla Ravnås 983eb7432a deps: Let an SDK be built for a Darwin kernel
Compile a machine whose config is kernel with the kernel's ABI,
against no system headers, and against the prefix being filled:
picolibc and the compiler-rt builtins are packages here, as they
are for a machine with no operating system at all.

Say the system is none, which is what a Darwin kernel is to
anything built here, and leave out the packages that belong to an
operating system.

Make a link work, since a build system asks its questions by
linking one: no dynamic linker, and the entry point named.
2026-08-27 17:45:36 +02:00

435 lines
12 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
import platform
import re
import subprocess
from typing import FrozenSet, List, Optional
if platform.system() == "Windows":
import ctypes
from ctypes import wintypes
@dataclass
class MachineSpec:
os: str
arch: str
config: Optional[str] = None
triplet: Optional[str] = None
@staticmethod
def make_from_local_system() -> MachineSpec:
os = detect_os()
arch = detect_arch()
config = None
if os == "linux":
try:
output = subprocess.run(["ldd", "--version"],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
encoding="utf-8").stdout
if "musl" in output:
config = "musl"
except:
pass
return MachineSpec(os, arch, config)
@staticmethod
def parse(raw_spec: str) -> MachineSpec:
os = None
arch = None
config = None
triplet = None
tokens = raw_spec.split("-")
if len(tokens) in {3, 4}:
arch = tokens[0]
m = TARGET_TRIPLET_ARCH_PATTERN.match(arch)
if m is not None:
kernel = tokens[-2]
system = tokens[-1]
if kernel == "w64":
os = "windows"
elif kernel == "nto":
os = "qnx"
else:
os = kernel
if arch[0] == "i":
arch = "x86"
elif arch == "arm":
if system.endswith("eabihf"):
arch = "armhf"
elif os == "qnx" and system.endswith("eabi"):
arch = "armeabi"
elif arch == "armeb":
arch = "armbe8"
elif arch == "aarch64":
arch = "arm64"
elif arch == "aarch64_be":
arch = "arm64be"
if system.endswith("_ilp32"):
arch += "ilp32"
if system.startswith("musl"):
config = "musl"
elif kernel == "w64":
config = "mingw"
triplet = raw_spec
if os is None:
os, arch, *rest = tokens
if rest:
assert len(rest) == 1
config = rest[0]
return MachineSpec(os, arch, config, triplet)
def evolve(self,
os: Optional[str] = None,
arch: Optional[str] = None,
config: Optional[str] = None,
triplet: Optional[str] = None) -> MachineSpec:
return MachineSpec(
os if os is not None else self.os,
arch if arch is not None else self.arch,
config if config is not None else self.config,
triplet if triplet is not None else self.triplet,
)
def default_missing(self, recommended_vscrt: Optional[str] = None) -> MachineSpec:
config = self.config
if config is None and self.toolchain_is_msvc:
if recommended_vscrt is not None:
config = recommended_vscrt
else:
config = "mt"
return self.evolve(config=config)
def maybe_adapt_to_host(self, host_machine: MachineSpec) -> MachineSpec:
if self.identifier == host_machine.identifier and host_machine.triplet is not None:
return host_machine
if self.os == "windows" and host_machine.os == "windows" and self.can_run(host_machine):
return host_machine
return self
def can_run(self, other: MachineSpec) -> bool:
if self.arch == other.arch:
return True
intel = {"x86", "x86_64"}
return self.arch in intel and other.arch in intel
@property
def identifier(self) -> str:
parts = [self.os, self.arch]
if self.config is not None:
parts += [self.config]
return "-".join(parts)
@property
def os_dash_arch(self) -> str:
return f"{self.os}-{self.arch}"
@property
def os_dash_config(self) -> str:
parts = [self.os]
if self.config is not None:
parts += [self.config]
return "-".join(parts)
@property
def config_tags(self) -> FrozenSet[str]:
if self.config is None:
return frozenset()
return frozenset(self.config.split("_"))
@property
def config_is_softfloat(self) -> bool:
return "softfloat" in self.config_tags
@property
def config_is_pic(self) -> bool:
return "pic" in self.config_tags
@property
def config_is_msabi(self) -> bool:
return "msabi" in self.config_tags
@property
def config_is_optimized(self) -> bool:
if self.toolchain_is_msvc:
return self.config in {"md", "mt"}
return True
@property
def meson_optimization_options(self) -> List[str]:
if self.config_is_optimized:
optimization = "s"
ndebug = "true"
else:
optimization = "0"
ndebug = "false"
return [
f"-Doptimization={optimization}",
f"-Db_ndebug={ndebug}",
]
@property
def executable_suffix(self) -> str:
return ".exe" if self.os == "windows" else ""
@property
def msvc_platform(self) -> str:
return "x64" if self.arch == "x86_64" else self.arch
@property
def is_apple(self) -> str:
return self.os in {"macos", "ios", "watchos", "tvos", "xros"}
@property
def is_freestanding(self) -> bool:
return self.os == "none" or self.config == "kernel"
@property
def system(self) -> str:
if self.config == "kernel":
return "none"
return "darwin" if self.is_apple else self.os
@property
def subsystem(self) -> str:
return self.os_dash_config if self.is_apple else self.os
@property
def kernel(self) -> str:
return KERNELS.get(self.os, self.os)
@property
def cpu_family(self) -> str:
arch = self.arch
return CPU_FAMILIES.get(arch, arch)
@property
def cpu(self) -> str:
arch = self.arch
mappings_to_search = [
CPU_TYPES_PER_OS_OVERRIDES.get(self.os, {}),
CPU_TYPES,
]
for m in mappings_to_search:
cpu = m.get(arch, None)
if cpu is not None:
return cpu
return arch
@property
def endian(self) -> str:
return "big" if self.arch in BIG_ENDIAN_ARCHS else "little"
@property
def pointer_size(self) -> int:
arch = self.arch
if arch in {"x86_64", "s390x"}:
return 8
if (arch.startswith("arm64") and not arch.endswith("ilp32")) or arch.startswith("mips64"):
return 8
return 4
@property
def libdatadir(self) -> str:
return "libdata" if self.os == "freebsd" else "lib"
@property
def toolchain_is_msvc(self) -> bool:
return self.os == "windows" and self.config != "mingw"
@property
def toolchain_can_strip(self) -> bool:
return not self.toolchain_is_msvc
def __eq__(self, other):
if isinstance(other, MachineSpec):
return other.identifier == self.identifier
return False
def detect_os() -> str:
os = platform.system().lower()
if os == "darwin":
os = "macos"
return os
def detect_arch() -> str:
if platform.system() == "Windows":
return detect_arch_windows()
arch = platform.machine().lower()
return ARCHS.get(arch, arch)
def detect_arch_windows():
try:
code = detect_arch_windows_modern()
except AttributeError:
code = detect_arch_windows_legacy()
if code == PROCESSOR_ARCHITECTURE_INTEL:
return "x86"
elif code in {PROCESSOR_ARCHITECTURE_AMD64, IMAGE_FILE_MACHINE_AMD64}:
return "x86_64"
elif code in {PROCESSOR_ARCHITECTURE_ARM64, IMAGE_FILE_MACHINE_ARM64}:
return "arm64"
else:
raise RuntimeError(f"unrecognized native architecture code: {code!r}")
def detect_arch_windows_modern():
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
try:
is_wow64_process = kernel32.IsWow64Process2
except AttributeError:
raise
is_wow64_process.argtypes = (
wintypes.HANDLE,
ctypes.POINTER(wintypes.WORD),
ctypes.POINTER(wintypes.WORD),
)
is_wow64_process.restype = wintypes.BOOL
process_machine = wintypes.WORD(0)
native_machine = wintypes.WORD(0)
ok = is_wow64_process(
kernel32.GetCurrentProcess(),
ctypes.byref(process_machine),
ctypes.byref(native_machine)
)
if not ok:
raise ctypes.WinError(ctypes.get_last_error())
return native_machine.value
def detect_arch_windows_legacy():
class SYSTEM_INFO(ctypes.Structure):
_fields_ = [
("wProcessorArchitecture", wintypes.WORD),
("wReserved", wintypes.WORD),
("dwPageSize", wintypes.DWORD),
("lpMinimumApplicationAddress", ctypes.c_void_p),
("lpMaximumApplicationAddress", ctypes.c_void_p),
("dwActiveProcessorMask", ctypes.c_void_p),
("dwNumberOfProcessors", wintypes.DWORD),
("dwProcessorType", wintypes.DWORD),
("dwAllocationGranularity", wintypes.DWORD),
("wProcessorLevel", wintypes.WORD),
("wProcessorRevision", wintypes.WORD),
]
kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
get_native_system_info = kernel32.GetNativeSystemInfo
get_native_system_info.argtypes = (ctypes.POINTER(SYSTEM_INFO),)
get_native_system_info.restype = None
info = SYSTEM_INFO()
get_native_system_info(ctypes.byref(info))
return info.wProcessorArchitecture
ARCHS = {
"amd64": "x86_64",
"armv7l": "armhf",
"aarch64": "arm64",
}
KERNELS = {
"windows": "nt",
"macos": "xnu",
"ios": "xnu",
"watchos": "xnu",
"tvos": "xnu",
"qnx": "nto",
}
CPU_FAMILIES = {
"armbe8": "arm",
"armeabi": "arm",
"armhf": "arm",
"armv6kz": "arm",
"arm64": "aarch64",
"arm64be": "aarch64",
"arm64beilp32": "aarch64",
"arm64e": "aarch64",
"arm64eoabi": "aarch64",
"mipsel": "mips",
"mips64el": "mips64",
"powerpc": "ppc"
}
CPU_TYPES = {
"arm": "armv7",
"armbe8": "armv6",
"armhf": "armv7hf",
"armeabi": "armv7eabi",
"armv6kz": "armv6",
"arm64": "aarch64",
"arm64be": "aarch64",
"arm64beilp32": "aarch64",
"arm64e": "aarch64",
"arm64eoabi": "aarch64",
}
CPU_TYPES_PER_OS_OVERRIDES = {
"linux": {
"arm": "armv5t",
"armbe8": "armv6t",
"armhf": "armv7a",
"armv6kz": "armv6t",
"mips": "mips1",
"mipsel": "mips1",
"mips64": "mips64r2",
"mips64el": "mips64r2",
},
"android": {
"x86": "i686",
},
"qnx": {
"arm": "armv6",
"armeabi": "armv7",
},
}
BIG_ENDIAN_ARCHS = {
"arm64be",
"arm64beilp32",
"armbe8",
"mips",
"mips64",
"ppc",
"ppc64",
"s390x",
}
TARGET_TRIPLET_ARCH_PATTERN = re.compile(r"^(i.86|x86_64|arm\w*|aarch64(_be)?|mips\w*|powerpc|s390x)$")
PROCESSOR_ARCHITECTURE_INTEL = 0
PROCESSOR_ARCHITECTURE_AMD64 = 9
PROCESSOR_ARCHITECTURE_ARM64 = 12
IMAGE_FILE_MACHINE_AMD64 = 0x8664
IMAGE_FILE_MACHINE_ARM64 = 0xAA64