#!/usr/bin/env python

#------------------------------------------------------------------------------
# Description of the header clean process
#------------------------------------------------------------------------------
# Here is the list of actions performed by this script to clean the original
# kernel headers.
#
# 1. Optimize well-known macros (e.g. __KERNEL__, __KERNEL_STRICT_NAMES)
#
#     This pass gets rid of everything that is guarded by a well-known macro
#     definition. This means that a block like:
#
#        #ifdef __KERNEL__
#        ....
#        #endif
#
#     Will be totally omitted from the output. The optimizer is smart enough to
#     handle all complex C-preprocessor conditional expression appropriately.
#     This means that, for example:
#
#        #if defined(__KERNEL__) || defined(FOO)
#        ...
#        #endif
#
#     Will be transformed into:
#
#        #ifdef FOO
#        ...
#        #endif
#
#     See tools/defaults.py for the list of well-known macros used in this pass,
#     in case you need to update it in the future.
#
#     Note that this also removes any reference to a kernel-specific
#     configuration macro like CONFIG_FOO from the clean headers.
#
#
# 2. Remove variable and function declarations:
#
#   This pass scans non-directive text and only keeps things that look like a
#   typedef/struct/union/enum declaration. This allows us to get rid of any
#   variables or function declarations that should only be used within the
#   kernel anyway (and which normally *should* be guarded by an #ifdef
#   __KERNEL__ ...  #endif block, if the kernel writers were not so messy).
#
#   There are, however, a few exceptions: it is seldom useful to keep the
#   definition of some static inline functions performing very simple
#   operations. A good example is the optimized 32-bit byte-swap function
#   found in:
#
#     arch-arm/asm/byteorder.h
#
#   The list of exceptions is in tools/defaults.py in case you need to update
#   it in the future.
#
#   Note that we do *not* remove macro definitions, including these macro that
#   perform a call to one of these kernel-header functions, or even define other
#   functions. We consider it safe since userland applications have no business
#   using them anyway.
#
#
# 3. Add a standard disclaimer:
#
#   The message:
#
#   /* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
#
#   Is prepended to each generated header.
#------------------------------------------------------------------------------

import sys, cpp, kernel, glob, os, re, getopt
from defaults import *
from utils import *

def print_error(no_update, msg):
    if no_update:
        panic(msg)
    sys.stderr.write("warning: " + msg)


def cleanupFile(dst_dir, src_dir, rel_path, no_update = True):
    """reads an original header and perform the cleanup operation on it
       this functions returns the destination path and the clean header
       as a single string"""
    # check the header path
    full_path = os.path.join(src_dir, rel_path)

    if not os.path.exists(full_path):
        print_error(no_update, "file does not exist: '%s'\n" % full_path)
        return None, None

    if not os.path.isfile(full_path):
        print_error(no_update, "path is not a file: '%s'\n" % full_path)
        return None, None

    # convert into destination path, extracting architecture if needed
    # and the corresponding list of known static functions
    #
    arch = None
    statics = kernel_known_generic_statics
    m = re.match(r"asm-([\w\d_\+\.\-]+)(/.*)", rel_path)
    if m and m.group(1) != 'generic':
        dst_path = "arch-%s/asm/%s" % m.groups()
        arch = m.group(1)
        statics  = statics.union(kernel_known_statics.get(arch, set()))
    else:
        # process headers under the uapi directory
        # note the "asm" level has been explicitly added in the original
        # kernel header tree for architectural-dependent uapi headers
        m_uapi = re.match(r"(uapi)/([\w\d_\+\.\-]+)(/.*)", rel_path)
        if m_uapi:
            dst_path = rel_path
            m_uapi_arch = re.match(r"asm-([\w\d_\+\.\-]+)", m_uapi.group(2))
            if m_uapi_arch and m_uapi_arch.group(1) != 'generic':
                arch = m_uapi_arch.group(1)
                statics = statics.union(kernel_known_statics.get(arch, set()))
        # common headers (ie non-asm and non-uapi)
        else:
            dst_path = os.path.join("android", rel_path)

    dst_path = os.path.join(dst_dir, dst_path)

    # now, let's parse the file
    #
    parser = cpp.BlockParser()
    blocks = parser.parseFile(full_path)
    if not parser.parsed:
        print_error(no_update, "can't parse '%s'%" % full_path)
        return None, None

    macros = kernel_known_macros.copy()
    if arch and arch in kernel_default_arch_macros:
        macros.update(kernel_default_arch_macros[arch])

    if arch and arch in kernel_arch_token_replacements:
        blocks.replaceTokens(kernel_arch_token_replacements[arch])

    blocks.optimizeMacros(macros)
    blocks.optimizeIf01()
    blocks.removeVarsAndFuncs(statics)
    blocks.replaceTokens(kernel_token_replacements)
    blocks.removeMacroDefines(kernel_ignored_macros)

    out = StringOutput()
    out.write(kernel_disclaimer)
    blocks.writeWithWarning(out, kernel_warning, 4)
    return dst_path, out.get()


if __name__ == "__main__":

    def usage():
        print """\
    usage:  %s [options] <header_path>

        options:
            -v    enable verbose mode

            -u    enabled update mode
                this will try to update the corresponding 'clean header'
                if the content has changed. with this, you can pass more
                than one file on the command-line

            -k<path>  specify path of original kernel headers
            -d<path>  specify path of cleaned kernel headers

        <header_path> must be in a subdirectory of 'original'
    """ % os.path.basename(sys.argv[0])
        sys.exit(1)

    try:
        optlist, args = getopt.getopt(sys.argv[1:], 'uvk:d:')
    except:
        # unrecognized option
        sys.stderr.write("error: unrecognized option\n")
        usage()

    no_update = True
    dst_dir = get_kernel_dir()
    src_dir = get_kernel_headers_original_dir()
    for opt, arg in optlist:
        if opt == '-u':
            no_update = False
        elif opt == '-v':
            logging.basicConfig(level=logging.DEBUG)
        elif opt == '-k':
            src_dir = arg
        elif opt == '-d':
            dst_dir = arg

    if len(args) == 0:
        usage()

    if no_update:
        for path in args:
            dst_path, newdata = cleanupFile(dst_dir, src_dir, path)
            print newdata

        sys.exit(0)

    # now let's update our files.

    b = BatchFileUpdater()

    for path in args:
        dst_path, newdata = cleanupFile(dst_dir, src_dir, path, no_update)
        if not dst_path:
            continue

        b.readFile(dst_path)
        r = b.editFile(dst_path, newdata)
        if r == 0:
            r = "unchanged"
        elif r == 1:
            r = "edited"
        else:
            r = "added"

        print "cleaning: %-*s -> %-*s (%s)" % (35, path, 35, dst_path, r)


    b.updateGitFiles()

    sys.exit(0)
