OpenMiInk/third_party/kde-ci-utilities/run-cppcheck.py
Hellings b8c8ff98e2 Hi
2026-10-16 09:05:56 -04:00

141 lines
5.6 KiB
Python
Executable file

#!/usr/bin/env python3
import os
import sys
import argparse
import subprocess
import multiprocessing
import yaml
from components import CommonUtils, EnvironmentHandler
# Capture our command line parameters
parser = argparse.ArgumentParser(description='Utility to perform a CI run for a KDE project.')
parser.add_argument('--project', type=str, required=True)
arguments = parser.parse_args()
####
# Load the project configuration
####
# This consists of:
# 0) Global configuration
configuration = yaml.safe_load( open(os.path.join(CommonUtils.scriptsBaseDirectory(), 'config', 'global.yml')) )
# 1) Project/branch specific configuration contained within the repository
if os.path.exists('.kde-ci.yml'):
localConfig = yaml.safe_load( open('.kde-ci.yml') )
CommonUtils.recursiveUpdate( configuration, localConfig )
# 2) Global overrides applied to the project configuration
projectConfigFile = os.path.join(CommonUtils.scriptsBaseDirectory(), 'config', arguments.project + '.yml')
if os.path.exists( projectConfigFile ):
projectConfig = yaml.safe_load( open(projectConfigFile) )
CommonUtils.recursiveUpdate( configuration, projectConfig )
####
# Determine a number of paths we will need later on
####
# Our sources are assumed to be in the current working directory
sourcesPath = os.getcwd()
# Determine where to locate the project build
buildPath = os.path.join( os.getcwd(), '_build' )
if configuration['Options']['in-source-build']:
buildPath = os.getcwd()
# Determine where to unpack the dependencies to
installPath = os.path.join( os.getcwd(), '_install' )
####
# Perform final steps needed to get ready to start the build process
####
# Determine what our build environment should be comprised of....
buildEnvironment = EnvironmentHandler.generateFor( installPrefix=installPath, configuration=configuration )
# Apply any environment changes from our configuration
for key, value in configuration['Environment'].items():
# Apply each key in turn
buildEnvironment[ key ] = value
####
# Extract cppcheck results for processing by Gitlab
####
cppcheckExitCode = 0
# If we aren't running on Linux then we skip this, as we consider that to be the canonical platform for code coverage...
if configuration['Options']['run-cppcheck']:
# Determine the cppcheck command we need to run
# First find out which version of cppcheck is available
getVersionCommand = ["cppcheck", "--version"]
try:
print("## RUNNING: " + " ".join(getVersionCommand))
cppcheckProcess = subprocess.run(getVersionCommand, check=True, capture_output=True)
except subprocess.CalledProcessError:
print("## Failed to determine which version of cppcheck is available")
sys.exit(1)
# cppcheck --version returns something like "Cppcheck 2.18.0"
cppcheckVersion = cppcheckProcess.stdout.rstrip().decode().split(" ")[-1]
print(f"## Found cppcheck version {cppcheckVersion}")
# Sometimes projects will want to customise things slightly so we provide for that as well
cppcheckCommand = ["cppcheck", "--output-format=xml", "--output-file=cppcheck_out.xml", "--relative-paths", "-j", str(multiprocessing.cpu_count()), "--library=qt", "--library=kde"]
if cppcheckVersion < "2.18.0":
localDefinitions = os.path.join(CommonUtils.scriptsBaseDirectory(), 'resources', 'cppcheck-kde-definitions.cfg')
cppcheckCommand += [f"--library={localDefinitions}"]
cppcheckCommand += ["--check-level=exhaustive", "--inline-suppr", "-i", "_build/"]
customArguments = configuration['Options']['cppcheck-arguments']
if isinstance(customArguments, str):
customArguments = customArguments.split(' ')
if not isinstance(customArguments, list):
customArguments = [customArguments]
if configuration['Options']['require-cppcheck-clean']:
customArguments += ['--error-exitcode=1']
cppcheckCommand += customArguments
for s in configuration['Options']['cppcheck-ignore-files']:
cppcheckCommand += ['-i', s]
cppcheckCommand += [sourcesPath]
# Determine the command to run to convert the cppcheck XML report into a CodeClimate format file
conversionCommand = ['cppcheck-codequality', '--input-file=cppcheck_out.xml', '--output-file=cppcheck.json']
# Determine the command to run to convert the cppcheck XML report into a human readable html report
htmlReportCommand = ['cppcheck-htmlreport', '--file=cppcheck_out.xml', '--report-dir=kde-ci-reports/cppcheck', f'--source-dir={sourcesPath}',
'--title', arguments.project, '--add-author-information', 'n,e,d']
# Now run it!
# If cppcheck exits with non-zero that means:
# 1) there was a command line usage error (in which case cppcheck_out.xml is empty)
# 2) user specified 'require-cppcheck-clean'
print("## RUNNING:", ' '.join(cppcheckCommand) )
cppcheckResult = subprocess.run( cppcheckCommand, env=buildEnvironment )
cppcheckExitCode = cppcheckResult.returncode
# Exit with failure now if a cppcheck command line usage error is detected
with open("cppcheck_out.xml","r") as f:
if len(f.readlines()) == 0: # empty
print("## Invalid Cppcheck command line specified")
sys.exit(1)
try:
print("## RUNNING:", ' '.join(conversionCommand) )
subprocess.run( conversionCommand, check=True, env=buildEnvironment )
except Exception:
pass
try:
print("## RUNNING:", ' '.join(htmlReportCommand) )
subprocess.run( htmlReportCommand, check=True, env=buildEnvironment )
except Exception:
pass
####
# Run complete!
####
print(f"## Cppcheck run successfully! (exit code={cppcheckExitCode})")
sys.exit(cppcheckExitCode)