cppcheck/tools/triage_py/triage_version.py

198 lines
6.4 KiB
Python

#!/usr/bin/env python
import os.path
import subprocess
import sys
import argparse
from packaging.version import Version
parser = argparse.ArgumentParser()
parser.add_argument('dir', help='directory with versioned folders')
parser.add_argument('infile', help='the file to analyze')
parser.add_argument('repo', nargs='?', default=None, help='the git repository (for sorting commit hashes)')
parser.add_argument('--compare', action='store_true', help='compare output and only show when changed')
parser.add_argument('--verbose', action='store_true', help='verbose output for debugging')
parser.add_argument('--debug', action='store_true', help='passed through to binary if supported')
parser.add_argument('--debug-warnings', action='store_true', help='passed through to binary if supported')
parser.add_argument('--check-library', action='store_true', help='passed through to binary if supported')
parser.add_argument('--timeout', type=int, default=2, help='the amount of seconds to wait for the analysis to finish')
parser.add_argument('--compact', action='store_true', help='only print versions with changes with --compare')
args = parser.parse_args()
def sort_commit_hashes(commits):
git_cmd = 'git rev-list --abbrev-commit --topo-order --no-walk=sorted --reverse ' + ' '.join(commits)
p = subprocess.Popen(git_cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=git_repo, universal_newlines=True)
stdout, stderr = p.communicate()
if p.returncode != 0:
print('error: sorting commit hashes failed')
print(stderr)
sys.exit(1)
return stdout.splitlines()
verbose = args.verbose
do_compare = args.compare
if args.compact:
if not do_compare:
print('error: --compact requires --compare')
sys.exit(1)
directory = args.dir
input_file = args.infile
git_repo = args.repo
use_hashes = None
versions = []
for filename in os.listdir(directory):
f = os.path.join(directory, filename)
if not os.path.isdir(f):
continue
versions.append(filename)
if not len(versions):
print("error: no versions found in '{}'".format(directory))
sys.exit(1)
if verbose:
print("found {} versions in '{}'".format(len(versions), directory))
try:
Version(versions[0])
use_hashes = False
versions.sort(key=Version)
except:
if verbose:
print("'{}' not a version - assuming commit hashes".format(versions[0]))
if not git_repo:
print('error: git repository argument required for commit hash sorting')
sys.exit(1)
if verbose:
print("using git repository '{}' to sort commit hashes".format(git_repo))
use_hashes = True
# if you use the folder from the bisect script that contains the repo as a folder - so remove it from the list
if versions.count('cppcheck'):
versions.remove('cppcheck')
len_in = len(versions)
versions = sort_commit_hashes(versions)
if len(versions) != len_in:
print('error: unexpected amount of versions after commit hash sorting')
sys.exit(1)
if verbose:
print("analyzing '{}'".format(input_file))
last_ec = None
last_out = None
for entry in versions:
exe_path = os.path.join(directory, entry)
exe = os.path.join(exe_path, 'cppcheck')
if not use_hashes:
version = entry
else:
# get version string
version_cmd = exe + ' ' + '--version'
version = subprocess.Popen(version_cmd.split(), stdout=subprocess.PIPE, universal_newlines=True).stdout.read().strip()
# sanitize version
version = version.replace('Cppcheck ', '').replace(' dev', '')
cmd = exe
cmd += ' '
if do_compare:
cmd += ' -q '
if args.debug and Version(version) >= Version('1.45'):
cmd += '--debug '
if args.debug_warnings and Version(version) >= Version('1.45'):
cmd += '--debug-warnings '
if args.check_library and Version(version) >= Version('1.61'):
cmd += '--check-library '
if Version(version) >= Version('1.39'):
cmd += '--enable=all '
if Version(version) >= Version('1.40'):
cmd += '--inline-suppr '
if Version(version) >= Version('1.48'):
cmd += '--suppress=missingInclude --suppress=missingIncludeSystem --suppress=unmatchedSuppression --suppress=unusedFunction '
if Version(version) >= Version('1.49'):
cmd += '--inconclusive '
cmd += input_file
if verbose:
print("running '{}'". format(cmd))
p = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=exe_path, universal_newlines=True)
try:
comm = p.communicate(timeout=args.timeout)
out = comm[0] + '\n' + comm[1]
except subprocess.TimeoutExpired:
out = "timeout"
p.kill()
comm = p.communicate()
ec = p.returncode
if not do_compare:
if not use_hashes:
print(version)
else:
print('{} ({})'.format(entry, version))
print(ec)
print(out)
continue
# filter out some false positives
# [*]: (information) Unmatched suppression: missingInclude
# [*]: (information) Unmatched suppression: missingIncludeSystem
# [*]: (information) Unmatched suppression: unmatchedSuppression
# [*]: (information) Unmatched suppression: unusedFunction
if not use_hashes and (Version(version) >= Version('1.48') or Version(version) <= Version('1.49')):
lines = out.splitlines()
out = ""
for line in lines:
if line.startswith('[*]: (information) Unmatched suppression:'):
continue
out += line + '\n'
out = out.strip()
if last_ec is None:
# first run - only print version
if not use_hashes:
print(version)
else:
print('{} ({})'.format(entry, version))
last_ec = ec
last_out = out
continue
do_print = False
if last_ec != ec:
if verbose:
print("{}: exitcode changed".format(version))
do_print = True
if last_out != out:
if verbose:
print("{}: output changed".format(version))
do_print = True
if do_print:
print(last_ec)
print(last_out)
# do not print intermediate versions with --compact
if not args.compact or do_print:
if not use_hashes:
print(version)
else:
print('{} ({})'.format(entry, version))
last_ec = ec
last_out = out
if do_compare:
print(last_ec)
print(last_out)
if verbose:
print('done')