#! /usr/bin/python
#  GRUB  --  GRand Unified Bootloader
#  Copyright (C) 2010,2011,2012,2013  Free Software Foundation, Inc.
#
#  GRUB is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  GRUB is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.

from __future__ import print_function

__metaclass__ = type

from optparse import OptionParser
import re

#
# This is the python script used to generate Makefile.*.am
#

GRUB_PLATFORMS = [ "emu", "i386_pc", "i386_efi", "i386_qemu", "i386_coreboot",
                   "i386_multiboot", "i386_ieee1275", "x86_64_efi",
                   "i386_xen", "x86_64_xen", "i386_xen_pvh",
                   "mips_loongson", "sparc64_ieee1275",
                   "powerpc_ieee1275", "mips_arc", "ia64_efi",
                   "mips_qemu_mips", "arm_uboot", "arm_efi", "arm64_efi",
                   "arm_coreboot", "loongarch64_efi", "riscv32_efi", "riscv64_efi" ]

GROUPS = {}

GROUPS["common"]   = GRUB_PLATFORMS[:]

# Groups based on CPU
GROUPS["i386"]        = [ "i386_pc", "i386_efi", "i386_qemu", "i386_coreboot", "i386_multiboot", "i386_ieee1275" ]
GROUPS["x86_64"]      = [ "x86_64_efi" ]
GROUPS["x86"]         = GROUPS["i386"] + GROUPS["x86_64"]
GROUPS["mips"]        = [ "mips_loongson", "mips_qemu_mips", "mips_arc" ]
GROUPS["sparc64"]     = [ "sparc64_ieee1275" ]
GROUPS["powerpc"]     = [ "powerpc_ieee1275" ]
GROUPS["arm"]         = [ "arm_uboot", "arm_efi", "arm_coreboot" ]
GROUPS["arm64"]       = [ "arm64_efi" ]
GROUPS["loongarch64"] = [ "loongarch64_efi" ]
GROUPS["riscv32"]     = [ "riscv32_efi" ]
GROUPS["riscv64"]     = [ "riscv64_efi" ]

# Groups based on firmware
GROUPS["efi"]  = [ "i386_efi", "x86_64_efi", "ia64_efi", "arm_efi", "arm64_efi",
		   "loongarch64_efi", "riscv32_efi", "riscv64_efi" ]
GROUPS["ieee1275"]   = [ "i386_ieee1275", "sparc64_ieee1275", "powerpc_ieee1275" ]
GROUPS["uboot"] = [ "arm_uboot" ]
GROUPS["xen"]  = [ "i386_xen", "x86_64_xen" ]
GROUPS["coreboot"]  = [ "i386_coreboot", "arm_coreboot" ]

# emu is a special case so many core functionality isn't needed on this platform
GROUPS["noemu"]   = GRUB_PLATFORMS[:]; GROUPS["noemu"].remove("emu")

# Groups based on hardware features
GROUPS["cmos"] = GROUPS["x86"][:] + ["mips_loongson", "mips_qemu_mips",
                                     "sparc64_ieee1275", "powerpc_ieee1275"]
GROUPS["cmos"].remove("i386_efi"); GROUPS["cmos"].remove("x86_64_efi");
GROUPS["pci"]      = GROUPS["x86"] + ["mips_loongson"]
GROUPS["usb"]      = GROUPS["pci"] + ["arm_coreboot"]

# If gfxterm is main output console integrate it into kernel
GROUPS["videoinkernel"] = ["mips_loongson", "i386_coreboot", "arm_coreboot" ]
GROUPS["videomodules"]   = GRUB_PLATFORMS[:];
for i in GROUPS["videoinkernel"]: GROUPS["videomodules"].remove(i)

# Similar for terminfo
GROUPS["terminfoinkernel"] = [ "emu", "mips_loongson", "mips_arc", "mips_qemu_mips", "i386_xen_pvh" ] + GROUPS["xen"] + GROUPS["ieee1275"] + GROUPS["uboot"];
GROUPS["terminfomodule"]   = GRUB_PLATFORMS[:];
for i in GROUPS["terminfoinkernel"]: GROUPS["terminfomodule"].remove(i)

# Flattened Device Trees (FDT)
GROUPS["fdt"] = [ "arm64_efi", "arm_uboot", "arm_efi", "loongarch64_efi", "riscv32_efi", "riscv64_efi" ]

# Needs software helpers for division
# Must match GRUB_DIVISION_IN_SOFTWARE in misc.h
GROUPS["softdiv"] = GROUPS["arm"] + ["ia64_efi"] + GROUPS["riscv32"]
GROUPS["no_softdiv"]   = GRUB_PLATFORMS[:]
for i in GROUPS["softdiv"]: GROUPS["no_softdiv"].remove(i)

# Miscellaneous groups scheduled to disappear in future
GROUPS["i386_coreboot_multiboot_qemu"] = ["i386_coreboot", "i386_multiboot", "i386_qemu"]
GROUPS["nopc"] = GRUB_PLATFORMS[:]; GROUPS["nopc"].remove("i386_pc")

#
# Create platform => groups reverse map, where groups covering that
# platform are ordered by their sizes
#
RMAP = {}
for platform in GRUB_PLATFORMS:
    # initialize with platform itself as a group
    RMAP[platform] = [ platform ]

    for k in GROUPS.keys():
        v = GROUPS[k]
        # skip groups that don't cover this platform
        if platform not in v: continue

        bigger = []
        smaller = []
        # partition currently known groups based on their size
        for group in RMAP[platform]:
            if group in GRUB_PLATFORMS: smaller.append(group)
            elif len(GROUPS[group]) < len(v): smaller.append(group)
            else: bigger.append(group)
        # insert in the middle
        RMAP[platform] = smaller + [ k ] + bigger

#
# Input
#

# We support a subset of the AutoGen definitions file syntax.  Specifically,
# compound names are disallowed; some preprocessing directives are
# disallowed (though #if/#endif are allowed; note that, like AutoGen, #if
# skips everything to the next #endif regardless of the value of the
# conditional); and shell-generated strings, Scheme-generated strings, and
# here strings are disallowed.

class AutogenToken:
    (autogen, definitions, eof, var_name, other_name, string, number,
     semicolon, equals, comma, lbrace, rbrace, lbracket, rbracket) = range(14)

class AutogenState:
    (init, need_def, need_tpl, need_semi, need_name, have_name, need_value,
     need_idx, need_rbracket, indx_name, have_value, done) = range(12)

class AutogenParseError(Exception):
    def __init__(self, message, path, line):
        super(AutogenParseError, self).__init__(message)
        self.path = path
        self.line = line

    def __str__(self):
        return (
            super(AutogenParseError, self).__str__() +
            " at file %s line %d" % (self.path, self.line))

class AutogenDefinition(list):
    def __getitem__(self, key):
        try:
            return super(AutogenDefinition, self).__getitem__(key)
        except TypeError:
            for name, value in self:
                if name == key:
                    return value

    def __contains__(self, key):
        for name, value in self:
            if name == key:
                return True
        return False

    def get(self, key, default):
        for name, value in self:
            if name == key:
                return value
        else:
            return default

    def find_all(self, key):
        for name, value in self:
            if name == key:
                yield value

class AutogenParser:
    def __init__(self):
        self.definitions = AutogenDefinition()
        self.def_stack = [("", self.definitions)]
        self.curdef = None
        self.new_name = None
        self.cur_path = None
        self.cur_line = 0

    @staticmethod
    def is_unquotable_char(c):
        return (ord(c) in range(ord("!"), ord("~") + 1) and
                c not in "#,;<=>[\\]`{}?*'\"()")

    @staticmethod
    def is_value_name_char(c):
        return c in ":^-_" or c.isalnum()

    def error(self, message):
        raise AutogenParseError(message, self.cur_file, self.cur_line)

    def read_tokens(self, f):
        data = f.read()
        end = len(data)
        offset = 0
        while offset < end:
            while offset < end and data[offset].isspace():
                if data[offset] == "\n":
                    self.cur_line += 1
                offset += 1
            if offset >= end:
                break
            c = data[offset]
            if c == "#":
                offset += 1
                try:
                    end_directive = data.index("\n", offset)
                    directive = data[offset:end_directive]
                    offset = end_directive
                except ValueError:
                    directive = data[offset:]
                    offset = end
                name, value = directive.split(None, 1)
                if name == "if":
                    try:
                        end_if = data.index("\n#endif", offset)
                        new_offset = end_if + len("\n#endif")
                        self.cur_line += data[offset:new_offset].count("\n")
                        offset = new_offset
                    except ValueError:
                        self.error("#if without matching #endif")
                else:
                    self.error("Unhandled directive '#%s'" % name)
            elif c == "{":
                yield AutogenToken.lbrace, c
                offset += 1
            elif c == "=":
                yield AutogenToken.equals, c
                offset += 1
            elif c == "}":
                yield AutogenToken.rbrace, c
                offset += 1
            elif c == "[":
                yield AutogenToken.lbracket, c
                offset += 1
            elif c == "]":
                yield AutogenToken.rbracket, c
                offset += 1
            elif c == ";":
                yield AutogenToken.semicolon, c
                offset += 1
            elif c == ",":
                yield AutogenToken.comma, c
                offset += 1
            elif c in ("'", '"'):
                s = []
                while True:
                    offset += 1
                    if offset >= end:
                        self.error("EOF in quoted string")
                    if data[offset] == "\n":
                        self.cur_line += 1
                    if data[offset] == "\\":
                        offset += 1
                        if offset >= end:
                            self.error("EOF in quoted string")
                        if data[offset] == "\n":
                            self.cur_line += 1
                        # Proper escaping unimplemented; this can be filled
                        # out if needed.
                        s.append("\\")
                        s.append(data[offset])
                    elif data[offset] == c:
                        offset += 1
                        break
                    else:
                        s.append(data[offset])
                yield AutogenToken.string, "".join(s)
            elif c == "/":
                offset += 1
                if data[offset] == "*":
                    offset += 1
                    try:
                        end_comment = data.index("*/", offset)
                        new_offset = end_comment + len("*/")
                        self.cur_line += data[offset:new_offset].count("\n")
                        offset = new_offset
                    except ValueError:
                        self.error("/* without matching */")
                elif data[offset] == "/":
                    try:
                        offset = data.index("\n", offset)
                    except ValueError:
                        pass
            elif (c.isdigit() or
                  (c == "-" and offset < end - 1 and
                   data[offset + 1].isdigit())):
                end_number = offset + 1
                while end_number < end and data[end_number].isdigit():
                    end_number += 1
                yield AutogenToken.number, data[offset:end_number]
                offset = end_number
            elif self.is_unquotable_char(c):
                end_name = offset
                while (end_name < end and
                       self.is_value_name_char(data[end_name])):
                    end_name += 1
                if end_name < end and self.is_unquotable_char(data[end_name]):
                    while (end_name < end and
                           self.is_unquotable_char(data[end_name])):
                        end_name += 1
                    yield AutogenToken.other_name, data[offset:end_name]
                    offset = end_name
                else:
                    s = data[offset:end_name]
                    if s.lower() == "autogen":
                        yield AutogenToken.autogen, s
                    elif s.lower() == "definitions":
                        yield AutogenToken.definitions, s
                    else:
                        yield AutogenToken.var_name, s
                    offset = end_name
            else:
                self.error("Invalid input character '%s'" % c)
        yield AutogenToken.eof, None

    def do_need_name_end(self, token):
        if len(self.def_stack) > 1:
            self.error("Definition blocks were left open")

    def do_need_name_var_name(self, token):
        self.new_name = token

    def do_end_block(self, token):
        if len(self.def_stack) <= 1:
            self.error("Too many close braces")
        new_name, parent_def = self.def_stack.pop()
        parent_def.append((new_name, self.curdef))
        self.curdef = parent_def

    def do_empty_val(self, token):
        self.curdef.append((self.new_name, ""))

    def do_str_value(self, token):
        self.curdef.append((self.new_name, token))

    def do_start_block(self, token):
        self.def_stack.append((self.new_name, self.curdef))
        self.curdef = AutogenDefinition()

    def do_indexed_name(self, token):
        self.new_name = token

    def read_definitions_file(self, f):
        self.curdef = self.definitions
        self.cur_line = 0
        state = AutogenState.init

        # The following transition table was reduced from the Autogen
        # documentation:
        #   info -f autogen -n 'Full Syntax'
        transitions = {
            AutogenState.init: {
                AutogenToken.autogen: (AutogenState.need_def, None),
            },
            AutogenState.need_def: {
                AutogenToken.definitions: (AutogenState.need_tpl, None),
            },
            AutogenState.need_tpl: {
                AutogenToken.var_name: (AutogenState.need_semi, None),
                AutogenToken.other_name: (AutogenState.need_semi, None),
                AutogenToken.string: (AutogenState.need_semi, None),
            },
            AutogenState.need_semi: {
                AutogenToken.semicolon: (AutogenState.need_name, None),
            },
            AutogenState.need_name: {
                AutogenToken.autogen: (AutogenState.need_def, None),
                AutogenToken.eof: (AutogenState.done, self.do_need_name_end),
                AutogenToken.var_name: (
                    AutogenState.have_name, self.do_need_name_var_name),
                AutogenToken.rbrace: (
                    AutogenState.have_value, self.do_end_block),
            },
            AutogenState.have_name: {
                AutogenToken.semicolon: (
                    AutogenState.need_name, self.do_empty_val),
                AutogenToken.equals: (AutogenState.need_value, None),
                AutogenToken.lbracket: (AutogenState.need_idx, None),
            },
            AutogenState.need_value: {
                AutogenToken.var_name: (
                    AutogenState.have_value, self.do_str_value),
                AutogenToken.other_name: (
                    AutogenState.have_value, self.do_str_value),
                AutogenToken.string: (
                    AutogenState.have_value, self.do_str_value),
                AutogenToken.number: (
                    AutogenState.have_value, self.do_str_value),
                AutogenToken.lbrace: (
                    AutogenState.need_name, self.do_start_block),
            },
            AutogenState.need_idx: {
                AutogenToken.var_name: (
                    AutogenState.need_rbracket, self.do_indexed_name),
                AutogenToken.number: (
                    AutogenState.need_rbracket, self.do_indexed_name),
            },
            AutogenState.need_rbracket: {
                AutogenToken.rbracket: (AutogenState.indx_name, None),
            },
            AutogenState.indx_name: {
                AutogenToken.semicolon: (
                    AutogenState.need_name, self.do_empty_val),
                AutogenToken.equals: (AutogenState.need_value, None),
            },
            AutogenState.have_value: {
                AutogenToken.semicolon: (AutogenState.need_name, None),
                AutogenToken.comma: (AutogenState.need_value, None),
            },
        }

        for code, token in self.read_tokens(f):
            if code in transitions[state]:
                state, handler = transitions[state][code]
                if handler is not None:
                    handler(token)
            else:
                self.error(
                    "Parse error in state %s: unexpected token '%s'" % (
                        state, token))
            if state == AutogenState.done:
                break

    def read_definitions(self, path):
        self.cur_file = path
        with open(path) as f:
            self.read_definitions_file(f)

defparser = AutogenParser()

#
# Output
#

outputs = {}

def output(s, section=''):
    if s == "":
        return
    outputs.setdefault(section, [])
    outputs[section].append(s)

def write_output(section=''):
    for s in outputs.get(section, []):
        print(s, end='')

#
# Global variables
#

def gvar_add(var, value):
    output(var + " += " + value + "\n")

#
# Per PROGRAM/SCRIPT variables 
#

seen_vars = set()

def vars_init(defn, *var_list):
    name = defn['name']

    if name not in seen_target and name not in seen_vars:
        for var in var_list:
            output(var + "  = \n", section='decl')
        seen_vars.add(name)

def var_set(var, value):
    output(var + "  = " + value + "\n")

def var_add(var, value):
    output(var + " += " + value + "\n")

#
# Variable names and rules
#

canonical_name_re = re.compile(r'[^0-9A-Za-z@_]')
canonical_name_suffix = ""

def set_canonical_name_suffix(suffix):
    global canonical_name_suffix
    canonical_name_suffix = suffix

def cname(defn):
    return canonical_name_re.sub('_', defn['name'] + canonical_name_suffix)

def rule(target, source, cmd):
    if cmd[0] == "\n":
        output("\n" + target + ": " + source + cmd.replace("\n", "\n\t") + "\n")
    else:
        output("\n" + target + ": " + source + "\n\t" + cmd.replace("\n", "\n\t") + "\n")

#
# Handle keys with platform names as values, for example:
#
# kernel = {
#   nostrip = emu;
#   ...
# }
#
def platform_tagged(defn, platform, tag):
    for value in defn.find_all(tag):
        for group in RMAP[platform]:
            if value == group:
                return True
    return False

def if_platform_tagged(defn, platform, tag, snippet_if, snippet_else=None):
    if platform_tagged(defn, platform, tag):
        return snippet_if
    elif snippet_else is not None:
        return snippet_else

#
# Handle tagged values
#
# module = {
#   extra_dist = ...
#   extra_dist = ...
#   ...
# };
#
def foreach_value(defn, tag, closure):
    r = []
    for value in defn.find_all(tag):
        r.append(closure(value))
    return ''.join(r)

#
# Handle best matched values for a platform, for example:
#
# module = {
#   cflags = '-Wall';
#   emu_cflags = '-Wall -DGRUB_EMU=1';
#   ...
# }
#
def foreach_platform_specific_value(defn, platform, suffix, nonetag, closure):
    r = []
    for group in RMAP[platform]:
        values = list(defn.find_all(group + suffix))
        if values:
            for value in values:
                r.append(closure(value))
            break
    else:
        for value in defn.find_all(nonetag):
            r.append(closure(value))
    return ''.join(r)

#
# Handle values from sum of all groups for a platform, for example:
#
# module = {
#   common = kern/misc.c;
#   emu = kern/emu/misc.c;
#   ...
# }
#
def foreach_platform_value(defn, platform, suffix, closure):
    r = []
    for group in RMAP[platform]:
        for value in defn.find_all(group + suffix):
            r.append(closure(value))
    r.sort()
    return ''.join(r)

def platform_conditional(platform, closure):
    output("\nif COND_" + platform + "\n")
    closure(platform)
    output("endif\n")

#
# Handle guarding with platform-specific "enable" keys, for example:
#
#  module = {
#    name = pci;
#    noemu = bus/pci.c;
#    emu = bus/emu/pci.c;
#    emu = commands/lspci.c;
#
#    enable = emu;
#    enable = i386_pc;
#    enable = x86_efi;
#    enable = i386_ieee1275;
#    enable = i386_coreboot;
#  };
#
def foreach_enabled_platform(defn, closure):
    if 'enable' in defn:
        for platform in GRUB_PLATFORMS:
            if platform_tagged(defn, platform, "enable"):
               platform_conditional(platform, closure)
    else:
        for platform in GRUB_PLATFORMS:
            platform_conditional(platform, closure)

#
# Handle guarding with platform-specific automake conditionals, for example:
#
#  module = {
#    name = usb;
#    common = bus/usb/usb.c;
#    noemu = bus/usb/usbtrans.c;
#    noemu = bus/usb/usbhub.c;
#    enable = emu;
#    enable = i386;
#    enable = mips_loongson;
#    emu_condition = COND_GRUB_EMU_SDL;
#  };
#
def under_platform_specific_conditionals(defn, platform, closure):
    output(foreach_platform_specific_value(defn, platform, "_condition", "condition", lambda cond: "if " + cond + "\n"))
    closure(defn, platform)
    output(foreach_platform_specific_value(defn, platform, "_condition", "condition", lambda cond: "endif " + cond + "\n"))

def platform_specific_values(defn, platform, suffix, nonetag):
    return foreach_platform_specific_value(defn, platform, suffix, nonetag,
                                           lambda value: value + " ")

def platform_values(defn, platform, suffix):
    return foreach_platform_value(defn, platform, suffix, lambda value: value + " ")

def extra_dist(defn):
    return foreach_value(defn, "extra_dist", lambda value: value + " ")

def extra_dep(defn):
    return foreach_value(defn, "depends", lambda value: value + " ")

def platform_sources(defn, p): return platform_values(defn, p, "_head") + platform_values(defn, p, "")
def platform_nodist_sources(defn, p): return platform_values(defn, p, "_nodist")

def platform_startup(defn, p): return platform_specific_values(defn, p, "_startup", "startup")
def platform_ldadd(defn, p): return platform_specific_values(defn, p, "_ldadd", "ldadd")
def platform_dependencies(defn, p): return platform_specific_values(defn, p, "_dependencies", "dependencies")
def platform_cflags(defn, p): return platform_specific_values(defn, p, "_cflags", "cflags")
def platform_ldflags(defn, p): return platform_specific_values(defn, p, "_ldflags", "ldflags")
def platform_cppflags(defn, p): return platform_specific_values(defn, p, "_cppflags", "cppflags")
def platform_ccasflags(defn, p): return platform_specific_values(defn, p, "_ccasflags", "ccasflags")
def platform_stripflags(defn, p): return platform_specific_values(defn, p, "_stripflags", "stripflags")
def platform_objcopyflags(defn, p): return platform_specific_values(defn, p, "_objcopyflags", "objcopyflags")

#
# Emit snippet only the first time through for the current name.
#
seen_target = set()

def first_time(defn, snippet):
    if defn['name'] not in seen_target:
        return snippet
    return ''

def is_platform_independent(defn):
    if 'enable' in defn:
        return False
    for suffix in [ "", "_head", "_nodist" ]:
        template = platform_values(defn, GRUB_PLATFORMS[0], suffix)
        for platform in GRUB_PLATFORMS[1:]:
            if template != platform_values(defn, platform, suffix):
                return False

    for suffix in [ "startup", "ldadd", "dependencies", "cflags", "ldflags", "cppflags", "ccasflags", "stripflags", "objcopyflags", "condition" ]:
        template = platform_specific_values(defn, GRUB_PLATFORMS[0], "_" + suffix, suffix)
        for platform in GRUB_PLATFORMS[1:]:
            if template != platform_specific_values(defn, platform, "_" + suffix, suffix):
                return False
    for tag in [ "nostrip" ]:
        template = platform_tagged(defn, GRUB_PLATFORMS[0], tag)
        for platform in GRUB_PLATFORMS[1:]:
            if template != platform_tagged(defn, platform, tag):
                return False

    return True

def module(defn, platform):
    name = defn['name']
    set_canonical_name_suffix(".module")

    gvar_add("platform_PROGRAMS", name + ".module")
    gvar_add("MODULE_FILES", name + ".module$(EXEEXT)")

    var_set(cname(defn) + "_SOURCES", platform_sources(defn, platform) + " ## platform sources")
    var_set("nodist_" + cname(defn) + "_SOURCES", platform_nodist_sources(defn, platform) + " ## platform nodist sources")
    var_set(cname(defn) + "_LDADD", platform_ldadd(defn, platform))
    var_set(cname(defn) + "_CFLAGS", "$(AM_CFLAGS) $(CFLAGS_MODULE) " + platform_cflags(defn, platform))
    var_set(cname(defn) + "_LDFLAGS", "$(AM_LDFLAGS) $(LDFLAGS_MODULE) " + platform_ldflags(defn, platform))
    var_set(cname(defn) + "_CPPFLAGS", "$(AM_CPPFLAGS) $(CPPFLAGS_MODULE) " + platform_cppflags(defn, platform))
    var_set(cname(defn) + "_CCASFLAGS", "$(AM_CCASFLAGS) $(CCASFLAGS_MODULE) " + platform_ccasflags(defn, platform))
    var_set(cname(defn) + "_DEPENDENCIES", "$(TARGET_OBJ2ELF) " + platform_dependencies(defn, platform))

    gvar_add("dist_noinst_DATA", extra_dist(defn))
    gvar_add("BUILT_SOURCES", "$(nodist_" + cname(defn) + "_SOURCES)")
    gvar_add("CLEANFILES", "$(nodist_" + cname(defn) + "_SOURCES)")

    gvar_add("MOD_FILES", name + ".mod")
    gvar_add("MARKER_FILES", name + ".marker")
    gvar_add("CLEANFILES", name + ".marker")

    for dep in defn.find_all("depends"):
        gvar_add("EXTRA_DEPS", "depends " + name + " " + dep + ":")

    output("""
""" + name + """.marker: $(""" + cname(defn) + """_SOURCES) $(nodist_""" + cname(defn) + """_SOURCES)
	$(TARGET_CPP) -DGRUB_LST_GENERATOR $(CPPFLAGS_MARKER) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(""" + cname(defn) + """_CPPFLAGS) $(CPPFLAGS) $^ > $@.new || (rm -f $@; exit 1)
	grep 'MARKER' $@.new | grep -v '^#' > $@; rm -f $@.new
""")

def kernel(defn, platform):
    name = defn['name']
    set_canonical_name_suffix(".exec")
    gvar_add("platform_PROGRAMS", name + ".exec")
    var_set(cname(defn) + "_SOURCES", platform_startup(defn, platform))
    var_add(cname(defn) + "_SOURCES", platform_sources(defn, platform))
    var_set("nodist_" + cname(defn) + "_SOURCES", platform_nodist_sources(defn, platform) + " ## platform nodist sources")
    var_set(cname(defn) + "_LDADD", platform_ldadd(defn, platform))
    var_set(cname(defn) + "_CFLAGS", "$(AM_CFLAGS) $(CFLAGS_KERNEL) " + platform_cflags(defn, platform))
    var_set(cname(defn) + "_LDFLAGS", "$(AM_LDFLAGS) $(LDFLAGS_KERNEL) " + platform_ldflags(defn, platform))
    var_set(cname(defn) + "_CPPFLAGS", "$(AM_CPPFLAGS) $(CPPFLAGS_KERNEL) " + platform_cppflags(defn, platform))
    var_set(cname(defn) + "_CCASFLAGS", "$(AM_CCASFLAGS) $(CCASFLAGS_KERNEL) " + platform_ccasflags(defn, platform))
    var_set(cname(defn) + "_STRIPFLAGS", "$(AM_STRIPFLAGS) $(STRIPFLAGS_KERNEL) " + platform_stripflags(defn, platform))
    var_set(cname(defn) + "_DEPENDENCIES", "$(TARGET_OBJ2ELF)")

    gvar_add("dist_noinst_DATA", extra_dist(defn))
    gvar_add("BUILT_SOURCES", "$(nodist_" + cname(defn) + "_SOURCES)")
    gvar_add("CLEANFILES", "$(nodist_" + cname(defn) + "_SOURCES)")

    gvar_add("platform_DATA", name + ".img")
    gvar_add("CLEANFILES", name + ".img")
    rule(name + ".img", name + ".exec$(EXEEXT)",
         if_platform_tagged(defn, platform, "nostrip",
"""if test x$(TARGET_APPLE_LINKER) = x1; then \
     $(TARGET_OBJCONV) -f$(TARGET_MODULE_FORMAT) -nr:_grub_mod_init:grub_mod_init -nr:_grub_mod_fini:grub_mod_fini -ed2022 -wd1106 -nu -nd $< $@; \
   elif test ! -z '$(TARGET_OBJ2ELF)'; then \
     $(TARGET_OBJ2ELF) $< $@ || (rm -f $@; exit 1); \
   else cp $< $@; fi""",
"""if test x$(TARGET_APPLE_LINKER) = x1; then \
  $(TARGET_STRIP) -S -x $(""" + cname(defn) + """) -o $@.bin $<; \
  $(TARGET_OBJCONV) -f$(TARGET_MODULE_FORMAT) -nr:_grub_mod_init:grub_mod_init -nr:_grub_mod_fini:grub_mod_fini -ed2022 -ed2016 -wd1106 -nu -nd $@.bin $@; \
  rm -f $@.bin; \
   elif test ! -z '$(TARGET_OBJ2ELF)'; then \
     """  + "$(TARGET_STRIP) $(" + cname(defn) + "_STRIPFLAGS) -o $@.bin $< && \
     $(TARGET_OBJ2ELF) $@.bin $@ || (rm -f $@; rm -f $@.bin; exit 1); \
     rm -f $@.bin; \
else """  + "$(TARGET_STRIP) $(" + cname(defn) + "_STRIPFLAGS) -o $@ $<; \
fi"""))

def image(defn, platform):
    name = defn['name']
    set_canonical_name_suffix(".image")
    gvar_add("platform_PROGRAMS", name + ".image")
    var_set(cname(defn) + "_SOURCES", platform_sources(defn, platform))
    var_set("nodist_" + cname(defn) + "_SOURCES", platform_nodist_sources(defn, platform) + "## platform nodist sources")
    var_set(cname(defn) + "_LDADD", platform_ldadd(defn, platform))
    var_set(cname(defn) + "_CFLAGS", "$(AM_CFLAGS) $(CFLAGS_IMAGE) " + platform_cflags(defn, platform))
    var_set(cname(defn) + "_LDFLAGS", "$(AM_LDFLAGS) $(LDFLAGS_IMAGE) " + platform_ldflags(defn, platform))
    var_set(cname(defn) + "_CPPFLAGS", "$(AM_CPPFLAGS) $(CPPFLAGS_IMAGE) " + platform_cppflags(defn, platform))
    var_set(cname(defn) + "_CCASFLAGS", "$(AM_CCASFLAGS) $(CCASFLAGS_IMAGE) " + platform_ccasflags(defn, platform))
    var_set(cname(defn) + "_OBJCOPYFLAGS", "$(OBJCOPYFLAGS_IMAGE) " + platform_objcopyflags(defn, platform))
    # var_set(cname(defn) + "_DEPENDENCIES", platform_dependencies(defn, platform) + " " + platform_ldadd(defn, platform))

    gvar_add("dist_noinst_DATA", extra_dist(defn))
    gvar_add("BUILT_SOURCES", "$(nodist_" + cname(defn) + "_SOURCES)")
    gvar_add("CLEANFILES", "$(nodist_" + cname(defn) + "_SOURCES)")

    gvar_add("platform_DATA", name + ".img")
    gvar_add("CLEANFILES", name + ".img")
    rule(name + ".img", name + ".image$(EXEEXT)", """
if test x$(TARGET_APPLE_LINKER) = x1; then \
  $(MACHO2IMG) $< $@; \
else \
  $(TARGET_OBJCOPY) $(""" + cname(defn) + """_OBJCOPYFLAGS) --strip-unneeded -R .note -R .comment -R .note.gnu.build-id -R .MIPS.abiflags -R .reginfo -R .rel.dyn -R .note.gnu.gold-version -R .note.gnu.property -R .ARM.exidx -R .interp $< $@; \
fi
""")

def library(defn, platform):
    name = defn['name']
    set_canonical_name_suffix("")

    vars_init(defn,
              cname(defn) + "_SOURCES",
              "nodist_" + cname(defn) + "_SOURCES",
              cname(defn) + "_CFLAGS",
              cname(defn) + "_CPPFLAGS",
              cname(defn) + "_CCASFLAGS")
    #         cname(defn) + "_DEPENDENCIES")

    if name not in seen_target:
        gvar_add("noinst_LIBRARIES", name)
    var_add(cname(defn) + "_SOURCES", platform_sources(defn, platform))
    var_add("nodist_" + cname(defn) + "_SOURCES", platform_nodist_sources(defn, platform))
    var_add(cname(defn) + "_CFLAGS", first_time(defn, "$(AM_CFLAGS) $(CFLAGS_LIBRARY) ") + platform_cflags(defn, platform))
    var_add(cname(defn) + "_CPPFLAGS", first_time(defn, "$(AM_CPPFLAGS) $(CPPFLAGS_LIBRARY) ") + platform_cppflags(defn, platform))
    var_add(cname(defn) + "_CCASFLAGS", first_time(defn, "$(AM_CCASFLAGS) $(CCASFLAGS_LIBRARY) ") + platform_ccasflags(defn, platform))
    # var_add(cname(defn) + "_DEPENDENCIES", platform_dependencies(defn, platform) + " " + platform_ldadd(defn, platform))

    gvar_add("dist_noinst_DATA", extra_dist(defn))
    if name not in seen_target:
        gvar_add("BUILT_SOURCES", "$(nodist_" + cname(defn) + "_SOURCES)")
        gvar_add("CLEANFILES", "$(nodist_" + cname(defn) + "_SOURCES)")

def installdir(defn, default="bin"):
    return defn.get('installdir', default)

def manpage(defn, adddeps):
    name = defn['name']
    mansection = defn['mansection']

    output("if COND_MAN_PAGES\n")
    gvar_add("man_MANS", name + "." + mansection)
    rule(name + "." + mansection, name + " " + adddeps, """
chmod a+x """ + name + """
PATH=$(builddir):$$PATH pkgdatadir=$(builddir) $(HELP2MAN) --section=""" + mansection + """ -i $(top_srcdir)/docs/man/""" + name + """.h2m -o $@ """ + name + """
""")
    gvar_add("CLEANFILES", name + "." + mansection)
    output("endif\n")

def program(defn, platform, test=False):
    name = defn['name']
    set_canonical_name_suffix("")

    if 'testcase' in defn:
        gvar_add("check_PROGRAMS_" + defn['testcase'], name)
    else:
        var_add(installdir(defn) + "_PROGRAMS", name)
        if 'mansection' in defn:
            manpage(defn, "")

    var_set(cname(defn) + "_SOURCES", platform_sources(defn, platform))
    var_set("nodist_" + cname(defn) + "_SOURCES", platform_nodist_sources(defn, platform))
    var_set(cname(defn) + "_LDADD", platform_ldadd(defn, platform))
    var_set(cname(defn) + "_CFLAGS", "$(AM_CFLAGS) $(CFLAGS_PROGRAM) " + platform_cflags(defn, platform))
    var_set(cname(defn) + "_LDFLAGS", "$(AM_LDFLAGS) $(LDFLAGS_PROGRAM) " + platform_ldflags(defn, platform))
    var_set(cname(defn) + "_CPPFLAGS", "$(AM_CPPFLAGS) $(CPPFLAGS_PROGRAM) " + platform_cppflags(defn, platform))
    var_set(cname(defn) + "_CCASFLAGS", "$(AM_CCASFLAGS) $(CCASFLAGS_PROGRAM) " + platform_ccasflags(defn, platform))
    # var_set(cname(defn) + "_DEPENDENCIES", platform_dependencies(defn, platform) + " " + platform_ldadd(defn, platform))

    gvar_add("dist_noinst_DATA", extra_dist(defn))
    gvar_add("BUILT_SOURCES", "$(nodist_" + cname(defn) + "_SOURCES)")
    gvar_add("CLEANFILES", "$(nodist_" + cname(defn) + "_SOURCES)")

def data(defn, platform):
    var_add("dist_" + installdir(defn) + "_DATA", platform_sources(defn, platform))
    gvar_add("dist_noinst_DATA", extra_dist(defn))

def transform_data(defn, platform):
    name = defn['name']

    var_add(installdir(defn) + "_DATA", name)

    rule(name, "$(top_builddir)/config.status " + platform_sources(defn, platform) + platform_dependencies(defn, platform), """
(for x in """ + platform_sources(defn, platform) + """; do cat $(srcdir)/"$$x"; done) | $(top_builddir)/config.status --file=$@:-
chmod a+x """ + name + """
""")

    gvar_add("CLEANFILES", name)
    gvar_add("EXTRA_DIST", extra_dist(defn))
    gvar_add("dist_noinst_DATA", platform_sources(defn, platform))

def script(defn, platform):
    name = defn['name']

    if 'testcase' in defn:
        gvar_add("check_SCRIPTS_" + defn['testcase'], name)
    else:
        var_add(installdir(defn) + "_SCRIPTS", name)
        if 'mansection' in defn:
            manpage(defn, "grub-mkconfig_lib")

    rule(name, "$(top_builddir)/config.status " + platform_sources(defn, platform) + platform_dependencies(defn, platform), """
(for x in """ + platform_sources(defn, platform) + """; do cat $(srcdir)/"$$x"; done) | $(top_builddir)/config.status --file=$@:-
chmod a+x """ + name + """
""")

    gvar_add("CLEANFILES", name)
    gvar_add("EXTRA_DIST", extra_dist(defn))
    gvar_add("dist_noinst_DATA", platform_sources(defn, platform))

def rules(target, closure):
    seen_target.clear()
    seen_vars.clear()

    for defn in defparser.definitions.find_all(target):
        if is_platform_independent(defn):
            under_platform_specific_conditionals(defn, GRUB_PLATFORMS[0], closure)
        else:
            foreach_enabled_platform(
                defn,
                lambda p: under_platform_specific_conditionals(defn, p, closure))
        # Remember that we've seen this target.
        seen_target.add(defn['name'])

parser = OptionParser(usage="%prog DEFINITION-FILES")
_, args = parser.parse_args()

for arg in args:
    defparser.read_definitions(arg)

rules("module", module)
rules("kernel", kernel)
rules("image", image)
rules("library", library)
rules("program", program)
rules("script", script)
rules("data", data)
rules("transform_data", transform_data)

write_output(section='decl')
write_output()
