diff options
Diffstat (limited to 'util/config_option_check.py')
-rwxr-xr-x | util/config_option_check.py | 388 |
1 files changed, 0 insertions, 388 deletions
diff --git a/util/config_option_check.py b/util/config_option_check.py deleted file mode 100755 index 29e8fb8611..0000000000 --- a/util/config_option_check.py +++ /dev/null @@ -1,388 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2015 The Chromium OS Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. -# -# Ignore indention messages, since legacy scripts use 2 spaces instead of 4. -# pylint: disable=bad-indentation,docstring-section-indent -# pylint: disable=docstring-trailing-quotes - -"""Configuration Option Checker. - -Script to ensure that all configuration options for the Chrome EC are defined -in config.h. -""" -from __future__ import print_function -import enum -import os -import re -import subprocess -import sys - - -class Line(object): - """Class for each changed line in diff output. - - Attributes: - line_num: The integer line number that this line appears in the file. - string: The literal string of this line. - line_type: '+' or '-' indicating if this line was an addition or - deletion. - """ - - def __init__(self, line_num, string, line_type): - """Inits Line with the line number and the actual string.""" - self.line_num = line_num - self.string = string - self.line_type = line_type - - -class Hunk(object): - """Class for a git diff hunk. - - Attributes: - filename: The name of the file that this hunk belongs to. - lines: A list of Line objects that are a part of this hunk. - """ - - def __init__(self, filename, lines): - """Inits Hunk with the filename and the list of lines of the hunk.""" - self.filename = filename - self.lines = lines - - -# Master file which is supposed to include all CONFIG_xxxx descriptions. -CONFIG_FILE = 'include/config.h' - -# Specific files which the checker should ignore. -ALLOWLIST = [CONFIG_FILE, 'util/config_option_check.py'] - -# Specific directories which the checker should ignore. -ALLOW_PATTERN = re.compile('zephyr/.*') - -# Specific CONFIG_* flags which the checker should ignore. -ALLOWLIST_CONFIGS = ['CONFIG_ZTEST'] - -def obtain_current_config_options(): - """Obtains current config options from include/config.h. - - Scans through the master config file defined in CONFIG_FILE for all CONFIG_* - options. - - Returns: - config_options: A list of all the config options in the master CONFIG_FILE. - """ - - config_options = [] - config_option_re = re.compile(r'^#(define|undef)\s+(CONFIG_[A-Z0-9_]+)') - with open(CONFIG_FILE, 'r') as config_file: - for line in config_file: - result = config_option_re.search(line) - if not result: - continue - word = result.groups()[1] - if word not in config_options: - config_options.append(word) - return config_options - -def obtain_config_options_in_use(): - """Obtains all the config options in use in the repo. - - Scans through the entire repo looking for all CONFIG_* options actively used. - - Returns: - options_in_use: A set of all the config options in use in the repo. - """ - file_list = [] - cwd = os.getcwd() - config_option_re = re.compile(r'\b(CONFIG_[a-zA-Z0-9_]+)') - config_debug_option_re = re.compile(r'\b(CONFIG_DEBUG_[a-zA-Z0-9_]+)') - options_in_use = set() - for (dirpath, dirnames, filenames) in os.walk(cwd, topdown=True): - # Ignore the build and private directories (taken from .gitignore) - if 'build' in dirnames: - dirnames.remove('build') - if 'private' in dirnames: - dirnames.remove('private') - for f in filenames: - # Ignore hidden files. - if f.startswith('.'): - continue - # Only consider C source, assembler, and Make-style files. - if (os.path.splitext(f)[1] in ('.c', '.h', '.inc', '.S', '.mk') or - 'Makefile' in f): - file_list.append(os.path.join(dirpath, f)) - - # Search through each file and build a set of the CONFIG_* options being - # used. - - for f in file_list: - if CONFIG_FILE in f: - continue - with open(f, 'r') as cur_file: - for line in cur_file: - match = config_option_re.findall(line) - if match: - for option in match: - if not in_comment(f, line, option): - if option not in options_in_use: - options_in_use.add(option) - - # Since debug options can be turned on at any time, assume that they are - # always in use in case any aren't being used. - - with open(CONFIG_FILE, 'r') as config_file: - for line in config_file: - match = config_debug_option_re.findall(line) - if match: - for option in match: - if not in_comment(CONFIG_FILE, line, option): - if option not in options_in_use: - options_in_use.add(option) - - return options_in_use - -def print_missing_config_options(hunks, config_options): - """Searches thru all the changes in hunks for missing options and prints them. - - Args: - hunks: A list of Hunk objects which represent the hunks from the git - diff output. - config_options: A list of all the config options in the master CONFIG_FILE. - - Returns: - missing_config_option: A boolean indicating if any CONFIG_* options - are missing from the master CONFIG_FILE in this commit or if any CONFIG_* - options removed are no longer being used in the repo. - """ - missing_config_option = False - print_banner = True - deprecated_options = set() - # Determine longest CONFIG_* length to be used for formatting. - max_option_length = max(len(option) for option in config_options) - config_option_re = re.compile(r'\b(CONFIG_[a-zA-Z0-9_]+)') - - # Search for all CONFIG_* options in use in the repo. - options_in_use = obtain_config_options_in_use() - - # Check each hunk's line for a missing config option. - for h in hunks: - for l in h.lines: - # Check for the existence of a CONFIG_* in the line. - match = filter(lambda opt: opt in ALLOWLIST_CONFIGS, - config_option_re.findall(l.string)) - if not match: - continue - - # At this point, an option was found in the line. However, we need to - # verify that it is not within a comment. - violations = set() - - for option in match: - if not in_comment(h.filename, l.string, option): - # Since the CONFIG_* option is not within a comment, we've found a - # violation. We now need to determine if this line is a deletion or - # not. For deletions, we will need to verify if this CONFIG_* option - # is no longer being used in the entire repo. - - if l.line_type == '-': - if option not in options_in_use and option in config_options: - deprecated_options.add(option) - else: - violations.add(option) - - # Check to see if the CONFIG_* option is in the config file and print the - # violations. - for option in match: - if option not in config_options and option in violations: - # Print the banner once. - if print_banner: - print('The following config options were found to be missing ' - 'from %s.\n' - 'Please add new config options there along with ' - 'descriptions.\n\n' % CONFIG_FILE) - print_banner = False - missing_config_option = True - # Print the misssing config option. - print('> %-*s %s:%s' % (max_option_length, option, - h.filename, - l.line_num)) - - if deprecated_options: - print('\n\nThe following config options are being removed and also appear' - ' to be the last uses\nof that option. Please remove these ' - 'options from %s.\n\n' % CONFIG_FILE) - for option in deprecated_options: - print('> %s' % option) - missing_config_option = True - - return missing_config_option - -def in_comment(filename, line, substr): - """Checks if given substring appears in a comment. - - Args: - filename: The filename where this line is from. This is used to determine - what kind of comments to look for. - line: String of line to search in. - substr: Substring to search for in the line. - - Returns: - is_in_comment: Boolean indicating if substr was in a comment. - """ - - c_style_ext = ('.c', '.h', '.inc', '.S') - make_style_ext = ('.mk') - is_in_comment = False - - extension = os.path.splitext(filename)[1] - substr_idx = line.find(substr) - - # Different files have different comment syntax; Handle appropriately. - if extension in c_style_ext: - beg_comment_idx = line.find('/*') - end_comment_idx = line.find('*/') - if end_comment_idx == -1: - end_comment_idx = len(line) - - if beg_comment_idx == -1: - # Check to see if this line is from a multi-line comment. - if line.lstrip().startswith('* '): - # It _seems_ like it is. - is_in_comment = True - else: - # Check to see if its actually inside the comment. - if beg_comment_idx < substr_idx < end_comment_idx: - is_in_comment = True - elif extension in make_style_ext or 'Makefile' in filename: - beg_comment_idx = line.find('#') - # Ignore everything to the right of the hash. - if beg_comment_idx < substr_idx and beg_comment_idx != -1: - is_in_comment = True - return is_in_comment - -def get_hunks(): - """Gets the hunks of the most recent commit. - - States: - new_file: Searching for a new file in the git diff. - filename_search: Searching for the filename of this hunk. - hunk: Searching for the beginning of a new hunk. - lines: Counting line numbers and searching for changes. - - Returns: - hunks: A list of Hunk objects which represent the hunks in the git diff - output. - """ - - diff = [] - hunks = [] - hunk_lines = [] - line = '' - filename = '' - i = 0 - line_num = 0 - - # Regex patterns - new_file_re = re.compile(r'^diff --git') - filename_re = re.compile(r'^[+]{3} (.*)') - hunk_line_num_re = re.compile(r'^@@ -[0-9]+,[0-9]+ \+([0-9]+),[0-9]+ @@.*') - line_re = re.compile(r'^([+| |-])(.*)') - - # Get the diff output. - proc = subprocess.run(['git', 'diff', '--cached', '-GCONFIG_*', '--no-prefix', - '--no-ext-diff', 'HEAD~1'], - stdout=subprocess.PIPE, - encoding='utf-8', - check=True) - diff = proc.stdout.splitlines() - if not diff: - return [] - line = diff[0] - - state = enum.Enum('state', 'NEW_FILE FILENAME_SEARCH HUNK LINES') - current_state = state.NEW_FILE - - while True: - # Search for the beginning of a new file. - if current_state is state.NEW_FILE: - match = new_file_re.search(line) - if match: - current_state = state.FILENAME_SEARCH - - # Search the diff output for a file name. - elif current_state is state.FILENAME_SEARCH: - # Search for a file name. - match = filename_re.search(line) - if match: - filename = match.groups(1)[0] - if filename in ALLOWLIST or ALLOW_PATTERN.match(filename): - # Skip the file if it's allowlisted. - current_state = state.NEW_FILE - else: - current_state = state.HUNK - - # Search for a hunk. Each hunk starts with a line describing the line - # numbers in the file. - elif current_state is state.HUNK: - hunk_lines = [] - match = hunk_line_num_re.search(line) - if match: - # Extract the line number offset. - line_num = int(match.groups(1)[0]) - current_state = state.LINES - - # Start looking for changes. - elif current_state is state.LINES: - # Check if state needs updating. - new_hunk = hunk_line_num_re.search(line) - new_file = new_file_re.search(line) - if new_hunk: - current_state = state.HUNK - hunks.append(Hunk(filename, hunk_lines)) - continue - elif new_file: - current_state = state.NEW_FILE - hunks.append(Hunk(filename, hunk_lines)) - continue - - match = line_re.search(line) - if match: - line_type = match.groups(1)[0] - # We only care about modifications. - if line_type != ' ': - hunk_lines.append(Line(line_num, match.groups(2)[1], line_type)) - # Deletions don't count towards the line numbers. - if line_type != '-': - line_num += 1 - - # Advance to the next line - try: - i += 1 - line = diff[i] - except IndexError: - # We've reached the end of the diff. Return what we have. - if hunk_lines: - hunks.append(Hunk(filename, hunk_lines)) - return hunks - -def main(): - """Searches through committed changes for missing config options. - - Checks through committed changes for CONFIG_* options. Then checks to make - sure that all CONFIG_* options used are defined in include/config.h. Finally, - reports any missing config options. - """ - # Obtain the hunks of the commit to search through. - hunks = get_hunks() - # Obtain config options from include/config.h. - config_options = obtain_current_config_options() - # Find any missing config options from the hunks and print them. - missing_opts = print_missing_config_options(hunks, config_options) - - if missing_opts: - print('\nIt may also be possible that you have a typo.') - sys.exit(1) - -if __name__ == '__main__': - main() |