summaryrefslogtreecommitdiff
path: root/util/config_option_check.py
diff options
context:
space:
mode:
Diffstat (limited to 'util/config_option_check.py')
-rwxr-xr-xutil/config_option_check.py705
1 files changed, 366 insertions, 339 deletions
diff --git a/util/config_option_check.py b/util/config_option_check.py
index 8bd8ecb1f0..25c026afe5 100755
--- a/util/config_option_check.py
+++ b/util/config_option_check.py
@@ -1,11 +1,7 @@
#!/usr/bin/env python3
-# Copyright 2015 The Chromium OS Authors. All rights reserved.
+# Copyright 2015 The ChromiumOS Authors
# 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.
@@ -13,6 +9,7 @@ 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
@@ -21,368 +18,398 @@ import sys
class Line(object):
- """Class for each changed line in diff output.
+ """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.
- """
+ 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
+ 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.
+ """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.
- """
+ 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
+ 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'
+CONFIG_FILE = "include/config.h"
# Specific files which the checker should ignore.
-ALLOWLIST = [CONFIG_FILE, 'util/config_option_check.py']
+ALLOWLIST = [CONFIG_FILE, "util/config_option_check.py"]
# Specific directories which the checker should ignore.
-ALLOW_PATTERN = re.compile('zephyr/.*')
+ALLOW_PATTERN = re.compile("zephyr/.*")
# Specific CONFIG_* flags which the checker should ignore.
-ALLOWLIST_CONFIGS = ['CONFIG_ZTEST']
+ALLOWLIST_CONFIGS = ["CONFIG_ZTEST"]
+
def obtain_current_config_options():
- """Obtains current config options from include/config.h.
-
- Scans through the main config file defined in CONFIG_FILE for all CONFIG_*
- options.
-
- Returns:
- config_options: A list of all the config options in the main 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
+ """Obtains current config options from include/config.h.
+
+ Scans through the main config file defined in CONFIG_FILE for all CONFIG_*
+ options.
+
+ Returns:
+ config_options: A list of all the config options in the main 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
+ """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 main CONFIG_FILE.
-
- Returns:
- missing_config_option: A boolean indicating if any CONFIG_* options
- are missing from the main 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
+ """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 main CONFIG_FILE.
+
+ Returns:
+ missing_config_option: A boolean indicating if any CONFIG_* options
+ are missing from the main 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
- # 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
- 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
+ """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
+ """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()
+ """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()