Files
lix/tests/functional2/testlib/nar.py
T
helle db56d236dd tests/functional2: fixes prerequisite to ruff upgrade to 0.11.10
Most of these are simple fixes and clarifications. One set of fixes will
come in the commit that actually upgrades nixpkgs and hence ruff as it
will otherwise cause errors here.

Change-Id: Ie857da0f6cf728478700ec2d24cf518f8c7b7815
2025-06-13 12:51:34 +02:00

141 lines
3.6 KiB
Python

"""
See "The Purely Functional Software Deployment Model", fig. 5.2 [1].
[1]: E. Dolstra, “The purely functional software deployment model,” Ph.D., Universeit Utrecht, Utrecht, NL, 2006. [Online]. Available: https://edolstra.github.io/pubs/phd-thesis.pdf
"""
from abc import ABCMeta, abstractmethod
import dataclasses
import struct
from typing import Protocol, ClassVar
class Writable(Protocol):
"""Realistically could just be IOBase but this is more constrained"""
def write(self, data: bytes, /) -> int: ...
@dataclasses.dataclass
class NarListener:
data: Writable
def literal(self, data: bytes):
self.data.write(data)
def int_(self, v: int):
self.literal(struct.pack("<Q", v))
def add_pad(self, data_len: int):
npad = 8 - data_len % 8
if npad == 8:
npad = 0
# FIXME(Jade): implement nonzero padding
self.literal(b"\0" * npad)
def str_(self, data: bytes):
self.int_(len(data))
self.literal(data)
self.add_pad(len(data))
class NarItem(metaclass=ABCMeta):
type_: bytes
def serialize(self, out: NarListener):
out.str_(b"type")
out.str_(self.type_)
self.serialize_type(out)
@abstractmethod
def serialize_type(self, out: NarListener):
pass
@dataclasses.dataclass
class Regular(NarItem):
executable: bool
contents: bytes
type_: ClassVar[bytes] = b"regular"
def serialize_type(self, out: NarListener):
if self.executable:
out.str_(b"executable")
out.str_(b"")
out.str_(b"contents")
out.str_(self.contents)
@dataclasses.dataclass
class DirectoryUnordered(NarItem):
entries: list[tuple[bytes, NarItem]]
"""Entries in the directory, not required to be in order because this nar is evil"""
type_: ClassVar[bytes] = b"directory"
@staticmethod
def entry(out: NarListener, name: bytes, item: "NarItem"):
# lol this format
out.str_(b"entry")
out.str_(b"(")
out.str_(b"name")
out.str_(name)
out.str_(b"node")
out.str_(b"(")
item.serialize(out)
out.str_(b")")
out.str_(b")")
def serialize_type(self, out: NarListener):
for name, entry in self.entries:
self.entry(out, name, entry)
@dataclasses.dataclass
class Directory(NarItem):
entries: dict[bytes, NarItem]
def serialize_type(self, out: NarListener):
for name, item in sorted(self.entries.items(), key=lambda v: v[0]):
DirectoryUnordered.entry(out, name, item)
@dataclasses.dataclass
class Symlink(NarItem):
target: bytes
type_: ClassVar[bytes] = b"symlink"
def serialize_type(self, out: NarListener):
out.str_(b"target")
out.str_(self.target)
def serialize_nar(toplevel: NarItem, out: NarListener):
out.str_(b"nix-archive-1")
out.str_(b"(")
toplevel.serialize(out)
out.str_(b")")
def write_with_export_header(nar: NarItem, name: bytes, out: NarListener):
# n.b. this is *not* actually a nar serialization, it just happens that nix
# used exactly the same format for ints and strings in its protocol (and
# nix-store --export) as it did in NARs lol
export_magic = 0x4558494E
# Store::exportPaths
# For each path, put 1 then exportPath
out.int_(1)
# Store::exportPath
serialize_nar(nar, out)
out.int_(export_magic)
out.str_(b"/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-" + name)
# no references
out.int_(0)
# no deriver
out.str_(b"")
# end of path
out.int_(0)
out.int_(0)