#!/usr/bin/env python3 # SPDX-License-Identifier: BSD-3-Clause # Copyright(c) 2021 Intel Corporation ''' A Python script to run some checks on meson.build files in DPDK ''' import sys import os import re from os.path import relpath, join from argparse import ArgumentParser VERBOSE = False def scan_dir(path): '''return meson.build files found in path''' for root, dirs, files in os.walk(path): if 'meson.build' in files: yield(relpath(join(root, 'meson.build'))) def split_code_comments(line): 'splits a line into a code part and a comment part, returns (code, comment) tuple' if line.lstrip().startswith('#'): return ('', line) elif '#' in line and '#include' not in line: # catch 99% of cases, not 100% idx = line.index('#') while (line[idx - 1].isspace()): idx -= 1 return line[:idx], line[idx:] else: return (line, '') def setline(contents, index, value): 'sets the contents[index] to value. Returns the line, along with code and comments part' line = contents[index] = value code, comments = split_code_comments(line) return line, code, comments def check_indentation(filename, contents): '''check that a list or files() is correctly indented''' infiles = False inlist = False edit_count = 0 for lineno, line in enumerate(contents): code, comments = split_code_comments(line) if not code.strip(): continue if re.match('^ *\t', code): print(f'Error parsing {filename}:{lineno}, got some tabulation') if code.endswith('files('): if infiles: raise(f'Error parsing {filename}:{lineno}, got "files(" when already parsing files list') if inlist: print(f'Error parsing {filename}:{lineno}, got "files(" when already parsing array list') infiles = True indent_count = len(code) - len(code.lstrip(' ')) indent = ' ' * (indent_count + 8) # double indent required elif code.endswith('= ['): if infiles: raise(f'Error parsing {filename}:{lineno}, got start of array when already parsing files list') if inlist: print(f'Error parsing {filename}:{lineno}, got start of array when already parsing array list') inlist = True indent_count = len(code) - len(code.lstrip(' ')) indent = ' ' * (indent_count + 8) # double indent required elif infiles and (code.endswith(')') or code.strip().startswith(')')): infiles = False continue elif inlist and (code.endswith(']') or code.strip().startswith(']')): inlist = False continue elif inlist or infiles: # skip further subarrays or lists if '[' in code or ']' in code: continue if not code.startswith(indent) or code[len(indent)] == ' ': print(f'Error: Incorrect indent at {filename}:{lineno + 1}') line, code, comments = setline(contents, lineno, indent + line.strip()) edit_count += 1 if not code.endswith(','): print(f'Error: Missing trailing "," in list at {filename}:{lineno + 1}') line, code, comments = setline(contents, lineno, code + ',' + comments) edit_count += 1 if len(code.split(',')) > 2: # only one comma per line print(f'Error: multiple entries per line in list at {filename}:{lineno +1}') entries = [e.strip() for e in code.split(',') if e.strip()] line, code, comments = setline(contents, lineno, indent + (',\n' + indent).join(entries) + ',' + comments) edit_count += 1 return edit_count def process_file(filename, fix): '''run checks on file "filename"''' if VERBOSE: print(f'Processing {filename}') with open(filename) as f: contents = [ln.rstrip() for ln in f.readlines()] if check_indentation(filename, contents) > 0 and fix: print(f"Fixing {filename}") with open(filename, 'w') as f: f.writelines([f'{ln}\n' for ln in contents]) def main(): '''parse arguments and then call other functions to do work''' global VERBOSE parser = ArgumentParser(description='Run syntax checks on DPDK meson.build files') parser.add_argument('-d', metavar='directory', default='.', help='Directory to process') parser.add_argument('--fix', action='store_true', help='Attempt to fix errors') parser.add_argument('-v', action='store_true', help='Verbose output') args = parser.parse_args() VERBOSE = args.v for f in scan_dir(args.d): process_file(f, args.fix) if __name__ == "__main__": main()