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