3 Commits

Author SHA1 Message Date
marv7000 f51dab51db shit 2026-06-02 21:38:47 +02:00
marv7000 d3c949b8a2 profile changes 2026-05-30 23:09:34 +02:00
Marvin Friedrich afb13bb8ad build_deps 2026-05-30 19:18:25 +02:00
82 changed files with 1864 additions and 156 deletions
+5
View File
@@ -26,6 +26,7 @@ RUN apk add --no-cache \
zlib-dev \
git \
pkgconf \
kmod \
patchelf \
gperf \
python3 \
@@ -50,6 +51,10 @@ RUN apk add --no-cache \
ninja \
meson \
glslang \
py3-jinja2 \
wayland-dev \
wayland-protocols \
glib-dev \
elfutils-dev \
libffi-dev \
expat-dev \
+23
View File
@@ -0,0 +1,23 @@
def profile(base):
# The base profile supplies defaults for every other profile, so it has no
# parent of its own (``base`` is None here). It deliberately omits the
# target-identifying core fields (arch/triple/libc); concrete profiles must
# provide those.
return Profile(
container_image="localhost/orchid-builder:latest",
options={
# Install layout (where files land in the target system).
"prefix": "/usr",
"bindir": "/usr/bin",
"sbindir": "/usr/bin",
"libdir": "/usr/lib",
"libexecdir": "/usr/libexec",
"includedir": "/usr/include",
"sysconfdir": "/etc",
"localstatedir": "/var",
# Flags for tools built to run on the build machine (host).
"host_cflags": "-O2 -pipe",
"host_cxxflags": "-O2 -pipe",
"host_ldflags": "-Wl,-O1 -Wl,--sort-common -Wl,--as-needed",
},
)
+16 -33
View File
@@ -1,34 +1,17 @@
def profile():
def profile(base):
arch = "x86_64"
libc = "glibc"
triple = f"{arch}-orchid-linux-gnu"
host_cflags = "-O2 -pipe"
host_cxxflags = host_cflags
host_ldflags = "-Wl,-O1 -Wl,--sort-common -Wl,--as-needed"
target_flags = " -march=x86-64-v3 -mtune=generic -fstack-clash-protection -fstack-protector-strong -fcf-protection"
cflags = host_cflags + target_flags
cxxflags = cflags
ldflags = host_ldflags + " -Wl,-z,now -Wl,-z,pack-relative-relocs"
return {
"arch": arch,
"libc": libc,
"triple": triple,
"container_image": "localhost/orchid-builder:latest",
"host_cflags": host_cflags,
"host_cxxflags": host_cxxflags,
"host_ldflags": host_ldflags,
"cflags": cflags,
"cxxflags": cxxflags,
"ldflags": ldflags,
"prefix": "/usr",
"bindir": "/usr/bin",
"sbindir": "/usr/bin",
"libdir": "/usr/lib",
"libexecdir": "/usr/libexec",
"includedir": "/usr/include",
"sysconfdir": "/etc",
"localstatedir": "/var",
}
target_flags = (
"-march=x86-64-v3 -mtune=generic -fstack-clash-protection "
"-fstack-protector-strong -fcf-protection"
)
return Profile(
arch=arch,
triple=f"{arch}-orchid-linux-gnu",
options={
"libc": "glibc",
# Target flags build on the base host flags.
"cflags": f"{base['host_cflags']} {target_flags}",
"cxxflags": f"{base['host_cflags']} {target_flags}",
"ldflags": f"{base['host_ldflags']} -Wl,-z,now -Wl,-z,pack-relative-relocs",
},
)
+15 -31
View File
@@ -1,34 +1,18 @@
def profile():
def profile(base):
arch = "x86_64"
libc = "musl"
triple = f"{arch}-orchid-linux-{libc}"
host_cflags = "-O2 -pipe"
host_cxxflags = host_cflags
host_ldflags = "-Wl,-O1 -Wl,--sort-common -Wl,--as-needed"
target_flags = " -march=x86-64-v3 -mtune=generic -fstack-clash-protection -fstack-protector-strong -fcf-protection"
cflags = host_cflags + target_flags
cxxflags = cflags
ldflags = host_ldflags + " -Wl,-z,now -Wl,-z,pack-relative-relocs"
return {
"arch": arch,
target_flags = (
"-march=x86-64-v3 -mtune=generic -fstack-clash-protection "
"-fstack-protector-strong -fcf-protection"
)
return Profile(
arch=arch,
triple=f"{arch}-orchid-linux-{libc}",
options={
"libc": libc,
"triple": triple,
"container_image": "localhost/orchid-builder:latest",
"host_cflags": host_cflags,
"host_cxxflags": host_cxxflags,
"host_ldflags": host_ldflags,
"cflags": cflags,
"cxxflags": cxxflags,
"ldflags": ldflags,
"prefix": "/usr",
"bindir": "/usr/bin",
"sbindir": "/usr/bin",
"libdir": "/usr/lib",
"libexecdir": "/usr/libexec",
"includedir": "/usr/include",
"sysconfdir": "/etc",
"localstatedir": "/var",
}
# Target flags build on the base host flags.
"cflags": f"{base['host_cflags']} {target_flags}",
"cxxflags": f"{base['host_cflags']} {target_flags}",
"ldflags": f"{base['host_ldflags']} -Wl,-z,now -Wl,-z,pack-relative-relocs",
},
)
+7
View File
@@ -0,0 +1,7 @@
# <file system> <dir> <type> <options> <dump> <pass>
proc /proc proc nosuid,noexec,nodev 0 0
sysfs /sys sysfs nosuid,noexec,nodev 0 0
devtmpfs /dev devtmpfs nosuid 0 0
devpts /dev/pts devpts gid=5,mode=620 0 0
tmpfs /run tmpfs nosuid,nodev 0 0
tmpfs /dev/shm tmpfs nosuid,nodev 0 0
+12
View File
@@ -0,0 +1,12 @@
root:x:0:
tty:x:5:user
disk:x:6:
kmem:x:9:
wheel:x:10:user
kvm:x:24:user
video:x:27:user
input:x:28:user
seat:x:29:user
render:x:30:user
nobody:x:65534:
user:x:1000:
+1
View File
@@ -0,0 +1 @@
orchid
+3
View File
@@ -0,0 +1,3 @@
/lib
/usr/lib
/usr/lib64
+5
View File
@@ -0,0 +1,5 @@
NAME="Orchid"
PRETTY_NAME="Orchid Linux"
ID=orchid
ANSI_COLOR="0;35"
HOME_URL="https://example.invalid/"
+3
View File
@@ -0,0 +1,3 @@
root:x:0:0:root:/root:/bin/bash
user:x:1000:1000:Orchid User:/home/user:/bin/bash
nobody:x:65534:65534:Nobody:/:/usr/bin/nologin
+9
View File
@@ -0,0 +1,9 @@
# System-wide shell profile (POSIX sh / bash login shells).
export PATH=/usr/local/bin:/usr/bin:/bin
export PAGER=less
umask 022
for _f in /etc/profile.d/*.sh; do
[ -r "$_f" ] && . "$_f"
done
unset _f
@@ -0,0 +1,12 @@
# Launch Weston automatically after autologin on the first VT.
# seatd (running as a service) grants access to DRM/input devices, and
# XDG_RUNTIME_DIR is created here since there is no logind to do it.
if [ "$(tty)" = "/dev/tty1" ] && [ -z "${WAYLAND_DISPLAY:-}" ] && [ -z "${DISPLAY:-}" ]; then
export XDG_RUNTIME_DIR="/run/user/$(id -u)"
if [ ! -d "$XDG_RUNTIME_DIR" ]; then
mkdir -p "$XDG_RUNTIME_DIR"
fi
chmod 0700 "$XDG_RUNTIME_DIR"
export XDG_SESSION_TYPE=wayland
exec weston
fi
+3
View File
@@ -0,0 +1,3 @@
root:!:19700:0:99999:7:::
user::19700:0:99999:7:::
nobody:!:19700:0:99999:7:::
@@ -0,0 +1,16 @@
[core]
idle-time=0
require-input=false
xwayland=false
[shell]
background-color=0xff1e1e2e
panel-position=top
locking=false
[keyboard]
keymap_layout=us
[terminal]
font=DejaVu Sans Mono
font-size=14
+41
View File
@@ -0,0 +1,41 @@
version = "0.1"
revision = 1
description = "Base filesystem layout, accounts and system configuration"
license = "MIT"
url = "https://example.invalid/"
# Config-only package: no upstream source, just static files + an install phase.
# Install the full target set (`orchid install <dest>`) to get a bootable system;
# this package supplies the glue (accounts, fstab, /sbin/init, weston.ini).
host_deps = []
# bash provides the system shell; base-files points /usr/bin/sh at it.
deps = ["bash"]
def install(self):
d = self.dest_dir
self.run(
"sh",
"-c",
"set -e; "
f"install -d -m0755 '{d}/etc' '{d}/etc/profile.d' '{d}/etc/xdg/weston' "
f"'{d}/etc/udev/rules.d' '{d}/sbin' '{d}/usr/bin' '{d}/root' '{d}/home/user' "
f"'{d}/run' '{d}/var' '{d}/proc' '{d}/sys' '{d}/dev'; "
# POSIX shell for #!/bin/sh scripts (dinit services, etc.).
f"ln -sf bash '{d}/usr/bin/sh'; "
# Static config tree from files/.
f"cp -rp '{self.files}/etc/.' '{d}/etc/'; "
# dinit as PID 1 (boot with kernel cmdline init=/sbin/init).
f"ln -sf /usr/bin/dinit '{d}/sbin/init'; "
# Conventional compat symlinks: the ELF interpreter is /lib64/ld-linux,
# but glibc lives in /lib; programs/shells live in /usr/bin.
f"ln -sf lib '{d}/lib64'; "
f"ln -sf usr/bin '{d}/bin'; "
# /var/run -> /run (dinit binds its control socket at /var/run/dinitctl).
f"ln -sf /run '{d}/var/run'; "
# Ownership + sensitive perms.
f"chown -R 0:0 '{d}/etc' '{d}/root'; "
f"chmod 0600 '{d}/etc/shadow'; "
f"chmod 0700 '{d}/root'; "
f"chown -R 1000:1000 '{d}/home/user'; "
f"chmod 0755 '{d}/home/user'",
)
+15
View File
@@ -0,0 +1,15 @@
version = "1.1.0"
revision = 1
description = "Generic-purpose lossless compression algorithm (Brotli)"
license = "MIT"
url = "https://github.com/google/brotli"
source = tarball(
url=f"https://github.com/google/brotli/archive/refs/tags/v{version}.tar.gz",
sha256="e720a6ca29428b803f4ad165371771f5398faba397edf6778837a18599ea13ff",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"]]
configure, build, install = cmake(
configure_args=["-DBROTLI_DISABLE_TESTS=ON"],
)
+22
View File
@@ -0,0 +1,22 @@
version = "1.18.4"
revision = 1
description = "2D graphics library with multiple output backends"
license = "LGPL-2.1-only OR MPL-1.1"
url = "https://www.cairographics.org/"
source = tarball(
url=f"https://www.cairographics.org/releases/cairo-{version}.tar.xz",
sha256="445ed8208a6e4823de1226a74ca319d3600e83f6369f99b14265006599c32ccb",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"], "pixman", "freetype2", "fontconfig", "libpng", "glib2", "zlib"]
configure, build, install = meson(
configure_args=[
# Wayland-only: no X backends. (cairo 1.18 has no GL-backend option.)
"-Dxlib=disabled",
"-Dxcb=disabled",
"-Dtests=disabled",
"-Dspectre=disabled",
"-Dsymbol-lookup=disabled", # avoids a libbfd/binutils dependency
],
)
+9
View File
@@ -0,0 +1,9 @@
# Default boot target. dinit starts this service as PID 1; it pulls in the
# rest of the system. waits-for (rather than depends-on) keeps the machine
# coming up even if an individual service fails.
type = internal
waits-for = early-fs
waits-for = udevd
waits-for = udev-trigger
waits-for = seatd
waits-for = tty1
+4
View File
@@ -0,0 +1,4 @@
# Mount the early virtual filesystems needed before anything else runs.
type = scripted
command = /etc/dinit.d/scripts/early-fs.sh
restart = false
@@ -0,0 +1,17 @@
#!/bin/sh
# Bring up the kernel virtual filesystems and runtime dirs before other
# services start. Safe to re-run: each mount is guarded by mountpoint checks.
set -eu
export PATH=/usr/bin:/usr/sbin:/bin:/sbin
mountpoint -q /proc || mount -t proc proc /proc
mountpoint -q /sys || mount -t sysfs sysfs /sys
mountpoint -q /dev || mount -t devtmpfs devtmpfs /dev
mkdir -p /dev/pts /dev/shm
mountpoint -q /dev/pts || mount -t devpts devpts /dev/pts
mountpoint -q /dev/shm || mount -t tmpfs tmpfs /dev/shm
# NOTE: /run is intentionally NOT remounted here. dinit (PID 1) binds its
# control socket under /run before this script runs; on the all-RAM initramfs
# boot /run is already writable, and a fresh tmpfs mount would hide that socket.
# (A disk-rooted system should instead mount /run before starting dinit.)
mkdir -p /run/user /run/udev
@@ -0,0 +1,14 @@
#!/bin/sh
# Coldplug devices that already existed before udevd started: replay "add"
# uevents for subsystems then devices, and wait for processing to finish so
# DRM/input nodes (and their autoloaded modules) are present before Weston.
set -eu
export PATH=/usr/bin:/usr/sbin:/bin:/sbin
udevadm trigger --action=add --type=subsystems
udevadm trigger --action=add --type=devices
udevadm settle || true
# Safety net for QEMU/virtio: ensure the virtio-gpu DRM driver is loaded even if
# coldplug autoload missed it. (Input/evdev is built into the kernel.)
modprobe virtio_gpu 2>/dev/null || true
+6
View File
@@ -0,0 +1,6 @@
# Seat management daemon. Weston (via libseat) asks seatd to open DRM/input
# devices, so no logind is needed. The "seat" group may use the seatd socket.
type = process
command = /usr/bin/seatd -g seat
restart = true
depends-on = early-fs
+7
View File
@@ -0,0 +1,7 @@
# Autologin getty on tty1. login runs the user's profile, which launches Weston
# (see /etc/profile.d/weston.sh shipped by base-files).
type = process
command = /usr/bin/agetty --autologin user --noclear --login-program /usr/bin/login tty1 linux
restart = true
depends-on = seatd
depends-on = udev-trigger
+5
View File
@@ -0,0 +1,5 @@
# Coldplug: replay add events so udev autoloads DRM/input modules, then settle.
type = scripted
command = /etc/dinit.d/scripts/udev-trigger.sh
restart = false
depends-on = udevd
+5
View File
@@ -0,0 +1,5 @@
# eudev device manager daemon (udevd). Loads/uevents DRM/input drivers.
type = process
command = /usr/bin/udevd
restart = true
depends-on = early-fs
+51
View File
@@ -0,0 +1,51 @@
version = "0.20.0"
revision = 1
description = "Service manager / init system (dinit)"
license = "Apache-2.0"
url = "https://davmac.org/projects/dinit/"
source = tarball(
url=f"https://github.com/davmac314/dinit/archive/refs/tags/v{version}.tar.gz",
sha256="cd75b572a2eab4a9bd0610a2bb8cc154da7e80074e61cb1059a996dfd977baae",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"], "libstdc++"]
def configure(self):
# dinit's hand-written configure expects an in-tree build and reads the
# toolchain from the environment; CXX_FOR_BUILD compiles its small build-time
# helper with the native compiler.
self.run("cp", "-rp", f"{self.source_dir}/.", self.build_dir)
self.run(
"./configure",
"--prefix=/usr",
"--sbindir=/usr/bin",
"--enable-utmpx",
"--enable-shutdown",
"--disable-strip",
env={
"CXX": f"{self.triple}-g++",
"CXX_FOR_BUILD": "g++",
# dinit 0.20.0 uses std::allocator<T>::construct(), removed from the
# default C++ mode in GCC 16's libstdc++; build it as C++17 where it
# still exists.
"CXXFLAGS": self.options["cxxflags"] + " -std=gnu++17",
"LDFLAGS": self.options["ldflags"],
},
)
def build(self):
self.run("make", f"-j{self.jobs}")
def install(self):
self.run("make", "install", f"DESTDIR={self.dest_dir}")
# Ship the dinit service set (boot target + udev/seatd/agetty services).
self.run("install", "-d", self.dest_dir / "etc/dinit.d")
self.run(
"sh",
"-c",
f"cp -rp {self.files}/dinit.d/. {self.dest_dir}/etc/dinit.d/ && "
f"chmod +x {self.dest_dir}/etc/dinit.d/scripts/*.sh",
)
+39
View File
@@ -0,0 +1,39 @@
version = "0.192"
revision = 1
description = "ELF object file access library and utilities (libelf)"
license = "GPL-3.0-or-later AND LGPL-3.0-or-later"
url = "https://sourceware.org/elfutils/"
source = tarball(
url=f"https://sourceware.org/elfutils/ftp/{version}/elfutils-{version}.tar.bz2",
sha256="616099beae24aba11f9b63d86ca6cc8d566d968b802391334c91df54eab416b4",
)
host_deps = ["binutils", "gcc", "pkgconf"]
# libstdc++: elfutils builds a C++ tool (srcfiles) needing the C++ headers.
deps = [profile["libc"], "libstdc++", "zlib", "xz", "zstd"]
def configure(self):
# debuginfod would pull libcurl/microhttpd; we only need libelf for Mesa.
# elfutils builds with -Werror by default, which GCC 16 trips on.
autotools_configure(
self,
[
"--disable-debuginfod",
"--disable-libdebuginfod",
"--disable-nls",
"--enable-deterministic-archives",
"--program-prefix=eu-",
],
{
"CFLAGS": self.options["cflags"] + " -Wno-error",
"CXXFLAGS": self.options["cxxflags"] + " -Wno-error",
},
)
def build(self):
autotools_build(self)
def install(self):
autotools_install(self)
+22
View File
@@ -0,0 +1,22 @@
version = "3.2.14"
revision = 1
description = "Standalone udev fork: device manager (udevd, udevadm, libudev)"
license = "GPL-2.0-only AND LGPL-2.1-or-later"
url = "https://github.com/eudev-project/eudev"
source = tarball(
url=f"https://github.com/eudev-project/eudev/releases/download/v{version}/eudev-{version}.tar.gz",
sha256="8da4319102f24abbf7fff5ce9c416af848df163b29590e666d334cc1927f006f",
)
host_deps = ["binutils", "gcc", "pkgconf"]
# blkid (libblkid/util-linux) and kmod (libkmod) builtins are enabled by default.
deps = [profile["libc"], "kmod", "util-linux"]
configure, build, install = autotools(
configure_args=[
"--disable-manpages",
"--disable-introspection",
"--enable-hwdb",
# Keep daemon/rules/libexec under /usr (merged-usr layout).
"--with-rootprefix=/usr",
],
)
+20
View File
@@ -0,0 +1,20 @@
version = "2.7.3"
revision = 1
description = "Stream-oriented XML parser library (Expat)"
license = "MIT"
url = "https://libexpat.github.io/"
_tag = "R_" + version.replace(".", "_")
source = tarball(
url=f"https://github.com/libexpat/libexpat/releases/download/{_tag}/expat-{version}.tar.xz",
sha256="71df8f40706a7bb0a80a5367079ea75d91da4f8c65c58ec59bcdfbf7decdab9f",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"]]
configure, build, install = autotools(
configure_args=[
"--without-docbook",
"--without-examples",
"--without-tests",
],
)
+19
View File
@@ -0,0 +1,19 @@
version = "2.16.2"
revision = 1
description = "Library for configuring and customizing font access"
license = "MIT"
url = "https://www.freedesktop.org/wiki/Software/fontconfig/"
source = tarball(
url=f"https://gitlab.freedesktop.org/fontconfig/fontconfig/-/archive/{version}/fontconfig-{version}.tar.gz",
sha256="8b9368ae99d9faf2fe26491645fbeebf0272d911cacc5d3e9cf954c2157c151e",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"], "freetype2", "expat"]
configure, build, install = meson(
configure_args=[
"-Ddoc=disabled",
"-Dtests=disabled",
"-Dtools=enabled",
],
)
+23
View File
@@ -0,0 +1,23 @@
version = "2.13.3"
revision = 1
description = "FreeType font rendering library"
license = "FTL OR GPL-2.0-or-later"
url = "https://freetype.org/"
source = tarball(
url=f"https://download.savannah.gnu.org/releases/freetype/freetype-{version}.tar.xz",
sha256="0550350666d427c74daeb85d5ac7bb353acba5f76956395995311a9c6f063289",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"], "zlib", "libpng", "brotli"]
configure, build, install = meson(
configure_args=[
# First pass without HarfBuzz to break the freetype<->harfbuzz cycle;
# HarfBuzz is built afterwards and links back against this freetype.
"-Dharfbuzz=disabled",
"-Dbrotli=enabled",
"-Dbzip2=disabled",
"-Dpng=enabled",
"-Dzlib=enabled",
],
)
+18
View File
@@ -0,0 +1,18 @@
version = "1.0.16"
revision = 1
description = "Free Implementation of the Unicode Bidirectional Algorithm"
license = "LGPL-2.1-or-later"
url = "https://github.com/fribidi/fribidi"
source = tarball(
url=f"https://github.com/fribidi/fribidi/releases/download/v{version}/fribidi-{version}.tar.xz",
sha256="1b1cde5b235d40479e91be2f0e88a309e3214c8ab470ec8a2744d82a5a9ea05c",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"]]
configure, build, install = meson(
configure_args=[
"-Dtests=false",
"-Ddocs=false",
],
)
+25
View File
@@ -0,0 +1,25 @@
version = "2.84.2"
revision = 1
description = "GLib core application building blocks (GLib/GObject/GIO)"
license = "LGPL-2.1-or-later"
url = "https://gitlab.gnome.org/GNOME/glib"
_series = ".".join(version.split(".")[:2])
source = tarball(
url=f"https://download.gnome.org/sources/glib/{_series}/glib-{version}.tar.xz",
sha256="88e960dd937057407d61fcb3b45a860704b25923c37ae2478b85f2ecb5a4021f",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"], "libffi", "pcre2", "zlib"]
configure, build, install = meson(
configure_args=[
"-Dtests=false",
"-Dman-pages=disabled",
# gobject-introspection needs to run target binaries; skip when cross.
"-Dintrospection=disabled",
"-Dselinux=disabled",
# Avoid pulling util-linux (libmount) into the GIO build.
"-Dlibmount=disabled",
"-Dnls=enabled",
],
)
+26
View File
@@ -0,0 +1,26 @@
version = "12.1.0"
revision = 1
description = "OpenType text shaping engine"
license = "MIT"
url = "https://harfbuzz.github.io/"
source = tarball(
url=f"https://github.com/harfbuzz/harfbuzz/releases/download/{version}/harfbuzz-{version}.tar.xz",
sha256="e5c81b7f6e0b102dfb000cfa424538b8e896ab78a2f4b8a5ec8cae62ab43369e",
)
host_deps = ["binutils", "gcc", "pkgconf"]
# C++ library: needs libstdc++ (its headers + runtime) in the sysroot.
deps = [profile["libc"], "libstdc++", "freetype2", "glib2", "fribidi"]
configure, build, install = meson(
configure_args=[
"-Dfreetype=enabled",
"-Dglib=enabled",
"-Dgobject=disabled",
"-Dicu=disabled",
# cairo support here would create a cycle (cairo is built after harfbuzz).
"-Dcairo=disabled",
"-Dtests=disabled",
"-Ddocs=disabled",
"-Dbenchmark=disabled",
],
)
+22
View File
@@ -0,0 +1,22 @@
version = "0.394"
revision = 1
description = "Hardware identification data (pci.ids, usb.ids, ...)"
license = "GPL-2.0-or-later OR XFree86-1.1"
url = "https://github.com/vcrhonek/hwdata"
source = tarball(
url=f"https://github.com/vcrhonek/hwdata/archive/v{version}/hwdata-{version}.tar.gz",
sha256="b7c3fd7214a3b7c49d2661db127a712dc11cffd1799f793947aa1cb20aaf3298",
)
host_deps = ["pkgconf"]
# Pure data + a hwdata.pc; nothing links against it.
deps = []
def configure(self):
# hwdata ships a hand-written configure that expects an in-tree build.
self.run("cp", "-rp", f"{self.source_dir}/.", self.build_dir)
self.run("./configure", "--prefix=/usr", "--disable-blacklist")
def install(self):
self.run("make", "install", f"DESTDIR={self.dest_dir}")
+23
View File
@@ -0,0 +1,23 @@
version = "33"
revision = 1
description = "Tools and library to handle kernel modules (modprobe/depmod/libkmod)"
license = "LGPL-2.1-or-later"
url = "https://github.com/kmod-project/kmod"
source = tarball(
url=f"https://www.kernel.org/pub/linux/utils/kernel/kmod/kmod-{version}.tar.xz",
sha256="dc768b3155172091f56dc69430b5481f2d76ecd9ccb54ead8c2540dbcf5ea9bc",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"], "zlib", "xz", "zstd"]
# The kernel.org release tarball ships the autotools build (no meson.build).
configure, build, install = autotools(
configure_args=[
"--with-zstd",
"--with-xz",
"--with-zlib",
"--without-openssl",
"--disable-manpages",
"--disable-test-modules",
],
)
+40
View File
@@ -0,0 +1,40 @@
version = "2.73"
revision = 1
description = "POSIX 1003.1e capabilities library"
license = "BSD-3-Clause OR GPL-2.0-only"
url = "https://sites.google.com/site/fullycapable/"
source = tarball(
url=f"https://www.kernel.org/pub/linux/libs/security/linux-privs/libcap2/libcap-{version}.tar.xz",
sha256="6405f6089cf4cdd8c271540cd990654d78dd0b1989b2d9bda20f933a75a795a5",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"]]
def _args(self):
# libcap is plain-make: CC builds target objects, BUILD_CC builds the small
# native code generator. No PAM/Go bindings; force lib= so it lands in /usr/lib.
return [
f"CC={self.triple}-gcc",
"BUILD_CC=gcc",
"GOLANG=no",
"PAM_CAP=no",
"lib=lib",
"prefix=/usr",
]
def build(self):
# libcap builds in-tree; /sources is read-only, so work in /build.
self.run("cp", "-rp", f"{self.source_dir}/.", self.build_dir)
self.run("make", f"-j{self.jobs}", *_args(self))
def install(self):
self.run(
"make",
"install",
f"DESTDIR={self.dest_dir}",
"RAISE_SETFCAP=no",
*_args(self),
)
+32
View File
@@ -0,0 +1,32 @@
version = "0.2.0"
revision = 1
description = "EDID and DisplayID parsing library"
license = "MIT"
url = "https://gitlab.freedesktop.org/emersion/libdisplay-info"
source = tarball(
url=f"https://gitlab.freedesktop.org/emersion/libdisplay-info/-/releases/{version}/downloads/libdisplay-info-{version}.tar.xz",
sha256="5a2f002a16f42dd3540c8846f80a90b8f4bdcd067a94b9d2087bc2feae974176",
)
host_deps = ["binutils", "gcc", "pkgconf"]
# Reads hwdata's pnp.ids at build time.
deps = [profile["libc"], "hwdata"]
def configure(self):
# libdisplay-info generates a C table from hwdata's pnp.ids at build time,
# looking on the *build* machine (/usr/share/hwdata/pnp.ids). Our hwdata is
# in the target sysroot, so stage the (arch-independent) data there first.
self.run(
"sh",
"-c",
"mkdir -p /usr/share/hwdata && "
"cp -f /sysroot/usr/share/hwdata/pnp.ids /usr/share/hwdata/pnp.ids",
)
meson_configure(self)
def build(self):
meson_build(self)
def install(self):
meson_install(self)
+31
View File
@@ -0,0 +1,31 @@
version = "2.4.124"
revision = 1
description = "Direct Rendering Manager userspace library"
license = "MIT"
url = "https://dri.freedesktop.org/"
source = tarball(
url=f"https://dri.freedesktop.org/libdrm/libdrm-{version}.tar.xz",
sha256="ac36293f61ca4aafaf4b16a2a7afff312aa4f5c37c9fbd797de9e3c0863ca379",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"], "libpciaccess"]
configure, build, install = meson(
configure_args=[
# udev support is optional and would create a cycle with systemd-udev,
# which itself is pulled in later; Mesa does not require it.
"-Dudev=false",
"-Dtests=false",
"-Dman-pages=disabled",
"-Dvalgrind=disabled",
# GPU-specific helper libs that Mesa's x86_64 drivers consume.
"-Dintel=enabled",
"-Dradeon=enabled",
"-Damdgpu=enabled",
"-Dnouveau=enabled",
# Embedded/SoC GPUs we don't target.
"-Dvc4=disabled",
"-Detnaviv=disabled",
"-Dfreedreno=disabled",
],
)
+18
View File
@@ -0,0 +1,18 @@
version = "1.13.6"
revision = 1
description = "Wrapper library for evdev input devices"
license = "MIT"
url = "https://gitlab.freedesktop.org/libevdev/libevdev"
source = tarball(
url=f"https://www.freedesktop.org/software/libevdev/libevdev-{version}.tar.xz",
sha256="73f215eccbd8233f414737ac06bca2687e67c44b97d2d7576091aa9718551110",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"]]
configure, build, install = meson(
configure_args=[
"-Dtests=disabled",
"-Ddocumentation=disabled",
],
)
+16
View File
@@ -0,0 +1,16 @@
version = "3.4.6"
revision = 1
description = "Portable foreign-function interface library"
license = "MIT"
url = "https://sourceware.org/libffi/"
source = tarball(
url=f"https://github.com/libffi/libffi/releases/download/v{version}/libffi-{version}.tar.gz",
sha256="b0dea9df23c863a7a50e825440f3ebffabd65df1497108e5d437747843895a4e",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"]]
configure, build, install = autotools(
# Install straight into ${libdir} rather than a gcc-triplet subdir.
configure_args=["--disable-multi-os-directory"],
)
+22
View File
@@ -0,0 +1,22 @@
version = "1.7.0"
revision = 1
description = "GL Vendor-Neutral Dispatch library"
license = "MIT"
url = "https://gitlab.freedesktop.org/glvnd/libglvnd"
source = tarball(
url=f"https://gitlab.freedesktop.org/glvnd/libglvnd/-/archive/v{version}/libglvnd-v{version}.tar.gz",
sha256="2b6e15b06aafb4c0b6e2348124808cbd9b291c647299eaaba2e3202f51ff2f3d",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"]]
configure, build, install = meson(
configure_args=[
# Wayland-only: no X11/GLX dispatch.
"-Dx11=disabled",
"-Dglx=disabled",
"-Degl=true",
"-Dgles1=true",
"-Dgles2=true",
],
)
+22
View File
@@ -0,0 +1,22 @@
version = "1.28.1"
revision = 1
description = "Input device handling library for Wayland compositors"
license = "MIT"
url = "https://gitlab.freedesktop.org/libinput/libinput"
source = tarball(
url=f"https://gitlab.freedesktop.org/libinput/libinput/-/archive/{version}/libinput-{version}.tar.gz",
sha256="a13f8c9a7d93df3c85c66afd135f0296701d8d32f911991b7aa4273fdd6a42a3",
)
host_deps = ["binutils", "gcc", "pkgconf"]
# eudev provides libudev here.
deps = [profile["libc"], "eudev", "libevdev", "mtdev"]
configure, build, install = meson(
configure_args=[
"-Dlibwacom=false",
"-Ddebug-gui=false",
"-Dtests=false",
"-Ddocumentation=false",
"-Dudev-dir=/usr/lib/udev",
],
)
+19
View File
@@ -0,0 +1,19 @@
version = "3.1.2"
revision = 1
description = "SIMD-accelerated libjpeg-compatible JPEG codec"
license = "BSD-3-Clause AND IJG"
url = "https://libjpeg-turbo.org/"
source = tarball(
url=f"https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/{version}/libjpeg-turbo-{version}.tar.gz",
sha256="8f0012234b464ce50890c490f18194f913a7b1f4e6a03d6644179fa0f867d0cf",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"]]
configure, build, install = cmake(
configure_args=[
"-DENABLE_STATIC=OFF",
"-DENABLE_SHARED=ON",
"-DWITH_JPEG8=ON",
],
)
+13
View File
@@ -0,0 +1,13 @@
version = "0.18.1"
revision = 1
description = "Generic PCI access library"
license = "MIT"
url = "https://gitlab.freedesktop.org/xorg/lib/libpciaccess"
source = tarball(
url=f"https://www.x.org/archive/individual/lib/libpciaccess-{version}.tar.xz",
sha256="4af43444b38adb5545d0ed1c2ce46d9608cc47b31c2387fc5181656765a6fa76",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"], "zlib"]
configure, build, install = meson()
+13
View File
@@ -0,0 +1,13 @@
version = "1.6.58"
revision = 1
description = "Reference library for the PNG image format"
license = "libpng-2.0"
url = "http://www.libpng.org/pub/png/libpng.html"
source = tarball(
url=f"https://github.com/pnggroup/libpng/archive/refs/tags/v{version}.tar.gz",
sha256="a9d4df463d36a6e5f9c29bd6f4967312d17e996c1854f3511f833924eb1993cf",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"], "zlib"]
configure, build, install = autotools()
+23
View File
@@ -0,0 +1,23 @@
version = "4.4.38"
revision = 1
description = (
"Extended crypt library (libcrypt: descrypt, md5crypt, sha*crypt, yescrypt)"
)
license = "LGPL-2.1-or-later"
url = "https://github.com/besser82/libxcrypt"
source = tarball(
url=f"https://github.com/besser82/libxcrypt/releases/download/v{version}/libxcrypt-{version}.tar.xz",
sha256="80304b9c306ea799327f01d9a7549bdb28317789182631f1b54f4511b4206dd6",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"]]
# glibc no longer provides libcrypt/crypt(); libxcrypt is the replacement.
# Build the glibc-compatible libcrypt.so.1 with all hash methods.
configure, build, install = autotools(
configure_args=[
"--enable-obsolete-api=glibc",
"--enable-hashes=all",
"--disable-failure-tokens",
],
)
+21
View File
@@ -0,0 +1,21 @@
version = "1.9.2"
revision = 1
description = "Library to handle keyboard descriptions and key events"
license = "MIT"
url = "https://xkbcommon.org/"
source = tarball(
url=f"https://github.com/xkbcommon/libxkbcommon/archive/refs/tags/xkbcommon-{version}.tar.gz",
sha256="d2eab57e1ce79de991f8ceb3fcd726a6978b970382c8ac8c8f112b61ceaa9167",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"], "xkeyboard-config", "libxml2"]
configure, build, install = meson(
configure_args=[
# Weston uses core libxkbcommon; no X11/Wayland helper tools needed.
"-Denable-x11=false",
"-Denable-wayland=false",
"-Denable-docs=false",
"-Dxkb-config-root=/usr/share/X11/xkb",
],
)
+21
View File
@@ -0,0 +1,21 @@
version = "2.14.2"
revision = 1
description = "XML parsing and toolkit library (libxml2)"
license = "MIT"
url = "https://gitlab.gnome.org/GNOME/libxml2"
_series = ".".join(version.split(".")[:2])
source = tarball(
url=f"https://download.gnome.org/sources/libxml2/{_series}/libxml2-{version}.tar.xz",
sha256="353f3c83535d4224a4e5f1e88c90b5d4563ea8fec11f6407df640fd28fc8b8c6",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"], "zlib", "xz"]
configure, build, install = autotools(
configure_args=[
"--without-python",
"--with-zlib",
"--with-lzma",
"--without-icu",
],
)
+5 -5
View File
@@ -10,9 +10,9 @@ source = tarball(
host_deps = ["autoconf", "automake", "binutils", "gcc"]
deps = [profile["libc"]]
build_if = profile["arch"] in ("x86_64", "aarch64", "riscv64", "loongarch64")
build_if = profile.arch in ("x86_64", "aarch64", "riscv64", "loongarch64")
_arch_args = {
arch_args = {
"x86_64": [
"--enable-uefi-x86-64",
"--enable-uefi-ia32",
@@ -25,8 +25,8 @@ _arch_args = {
}
configure, build, install = autotools(
configure_args=["--enable-uefi-cd", *_arch_args.get(profile["arch"], [])],
configure_env={"TOOLCHAIN_FOR_TARGET": profile["triple"] + "-"},
configure_args=["--enable-uefi-cd", *arch_args[profile.arch]],
configure_env={"TOOLCHAIN_FOR_TARGET": profile.triple + "-"},
)
subpackages = [
@@ -40,7 +40,7 @@ subpackages = [
),
]
if profile["arch"] == "x86_64":
if profile.arch == "x86_64":
subpackages.append(
subpackage(
"limine-bios",
+1 -1
View File
@@ -14,7 +14,7 @@ def build(self):
# Stage the source into the (writable) build dir before invoking the kernel
# build system, which is not happy with a read-only source tree.
self.run("cp", "-rp", f"{self.source_dir}/.", self.build_dir)
arch = linux_archs.get(self.profile["arch"], self.profile["arch"])
arch = linux_archs.get(self.arch, self.arch)
self.run("make", "headers_install", f"ARCH={arch}")
self.run(
"find",
+33 -3
View File
@@ -14,8 +14,8 @@ linux_subarchs = {"x86_64": "x86"}
def _make(self, *extra):
arch = linux_archs.get(self.profile["arch"], self.profile["arch"])
subarch = linux_subarchs.get(self.profile["arch"])
arch = linux_archs.get(self.arch, self.arch)
subarch = linux_subarchs.get(self.arch)
subarch_arg = (f"SUBARCH={subarch}",) if subarch else ()
return (
"make",
@@ -30,7 +30,7 @@ def _make(self, *extra):
def configure(self):
self.run("cp", "-rp", f"{self.source_dir}/.", self.build_dir)
self.run(
"cp", self.files / f"config.{self.profile['arch']}", self.build_dir / ".config"
"cp", self.files / f"config.{self.arch}", self.build_dir / ".config"
)
self.run(*_make(self, "olddefconfig"))
@@ -46,3 +46,33 @@ def install(self):
self.build_dir / "arch/x86/boot/bzImage",
self.dest_dir / f"boot/vmlinuz-{self.version}",
)
self.run(
"install",
"-Dm644",
self.build_dir / ".config",
self.dest_dir / f"boot/config-{self.version}",
)
self.run(
"install",
"-Dm644",
self.build_dir / "System.map",
self.dest_dir / f"boot/System.map-{self.version}",
)
# Install loadable modules so udev can autoload DRM/input drivers at boot.
# The kernel's modules_install target runs depmod itself (host kmod).
self.run(
*_make(
self,
f"INSTALL_MOD_PATH={self.dest_dir}",
"INSTALL_MOD_STRIP=1",
"modules_install",
)
)
# Drop the dangling build/source symlinks that point at the (ephemeral)
# build tree; they are useless in the target image.
self.run(
"sh",
"-c",
f"rm -f {self.dest_dir}/lib/modules/*/build "
f"{self.dest_dir}/lib/modules/*/source",
)
+72
View File
@@ -0,0 +1,72 @@
version = "21.1.6"
revision = 1
description = "LLVM compiler infrastructure (target libLLVM for Mesa)"
license = "Apache-2.0 WITH LLVM-exception"
url = "https://llvm.org/"
source = tarball(
url=f"https://github.com/llvm/llvm-project/releases/download/llvmorg-{version}/llvm-project-{version}.src.tar.xz",
sha256="ae67086eb04bed7ca11ab880349b5f1ab6f50e1b88cda376eaf8a845b935762b",
)
host_deps = ["binutils", "gcc", "pkgconf"]
# Pinned independently of the host LLVM (22.x): this must be a release that
# Mesa 25.3 accepts. Provides target libLLVM for the llvmpipe/radeonsi gallium
# drivers; X86 + AMDGPU backends cover software rendering and AMD GPUs.
deps = [profile["libc"], "ncurses", "zlib", "zstd", "libffi", "libxml2"]
def configure(self):
p = self.options
self.run(
"cmake",
"-S",
self.source_dir / "llvm",
"-B",
self.build_dir,
"-GNinja",
"-DCMAKE_BUILD_TYPE=Release",
f"-DCMAKE_INSTALL_PREFIX={p.get('prefix', '/usr')}",
# Cross toolchain. CMAKE_CROSSCOMPILING triggers LLVM to build a native
# llvm-tblgen under build/NATIVE with the default host compiler.
"-DCMAKE_SYSTEM_NAME=Linux",
f"-DCMAKE_SYSTEM_PROCESSOR={self.arch}",
f"-DCMAKE_SYSROOT={self.sysroot}",
f"-DCMAKE_C_COMPILER={self.triple}-gcc",
f"-DCMAKE_CXX_COMPILER={self.triple}-g++",
"-DCMAKE_FIND_ROOT_PATH_MODE_PROGRAM=NEVER",
"-DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY",
"-DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY",
"-DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=ONLY",
f"-DLLVM_HOST_TRIPLE={self.triple}",
f"-DLLVM_DEFAULT_TARGET_TRIPLE={self.triple}",
"-DLLVM_TARGETS_TO_BUILD=X86;AMDGPU",
"-DLLVM_BUILD_LLVM_DYLIB=ON",
"-DLLVM_LINK_LLVM_DYLIB=ON",
"-DLLVM_ENABLE_RTTI=ON",
"-DLLVM_ENABLE_FFI=ON",
"-DLLVM_ENABLE_ZLIB=FORCE_ON",
"-DLLVM_ENABLE_ZSTD=FORCE_ON",
"-DLLVM_ENABLE_LIBXML2=FORCE_ON",
"-DLLVM_ENABLE_TERMINFO=ON",
"-DLLVM_INCLUDE_TESTS=OFF",
"-DLLVM_INCLUDE_EXAMPLES=OFF",
"-DLLVM_INCLUDE_BENCHMARKS=OFF",
"-DLLVM_INCLUDE_DOCS=OFF",
"-DLLVM_ENABLE_PROJECTS=",
"-Wno-dev",
env={
"CFLAGS": p.get("cflags", ""),
"CXXFLAGS": p.get("cxxflags", ""),
"LDFLAGS": p.get("ldflags", ""),
},
)
def build(self):
self.run("cmake", "--build", self.build_dir, f"-j{self.jobs}")
def install(self):
self.run("cmake", "--install", self.build_dir, env={"DESTDIR": str(self.dest_dir)})
# Runtime needs the shared libLLVM + headers/llvm-config; static archives are
# large and unnecessary for Mesa.
self.run("sh", "-c", f"rm -f {self.dest_dir}/usr/lib/*.a")
+56
View File
@@ -0,0 +1,56 @@
version = "25.3.0"
revision = 1
description = "Mesa 3D graphics library (OpenGL/EGL/GLES, gallium drivers)"
license = "MIT"
url = "https://www.mesa3d.org/"
source = tarball(
url=f"https://archive.mesa3d.org/mesa-{version}.tar.xz",
sha256="0fd54fea7dbbddb154df05ac752b18621f26d97e27863db3be951417c6abe8ae",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [
profile["libc"],
"libstdc++", # C++ components need the libstdc++ headers/runtime
"libdrm",
"expat",
"llvm",
"zlib",
"zstd",
"wayland",
"wayland-protocols",
"libglvnd",
"hwdata",
"eudev", # libudev: DRM device enumeration
"libdisplay-info", # EDID/DisplayID parsing for the KMS backend
"elfutils", # libelf: required by the radeonsi gallium driver
]
_mesa_args = [
"-Dplatforms=wayland",
"-Dglx=disabled",
"-Degl=enabled",
"-Dgbm=enabled",
"-Dgles1=enabled",
"-Dgles2=enabled",
"-Dglvnd=enabled",
"-Dshared-glapi=enabled",
"-Dllvm=enabled",
"-Dshared-llvm=enabled",
"-Dexpat=enabled",
"-Dzstd=enabled",
"-Dvideo-codecs=all_free",
"-Db_ndebug=true",
"-Dbuild-tests=false",
# Broad HW + software gallium set (Weston's GL renderer uses these).
# NOTE: 'iris' (modern Intel) is omitted because it requires libclc
# (a clang-based OpenCL-bitcode build); 'crocus' covers older Intel.
# Add iris back once a libclc recipe exists.
"-Dgallium-drivers=radeonsi,r600,crocus,nouveau,virgl,svga,zink,llvmpipe,softpipe",
# Vulkan deferred: needs a vulkan-headers + SPIRV-Headers/Tools sub-stack
# and is not required for Weston's GL renderer. Add radv/anv/lavapipe later.
"-Dvulkan-drivers=",
]
# LLVM is found via the cross llvm-config emulator wired into the meson cross
# file (src/lib/meson.py), which reports sysroot paths — no target-binary exec.
configure, build, install = meson(configure_args=_mesa_args)
+13
View File
@@ -0,0 +1,13 @@
version = "1.1.7"
revision = 1
description = "Multitouch protocol translation library"
license = "MIT"
url = "https://bitmath.org/code/mtdev/"
source = tarball(
url=f"https://bitmath.org/code/mtdev/mtdev-{version}.tar.gz",
sha256="a55bd02a9af4dd266c0042ec608744fff3a017577614c057da09f1f4566ea32c",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"]]
configure, build, install = autotools()
+11 -4
View File
@@ -5,7 +5,7 @@ license = "Apache-2.0"
url = "https://www.openssl.org/"
source = tarball(
url=f"https://github.com/openssl/openssl/releases/download/openssl-{version}/openssl-{version}.tar.gz",
sha256="?",
sha256="002a2d6b30b58bf4bea46c43bdd96365aaf8daa6c428782aa4feee06da197df3",
)
host_deps = ["binutils", "gcc"]
deps = ["zlib"]
@@ -18,9 +18,9 @@ ossl_targets = {
def configure(self):
target = ossl_targets.get(self.profile["arch"])
target = ossl_targets.get(self.arch)
if target is None:
raise ValueError(f"openssl: unsupported arch {self.profile['arch']}")
raise ValueError(f"openssl: unsupported arch {self.arch}")
self.run(
self.source_dir / "Configure",
@@ -48,6 +48,13 @@ def build(self):
def install(self):
# OpenSSL's Makefile assigns `DESTDIR=` itself, so an environment DESTDIR is
# ignored; it must be passed as a make command-line variable to take effect.
# Without this, the (glibc) target libs install straight into the musl
# builder's /usr/lib and break it mid-install.
self.run(
"make", "install_sw", "install_ssldirs", env={"DESTDIR": str(self.dest_dir)}
"make",
"install_sw",
"install_ssldirs",
f"DESTDIR={self.dest_dir}",
)
+28
View File
@@ -0,0 +1,28 @@
version = "1.56.3"
revision = 1
description = "Library for laying out and rendering internationalized text"
license = "LGPL-2.1-or-later"
url = "https://www.gtk.org/"
_series = ".".join(version.split(".")[:2])
source = tarball(
url=f"https://download.gnome.org/sources/pango/{_series}/pango-{version}.tar.xz",
sha256="2606252bc25cd8d24e1b7f7e92c3a272b37acd6734347b73b47a482834ba2491",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [
profile["libc"],
"glib2",
"harfbuzz",
"fribidi",
"fontconfig",
"freetype2",
"cairo",
]
configure, build, install = meson(
configure_args=[
"-Dintrospection=disabled",
"-Dgtk_doc=false",
"-Dxft=disabled",
],
)
+19
View File
@@ -0,0 +1,19 @@
version = "10.45"
revision = 1
description = "Perl-compatible regular expression library (PCRE2)"
license = "BSD-3-Clause"
url = "https://www.pcre.org/"
source = tarball(
url=f"https://github.com/PCRE2Project/pcre2/releases/download/pcre2-{version}/pcre2-{version}.tar.gz",
sha256="0e138387df7835d7403b8351e2226c1377da804e0737db0e071b48f07c9d12ee",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"]]
configure, build, install = autotools(
configure_args=[
"--enable-pcre2-16",
"--enable-pcre2-32",
"--enable-jit",
],
)
+19
View File
@@ -0,0 +1,19 @@
version = "0.46.0"
revision = 1
description = "Low-level pixel manipulation library"
license = "MIT"
url = "https://www.pixman.org/"
source = tarball(
url=f"https://www.x.org/archive/individual/lib/pixman-{version}.tar.xz",
sha256="d2eab57e1ce79de991f8ceb3fcd726a6978b970382c8ac8c8f112b61ceaa9167",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"], "libpng"]
configure, build, install = meson(
configure_args=[
"-Dtests=disabled",
"-Ddemos=disabled",
"-Dgtk=disabled",
],
)
+2 -2
View File
@@ -1,11 +1,11 @@
version = "3.3.3"
version = "2.5.1"
revision = 1
description = "Lightweight pkg-config implementation"
license = "ISC"
url = "http://pkgconf.org/"
source = tarball(
url=f"https://distfiles.ariadne.space/pkgconf/pkgconf-{version}.tar.xz",
sha256="?",
sha256="cd05c9589b9f86ecf044c10a2269822bc9eb001eced2582cfffd658b0a50c243",
)
host_deps = ["autoconf", "automake", "binutils", "gcc"]
deps = [profile["libc"]]
+23
View File
@@ -0,0 +1,23 @@
version = "0.6.3"
revision = 1
description = "Seat management daemon and libseat (logind-free)"
license = "MIT"
url = "https://git.sr.ht/~kennylevinsen/seatd"
source = tarball(
url=f"https://git.sr.ht/~kennylevinsen/seatd/archive/{version}.tar.gz",
sha256="5226850c163b485aebe71da0d3f4941761637e146a5c9393cb40c52617ad84a8",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"]]
configure, build, install = meson(
configure_args=[
# No logind: libseat talks to the bundled seatd daemon, which Weston
# uses (via libseat) to open DRM/input devices on the user's behalf.
"-Dlibseat-logind=disabled",
"-Dlibseat-seatd=enabled",
"-Dserver=enabled",
"-Dexamples=disabled",
"-Dman-pages=disabled",
],
)
+33
View File
@@ -0,0 +1,33 @@
version = "4.16.0"
revision = 1
description = "Password and account management utilities (login, passwd, useradd)"
license = "BSD-3-Clause"
url = "https://github.com/shadow-maint/shadow"
source = tarball(
url=f"https://github.com/shadow-maint/shadow/releases/download/{version}/shadow-{version}.tar.xz",
sha256="b78e3921a95d53282a38e90628880624736bf6235e36eea50c50835f59a3530b",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"], "libxcrypt"]
configure, build, _ = autotools(
configure_args=[
"--without-libpam",
"--without-libbsd",
"--without-selinux",
"--without-acl",
"--without-attr",
],
)
def install(self):
autotools_install(self, [f"DESTDIR={self.dest_dir}"])
# `groups` is also shipped by coreutils; drop shadow's copy (and its man
# page) so the two packages don't conflict at install time.
self.run(
"sh",
"-c",
f"rm -f {self.dest_dir}/usr/bin/groups "
f"{self.dest_dir}/usr/share/man/man1/groups.1",
)
+18
View File
@@ -0,0 +1,18 @@
version = "2.37"
revision = 1
description = "DejaVu TrueType font family"
license = "Bitstream-Vera"
url = "https://dejavu-fonts.github.io/"
_tag = "version_" + version.replace(".", "_")
source = tarball(
url=f"https://github.com/dejavu-fonts/dejavu-fonts/releases/download/{_tag}/dejavu-fonts-ttf-{version}.tar.bz2",
sha256="fa9ca4d13871dd122f61258a80d01751d603b4d3ee14095d65453b4e846e17d7",
)
host_deps = []
deps = []
def install(self):
dest = self.dest_dir / "usr/share/fonts/dejavu"
self.run("install", "-d", dest)
self.run("sh", "-c", f"install -m644 {self.source_dir}/ttf/*.ttf {dest}/")
+58
View File
@@ -0,0 +1,58 @@
version = "2.41"
revision = 1
description = "Miscellaneous system utilities (agetty, mount, libblkid, libmount)"
license = "GPL-2.0-or-later AND LGPL-2.1-or-later"
url = "https://github.com/util-linux/util-linux"
_series = "v" + ".".join(version.split(".")[:2])
source = tarball(
url=f"https://www.kernel.org/pub/linux/utils/util-linux/{_series}/util-linux-{version}.tar.xz",
sha256="81ee93b3cfdfeb7d7c4090cedeba1d7bbce9141fd0b501b686b3fe475ddca4c6",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"], "zlib"]
configure, build, _ = autotools(
configure_args=[
# We need agetty + the libs (blkid/mount/uuid); login/su come from shadow
# to avoid a /usr/bin/login file conflict.
"--disable-login",
"--disable-su",
"--disable-nologin",
# liblastlog2 (new in 2.41) pulls in sqlite3; we don't need it.
"--disable-liblastlog2",
"--disable-pam-lastlog2",
# We only need agetty + the libs; skip the curses TUI tools (irqtop,
# cfdisk, ...) which otherwise trip over the ncurses termlib split.
"--without-ncurses",
"--without-ncursesw",
"--without-systemd",
"--without-systemdsystemunitdir",
"--without-python",
"--disable-bash-completion",
"--disable-rpath",
# These would chown/setuid during install, which fails unprivileged.
"--disable-makeinstall-chown",
"--disable-makeinstall-setuid",
],
)
def install(self):
autotools_install(self, [f"DESTDIR={self.dest_dir}"])
# libtool's install-time relink miscompiles libmount/libfdisk against the
# build host's musl libc instead of the target glibc (the pre-relink
# build-tree libs are correct). Restore the build-tree shared objects over
# the relinked ones and strip their build-tree rpath.
self.run(
"sh",
"-ec",
"for real in /build/.libs/lib*.so.*.*.*; do "
' [ -e "$real" ] || continue; '
' base=$(basename "$real"); '
f' dst="{self.dest_dir}/usr/lib/$base"; '
' [ -e "$dst" ] || continue; '
' cp -a "$real" "$dst"; '
' patchelf --remove-rpath "$dst" 2>/dev/null || true; '
"done",
)
+16
View File
@@ -0,0 +1,16 @@
version = "1.43"
revision = 1
description = "Wayland protocol extension definitions"
license = "MIT"
url = "https://gitlab.freedesktop.org/wayland/wayland-protocols"
source = tarball(
url=f"https://gitlab.freedesktop.org/wayland/wayland-protocols/-/releases/{version}/downloads/wayland-protocols-{version}.tar.xz",
sha256="ba3c3425dd27c57b5291e93dba97be12479601e00bcab24d26471948cb643653",
)
host_deps = ["pkgconf"]
# Architecture-independent XML + pkg-config data; nothing to link.
deps = []
configure, build, install = meson(
configure_args=["-Dtests=false"],
)
+23
View File
@@ -0,0 +1,23 @@
# Must match the build machine's wayland-scanner (from the Alpine image), which
# wayland requires to be the exact same version when cross-compiling.
version = "1.25.0"
revision = 1
description = "Core Wayland protocol library and scanner"
license = "MIT"
url = "https://wayland.freedesktop.org/"
source = tarball(
url=f"https://gitlab.freedesktop.org/wayland/wayland/-/releases/{version}/downloads/wayland-{version}.tar.xz",
sha256="c065f040afdff3177680600f249727e41a1afc22fccf27222f15f5306faa1f03",
)
host_deps = ["binutils", "gcc", "pkgconf"]
# The build machine's wayland-scanner (from the builder image) generates protocol
# glue; the target libwayland-{client,server} link libffi/expat/libxml2.
deps = [profile["libc"], "libffi", "expat", "libxml2"]
configure, build, install = meson(
configure_args=[
"-Dtests=false",
"-Ddocumentation=false",
"-Ddtd_validation=true",
],
)
+58
View File
@@ -0,0 +1,58 @@
version = "14.0.2"
revision = 1
description = "Reference Wayland compositor (Weston)"
license = "MIT"
url = "https://gitlab.freedesktop.org/wayland/weston"
source = tarball(
url=f"https://gitlab.freedesktop.org/wayland/weston/-/releases/{version}/downloads/weston-{version}.tar.xz",
sha256="b47216b3530da76d02a3a1acbf1846a9cd41d24caa86448f9c46f78f20b6e0ac",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [
profile["libc"],
"wayland",
"wayland-protocols",
"libxkbcommon",
"pixman",
"cairo",
"libinput",
"mesa",
"libdrm",
"libdisplay-info",
"seatd",
"pango",
"glib2",
"libjpeg-turbo",
"libpng",
"freetype2",
"fontconfig",
"ttf-dejavu",
]
configure, build, install = meson(
configure_args=[
# DRM backend with the GL renderer; seatd (libseat) launcher, no logind.
"-Dbackend-drm=true",
"-Dbackend-headless=true",
"-Dbackend-wayland=true",
"-Dbackend-x11=false",
"-Dbackend-rdp=false",
"-Dbackend-vnc=false",
"-Dbackend-pipewire=false",
"-Dbackend-drm-screencast-vaapi=false",
"-Drenderer-gl=true",
"-Dxwayland=false",
"-Dsystemd=false",
# libseat (seatd) is auto-detected in weston 14; no explicit option.
"-Dimage-jpeg=true",
"-Dimage-webp=false",
"-Dremoting=false",
"-Dpipewire=false",
"-Dcolor-management-lcms=false",
"-Ddemo-clients=true",
"-Dsimple-clients=damage,im,egl,shm,touch",
# Test suite needs extra generated protocol headers and isn't needed.
"-Dtests=false",
"-Dtest-junit-xml=false",
],
)
+16
View File
@@ -0,0 +1,16 @@
version = "2.44"
revision = 1
description = "X keyboard configuration database (keymaps for libxkbcommon)"
license = "MIT"
url = "https://www.freedesktop.org/wiki/Software/XKeyboardConfig/"
source = tarball(
url=f"https://www.x.org/archive/individual/data/xkeyboard-config/xkeyboard-config-{version}.tar.xz",
sha256="54d2c33eeebb031d48fa590c543e54c9bcbd0f00386ebc6489b2f47a0da4342a",
)
host_deps = ["pkgconf"]
# Pure keymap data installed under /usr/share/X11/xkb.
deps = []
configure, build, install = meson(
configure_args=["-Dxorg-rules-symlinks=true"],
)
+1 -1
View File
@@ -5,7 +5,7 @@ license = "Zlib"
url = "https://zlib.net/"
source = tarball(
url=f"https://github.com/madler/zlib/releases/download/v{version}/zlib-{version}.tar.gz",
sha256="?",
sha256="bb329a0a2cd0274d05519d61c667c062e06990d72e125ee2dfa8de64f0119d16",
)
host_deps = ["binutils", "gcc"]
deps = [profile["libc"]]
+29
View File
@@ -0,0 +1,29 @@
version = "1.5.6"
revision = 1
description = "Zstandard fast lossless compression library"
license = "BSD-3-Clause"
url = "https://facebook.github.io/zstd/"
source = tarball(
url=f"https://github.com/facebook/zstd/releases/download/v{version}/zstd-{version}.tar.gz",
sha256="8c29e06cf42aacc1eafc4077ae2ec6c6fcb96a626157e0593d5e82a34fd403c1",
)
host_deps = ["binutils", "gcc", "pkgconf"]
deps = [profile["libc"]]
def configure(self):
# zstd's top-level autotools/Makefile don't cross-compile cleanly; use the
# maintained meson port shipped under build/meson.
meson_configure(
self,
["-Dbin_programs=true", "-Dzlib=disabled", "-Dlzma=disabled", "-Dlz4=disabled"],
source_dir=self.source_dir / "build/meson",
)
def build(self):
meson_build(self)
def install(self):
meson_install(self)
+128
View File
@@ -0,0 +1,128 @@
#!/usr/bin/env bash
#
# Build a bootable initramfs from the installed package set and (optionally)
# launch it in QEMU to test the Weston desktop.
#
# Approach: the entire root filesystem is packed into a single initramfs and
# booted straight from RAM (no disk image, no loop mounts, no root needed).
# dinit runs as PID 1, brings up udev/seatd, autologins, and starts Weston.
#
# Usage:
# scripts/mkimage.sh [-C build-dir] [--run] [--mem MB]
#
# -C, --build-dir orchid build dir (default: $ORCHID_BUILD or build-x86_64)
# --out DIR output dir for artifacts (default: <build-dir>/qemu)
# --mem MB QEMU RAM in MiB (default: 4096)
# --run launch QEMU after building (otherwise just print the command)
#
# Artifacts written to <out>: rootfs/ (installed tree), vmlinuz, initramfs.cpio.gz
set -euo pipefail
here=$(cd "$(dirname "$0")/.." && pwd)
orchid="$here/orchid"
build_dir=${ORCHID_BUILD:-build-x86_64}
out=""
mem=4096
run=0
while [ $# -gt 0 ]; do
case "$1" in
-C|--build-dir) build_dir=$2; shift 2 ;;
--out) out=$2; shift 2 ;;
--mem) mem=$2; shift 2 ;;
--run) run=1; shift ;;
-h|--help) sed -n '2,20p' "$0"; exit 0 ;;
*) echo "unknown argument: $1" >&2; exit 2 ;;
esac
done
[ -d "$build_dir" ] || { echo "build dir not found: $build_dir" >&2; exit 1; }
out=${out:-$build_dir/qemu}
rootfs="$out/rootfs"
mkdir -p "$out"
echo ">> installing packages into $rootfs"
rm -rf "$rootfs"
"$orchid" install -C "$build_dir" "$rootfs"
echo ">> wiring up init"
# The kernel execs /init from an initramfs but does NOT auto-mount devtmpfs, so
# dinit would have no /dev/null to set up service stdio. This tiny bootstrap
# mounts the kernel vfs, then hands off to dinit (PID 1) which runs the rest.
mkdir -p "$rootfs"/{proc,sys,dev,run,tmp}
cat > "$rootfs/init" <<'EOF'
#!/bin/bash
export PATH=/usr/bin:/usr/sbin:/bin:/sbin
mount -t devtmpfs dev /dev 2>/dev/null || true
mount -t proc proc /proc 2>/dev/null || true
mount -t sysfs sys /sys 2>/dev/null || true
exec /sbin/init
EOF
chmod +x "$rootfs/init"
echo ">> regenerating ld.so.cache"
# Target libs are split across /lib, /usr/lib and /usr/lib64; without a cache
# the loader can't find e.g. libstdc++ for dinit (PID 1, before any service
# runs). base-files ships /etc/ld.so.conf + the /lib64 and /bin compat links;
# build the cache now from the assembled rootfs.
# Prefer the *target* ldconfig (run via the target loader) so the cache is in a
# format the target glibc loader accepts; fall back to the host ldconfig.
loader="$rootfs/lib/ld-linux-x86-64.so.2"
tgt_ldconfig="$rootfs/sbin/ldconfig"
if [ -x "$loader" ] && [ -e "$tgt_ldconfig" ]; then
"$loader" --library-path "$rootfs/lib:$rootfs/usr/lib:$rootfs/usr/lib64" \
"$tgt_ldconfig" -r "$rootfs"
elif command -v ldconfig >/dev/null; then
ldconfig -r "$rootfs"
else
echo " warning: no ldconfig available; libraries may not resolve" >&2
fi
echo ">> extracting kernel"
kernel=$(ls -1 "$rootfs"/boot/vmlinuz-* 2>/dev/null | head -n1 || true)
if [ -z "$kernel" ]; then
echo "no kernel in $rootfs/boot (build the 'linux' recipe first)" >&2
exit 1
fi
cp "$kernel" "$out/vmlinuz"
if [ ! -d "$rootfs"/lib/modules ] && [ ! -d "$rootfs"/usr/lib/modules ]; then
echo "warning: no /lib/modules in rootfs — DRM/input drivers built as" >&2
echo " modules won't load (rebuild 'linux' with modules_install)." >&2
fi
echo ">> packing initramfs"
# Pack as a newc cpio + gzip. Run from within rootfs so paths are relative.
( cd "$rootfs" && find . -print0 | cpio --null --create --format=newc --quiet ) \
| gzip -9 > "$out/initramfs.cpio.gz"
echo " initramfs: $(du -h "$out/initramfs.cpio.gz" | cut -f1)"
# Assemble the QEMU command. virtio-gpu gives Weston a DRM/KMS device; virtio
# keyboard/tablet feed libinput; serial is the console for boot logs.
kvm=()
[ -w /dev/kvm ] && kvm=(-enable-kvm -cpu host)
cmd=(
qemu-system-x86_64
"${kvm[@]}"
-m "$mem" -smp 4
-kernel "$out/vmlinuz"
-initrd "$out/initramfs.cpio.gz"
-append "console=ttyS0,115200 loglevel=6"
-device virtio-gpu-pci
-device virtio-keyboard-pci
-device virtio-tablet-pci
-display gtk,gl=on
-serial mon:stdio
)
echo ""
echo ">> QEMU command:"
printf ' %q' "${cmd[@]}"; echo
if [ "$run" -eq 1 ]; then
echo ">> launching QEMU (Ctrl-A X to quit the serial console)"
exec "${cmd[@]}"
fi
+1 -2
View File
@@ -40,7 +40,6 @@ def _info_args(
def mkpkg_base(container: Container, recipe: Recipe, arch: str) -> None:
name = recipe.name
deps = list(recipe.deps) + list(recipe.run_deps)
args = [
"apk",
"mkpkg",
@@ -58,7 +57,7 @@ def mkpkg_base(container: Container, recipe: Recipe, arch: str) -> None:
recipe.license,
recipe.url,
recipe.maintainer,
deps,
list(recipe.deps),
),
]
container.exec(args)
+63 -18
View File
@@ -7,6 +7,7 @@ from src import apk, fetch, log
from src.container import Container, Mount
from src.context import RecipeContext
from src.layout import Layout
from src.profile import Profile
from src.plan import (
PHASE_STAMPS,
Plan,
@@ -39,11 +40,11 @@ def _source_tree(layout: Layout, r: Recipe, key: str | None) -> Path:
return base / key
def _prepare_all_sources(layout: Layout, r: Recipe) -> None:
def _prepare_all_sources(layout: Layout, r: Recipe, *, strict: bool = False) -> None:
for key, src in r.sources.items():
tree = _source_tree(layout, r, key)
tree.parent.mkdir(parents=True, exist_ok=True)
fetch.prepare_source(layout, r.dir, src, tree)
fetch.prepare_source(layout, r.dir, src, tree, strict=strict)
def _build_path_env(host_deps_order: list[str], layout: Layout) -> str:
@@ -65,7 +66,7 @@ def _build_path_env(host_deps_order: list[str], layout: Layout) -> str:
def _container_for(
rs: RecipeSet, layout: Layout, profile: dict, r: Recipe
rs: RecipeSet, layout: Layout, profile: Profile, r: Recipe
) -> tuple[Container, list[str]]:
host_order = transitive_host_deps(rs, r)
name = _container_name("orchid", layout.build.name, r.kind, r.name)
@@ -96,13 +97,13 @@ def _container_for(
env = {
"PATH": _build_path_env(host_order, layout),
"ORCHID_ARCH": profile["arch"],
"ORCHID_TRIPLE": profile["triple"],
"ORCHID_ARCH": profile.arch,
"ORCHID_TRIPLE": profile.triple,
"ORCHID_JOBS": str(os.cpu_count() or 1),
}
c = Container(
name=name,
image=profile["container_image"],
image=profile.container_image,
mounts=mounts,
tmpfs=tmpfs,
network=False,
@@ -203,22 +204,64 @@ def _finalize_host(c: Container, layout: Layout, r: Recipe) -> None:
layout.host_pkg_marker(r.name, r.version, r.revision).write_text("ok\n")
def _sysroot_sync(c: Container, r: Recipe) -> None:
direct_deps = list(r.deps)
if not direct_deps:
def _target_output_index(rs: RecipeSet) -> dict[str, Recipe]:
"""Map every installable target package name (incl. subpackages) to its recipe."""
idx: dict[str, Recipe] = {}
for rec in rs.target.values():
for out in rec.outputs:
idx[out] = rec
return idx
def _transitive_runtime_deps(rs: RecipeSet, r: Recipe) -> list[str]:
"""Full closure of `r`'s deps, so the sysroot carries everything its
dependencies need (e.g. pkg-config Requires.private / linked SONAMEs).
Starts from `r`'s direct deps + build_deps and follows each package's own
`deps`. Unknown names (external packages, subpackages we can't resolve) are
still installed but not recursed into.
"""
idx = _target_output_index(rs)
order: list[str] = []
visited: set[str] = set()
def visit(name: str) -> None:
if name in visited:
return
visited.add(name)
order.append(name)
dep_recipe = idx.get(name)
if dep_recipe is not None and dep_recipe.name != r.name:
for d in dep_recipe.deps:
visit(d)
for name in (*r.deps, *r.build_deps):
visit(name)
return order
def _sysroot_sync(c: Container, rs: RecipeSet, r: Recipe) -> None:
pkgs = _transitive_runtime_deps(rs, r)
if not pkgs:
return
initdb = not apk.sysroot_initialized(c)
apk.sysroot_install(c, direct_deps, initdb=initdb)
apk.sysroot_install(c, pkgs, initdb=initdb)
def build_one(
rs: RecipeSet, layout: Layout, profile: dict, r: Recipe, *, forced: bool = False
rs: RecipeSet,
layout: Layout,
profile: Profile,
r: Recipe,
*,
forced: bool = False,
strict: bool = False,
) -> None:
log.info_field("recipe", _recipe_ref(r))
if forced:
_clear_stamps(layout, r)
_wipe_workdir(layout, r)
_prepare_all_sources(layout, r)
_prepare_all_sources(layout, r, strict=strict)
c, _host_order = _container_for(rs, layout, profile, r)
c.start()
@@ -226,7 +269,7 @@ def build_one(
# Ensure base output dest dir exists.
c.exec(["mkdir", "-p", f"/dest/{r.name}"])
_sysroot_sync(c, r)
_sysroot_sync(c, rs, r)
ctx = RecipeContext(
recipe=r, profile=profile, container=c, jobs=os.cpu_count() or 1
@@ -237,7 +280,7 @@ def build_one(
if r.kind == "target":
log.info_field("package", _recipe_ref(r))
_split_subpackages(c, r)
_package_target(c, r, profile["arch"])
_package_target(c, r, profile.arch)
else:
log.info_field("finalize", _recipe_ref(r))
_finalize_host(c, layout, r)
@@ -246,18 +289,20 @@ def build_one(
log.ok_field("done", _recipe_ref(r))
def execute(plan: Plan, rs: RecipeSet, layout: Layout, profile: dict) -> None:
def execute(
plan: Plan, rs: RecipeSet, layout: Layout, profile: Profile, *, strict: bool = False
) -> None:
if not plan.order:
log.info_field("plan", "nothing to do")
return
for k in plan.order:
r = plan.recipes[k]
build_one(rs, layout, profile, r, forced=k in plan.forced)
build_one(rs, layout, profile, r, forced=k in plan.forced, strict=strict)
def install_to(
layout: Layout,
profile: dict,
profile: Profile,
dest: Path,
pkgs: list[str],
*,
@@ -268,7 +313,7 @@ def install_to(
return
c = Container(
name=_container_name("orchid", layout.build.name, "install"),
image=profile["container_image"],
image=profile.container_image,
mounts=[
Mount(layout.pkgs_dir, "/pkgs", readonly=True),
Mount(dest, "/sysroot", readonly=False),
+15 -5
View File
@@ -50,7 +50,7 @@ def cmd_image(args) -> int:
layout.ensure()
prof = profile_mod.load_profile(layout)
container.ensure_image(
layout.dockerfile, prof["container_image"], layout.image_hash_file
layout.dockerfile, prof.container_image, layout.image_hash_file
)
return 0
@@ -115,12 +115,12 @@ def cmd_build(args) -> int:
layout.ensure()
prof, rs = _load(layout)
container.ensure_image(
layout.dockerfile, prof["container_image"], layout.image_hash_file
layout.dockerfile, prof.container_image, layout.image_hash_file
)
p = plan.build_plan(rs, layout, args.recipes or None, rebuild=args.rebuild)
if args.dry_run:
return cmd_plan(args)
builder.execute(p, rs, layout, prof)
builder.execute(p, rs, layout, prof, strict=args.strict)
return 0
@@ -129,7 +129,7 @@ def cmd_install(args) -> int:
layout.ensure()
prof, rs = _load(layout)
container.ensure_image(
layout.dockerfile, prof["container_image"], layout.image_hash_file
layout.dockerfile, prof.container_image, layout.image_hash_file
)
dest = Path(args.dest).resolve()
dest.mkdir(parents=True, exist_ok=True)
@@ -159,7 +159,7 @@ def cmd_fetch(args) -> int:
for k in targets:
r = rs.get(k)
for key, src in r.sources.items():
fetch_mod.fetch(layout, src)
fetch_mod.fetch(layout, src, strict=args.strict)
return 0
@@ -197,6 +197,11 @@ def make_parser() -> argparse.ArgumentParser:
_common(p_build)
p_build.add_argument("--rebuild", action="store_true")
p_build.add_argument("-n", "--dry-run", action="store_true")
p_build.add_argument(
"--strict",
action="store_true",
help='fail on recipes with a placeholder checksum (sha256="?")',
)
p_build.set_defaults(func=cmd_build)
p_inst = sub.add_parser(
@@ -221,6 +226,11 @@ def make_parser() -> argparse.ArgumentParser:
p_fetch = sub.add_parser("fetch", help="fetch sources only")
_common(p_fetch)
p_fetch.add_argument(
"--strict",
action="store_true",
help='fail on recipes with a placeholder checksum (sha256="?")',
)
p_fetch.set_defaults(func=cmd_fetch)
return parser
+20 -3
View File
@@ -1,9 +1,11 @@
import os
from collections.abc import Mapping
from dataclasses import dataclass, field
from pathlib import PurePosixPath
from typing import Any
from src.container import Container
from src.profile import Profile
from src.recipe import Recipe
@@ -12,7 +14,7 @@ class RecipeContext:
"""The `self` value passed to recipe phase functions."""
recipe: Recipe
profile: dict
profile: Profile
container: Container
jobs: int
_dest_output: str | None = None
@@ -73,13 +75,17 @@ class RecipeContext:
def prefix(self) -> str:
return f"/tools/{self.name}" if self.recipe.kind == "host" else "/usr"
@property
def options(self) -> Mapping[str, Any]:
return self.profile.options
@property
def triple(self) -> str:
return self.profile["triple"]
return self.profile.triple
@property
def arch(self) -> str:
return self.profile["arch"]
return self.profile.arch
def run(
self,
@@ -102,3 +108,14 @@ class RecipeContext:
merged.update(env)
cwd_s = str(cwd) if cwd is not None else "/build"
self.container.exec(flat, env=merged, cwd=cwd_s)
def write_text(self, path, content: str) -> None:
"""Write *content* to *path* inside the container.
Round-tripped through base64 so arbitrary text (quotes, newlines,
shell metacharacters) survives the shell without escaping.
"""
import base64
data = base64.b64encode(content.encode()).decode()
self.run("sh", "-c", f"echo '{data}' | base64 -d > '{path}'")
+12 -5
View File
@@ -26,11 +26,16 @@ def cache_lock(layout: Layout):
f.close()
def fetch_tarball(layout: Layout, src: Tarball) -> Path:
def fetch_tarball(layout: Layout, src: Tarball, *, strict: bool = False) -> Path:
dest = layout.tarball_cache / src.sha256
if dest.is_file():
return dest
if src.sha256 == "?":
if strict:
raise RuntimeError(
f"{src.url}: missing checksum (sha256=\"?\") is not allowed in "
f"strict mode; pin the real sha256 in the recipe"
)
log.warn(f"fetching {src.url} (sha256 unknown)")
else:
log.info(f"fetching {src.url}")
@@ -82,10 +87,10 @@ def fetch_git(layout: Layout, src: Git) -> Path:
return dest
def fetch(layout: Layout, src) -> Path:
def fetch(layout: Layout, src, *, strict: bool = False) -> Path:
with cache_lock(layout):
if isinstance(src, Tarball):
return fetch_tarball(layout, src)
return fetch_tarball(layout, src, strict=strict)
if isinstance(src, Git):
return fetch_git(layout, src)
raise TypeError(f"unknown source type {type(src).__name__}")
@@ -165,9 +170,11 @@ def apply_patches(tree: Path, recipe_dir: Path, patches: tuple[str, ...]) -> Non
_patched_marker(tree).write_text("\n".join(patches) + "\n")
def prepare_source(layout: Layout, recipe_dir: Path, src, tree: Path) -> None:
def prepare_source(
layout: Layout, recipe_dir: Path, src, tree: Path, *, strict: bool = False
) -> None:
"""Fetch + extract + patch into `tree`. Idempotent via marker files."""
cache_path = fetch(layout, src)
cache_path = fetch(layout, src, strict=strict)
expected_patches = "\n".join(src.patches) + "\n" if src.patches else "\n"
if (
_patched_marker(tree).is_file()
+2 -2
View File
@@ -1,5 +1,5 @@
def autotools_configure(self, extra_args=(), extra_env=None):
p = self.profile
p = self.options
env = {
"CFLAGS": p.get("cflags", ""),
"CXXFLAGS": p.get("cxxflags", ""),
@@ -9,7 +9,7 @@ def autotools_configure(self, extra_args=(), extra_env=None):
env.update(extra_env)
args = [
self.source_dir / "configure",
f"--host={p['triple']}",
f"--host={self.triple}",
f"--with-sysroot={self.sysroot}",
f"--prefix={p.get('prefix', '/usr')}",
f"--sysconfdir={p.get('sysconfdir', '/etc')}",
+4 -4
View File
@@ -1,5 +1,5 @@
def cmake_configure(self, extra_args=(), extra_env=None, *, host=False):
p = self.profile
p = self.options
if host:
env = {
"CFLAGS": p.get("host_cflags", ""),
@@ -15,10 +15,10 @@ def cmake_configure(self, extra_args=(), extra_env=None, *, host=False):
}
toolchain = [
"-DCMAKE_SYSTEM_NAME=Linux",
f"-DCMAKE_SYSTEM_PROCESSOR={p['arch']}",
f"-DCMAKE_SYSTEM_PROCESSOR={self.arch}",
f"-DCMAKE_SYSROOT={self.sysroot}",
f"-DCMAKE_C_COMPILER={p['triple']}-gcc",
f"-DCMAKE_CXX_COMPILER={p['triple']}-g++",
f"-DCMAKE_C_COMPILER={self.triple}-gcc",
f"-DCMAKE_CXX_COMPILER={self.triple}-g++",
"-DCMAKE_FIND_ROOT_PATH_MODE_PROGRAM=NEVER",
"-DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=ONLY",
"-DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=ONLY",
+95
View File
@@ -1,5 +1,98 @@
# A cross `llvm-config`. The target llvm-config is a glibc binary that can't run
# on the (musl) build host, so instead of executing it we emulate the queries
# meson/mesa make, reporting paths inside the sysroot. Self-configuring: it reads
# the LLVM version, library soname, and built targets from the installed sysroot,
# so it works for any LLVM consumer (mesa now, rust/etc. later) with no hardcoding.
_LLVM_CONFIG_EMULATOR = r'''#!/usr/bin/env python3
import glob, re, sys
SYSROOT = "/sysroot"
LIBDIR = SYSROOT + "/usr/lib"
INCDIR = SYSROOT + "/usr/include"
_major = ""
for _p in sorted(glob.glob(LIBDIR + "/libLLVM-*.so*")):
_m = re.search(r"libLLVM-(\d+)", _p)
if _m:
_major = _m.group(1)
break
_version = (_major + ".0.0") if _major else "0.0.0"
try:
with open(INCDIR + "/llvm/Config/llvm-config.h") as _f:
_m = re.search(r'LLVM_VERSION_STRING\s+"([^"]+)"', _f.read())
if _m:
_version = _m.group(1)
except OSError:
pass
_targets = []
try:
with open(INCDIR + "/llvm/Config/Targets.def") as _f:
_targets = re.findall(r"LLVM_TARGET\((\w+)\)", _f.read())
except OSError:
pass
if not _targets:
_targets = ["X86", "AMDGPU"]
_GENERIC = (
"aggressiveinstcombine all all-targets analysis asmparser asmprinter "
"binaryformat bitreader bitstreamreader bitwriter cfguard codegen "
"codegentypes core coroutines coverage debuginfocodeview debuginfodwarf "
"debuginfomsf debuginfopdb demangle dlltooldriver engine executionengine "
"extensions frontenddriver frontendhlsl frontendoffloading frontendopenmp "
"fuzzmutate globalisel instcombine instrumentation interpreter ipo irreader "
"irprinter jitlink libdriver lineeditor linker lto mc mca mcdisassembler "
"mcjit mcparser native nativecodegen object objectyaml option orcjit "
"orcshared orctargetprocess passes profiledata remarks runtimedyld "
"scalaropts selectiondag support symbolize target targetparser textapi "
"transformutils vectorize windowsdriver windowsmanifest"
).split()
def _components():
comps = list(_GENERIC)
for t in _targets:
tl = t.lower()
comps += [tl, tl + "asmparser", tl + "codegen", tl + "desc",
tl + "disassembler", tl + "info", tl + "targetmca", tl + "utils"]
return " ".join(sorted(set(comps)))
_H = {
"--version": lambda: _version,
"--components": _components,
"--targets-built": lambda: " ".join(_targets),
"--prefix": lambda: SYSROOT + "/usr",
"--bindir": lambda: SYSROOT + "/usr/bin",
"--includedir": lambda: INCDIR,
"--libdir": lambda: LIBDIR,
"--cmakedir": lambda: LIBDIR + "/cmake/llvm",
"--has-rtti": lambda: "YES",
"--shared-mode": lambda: "shared",
"--libs": lambda: "-lLLVM-" + _major,
"--system-libs": lambda: "",
"--cflags": lambda: "-I" + INCDIR,
"--cppflags": lambda: ("-I" + INCDIR
+ " -D__STDC_CONSTANT_MACROS"
+ " -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS"),
"--cxxflags": lambda: ("-I" + INCDIR
+ " -D__STDC_CONSTANT_MACROS"
+ " -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS"),
"--ldflags": lambda: "-L" + LIBDIR,
}
for _a in sys.argv[1:]:
if _a in _H:
print(_H[_a]())
'''
def meson_cross_file(self):
cross = self.build_dir / "meson-cross.ini"
llvm_config = self.build_dir / "llvm-config"
self.write_text(llvm_config, _LLVM_CONFIG_EMULATOR)
self.run("chmod", "+x", llvm_config)
self.write_text(
cross,
f"""\
@@ -12,6 +105,8 @@ objcopy = '{self.triple}-objcopy'
ranlib = '{self.triple}-ranlib'
strip = '{self.triple}-strip'
pkg-config = '{self.triple}-pkg-config'
cmake = '/usr/bin/cmake'
llvm-config = '{llvm_config}'
[host_machine]
system = 'linux'
+20 -13
View File
@@ -111,6 +111,21 @@ def build_plan(
seen: dict[str, Recipe] = {}
ts: graphlib.TopologicalSorter[str] = graphlib.TopologicalSorter()
forced: set[str] = set()
target_outputs: dict[str, Recipe] = {}
for tr in rs.target.values():
for out in tr.outputs:
if out in target_outputs:
raise ValueError(f"duplicate target output: {out}")
target_outputs[out] = tr
def target_dep(owner: str, name: str) -> Recipe:
tr = target_outputs.get(name)
if tr is None:
raise KeyError(f"{owner}: unknown target dep {name!r}")
if not tr.enabled:
raise ValueError(f"{owner}: dep {name!r} disabled by build_if")
return tr
def add(r: Recipe) -> None:
k = _key(r)
@@ -127,22 +142,14 @@ def build_plan(
add(hr)
deps.append(_key(hr))
if r.kind == "target":
for d in (*r.deps, *r.run_deps):
tr = rs.target.get(d)
if tr is None:
raise KeyError(f"{r.name}: unknown dep {d!r}")
if not tr.enabled:
raise ValueError(f"{r.name}: dep {d!r} disabled by build_if")
for d in (*r.deps, *r.build_deps):
tr = target_dep(r.name, d)
add(tr)
deps.append(_key(tr))
else:
# host recipes may declare target `deps` that need to land in /sysroot
for d in r.deps:
tr = rs.target.get(d)
if tr is None:
raise KeyError(f"host:{r.name}: unknown target dep {d!r}")
if not tr.enabled:
raise ValueError(f"host:{r.name}: dep {d!r} disabled by build_if")
# host recipes may declare target deps that need to land in /sysroot
for d in (*r.deps, *r.build_deps):
tr = target_dep(f"host:{r.name}", d)
add(tr)
deps.append(_key(tr))
ts.add(k, *deps)
+95 -14
View File
@@ -1,42 +1,123 @@
import importlib.util
from collections.abc import Mapping
from dataclasses import dataclass, field, replace
from pathlib import Path
from typing import Any
from src.layout import Layout
REQUIRED_KEYS = ("arch", "triple", "container_image")
# Core fields identify the build target. Every resolved profile must define
# them, and they are read as attributes (profile.arch) rather than options.
CORE_FIELDS = ("arch", "triple", "container_image")
# Name of the profile that supplies defaults for every other profile.
BASE_PROFILE = "base"
_MISSING = object()
def _load_module(path: Path, name: str):
@dataclass(frozen=True)
class Profile:
"""A resolved build profile.
The *core* fields (see ``CORE_FIELDS``) identify the build target and must
be present. Everything that tunes how packages are built from source --
install layout, compiler flags, and free-form feature switches -- lives in
``options`` and is looked up by key.
"""
name: str = ""
arch: str = ""
triple: str = ""
container_image: str = ""
options: Mapping[str, Any] = field(default_factory=dict)
def option(self, key: str, default: Any = _MISSING) -> Any:
"""Return option ``key``; fall back to ``default`` or raise KeyError."""
if key in self.options:
return self.options[key]
if default is not _MISSING:
return default
if key in CORE_FIELDS:
raise KeyError(
f"profile {self.name!r}: {key!r} is a core field; "
f"access it as profile.{key}, not as an option"
)
raise KeyError(f"profile {self.name!r}: option {key!r} is not defined")
def __getitem__(self, key: str) -> Any:
return self.option(key)
def get(self, key: str, default: Any = None) -> Any:
return self.option(key, default)
def overlay(self, child: "Profile") -> "Profile":
"""Layer ``child`` over ``self`` (the base) and return the result.
Core fields fall back to the base wherever the child leaves them empty;
options are merged key-by-key, with the child winning.
"""
return Profile(
name=child.name or self.name,
arch=child.arch or self.arch,
triple=child.triple or self.triple,
container_image=child.container_image or self.container_image,
options={**self.options, **child.options},
)
def _load_module(path: Path, name: str, ns: dict):
spec = importlib.util.spec_from_file_location(name, path)
if spec is None or spec.loader is None:
raise RuntimeError(f"cannot load profile module {path}")
mod = importlib.util.module_from_spec(spec)
mod.__dict__.update(ns)
spec.loader.exec_module(mod)
return mod
def load_profile(layout: Layout) -> dict[str, Any]:
def _eval_profile(path: Path, name: str, base: "Profile | None") -> Profile:
mod = _load_module(path, f"orchid_profile_{name}", {"Profile": Profile})
if not hasattr(mod, "profile"):
raise AttributeError(f"profile {name!r}: must define profile(base)")
spec = mod.profile(base)
if not isinstance(spec, Profile):
raise TypeError(f"profile {name!r}: profile(base) must return a Profile(...)")
if not isinstance(spec.options, Mapping) or any(
not isinstance(k, str) for k in spec.options
):
raise TypeError(f"profile {name!r}: options must be a dict with string keys")
return replace(spec, name=name)
def load_profile(layout: Layout) -> Profile:
link = layout.profile_link
if not link.exists():
raise FileNotFoundError(f"{link} missing a profile")
target = link.resolve()
if not target.is_file():
raise FileNotFoundError(f"profile {target.name}: missing config.py")
mod = _load_module(target, f"orchid_profile_{target.name}")
if not hasattr(mod, "profile"):
raise AttributeError(f"profile {target.name}: config.py must define profile()")
data = mod.profile()
if not isinstance(data, dict):
raise TypeError(f"profile {target.name}: profile() must return a dict")
missing = [k for k in REQUIRED_KEYS if k not in data]
raise FileNotFoundError(f"profile {target.name}: missing config")
# The base profile is optional, but supplies defaults when present.
base = Profile(name=BASE_PROFILE)
base_file = layout.profiles_dir / f"{BASE_PROFILE}.py"
if base_file.is_file():
base = _eval_profile(base_file, BASE_PROFILE, None)
child = _eval_profile(target, target.stem, base)
resolved = base.overlay(child)
missing = [f for f in CORE_FIELDS if not getattr(resolved, f)]
if missing:
raise ValueError(f"profile {target.name}: missing keys {missing}")
data["__name__"] = target.name
return data
raise ValueError(f"profile {resolved.name!r}: missing core fields {missing}")
return resolved
def init_build_dir(build: Path, repo: Path, profile_name: str) -> None:
if profile_name == BASE_PROFILE:
raise ValueError(
f"{BASE_PROFILE!r} only supplies defaults and cannot be built directly"
)
profile_src = repo / "profiles" / (profile_name + ".py")
if not (profile_src).is_file():
raise FileNotFoundError(f"profile {profile_name!r} not found at {profile_src}")
+20 -9
View File
@@ -8,6 +8,7 @@ from typing import Any, Callable
from src import source as src_mod
from src.layout import Layout
from src.profile import Profile
from src.source import Subpackage, Tarball, subpackage, tarball, git
PHASE_NAMES = ("prepare", "configure", "build", "install")
@@ -28,8 +29,8 @@ class Recipe:
maintainer: str
sources: dict[str | None, Tarball | src_mod.Git] # key=None for single source
host_deps: tuple[str, ...]
build_deps: tuple[str, ...]
deps: tuple[str, ...]
run_deps: tuple[str, ...]
subpackages: tuple[Subpackage, ...]
phases: dict[str, Callable[[Any], None]]
enabled: bool
@@ -79,7 +80,7 @@ def _lib_symbols() -> dict:
# Symbols which are injected into the recipe
def _builtins(profile: dict) -> dict:
def _builtins(profile: Profile) -> dict:
return {
**_lib_symbols(),
"tarball": tarball,
@@ -112,7 +113,12 @@ def _plain_field(mod, recipe_name: str, field_name: str) -> str:
def _load_one(
name: str, kind: str, recipe_file: Path, recipe_dir: Path, pure: bool, profile: dict
name: str,
kind: str,
recipe_file: Path,
recipe_dir: Path,
pure: bool,
profile: Profile,
) -> Recipe | None:
mod = _load_module(recipe_file, _builtins(profile))
@@ -152,8 +158,8 @@ def _load_one(
else:
for k, v in multi_items:
sources[k] = v
else:
raise ValueError(f"{name}: 'source' or 'sources' required")
# else: no source. Allowed for config-only recipes (e.g. base-files); the
# presence of at least one phase is validated below.
if pure:
for s in sources.values():
@@ -163,8 +169,8 @@ def _load_one(
)
host_deps = tuple(getattr(mod, "host_deps", ()) or ())
build_deps = tuple(getattr(mod, "build_deps", ()) or ())
deps = tuple(getattr(mod, "deps", ()) or ())
run_deps = tuple(getattr(mod, "run_deps", ()) or ())
subs = tuple(getattr(mod, "subpackages", ()) or ())
for s in subs:
if not isinstance(s, Subpackage):
@@ -185,6 +191,11 @@ def _load_one(
# Purely declarative pkgs are unusual but allowed
pass
if not sources and not phases:
raise ValueError(
f"{name}: define a 'source'/'sources' or at least one build phase"
)
enabled = bool(getattr(mod, "build_if", True))
return Recipe(
@@ -200,15 +211,15 @@ def _load_one(
maintainer=maintainer,
sources=sources,
host_deps=host_deps,
build_deps=build_deps,
deps=deps,
run_deps=run_deps,
subpackages=subs,
phases=phases,
enabled=enabled,
)
def _discover(root: Path, kind: str, profile: dict) -> dict[str, Recipe]:
def _discover(root: Path, kind: str, profile: Profile) -> dict[str, Recipe]:
out: dict[str, Recipe] = {}
if not root.is_dir():
return out
@@ -252,7 +263,7 @@ class RecipeSet:
return [*self.target.values(), *self.host.values()]
def load_recipes(layout: Layout, profile: dict) -> RecipeSet:
def load_recipes(layout: Layout, profile: Profile) -> RecipeSet:
target = _discover(layout.recipes_dir, "target", profile)
host = _discover(layout.host_recipes_dir, "host", profile)
return RecipeSet(target=target, host=host)