#!/usr/bin/env python3 """converts Guix package definitions into holy recipes, builds the mechanical one and refuses the definitions that name no origin.""" import hashlib import subprocess import sys import tempfile from pathlib import Path binary = str(Path(sys.argv[1]).resolve()) ALPHABET = "0123456789abcdfghijklmnopqrstuvwxyz" def base32(data): """the Guix base32 form of a digest, which omits the letters hex can confuse""" bits = 0 value = 0 out = [] for byte in data: value = (value << 8) | byte bits += 8 while bits >= 5: bits -= 5 out.append(ALPHABET[(value >> bits) & 31]) if bits: out.append(ALPHABET[(value << (5 - bits)) & 31]) return "".join(out) def call(*args, status=0): result = subprocess.run([binary, *map(str, args)], capture_output=True, text=True, errors="replace") assert result.returncode == status, (args, result.returncode, result.stdout, result.stderr) return result.stdout def convert(definition, out, status=0): return call("convert", definition, "--source", "guix", "--output", out, status=status) def source_tree(root, name, version): directory = root / f"{name}-{version}" directory.mkdir(parents=True) (directory / "configure").write_text( "#!/bin/sh\n" "# a hand written configure so the fixture needs no autotools\n" "prefix=/usr\n" "for argument in \"$@\"; do\n" " case $argument in\n" " --prefix=*) prefix=${argument#--prefix=} ;;\n" " esac\n" "done\n" "printf 'prefix=%s\\n' \"$prefix\" > config.prefix\n" "exit 0\n") (directory / "configure").chmod(0o755) # the configure script writes the prefix it was given, which is the build root, so # the install target reads it back instead of guessing a prefix of its own (directory / "Makefile").write_text( "CFLAGS ?= -O2 -pipe -static\n" "PREFIX = $(shell sed -n 's/^prefix=//p' config.prefix)\n" "all: bin/%(name)s\n" "bin/%(name)s: main.c\n" "\tmkdir -p bin\n" "\t$(CC) $(CFLAGS) -o $@ main.c\n" "install: all\n" "\tmkdir -p $(DESTDIR)$(PREFIX)/bin\n" "\tcp bin/%(name)s $(DESTDIR)$(PREFIX)/bin/\n" % {"name": name}) (directory / "main.c").write_text( '#include \nint main(void){printf("%s\\n");return 0;}\n' % name) return directory def main(): with tempfile.TemporaryDirectory() as scratch: root = Path(scratch) tree = source_tree(root, "guixfruit", "4.2.0") archive = root / "guixfruit-4.2.0.tar.gz" subprocess.run(["tar", "czf", str(archive), "-C", str(root), tree.name], check=True) digest = hashlib.sha256(archive.read_bytes()).hexdigest() encoded = base32(bytes.fromhex(digest)) # a definition whose fields are all literal converts natively, and the recipe it # produced builds a real package simple = root / "guixfruit.scm" simple.write_text( '(define-public guixfruit\n' ' (package\n' ' (name "guixfruit")\n' ' (version "4.2.0")\n' ' (source (origin\n' ' (method url-fetch)\n' ' (uri "%s")\n' ' (sha256 (base32 "%s"))))\n' ' (build-system gnu-build-system)\n' ' (native-inputs (list pkg-config))\n' ' (inputs (list zlib openssl))\n' ' (arguments (list #:configure-flags (list "--with-x=y")))\n' ' (synopsis "a fixture program for the Guix converter")\n' ' (home-page "https://example.org/guixfruit")\n' ' (license (list license:asl2.0))))\n' % (archive, encoded)) out = root / "simple" report = convert(simple, out) assert "converted guixfruit status native" in report, report recipe = (out / "guixfruit.recipe").read_text() assert 'name "guixfruit"\n' in recipe, recipe assert 'version "4.2.0"\n' in recipe, recipe assert 'x-guix-build-system "gnu-build-system"\n' in recipe, recipe assert 'source source "%s"\n' % archive in recipe, recipe # the base32 digest is decoded into the hex a Holy source records assert 'source-sha256 source "%s"\n' % digest in recipe, recipe assert 'build-depend "pkg-config" "any" "-"\n' in recipe, recipe assert 'depend "zlib" "any" "-"\n' in recipe, recipe assert 'depend "openssl" "any" "-"\n' in recipe, recipe assert 'output "guixfruit" runtime\n' in recipe, recipe # the gnu build system becomes the four tools it runs, in order assert "step prepare /bin/sh <