#!/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)