25cfa1
#!/usr/bin/env python3
25cfa1
#
25cfa1
# This script inspects a given json proving a list of addons, and
25cfa1
# creates an addon for each key/value pair matching the given uki, distro and
25cfa1
# arch provided in input.
25cfa1
#
25cfa1
# Usage: python uki_create_addons.py input_json out_dir uki distro arch
25cfa1
#
25cfa1
# This tool requires the systemd-ukify and systemd-boot packages.
25cfa1
#
25cfa1
# Addon file
25cfa1
#-----------
25cfa1
# Each addon terminates with .addon
25cfa1
# Each addon contains only two types of lines:
25cfa1
# Lines beginning with '#' are description and thus ignored
25cfa1
# All other lines are command line to be added.
25cfa1
# The name of the end resulting addon is taken from the json hierarchy.
25cfa1
# For example, and addon in json['virt']['rhel']['x86_64']['hello.addon'] will
25cfa1
# result in an UKI addon file generated in out_dir called
25cfa1
# hello-virt.rhel.x86_64.addon.efi
25cfa1
#
25cfa1
# The common key, present in any sub-dict in the provided json (except the leaf dict)
25cfa1
# is used as place for default addons when the same addon is not defined deep
25cfa1
# in the hierarchy. For example, if we define test.addon (text: 'test1\n') in
25cfa1
# json['common']['test.addon'] = ['test1\n'] and another test.addon (text: test2) in
25cfa1
# json['virt']['common']['test.addon'] = ['test2'], any other uki except virt
25cfa1
# will have a test.addon.efi with text "test1", and virt will have a
25cfa1
# test.addon.efi with "test2"
25cfa1
#
25cfa1
# sbat.conf
25cfa1
#----------
25cfa1
# This dict is containing the sbat string for *all* addons being created.
25cfa1
# This dict is optional, but when used has to be put in a sub-dict with
25cfa1
# { 'sbat' : { 'sbat.conf' : ['your text here'] }}
25cfa1
# It follows the same syntax as the addon files, meaning '#' is comment and
25cfa1
# the rest is taken as sbat string and feed to ukify.
25cfa1
25cfa1
import os
25cfa1
import sys
25cfa1
import json
25cfa1
import collections
25cfa1
import subprocess
25cfa1
25cfa1
25cfa1
UKIFY_PATH = '/usr/lib/systemd/ukify'
25cfa1
25cfa1
def usage(err):
25cfa1
    print(f'Usage: {os.path.basename(__file__)} input_json output_dir uki distro arch')
25cfa1
    print(f'Error:{err}')
25cfa1
    sys.exit(1)
25cfa1
25cfa1
def check_clean_arguments(input_json, out_dir):
25cfa1
    # Remove end '/'
25cfa1
    if out_dir[-1:] == '/':
25cfa1
        out_dir = out_dir[:-1]
25cfa1
    if not os.path.isfile(input_json):
25cfa1
        usage(f'input_json {input_json} is not a file, or does not exist!')
25cfa1
    if not os.path.isdir(out_dir):
25cfa1
        usage(f'out_dir_dir {out_dir} is not a dir, or does not exist!')
25cfa1
    return out_dir
25cfa1
25cfa1
UKICmdlineAddon = collections.namedtuple('UKICmdlineAddon', ['name', 'cmdline'])
25cfa1
uki_addons_list = []
25cfa1
uki_addons = {}
25cfa1
addon_sbat_string = None
25cfa1
25cfa1
def parse_lines(lines, rstrip=True):
25cfa1
    cmdline = ''
25cfa1
    for l in lines:
25cfa1
        l = l.lstrip()
25cfa1
        if not l:
25cfa1
            continue
25cfa1
        if l[0] == '#':
25cfa1
            continue
25cfa1
        # rstrip is used only for addons cmdline, not sbat.conf, as it replaces
25cfa1
        # return lines with spaces.
25cfa1
        if rstrip:
25cfa1
            l = l.rstrip() + ' '
25cfa1
        cmdline += l
25cfa1
    if cmdline == '':
25cfa1
        return ''
25cfa1
    return cmdline
25cfa1
25cfa1
def parse_all_addons(in_obj):
25cfa1
    global addon_sbat_string
25cfa1
25cfa1
    for el in in_obj.keys():
25cfa1
        # addon found: copy it in our global dict uki_addons
25cfa1
        if el.endswith('.addon'):
25cfa1
            uki_addons[el] = in_obj[el]
25cfa1
25cfa1
    if 'sbat' in in_obj and 'sbat.conf' in in_obj['sbat']:
25cfa1
        # sbat.conf found: override sbat with the most specific one found
25cfa1
        addon_sbat_string = parse_lines(in_obj['sbat']['sbat.conf'], rstrip=False)
25cfa1
25cfa1
def recursively_find_addons(in_obj, folder_list):
25cfa1
    # end of recursion, leaf directory. Search all addons here
25cfa1
    if len(folder_list) == 0:
25cfa1
        parse_all_addons(in_obj)
25cfa1
        return
25cfa1
25cfa1
    # first, check for common folder
25cfa1
    if 'common' in in_obj:
25cfa1
        parse_all_addons(in_obj['common'])
25cfa1
25cfa1
    # second, check if there is a match with the searched folder
25cfa1
    if folder_list[0] in in_obj:
25cfa1
        folder_next = in_obj[folder_list[0]]
25cfa1
        folder_list = folder_list[1:]
25cfa1
        recursively_find_addons(folder_next, folder_list)
25cfa1
25cfa1
def parse_in_json(in_json, uki_name, distro, arch):
25cfa1
    with open(in_json, 'r') as f:
25cfa1
        in_obj = json.load(f)
25cfa1
    recursively_find_addons(in_obj, [uki_name, distro, arch])
25cfa1
25cfa1
    for addon_name, cmdline in uki_addons.items():
25cfa1
        addon_name = addon_name.replace(".addon","")
25cfa1
        addon_full_name = f'{addon_name}-{uki_name}.{distro}.{arch}.addon.efi'
25cfa1
        cmdline = parse_lines(cmdline).rstrip()
25cfa1
        if cmdline:
25cfa1
            uki_addons_list.append(UKICmdlineAddon(addon_full_name, cmdline))
25cfa1
25cfa1
def create_addons(out_dir):
25cfa1
    for uki_addon in uki_addons_list:
25cfa1
        out_path = os.path.join(out_dir, uki_addon.name)
25cfa1
        cmd = [
25cfa1
            f'{UKIFY_PATH}', 'build',
25cfa1
            f'--cmdline="{uki_addon.cmdline}"',
25cfa1
            f'--output={out_path}']
25cfa1
        if addon_sbat_string:
25cfa1
            cmd.append('--sbat="' + addon_sbat_string.rstrip() +'"')
25cfa1
25cfa1
        subprocess.check_call(cmd, text=True)
25cfa1
25cfa1
if __name__ == "__main__":
25cfa1
    argc = len(sys.argv) - 1
25cfa1
    if argc != 5:
25cfa1
        usage('too few or too many parameters!')
25cfa1
25cfa1
    input_json = sys.argv[1]
25cfa1
    out_dir = sys.argv[2]
25cfa1
    uki_name = sys.argv[3]
25cfa1
    distro = sys.argv[4]
25cfa1
    arch = sys.argv[5]
25cfa1
25cfa1
    out_dir = check_clean_arguments(input_json, out_dir)
25cfa1
    parse_in_json(input_json, uki_name, distro, arch)
25cfa1
    create_addons(out_dir)
25cfa1
25cfa1