aocpu: add coding style check. [1/1]

PD#SWPL-36828

Problem:
add coding style check when commit.

Solution:
add detection relus to the .git/hooks/pre-commit file.

Verify:
build pass.

Change-Id: I88776ac691a69437812ff075ae286b6d8f9d3d5d
Signed-off-by: Jianxiong Pan <jianxiong.pan@amlogic.com>
diff --git a/scripts/amlogic/coding_style/checkpatch.py b/scripts/amlogic/coding_style/checkpatch.py
new file mode 100644
index 0000000..15e94ab
--- /dev/null
+++ b/scripts/amlogic/coding_style/checkpatch.py
@@ -0,0 +1,346 @@
+#!/usr/bin/env python2
+
+import json
+import logging
+import os.path
+import re
+import pprint
+import sys
+
+__author__ = 'lawrence'
+
+MAX_TRAILING_SPACES_MSGS_PER_FILE = 1000
+MAX_MIXED_TABS_MSGS_PER_FILE = 1000
+MAX_SPACING_MSGS_PER_FILE = 1000
+MAX_INDENT_MSGS_PER_FILE = 1000
+
+INDENT_UNKNOWN = 0
+INDENT_SPACES = 1
+INDENT_TABS = 2
+
+class ChangedFile:
+    SOURCE_EXT = ['.c', '.cpp', '.cc', '.h', '.java', '.mk', '.xml']
+    C_JAVA_EXT = ['.c', '.cpp', '.java']
+    TEXT_RESOURCE_EXT = ['.rc', '.prop', '.te', '.kl', '.cfg', '.conf', '.dtd']
+    BINARY_RESOURCE_EXT = ['.txt', '.so', '.ko', '.apk', '.png', '.jpg', '.jpeg', '.gif']
+
+    def __init__(self, filename=None, is_new=False, mode=None):
+        self.filename = filename
+        self.file_ext = None
+        if filename:
+            self.on_update_filename()
+        self.is_new = is_new
+        self.mode = mode
+        self.formattable_carriage_returns = False
+        self.comments = {}
+
+    def on_update_filename(self):
+        if not self.filename:
+            logging.error("couldn't get filename")
+            return
+        self.file_ext = os.path.splitext(self.filename)[1].lower()
+
+    def is_source(self):
+        #if self.file_ext in self.SOURCE_EXT:
+        #    return True
+        return True # return true directly, doesn't check file type
+        if self.filename:
+            b = os.path.basename(self.filename)
+            if (b and (
+                    b.startswith("Kconfig") or
+                    b == "Makefile")):
+                return True
+        return False
+
+    def is_binary_resource(self):
+        if self.file_ext in self.BINARY_RESOURCE_EXT:
+            return True
+        return False
+
+    def is_text_resource(self):
+        if self.file_ext in self.TEXT_RESOURCE_EXT:
+            return True
+        return False
+
+    def has_errors(self):
+        if self.comments:
+            return True
+        # same as add_file_comments:
+        if self.mode == 755 and self.should_not_be_executable():
+            return True
+        if self.formattable_carriage_returns and self.should_not_have_carriage_return():
+            return True
+        return False
+
+    def should_check_line_diff(self):
+        if self.is_source() or self.is_text_resource():
+            return True
+        return False
+
+    def should_not_be_executable(self):
+        return self.is_source() or self.is_text_resource() or self.is_binary_resource()
+
+    def should_not_have_carriage_return(self):
+        if self.is_new:
+            if self.is_source() or self.is_text_resource():
+                return True
+        return False
+
+    def should_check_statement_spacing(self):
+        if self.file_ext in self.C_JAVA_EXT:
+            return True
+        return False
+
+    def should_check_indent(self):
+        if self.file_ext in self.C_JAVA_EXT:
+            return True
+        return False
+
+    def add_file_comments(self):
+        if self.mode == 755 and self.should_not_be_executable():
+            self.append_comment(0, "{} file should not be executable".format(self.file_ext))
+        if self.formattable_carriage_returns and self.should_not_have_carriage_return():
+            self.append_comment(0, "{} file should not have carriage returns (DOS line endings)".format(self.file_ext))
+
+    def append_comment(self, line, msg):
+        if line in self.comments:
+            self.comments[line] += "\n\n"
+            self.comments[line] += msg
+        else:
+            self.comments[line] = msg
+
+
+    # types of files/checks
+    # source/resource:
+    #       should be non-executable            (new/changed source + .ko, etc)
+    #   source:
+    #       should not have carriage return     (new source + text resources)
+    #   text resource:
+    #       should not have trailing spaces     (source + text resources)
+    #       should not have mixed spaces/tabs   (source + text resources)
+    #   source + syntax
+    #       should have space in if statements  (source c/java)
+    #       added line indent should match context
+    # *could be imported code - warn only..?
+
+
+def check(filename):
+    """
+    Checks unified diff.
+    :param filename: diff file to check
+    :return: 0 on patch errors, 1 on no patch errors, < 0 on other errors
+    """
+    if not filename:
+        return -1
+
+    try:
+        with open(filename) as fp:
+            return check_fp(fp)
+    except OSError:
+        logging.error(" failed to open? OSError %s", filename)
+        return -2
+    except IOError:
+        logging.error(" failed to open? IOError %s", filename)
+        return -3
+    return -4
+
+
+# TODO split checks into separate functions
+def check_fp(fp):
+    file_sections = []
+    f = None
+    check_lines = False
+    check_statement_spacing = False
+    trailing_sp_msg_count = 0
+    mixed_tabs_msg_count = 0
+    spacing_msg_count = 0
+    in_line_diff = False
+    section_line_start = 0
+    section_line_start_err = False
+    cur_line = 0
+    for line in fp:
+        if line.startswith("diff"):
+            if f and f.has_errors():
+                f.add_file_comments()
+                file_sections.append(f)
+            # start of new file
+            f = ChangedFile()
+            check_lines = False
+            trailing_sp_msg_count = 0
+            mixed_tabs_msg_count = 0
+            spacing_msg_count = 0
+            indent_msg_count = 0
+            context_indent = INDENT_UNKNOWN
+            in_line_diff = False
+
+            # get filename
+            # might fail on paths like "dir b/file.txt"
+            m = re.match(r"^diff --git a/(.*) b/.*", line)
+            if m:
+                f.filename = m.group(1)
+                f.on_update_filename()
+                check_lines = f.should_check_line_diff()
+                check_statement_spacing = f.should_check_statement_spacing()
+                check_indent = f.should_check_indent()
+        elif line.startswith("new file mode "):
+            f.is_new = True
+            if line.startswith("100755", len("new file mode ")):
+                f.mode = 755
+        elif line.startswith("new mode 100755"):
+            f.mode = 755
+        elif f and not f.filename and line.startswith("+++ b/"):
+            # get filename if previously failed for some reason
+            f.filename = line[len("+++ b/"):].rstrip('\r\n ')
+            f.on_update_filename()
+            check_lines = f.should_check_line_diff()
+            check_statement_spacing = f.should_check_statement_spacing()
+            check_indent = f.should_check_indent()
+        else:
+            if not check_lines:
+                continue
+            if line.startswith("@@ "):
+                # keep track of line numbers
+                # @@ -584,7 +681,7 @@
+                m = re.match(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)?\ @@", line)
+                try:
+                    section_line_start = int(m.group(1))
+                except ValueError:
+                    logging.error("failed to parse section line start")
+                    section_line_start_err = True
+                in_line_diff = True
+                cur_line = section_line_start - 1  # next line is the start
+                continue
+            if in_line_diff:
+                # keep track of line numbers
+                if line[0] in ' +':
+                    cur_line += 1
+                # get last context line's indent
+                if line[0] == " ":
+                    if line.startswith("    ", 1):
+                        context_indent = INDENT_SPACES
+                    elif line.startswith("\t", 1):
+                        context_indent = INDENT_TABS
+            if line[0] == '+' and line[1] != '+':
+                if check_lines and not section_line_start_err:
+                    if (f.is_new and
+                            not f.formattable_carriage_returns and
+                                line[-2] == '\r'):
+                        f.formattable_carriage_returns = True
+
+                    if trailing_sp_msg_count < MAX_TRAILING_SPACES_MSGS_PER_FILE:
+                        if (line.endswith(" \n") or
+                                line.endswith(" \r\n") or
+                                line.endswith("\t\n") or
+                                line.endswith("\t\r\n")):
+                            f.append_comment(cur_line, "trailing spaces")
+                            trailing_sp_msg_count += 1
+
+                    if mixed_tabs_msg_count < MAX_MIXED_TABS_MSGS_PER_FILE:
+                        if re.match(r" +\t", line[1:]) or re.match(r"\t+ +\t", line[1:]):
+                            # tab space can be correct, but not space tab and tab space tab
+                            f.append_comment(cur_line, "possibly incorrect mixed spaces then tabs indentation")
+                            mixed_tabs_msg_count += 1
+
+                    if check_statement_spacing and spacing_msg_count < MAX_SPACING_MSGS_PER_FILE:
+                        m = re.match(r"\s*(if|while|for|switch)", line[1:])
+                        if (m):
+                            # line starts with if|while|for|switch
+                            keyword = m.group(1)
+                            # check parenthesis/brace spacing. if( -> if (    or    ){ -> ) {
+                            m = re.match(r"\s*(?:if|while|for|switch)( ?)\(.*\)( ?)(\{?)", line[1:])
+                            if (m):
+                                keyword_sp, brace_space, brace = m.groups()
+                                if keyword_sp != ' ' or (
+                                                brace == '{' and brace_space != ' '):
+                                    f.append_comment(cur_line,
+                                                     "%s (...) %s  // spacing around parenthesis" % (keyword, brace))
+                                    spacing_msg_count += 1
+
+                            # check binary operator spacing on if|while line
+                            # cpplint.py: match = Search(r'[^<>=!\s](==|!=|<=|>=|\|\|)[^<>=!\s,;\)]', line
+                            if keyword in ['if', 'while']:
+                                m = re.search(r"[^<>=!\s](==|!=|<=|>=|\|\||&&)[^<>=!\s,;\)]", line[1:])
+                                if (m):
+                                    f.append_comment(cur_line, "spacing around %s" % m.group(1))
+                                    spacing_msg_count += 1
+                            continue
+                        # do{ -> do {
+                        elif re.match(r"\s*do\{", line[1:]):
+                            f.append_comment(cur_line, 'do {')
+                            spacing_msg_count += 1
+
+                    if check_indent and indent_msg_count < MAX_INDENT_MSGS_PER_FILE:
+                        if ((context_indent == INDENT_SPACES and line.startswith("\t", 1)) or
+                                (context_indent == INDENT_TABS and line.startswith("    ", 1))):
+                            f.append_comment(cur_line, "make sure indent style matches rest of file")
+                            indent_msg_count += 1
+
+    if f and f.has_errors():
+        f.add_file_comments()
+        file_sections.append(f)
+
+    if False:
+        for f in file_sections:
+            assert isinstance(f, ChangedFile)
+            if f.comments:
+                print f.filename
+                pprint.pprint(f.comments)
+                print "---"
+    json_ret = file_comments_to_review(file_sections)
+    if json_ret:
+        print json_ret
+        return 0
+    else:
+        return 1
+
+REPLY_MSG = "This is an automated message.\n\nIf you think these comments are incorrect, they can be ignored."
+POSITIVE_REPLY_MSG = "This is an automated message.\n\nNo problems found."
+
+def file_comments_to_array(changed_file):
+    """
+    Return a list of comments for a CommentInput entry from a ChangedFile
+    :param changed_file: a ChangedFile object
+    :return: a list of comments for CommentInput
+    """
+    ret = []
+    assert isinstance(changed_file, ChangedFile)
+    for line, msg in changed_file.comments.iteritems():
+        ret.append({"line": line,
+                    "message": msg})
+    return ret
+
+def file_comments_to_review(changed_files):
+    """
+    Create a JSON ReviewInput from a list of ChangedFiles
+    :param changed_files: list of ChangedFiles
+    :return: JSON ReviewInput string
+    """
+    review = {}
+    review['comments'] = {}
+    for f in changed_files:
+        if f.filename and f.comments:
+
+            c = file_comments_to_array(f)
+            if not c:
+                logging.error("no comments for file")
+            review['comments'][f.filename] = c
+    if review['comments']:
+        review['message'] = REPLY_MSG
+        review['labels'] = {'Verified': -1}
+        review['notify'] = 'OWNER'
+    else:
+        del review['comments']
+        review['message'] = POSITIVE_REPLY_MSG
+        review['labels'] = {'Verified': +1}
+        review['notify'] = 'OWNER'
+    #return json.dumps(review, indent=2)
+    return json.dumps(review)
+
+if __name__ == '__main__':
+    if len(sys.argv) == 2:
+        r = check(sys.argv[1])
+        sys.exit(r)
+    else:
+        sys.stderr.write("%s <patch filename>\n" % sys.argv[0])
+    sys.exit(0)
+