141 lines
5.6 KiB
Python
Executable file
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)
|