mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2025-12-29 18:11:05 +00:00
This commit enhances the config option check python script to significantly reduce the number of false positives. - Now only checks committed changes. - Only checks additions, not the whole file. - Only checks uses of CONFIG_* not in a comment for both C-style and Make-style files. - Suports a whitelist. BUG=chromium:510672 BRANCH=None TEST=Detected missing configs in Makefiles and C source files. TEST=/* CONFIG_FOO */ was not detected. TEST=' board-$(CONFIG_OPT)=board.o # CONFIG_OPT2' only CONFIG_OPT was detected. TEST=Changes in working dir were not detected. TEST=Changes to config_option_check.py were not detected. TEST=cros lint --debug util/config_option_check.py TEST=CONFIG_FOO in a multi-line C comment was not detected. Change-Id: I5fc2ccc77bb4f319a3c85b7d81c83027959dc96b Signed-off-by: Aseda Aboagye <aaboagye@google.com> Reviewed-on: https://chromium-review.googlesource.com/287519 Tested-by: Aseda Aboagye <aaboagye@chromium.org> Reviewed-by: Vadim Bendebury <vbendeb@google.com> Commit-Queue: Aseda Aboagye <aaboagye@chromium.org>
276 lines
9.0 KiB
Python
Executable File
276 lines
9.0 KiB
Python
Executable File
#!/usr/bin/python2
|
|
# 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.
|
|
"""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 os
|
|
import re
|
|
import subprocess
|
|
|
|
|
|
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.
|
|
"""
|
|
|
|
def __init__(self, line_num, string):
|
|
"""Inits Line with the line number and the actual string."""
|
|
self.line_num = line_num
|
|
self.string = string
|
|
|
|
|
|
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.
|
|
WHITELIST = [CONFIG_FILE, 'util/config_option_check.py']
|
|
|
|
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:
|
|
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 print_missing_config_options(hunks, config_options):
|
|
"""Searches thru all the changes in hunks for missing options and prints them.
|
|
|
|
TODO(aaboagye): Improve upon this to detect when files are being removed and a
|
|
CONFIG_* option is no longer used elsewhere in the repo.
|
|
|
|
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.
|
|
"""
|
|
missing_config_option = False
|
|
print_banner = True
|
|
# 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_]+)')
|
|
c_style_ext = ('.c', '.h', '.inc', '.S')
|
|
make_style_ext = ('.mk')
|
|
|
|
# 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 = config_option_re.findall(l.string)
|
|
if not match:
|
|
continue
|
|
|
|
# At this point, an option was found in the string. However, we need to
|
|
# verify that it is not within a comment. Assume every option is a
|
|
# violation until proven otherwise.
|
|
|
|
violations = set()
|
|
for option in match:
|
|
violations.add(option)
|
|
|
|
# Different files have different comment syntax; Handle appropriately.
|
|
extension = os.path.splitext(h.filename)[1]
|
|
if extension in c_style_ext:
|
|
beg_comment_idx = l.string.find('/*')
|
|
end_comment_idx = l.string.find('*/')
|
|
if end_comment_idx == -1:
|
|
end_comment_idx = len(l.string)
|
|
for option in match:
|
|
option_idx = l.string.find(option)
|
|
if beg_comment_idx == -1:
|
|
# Check to see if this line is from a multi-line comment.
|
|
if l.string.lstrip()[0] == '*':
|
|
# It _seems_ like it is, therefore ignore this instance.
|
|
violations.remove(option)
|
|
else:
|
|
# Check to see if its actually inside the comment.
|
|
if beg_comment_idx < option_idx < end_comment_idx:
|
|
# The config option is in the comment. Ignore it.
|
|
violations.remove(option)
|
|
elif extension in make_style_ext or 'Makefile' in h.filename:
|
|
beg_comment_idx = l.string.find('#')
|
|
for option in match:
|
|
option_idx = l.string.find(option)
|
|
# Ignore everything to the right of the hash.
|
|
if beg_comment_idx < option_idx and beg_comment_idx != -1:
|
|
# The option is within a comment. Ignore it.
|
|
violations.remove(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))
|
|
return missing_config_option
|
|
|
|
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.
|
|
"""
|
|
|
|
# Get the 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'^([+| ])(.*)')
|
|
|
|
cmd = 'git diff --cached -GCONFIG_* --no-prefix --no-ext-diff HEAD~1'
|
|
diff = subprocess.check_output(cmd.split()).split('\n')
|
|
line = diff[0]
|
|
current_state = 'new_file'
|
|
|
|
while True:
|
|
# Search for the beginning of a new file.
|
|
if current_state is 'new_file':
|
|
match = new_file_re.search(line)
|
|
if match:
|
|
current_state = 'filename_search'
|
|
|
|
# Search the diff output for a file name.
|
|
elif current_state is 'filename_search':
|
|
# If we're deleting a file, ignore it entirely.
|
|
if 'deleted' in line:
|
|
current_state = 'new_file'
|
|
continue
|
|
|
|
# Search for a file name.
|
|
match = filename_re.search(line)
|
|
if match:
|
|
filename = match.groups(1)[0]
|
|
if filename in WHITELIST:
|
|
# Skip the file if it's whitelisted.
|
|
current_state = 'new_file'
|
|
else:
|
|
current_state = 'hunk'
|
|
|
|
# Search for a hunk. Each hunk starts with a line describing the line
|
|
# numbers in the file.
|
|
elif current_state is '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 = 'lines'
|
|
|
|
# Start looking for changes.
|
|
elif current_state is '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 = 'hunk'
|
|
hunks.append(Hunk(filename, hunk_lines))
|
|
continue
|
|
elif new_file:
|
|
current_state = 'new_file'
|
|
hunks.append(Hunk(filename, hunk_lines))
|
|
continue
|
|
|
|
match = line_re.search(line)
|
|
if match:
|
|
# Consider this line iff it's an addition.
|
|
if match.groups(1)[0] == '+':
|
|
hunk_lines.append(Line(line_num, match.groups(2)[1]))
|
|
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.')
|
|
os.sys.exit(1)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|