1#!/usr/bin/env python3
2#
3# Arm SCP/MCP Software
4# Copyright (c) 2015-2021, Arm Limited and Contributors. All rights reserved.
5#
6# SPDX-License-Identifier: BSD-3-Clause
7#
8
9"""
10Check whether the files adhere to the prescribed coding style. Validation
11is performed by checkpatch.pl which is found on the environment path or
12via user supplied path.
13"""
14
15import argparse
16import os
17import fnmatch
18import sys
19import subprocess
20
21#
22# Checkpatch.pl location (assume it is available through the environment path
23# by default)
24#
25script_path = 'checkpatch.pl'
26
27#
28# Directories to scan. Only used when --input-mode is set to "project".
29#
30DIRECTORIES = [
31    'arch',
32    'framework',
33    'module',
34    'product',
35    'tools',
36]
37
38#
39# Supported file types. Only used when --input-mode is set to "project".
40#
41FILE_TYPES = [
42    '*.c',
43    '*.h',
44]
45
46#
47# Default ignored types. These are rules within checkpatch that conflict with
48# the SCP/MCP Software coding style and so they should never be enabled.
49#
50IGNORED_TYPES = [
51    'LEADING_SPACE',  # Incompatible with spaces for indentation
52    'CODE_INDENT',  # Incompatible with spaces for indentation
53    'SUSPECT_CODE_INDENT',  # Incompatible with spaces for indentation
54    'POINTER_LOCATION',  # Doesn't agree with our function declaration style
55    'BLOCK_COMMENT_STYLE',  # Doesn't tolerate asterisks on each block line
56    'AVOID_EXTERNS',  # We use the extern keyword
57    'NEW_TYPEDEFS',  # We add new typedefs
58    'VOLATILE',  # We use volatile
59    'MACRO_WITH_FLOW_CONTROL',  # Some 'capture' macros use do/while loops
60    'LINE_SPACING',  # We don't require a blank line after declarations
61    'SPLIT_STRING',  # We allow strings to be split across lines
62    'FILE_PATH_CHANGES',  # Specific to the kernel development process
63    'PREFER_PACKED',  # __packed is not available in Arm Compiler 6
64]
65
66error_count = 0
67
68
69def is_valid_file_type(filename):
70    return any([fnmatch.fnmatch(filename, t) for t in FILE_TYPES])
71
72
73def check_file(checkpatch_params, filename):
74    global error_count
75
76    cmd = '{} {}'.format(checkpatch_params, filename)
77
78    try:
79        subprocess.check_call(cmd, shell=True, stdin=0)
80    except subprocess.CalledProcessError:
81        error_count += 1
82
83
84def main(argv=[], prog_name=''):
85    global script_path
86    print('Arm SCP/MCP Software Checkpatch Wrapper')
87    parser = argparse.ArgumentParser(prog=prog_name)
88
89    input_mode_list = ['stdin', 'project']
90
91    # Optional parameters
92    parser.add_argument('-s', '--spacing', action='store_true',
93                        help='Check for correct use of spaces',
94                        required=False)
95
96    parser.add_argument('-l', '--line-length', action='store_true',
97                        dest='length',
98                        help='Check for lines longer than 80 characters',
99                        required=False)
100
101    parser.add_argument('-i', '--initializers', action='store_true',
102                        help='Check for redundant variable initialization',
103                        required=False)
104
105    parser.add_argument('-m', '--input-mode', choices=input_mode_list,
106                        help='Input mode for the content to be checked. '
107                             'Default: %(default)s',
108                        required=False, default=input_mode_list[0])
109
110    parser.add_argument('-p', '--path', action='store', dest='path',
111                        help='Path to checkpatch.pl file. If not specified, '
112                             'the script will be found on the environment '
113                             'path.',
114                        required=False)
115
116    args = parser.parse_args(argv)
117
118    # Override path to checkpatch.pl if necessary
119    if args.path:
120        script_path = args.path
121
122    # Print the path to checkpatch.pl as confirmation
123    print('checkpatch.pl path:', script_path, '\n')
124
125    # Enable optional tests
126    if not args.spacing:
127        IGNORED_TYPES.extend(['SPACING', 'MISSING_SPACE', 'BRACKET_SPACE'])
128
129    if not args.length:
130        IGNORED_TYPES.extend(['LONG_LINE', 'LONG_LINE_COMMENT',
131                              'LONG_LINE_STRING'])
132    if not args.initializers:
133        IGNORED_TYPES.extend(['GLOBAL_INITIALISERS', 'INITIALISED_STATIC'])
134
135    ignore_list = '--ignore ' + (','.join(map(str, IGNORED_TYPES)))
136
137    checkpatch_params = '{} --show-types --no-tree --no-summary {}'.format(
138        script_path,
139        ignore_list,
140    )
141
142    if args.input_mode == 'project':
143        print("Checking the coding style of the whole project...")
144        checkpatch_params += ' --terse --file'
145        for directory in DIRECTORIES:
146            for root, dirs, files in os.walk(directory):
147                for file in files:
148                    filename = os.path.join(root, file)
149                    if is_valid_file_type(file):
150                        check_file(checkpatch_params, filename)
151        if error_count > 0:
152            print('{} files contained coding style errors.'.
153                  format(error_count))
154
155    elif args.input_mode == 'stdin':
156        print("Checking content via standard input...")
157        check_file(checkpatch_params, '-')
158
159    else:
160        print('FAILED: Invalid input mode')
161        return 1
162
163    if error_count > 0:
164        print('FAILED: One or more files contained coding style errors.')
165        return 1
166
167    print('PASSED: No files contained coding style errors.')
168    return 0
169
170
171if __name__ == '__main__':
172    sys.exit(main(sys.argv[1:], sys.argv[0]))
173