xref: /dpdk/devtools/check-meson.py (revision f88b0b892204d071a46a629b00424a8d47055ba4)
1#!/usr/bin/env python3
2# SPDX-License-Identifier: BSD-3-Clause
3# Copyright(c) 2021 Intel Corporation
4
5'''
6A Python script to run some checks on meson.build files in DPDK
7'''
8
9import sys
10import os
11import re
12from os.path import relpath, join
13from argparse import ArgumentParser
14
15VERBOSE = False
16
17
18def scan_dir(path):
19    '''return meson.build files found in path'''
20    for root, dirs, files in os.walk(path):
21        if 'meson.build' in files:
22            yield(relpath(join(root, 'meson.build')))
23
24
25def split_code_comments(line):
26    'splits a line into a code part and a comment part, returns (code, comment) tuple'
27    if line.lstrip().startswith('#'):
28        return ('', line)
29    elif '#' in line and '#include' not in line:  # catch 99% of cases, not 100%
30        idx = line.index('#')
31        while (line[idx - 1].isspace()):
32            idx -= 1
33        return line[:idx], line[idx:]
34    else:
35        return (line, '')
36
37
38def setline(contents, index, value):
39    'sets the contents[index] to value. Returns the line, along with code and comments part'
40    line = contents[index] = value
41    code, comments = split_code_comments(line)
42    return line, code, comments
43
44
45def check_indentation(filename, contents):
46    '''check that a list or files() is correctly indented'''
47    infiles = False
48    inlist = False
49    edit_count = 0
50    for lineno, line in enumerate(contents):
51        code, comments = split_code_comments(line)
52        if not code.strip():
53            continue
54        if re.match('^ *\t', code):
55            print(f'Error parsing {filename}:{lineno}, got some tabulation')
56        if code.endswith('files('):
57            if infiles:
58                raise(f'Error parsing {filename}:{lineno}, got "files(" when already parsing files list')
59            if inlist:
60                print(f'Error parsing {filename}:{lineno}, got "files(" when already parsing array list')
61            infiles = True
62            indent_count = len(code) - len(code.lstrip(' '))
63            indent = ' ' * (indent_count + 8)  # double indent required
64        elif code.endswith('= ['):
65            if infiles:
66                raise(f'Error parsing {filename}:{lineno}, got start of array when already parsing files list')
67            if inlist:
68                print(f'Error parsing {filename}:{lineno}, got start of array when already parsing array list')
69            inlist = True
70            indent_count = len(code) - len(code.lstrip(' '))
71            indent = ' ' * (indent_count + 8)  # double indent required
72        elif infiles and (code.endswith(')') or code.strip().startswith(')')):
73            infiles = False
74            continue
75        elif inlist and (code.endswith(']') or code.strip().startswith(']')):
76            inlist = False
77            continue
78        elif inlist or infiles:
79            # skip further subarrays or lists
80            if '[' in code or ']' in code:
81                continue
82            if not code.startswith(indent) or code[len(indent)] == ' ':
83                print(f'Error: Incorrect indent at {filename}:{lineno + 1}')
84                line, code, comments = setline(contents, lineno, indent + line.strip())
85                edit_count += 1
86            if not code.endswith(','):
87                print(f'Error: Missing trailing "," in list at {filename}:{lineno + 1}')
88                line, code, comments = setline(contents, lineno, code + ',' + comments)
89                edit_count += 1
90            if len(code.split(',')) > 2:  # only one comma per line
91                print(f'Error: multiple entries per line in list at {filename}:{lineno +1}')
92                entries = [e.strip() for e in code.split(',') if e.strip()]
93                line, code, comments = setline(contents, lineno,
94                                               indent + (',\n' + indent).join(entries) +
95                                               ',' + comments)
96                edit_count += 1
97    return edit_count
98
99
100def process_file(filename, fix):
101    '''run checks on file "filename"'''
102    if VERBOSE:
103        print(f'Processing {filename}')
104    with open(filename) as f:
105        contents = [ln.rstrip() for ln in f.readlines()]
106
107    if check_indentation(filename, contents) > 0 and fix:
108        print(f"Fixing {filename}")
109        with open(filename, 'w') as f:
110            f.writelines([f'{ln}\n' for ln in contents])
111
112
113def main():
114    '''parse arguments and then call other functions to do work'''
115    global VERBOSE
116    parser = ArgumentParser(description='Run syntax checks on DPDK meson.build files')
117    parser.add_argument('-d', metavar='directory', default='.', help='Directory to process')
118    parser.add_argument('--fix', action='store_true', help='Attempt to fix errors')
119    parser.add_argument('-v', action='store_true', help='Verbose output')
120    args = parser.parse_args()
121
122    VERBOSE = args.v
123    for f in scan_dir(args.d):
124        process_file(f, args.fix)
125
126
127if __name__ == "__main__":
128    main()
129