2018-08-23 21:31:02 +02:00
|
|
|
# Donate CPU
|
|
|
|
#
|
|
|
|
# A script a user can run to donate CPU to cppcheck project
|
|
|
|
#
|
2019-01-18 21:36:03 +01:00
|
|
|
# Syntax: donate-cpu.py [-jN] [--package=url] [--stop-time=HH:MM] [--work-path=path] [--test] [--bandwidth-limit=limit]
|
2018-08-31 14:28:42 +02:00
|
|
|
# -jN Use N threads in compilation/analysis. Default is 1.
|
2019-03-29 09:40:09 +01:00
|
|
|
# --package=url Check a specific package and then stop. Can be useful if you want to reproduce
|
|
|
|
# some warning/crash/exception/etc..
|
2018-08-31 14:28:42 +02:00
|
|
|
# --stop-time=HH:MM Stop analysis when time has passed. Default is that you must terminate the script.
|
2018-09-06 13:15:54 +02:00
|
|
|
# --work-path=path Work folder path. Default path is cppcheck-donate-cpu-workfolder in your home folder.
|
2019-01-03 13:42:33 +01:00
|
|
|
# --test Connect to a donate-cpu-server that is running locally on port 8001 for testing.
|
2019-01-18 21:36:03 +01:00
|
|
|
# --bandwidth-limit=limit Limit download rate for packages. Format for limit is the same that wget uses.
|
|
|
|
# Examples: --bandwidth-limit=250k => max. 250 kilobytes per second
|
|
|
|
# --bandwidth-limit=2m => max. 2 megabytes per second
|
2019-03-01 22:34:50 +01:00
|
|
|
# --max-packages=N Process N packages and then exit. A value of 0 means infinitely.
|
2019-03-28 15:49:20 +01:00
|
|
|
# --no-upload Do not upload anything. Defaults to False.
|
2018-08-31 14:28:42 +02:00
|
|
|
#
|
|
|
|
# What this script does:
|
2018-08-23 21:31:02 +02:00
|
|
|
# 1. Check requirements
|
|
|
|
# 2. Pull & compile Cppcheck
|
|
|
|
# 3. Select a package
|
|
|
|
# 4. Download package
|
|
|
|
# 5. Analyze source code
|
|
|
|
# 6. Upload results
|
|
|
|
# 7. Repeat from step 2
|
2018-08-31 14:28:42 +02:00
|
|
|
#
|
|
|
|
# Quick start: just run this script without any arguments
|
2018-08-23 21:31:02 +02:00
|
|
|
|
|
|
|
import shutil
|
|
|
|
import os
|
|
|
|
import subprocess
|
|
|
|
import sys
|
|
|
|
import socket
|
|
|
|
import time
|
2018-08-24 13:20:38 +02:00
|
|
|
import re
|
2018-09-15 18:56:46 +02:00
|
|
|
import tarfile
|
2019-01-11 20:07:11 +01:00
|
|
|
import platform
|
2018-08-23 21:31:02 +02:00
|
|
|
|
2019-01-09 16:01:53 +01:00
|
|
|
|
2019-02-09 09:27:54 +01:00
|
|
|
# Version scheme (MAJOR.MINOR.PATCH) should orientate on "Semantic Versioning" https://semver.org/
|
|
|
|
# Every change in this script should result in increasing the version number accordingly (exceptions may be cosmetic
|
|
|
|
# changes)
|
2019-09-12 15:14:18 +02:00
|
|
|
CLIENT_VERSION = "1.1.37"
|
2019-02-09 09:27:54 +01:00
|
|
|
|
|
|
|
|
2019-06-30 12:43:05 +02:00
|
|
|
def check_requirements():
|
2018-08-23 21:31:02 +02:00
|
|
|
result = True
|
2019-03-28 15:49:20 +01:00
|
|
|
for app in ['g++', 'git', 'make', 'wget', 'gdb']:
|
2018-08-23 21:31:02 +02:00
|
|
|
try:
|
|
|
|
subprocess.call([app, '--version'])
|
|
|
|
except OSError:
|
2018-12-20 14:26:00 +01:00
|
|
|
print(app + ' is required')
|
|
|
|
result = False
|
2018-08-23 21:31:02 +02:00
|
|
|
return result
|
|
|
|
|
2019-01-09 16:01:53 +01:00
|
|
|
|
2019-08-27 06:48:00 +02:00
|
|
|
def get_cppcheck(cppcheck_path, work_path):
|
2018-08-23 21:31:02 +02:00
|
|
|
print('Get Cppcheck..')
|
2018-08-26 16:23:42 +02:00
|
|
|
for i in range(5):
|
2019-06-30 12:43:05 +02:00
|
|
|
if os.path.exists(cppcheck_path):
|
2019-08-27 06:48:00 +02:00
|
|
|
try:
|
|
|
|
os.chdir(cppcheck_path)
|
|
|
|
subprocess.check_call(['git', 'checkout', '-f'])
|
|
|
|
subprocess.check_call(['git', 'pull'])
|
|
|
|
except:
|
|
|
|
print('Failed to update Cppcheck sources! Retrying..')
|
|
|
|
time.sleep(10)
|
|
|
|
continue
|
2018-08-26 16:23:42 +02:00
|
|
|
else:
|
2019-08-27 06:48:00 +02:00
|
|
|
try:
|
|
|
|
subprocess.check_call(['git', 'clone', 'https://github.com/danmar/cppcheck.git', cppcheck_path])
|
|
|
|
except:
|
2018-08-26 16:23:42 +02:00
|
|
|
print('Failed to clone, will try again in 10 minutes..')
|
|
|
|
time.sleep(600)
|
|
|
|
continue
|
|
|
|
time.sleep(2)
|
|
|
|
return True
|
2019-08-27 06:48:00 +02:00
|
|
|
if os.path.exists(cppcheck_path):
|
|
|
|
print('Failed to update Cppcheck sources, trying a fresh clone..')
|
|
|
|
try:
|
|
|
|
os.chdir(work_path)
|
|
|
|
shutil.rmtree(cppcheck_path)
|
|
|
|
get_cppcheck(cppcheck_path, work_path)
|
|
|
|
except:
|
|
|
|
print('Failed to remove Cppcheck folder, please manually remove ' + work_path)
|
|
|
|
return False
|
2018-08-26 16:23:42 +02:00
|
|
|
return False
|
2018-08-24 18:49:11 +02:00
|
|
|
|
2018-08-23 21:31:02 +02:00
|
|
|
|
2019-09-05 09:08:45 +02:00
|
|
|
def get_cppcheck_info(cppcheck_path):
|
|
|
|
try:
|
|
|
|
os.chdir(cppcheck_path)
|
|
|
|
return subprocess.check_output(['git', 'show', "--pretty=%h (%ci)", 'HEAD', '--no-patch', '--no-notes']).decode('utf-8').strip()
|
|
|
|
except:
|
|
|
|
return ''
|
|
|
|
|
|
|
|
|
2019-06-30 12:43:05 +02:00
|
|
|
def compile_version(work_path, jobs, version):
|
|
|
|
if os.path.isfile(work_path + '/' + version + '/cppcheck'):
|
2018-08-26 16:23:42 +02:00
|
|
|
return True
|
2019-06-30 12:43:05 +02:00
|
|
|
os.chdir(work_path + '/cppcheck')
|
2018-08-24 21:07:50 +02:00
|
|
|
subprocess.call(['git', 'checkout', version])
|
|
|
|
subprocess.call(['make', 'clean'])
|
2019-06-23 13:43:09 +02:00
|
|
|
subprocess.call(['make', jobs, 'MATCHCOMPILER=yes', 'CXXFLAGS=-O2 -g'])
|
2019-06-30 12:43:05 +02:00
|
|
|
if os.path.isfile(work_path + '/cppcheck/cppcheck'):
|
|
|
|
os.mkdir(work_path + '/' + version)
|
|
|
|
destPath = work_path + '/' + version + '/'
|
|
|
|
subprocess.call(['cp', '-R', work_path + '/cppcheck/cfg', destPath])
|
2018-08-24 21:07:50 +02:00
|
|
|
subprocess.call(['cp', 'cppcheck', destPath])
|
|
|
|
subprocess.call(['git', 'checkout', 'master'])
|
2018-08-26 16:23:42 +02:00
|
|
|
try:
|
2019-06-30 12:43:05 +02:00
|
|
|
subprocess.call([work_path + '/' + version + '/cppcheck', '--version'])
|
2018-08-26 16:23:42 +02:00
|
|
|
except OSError:
|
|
|
|
return False
|
|
|
|
return True
|
2018-08-24 21:07:50 +02:00
|
|
|
|
|
|
|
|
2019-06-30 12:43:05 +02:00
|
|
|
def compile(cppcheck_path, jobs):
|
2018-08-23 21:31:02 +02:00
|
|
|
print('Compiling Cppcheck..')
|
2018-08-24 13:20:38 +02:00
|
|
|
try:
|
2019-06-30 12:43:05 +02:00
|
|
|
os.chdir(cppcheck_path)
|
2019-06-23 13:43:09 +02:00
|
|
|
subprocess.call(['make', jobs, 'MATCHCOMPILER=yes', 'CXXFLAGS=-O2 -g'])
|
2019-06-30 12:43:05 +02:00
|
|
|
subprocess.call([cppcheck_path + '/cppcheck', '--version'])
|
2018-08-24 13:20:38 +02:00
|
|
|
except OSError:
|
|
|
|
return False
|
|
|
|
return True
|
2018-08-23 21:31:02 +02:00
|
|
|
|
2018-08-24 18:49:11 +02:00
|
|
|
|
2019-06-30 12:43:05 +02:00
|
|
|
def get_cppcheck_versions(server_address):
|
2018-11-29 21:19:45 +01:00
|
|
|
print('Connecting to server to get Cppcheck versions..')
|
|
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
try:
|
|
|
|
sock.connect(server_address)
|
|
|
|
sock.send(b'GetCppcheckVersions\n')
|
|
|
|
versions = sock.recv(256)
|
2019-04-04 13:09:35 +02:00
|
|
|
except socket.error as err:
|
|
|
|
print('Failed to get cppcheck versions: ' + str(err))
|
2018-12-08 11:39:44 +01:00
|
|
|
return None
|
2018-11-29 21:19:45 +01:00
|
|
|
sock.close()
|
|
|
|
return versions.decode('utf-8').split()
|
|
|
|
|
|
|
|
|
2019-06-30 12:43:05 +02:00
|
|
|
def get_package(server_address):
|
2018-08-23 21:31:02 +02:00
|
|
|
print('Connecting to server to get assigned work..')
|
|
|
|
package = None
|
|
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
try:
|
2018-11-20 11:18:18 +01:00
|
|
|
sock.connect(server_address)
|
2018-08-25 20:34:43 +02:00
|
|
|
sock.send(b'get\n')
|
2018-08-23 21:31:02 +02:00
|
|
|
package = sock.recv(256)
|
2018-11-14 21:41:16 +01:00
|
|
|
except socket.error:
|
|
|
|
package = ''
|
|
|
|
sock.close()
|
2018-08-25 20:34:43 +02:00
|
|
|
return package.decode('utf-8')
|
2018-08-23 21:31:02 +02:00
|
|
|
|
2018-08-24 18:49:11 +02:00
|
|
|
|
2019-06-30 12:43:05 +02:00
|
|
|
def handle_remove_readonly(func, path, exc):
|
2018-09-01 17:04:34 +02:00
|
|
|
import stat
|
|
|
|
if not os.access(path, os.W_OK):
|
|
|
|
# Is the error an access error ?
|
|
|
|
os.chmod(path, stat.S_IWUSR)
|
|
|
|
func(path)
|
|
|
|
|
|
|
|
|
2019-06-30 12:43:05 +02:00
|
|
|
def remove_tree(folderName):
|
2018-09-01 17:04:34 +02:00
|
|
|
if not os.path.exists(folderName):
|
|
|
|
return
|
|
|
|
count = 5
|
|
|
|
while count > 0:
|
|
|
|
count -= 1
|
|
|
|
try:
|
2019-06-30 12:43:05 +02:00
|
|
|
shutil.rmtree(folderName, onerror=handle_remove_readonly)
|
2018-09-01 17:04:34 +02:00
|
|
|
break
|
|
|
|
except OSError as err:
|
|
|
|
time.sleep(30)
|
|
|
|
if count == 0:
|
|
|
|
print('Failed to cleanup {}: {}'.format(folderName, err))
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
2019-01-18 21:36:03 +01:00
|
|
|
def wget(url, destfile, bandwidth_limit):
|
2018-09-07 15:59:59 +02:00
|
|
|
if os.path.exists(destfile):
|
|
|
|
if os.path.isfile(destfile):
|
|
|
|
os.remove(destfile)
|
|
|
|
else:
|
|
|
|
print('Error: ' + destfile + ' exists but it is not a file! Please check the path and delete it manually.')
|
|
|
|
sys.exit(1)
|
2019-03-28 15:49:20 +01:00
|
|
|
wget_call = ['wget', '--tries=10', '--timeout=300', '-O', destfile, url]
|
2019-01-18 21:36:03 +01:00
|
|
|
if bandwidth_limit and isinstance(bandwidth_limit, str):
|
2019-03-28 15:49:20 +01:00
|
|
|
wget_call.append('--limit-rate=' + bandwidth_limit)
|
|
|
|
exitcode = subprocess.call(wget_call)
|
|
|
|
if exitcode != 0:
|
|
|
|
print('wget failed with ' + str(exitcode))
|
|
|
|
os.remove(destfile)
|
|
|
|
return False
|
|
|
|
if not os.path.isfile(destfile):
|
|
|
|
return False
|
|
|
|
return True
|
2018-08-23 21:31:02 +02:00
|
|
|
|
2018-08-24 21:07:50 +02:00
|
|
|
|
2019-06-30 12:43:05 +02:00
|
|
|
def download_package(work_path, package, bandwidth_limit):
|
2018-08-23 21:31:02 +02:00
|
|
|
print('Download package ' + package)
|
2019-06-30 12:43:05 +02:00
|
|
|
destfile = work_path + '/temp.tgz'
|
2019-01-18 21:36:03 +01:00
|
|
|
if not wget(package, destfile, bandwidth_limit):
|
2019-03-28 15:49:20 +01:00
|
|
|
return None
|
2018-08-24 21:07:50 +02:00
|
|
|
return destfile
|
|
|
|
|
|
|
|
|
2019-06-30 12:43:05 +02:00
|
|
|
def unpack_package(work_path, tgz):
|
2018-08-23 21:31:02 +02:00
|
|
|
print('Unpacking..')
|
2019-06-30 12:43:05 +02:00
|
|
|
temp_path = work_path + '/temp'
|
|
|
|
remove_tree(temp_path)
|
|
|
|
os.mkdir(temp_path)
|
|
|
|
os.chdir(temp_path)
|
2019-04-03 09:27:04 +02:00
|
|
|
found = False
|
2018-09-15 18:56:46 +02:00
|
|
|
if tarfile.is_tarfile(tgz):
|
|
|
|
tf = tarfile.open(tgz)
|
|
|
|
for member in tf:
|
|
|
|
if member.name.startswith(('/', '..')):
|
|
|
|
# Skip dangerous file names
|
|
|
|
continue
|
2019-02-27 14:59:19 +01:00
|
|
|
elif member.name.lower().endswith(('.c', '.cpp', '.cxx', '.cc', '.c++', '.h', '.hpp',
|
2019-02-28 22:41:21 +01:00
|
|
|
'.h++', '.hxx', '.hh', '.tpp', '.txx', '.qml')):
|
2018-11-12 14:45:39 +01:00
|
|
|
try:
|
|
|
|
tf.extract(member.name)
|
2019-04-03 09:27:04 +02:00
|
|
|
found = True
|
2018-11-12 14:45:39 +01:00
|
|
|
except OSError:
|
|
|
|
pass
|
2018-11-22 08:44:24 +01:00
|
|
|
except AttributeError:
|
|
|
|
pass
|
2018-09-15 18:56:46 +02:00
|
|
|
tf.close()
|
2019-06-30 12:43:05 +02:00
|
|
|
os.chdir(work_path)
|
2019-04-03 09:27:04 +02:00
|
|
|
return found
|
2018-08-24 21:07:50 +02:00
|
|
|
|
|
|
|
|
2019-06-30 12:43:05 +02:00
|
|
|
def has_include(path, includes):
|
2019-03-28 15:49:20 +01:00
|
|
|
re_includes = [re.escape(inc) for inc in includes]
|
|
|
|
re_expr = '^[ \t]*#[ \t]*include[ \t]*(' + '|'.join(re_includes) + ')'
|
2018-12-03 18:15:07 +01:00
|
|
|
for root, _, files in os.walk(path):
|
|
|
|
for name in files:
|
|
|
|
filename = os.path.join(root, name)
|
2018-12-05 10:25:35 +01:00
|
|
|
try:
|
2019-02-27 20:01:38 +01:00
|
|
|
if sys.version_info.major < 3:
|
|
|
|
f = open(filename, 'rt')
|
|
|
|
else:
|
|
|
|
f = open(filename, 'rt', errors='ignore')
|
2018-12-07 06:51:07 +01:00
|
|
|
filedata = f.read()
|
|
|
|
try:
|
|
|
|
# Python2 needs to decode the data first
|
|
|
|
filedata = filedata.decode(encoding='utf-8', errors='ignore')
|
|
|
|
except AttributeError:
|
|
|
|
# Python3 directly reads the data into a string object that has no decode()
|
|
|
|
pass
|
2018-12-05 10:25:35 +01:00
|
|
|
f.close()
|
2019-03-28 15:49:20 +01:00
|
|
|
if re.search(re_expr, filedata, re.MULTILINE):
|
2019-02-21 08:14:47 +01:00
|
|
|
return True
|
2018-12-05 10:25:35 +01:00
|
|
|
except IOError:
|
|
|
|
pass
|
2018-12-01 20:02:13 +01:00
|
|
|
return False
|
|
|
|
|
2019-03-29 09:40:09 +01:00
|
|
|
|
2019-06-30 12:43:05 +02:00
|
|
|
def run_command(cmd):
|
2019-03-28 15:49:20 +01:00
|
|
|
print(cmd)
|
|
|
|
startTime = time.time()
|
|
|
|
p = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
|
comm = p.communicate()
|
2019-06-30 12:43:05 +02:00
|
|
|
stop_time = time.time()
|
2019-03-28 15:49:20 +01:00
|
|
|
stdout = comm[0].decode(encoding='utf-8', errors='ignore')
|
|
|
|
stderr = comm[1].decode(encoding='utf-8', errors='ignore')
|
2019-06-30 12:43:05 +02:00
|
|
|
elapsed_time = stop_time - startTime
|
|
|
|
return p.returncode, stdout, stderr, elapsed_time
|
2019-03-28 15:49:20 +01:00
|
|
|
|
2018-12-01 20:02:13 +01:00
|
|
|
|
2019-09-09 13:42:57 +02:00
|
|
|
def scan_package(work_path, cppcheck_path, jobs, libraries):
|
2018-08-23 21:31:02 +02:00
|
|
|
print('Analyze..')
|
2019-06-30 12:43:05 +02:00
|
|
|
os.chdir(work_path)
|
2019-09-09 13:42:57 +02:00
|
|
|
libs = ''
|
|
|
|
for library in libraries:
|
|
|
|
if os.path.exists(os.path.join(cppcheck_path, 'cfg', library + '.cfg')):
|
|
|
|
libs += ' --library=' + library
|
2019-02-03 21:56:48 +01:00
|
|
|
|
2019-03-28 15:49:20 +01:00
|
|
|
# Reference for GNU C: https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html
|
2019-09-09 13:42:57 +02:00
|
|
|
options = jobs + libs + ' -D__GNUC__ --check-library --inconclusive --enable=style,information --platform=unix64 --template=daca2 -rp=temp temp'
|
2019-06-30 12:43:05 +02:00
|
|
|
cppcheck_cmd = cppcheck_path + '/cppcheck' + ' ' + options
|
2019-03-28 15:49:20 +01:00
|
|
|
cmd = 'nice ' + cppcheck_cmd
|
2019-06-30 12:43:05 +02:00
|
|
|
returncode, stdout, stderr, elapsed_time = run_command(cmd)
|
2019-07-15 15:37:31 +02:00
|
|
|
sig_num = -1
|
|
|
|
sig_msg = 'Internal error: Child process crashed with signal '
|
|
|
|
sig_pos = stderr.find(sig_msg)
|
|
|
|
if sig_pos != -1:
|
|
|
|
sig_start_pos = sig_pos + len(sig_msg)
|
|
|
|
sig_num = int(stderr[sig_start_pos:stderr.find(' ', sig_start_pos)])
|
|
|
|
print('cppcheck finished with ' + str(returncode) + ('' if sig_num == -1 else ' (signal ' + str(sig_num) + ')'))
|
|
|
|
# generate stack trace for SIGSEGV, SIGABRT, SIGILL, SIGFPE, SIGBUS
|
|
|
|
if returncode in (-11,-6,-4,-8,-7) or sig_num in (11,6,4,8,7):
|
2018-12-01 13:25:48 +01:00
|
|
|
print('Crash!')
|
2019-03-28 15:49:20 +01:00
|
|
|
stacktrace = ''
|
2019-06-30 12:43:05 +02:00
|
|
|
if cppcheck_path == 'cppcheck':
|
2019-03-28 15:49:20 +01:00
|
|
|
# re-run within gdb to get a stacktrace
|
|
|
|
cmd = 'gdb --batch --eval-command=run --eval-command=bt --return-child-result --args ' + cppcheck_cmd + " -j1"
|
2019-07-15 15:37:31 +02:00
|
|
|
dummy, stdout, stderr, elapsed_time = run_command(cmd)
|
2019-03-28 15:49:20 +01:00
|
|
|
gdb_pos = stdout.find(" received signal")
|
|
|
|
if not gdb_pos == -1:
|
|
|
|
last_check_pos = stdout.rfind('Checking ', 0, gdb_pos)
|
|
|
|
if last_check_pos == -1:
|
|
|
|
stacktrace = stdout[gdb_pos:]
|
|
|
|
else:
|
|
|
|
stacktrace = stdout[last_check_pos:]
|
2019-07-15 15:37:31 +02:00
|
|
|
return returncode, stacktrace, '', returncode, options
|
2019-03-29 06:53:40 +01:00
|
|
|
if returncode != 0:
|
|
|
|
print('Error!')
|
2019-04-03 09:27:04 +02:00
|
|
|
if returncode > 0:
|
|
|
|
returncode = -100-returncode
|
|
|
|
return returncode, stdout, '', returncode, options
|
2019-03-29 06:53:40 +01:00
|
|
|
if stderr.find('Internal error: Child process crashed with signal ') > 0:
|
|
|
|
print('Error!')
|
|
|
|
s = 'Internal error: Child process crashed with signal '
|
|
|
|
pos1 = stderr.find(s)
|
|
|
|
pos2 = stderr.find(' [cppcheckError]', pos1)
|
|
|
|
signr = int(stderr[pos1+len(s):pos2])
|
|
|
|
return -signr, '', '', -signr, options
|
|
|
|
if stderr.find('#### ThreadExecutor') > 0:
|
|
|
|
print('Thread!')
|
2019-04-03 09:27:04 +02:00
|
|
|
return -222, '', '', -222, options
|
2019-02-12 09:47:57 +01:00
|
|
|
information_messages_list = []
|
|
|
|
issue_messages_list = []
|
2018-08-24 13:20:38 +02:00
|
|
|
count = 0
|
2018-08-29 22:07:48 +02:00
|
|
|
for line in stderr.split('\n'):
|
2019-01-22 15:27:13 +01:00
|
|
|
if ': information: ' in line:
|
2019-02-10 08:57:35 +01:00
|
|
|
information_messages_list.append(line + '\n')
|
2019-02-04 11:25:29 +01:00
|
|
|
elif line:
|
2019-02-10 08:57:35 +01:00
|
|
|
issue_messages_list.append(line + '\n')
|
2019-02-04 11:25:29 +01:00
|
|
|
if re.match(r'.*:[0-9]+:.*\]$', line):
|
|
|
|
count += 1
|
2018-08-26 16:23:42 +02:00
|
|
|
print('Number of issues: ' + str(count))
|
2019-06-30 12:43:05 +02:00
|
|
|
return count, ''.join(issue_messages_list), ''.join(information_messages_list), elapsed_time, options
|
2018-08-23 21:31:02 +02:00
|
|
|
|
2018-08-24 21:07:50 +02:00
|
|
|
|
2019-06-30 12:43:05 +02:00
|
|
|
def split_results(results):
|
2018-11-18 16:26:56 +01:00
|
|
|
ret = []
|
|
|
|
w = None
|
|
|
|
for line in results.split('\n'):
|
|
|
|
if line.endswith(']') and re.search(r': (error|warning|style|performance|portability|information|debug):', line):
|
|
|
|
if w is not None:
|
|
|
|
ret.append(w.strip())
|
|
|
|
w = ''
|
|
|
|
if w is not None:
|
2018-12-06 17:36:22 +01:00
|
|
|
w += ' ' * 5 + line + '\n'
|
2018-11-18 16:26:56 +01:00
|
|
|
if w is not None:
|
|
|
|
ret.append(w.strip())
|
|
|
|
return ret
|
|
|
|
|
2019-01-09 16:01:53 +01:00
|
|
|
|
2019-06-30 12:43:05 +02:00
|
|
|
def diff_results(work_path, ver1, results1, ver2, results2):
|
2018-08-26 13:42:01 +02:00
|
|
|
print('Diff results..')
|
|
|
|
ret = ''
|
2019-06-30 12:43:05 +02:00
|
|
|
r1 = sorted(split_results(results1))
|
|
|
|
r2 = sorted(split_results(results2))
|
2018-08-26 13:42:01 +02:00
|
|
|
i1 = 0
|
|
|
|
i2 = 0
|
|
|
|
while i1 < len(r1) and i2 < len(r2):
|
|
|
|
if r1[i1] == r2[i2]:
|
|
|
|
i1 += 1
|
|
|
|
i2 += 1
|
|
|
|
elif r1[i1] < r2[i2]:
|
|
|
|
ret += ver1 + ' ' + r1[i1] + '\n'
|
|
|
|
i1 += 1
|
|
|
|
else:
|
|
|
|
ret += ver2 + ' ' + r2[i2] + '\n'
|
|
|
|
i2 += 1
|
|
|
|
while i1 < len(r1):
|
|
|
|
ret += ver1 + ' ' + r1[i1] + '\n'
|
|
|
|
i1 += 1
|
|
|
|
while i2 < len(r2):
|
|
|
|
ret += ver2 + ' ' + r2[i2] + '\n'
|
|
|
|
i2 += 1
|
2018-11-18 16:26:56 +01:00
|
|
|
|
2018-08-26 13:42:01 +02:00
|
|
|
return ret
|
|
|
|
|
|
|
|
|
2019-06-30 12:43:05 +02:00
|
|
|
def send_all(connection, data):
|
2018-12-03 09:02:05 +01:00
|
|
|
bytes = data.encode('ascii', 'ignore')
|
2018-08-25 20:34:43 +02:00
|
|
|
while bytes:
|
|
|
|
num = connection.send(bytes)
|
|
|
|
if num < len(bytes):
|
|
|
|
bytes = bytes[num:]
|
2018-08-25 10:25:05 +02:00
|
|
|
else:
|
2018-08-25 20:34:43 +02:00
|
|
|
bytes = None
|
2018-08-25 10:25:05 +02:00
|
|
|
|
|
|
|
|
2019-06-30 12:43:05 +02:00
|
|
|
def upload_results(package, results, server_address):
|
2019-04-05 14:11:03 +02:00
|
|
|
print('Uploading results.. ' + str(len(results)) + ' bytes')
|
2019-04-02 10:23:34 +02:00
|
|
|
max_retries = 4
|
|
|
|
for retry in range(max_retries):
|
2018-11-27 12:05:21 +01:00
|
|
|
try:
|
|
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
sock.connect(server_address)
|
2019-04-02 10:23:34 +02:00
|
|
|
cmd = 'write\n'
|
2019-06-30 12:43:05 +02:00
|
|
|
send_all(sock, cmd + package + '\n' + results + '\nDONE')
|
2018-11-27 12:05:21 +01:00
|
|
|
sock.close()
|
2019-01-22 15:27:13 +01:00
|
|
|
print('Results have been successfully uploaded.')
|
|
|
|
return True
|
2019-04-02 10:23:34 +02:00
|
|
|
except socket.error as err:
|
|
|
|
print('Upload error: ' + str(err))
|
|
|
|
if retry < (max_retries - 1):
|
|
|
|
print('Retrying upload in 30 seconds')
|
|
|
|
time.sleep(30)
|
2019-01-22 15:27:13 +01:00
|
|
|
print('Upload permanently failed!')
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
2019-06-30 12:43:05 +02:00
|
|
|
def upload_info(package, info_output, server_address):
|
2019-04-05 14:11:03 +02:00
|
|
|
print('Uploading information output.. ' + str(len(info_output)) + ' bytes')
|
2019-04-02 10:23:34 +02:00
|
|
|
max_retries = 3
|
|
|
|
for retry in range(max_retries):
|
2019-01-22 15:27:13 +01:00
|
|
|
try:
|
|
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
sock.connect(server_address)
|
2019-06-30 12:43:05 +02:00
|
|
|
send_all(sock, 'write_info\n' + package + '\n' + info_output + '\nDONE')
|
2019-01-22 15:27:13 +01:00
|
|
|
sock.close()
|
|
|
|
print('Information output has been successfully uploaded.')
|
2018-11-28 19:30:14 +01:00
|
|
|
return True
|
2019-04-02 10:23:34 +02:00
|
|
|
except socket.error as err:
|
|
|
|
print('Upload error: ' + str(err))
|
|
|
|
if retry < (max_retries - 1):
|
|
|
|
print('Retrying upload in 30 seconds')
|
|
|
|
time.sleep(30)
|
2019-01-22 15:27:13 +01:00
|
|
|
print('Upload permanently failed!')
|
2018-11-28 19:30:14 +01:00
|
|
|
return False
|
2018-08-23 21:31:02 +02:00
|
|
|
|
2019-01-09 16:01:53 +01:00
|
|
|
|
2019-09-09 13:42:57 +02:00
|
|
|
def get_libraries():
|
|
|
|
libraries = ['posix', 'gnu']
|
|
|
|
library_includes = {'boost': ['<boost/'],
|
2019-09-19 08:48:04 +02:00
|
|
|
# 'cairo': ['<cairo.h>'], <= enable after release of version 1.90
|
2019-09-09 13:42:57 +02:00
|
|
|
'cppunit': ['<cppunit/'],
|
|
|
|
'googletest': ['<gtest/gtest.h>'],
|
|
|
|
'gtk': ['<gtk/gtk.h>', '<glib.h>', '<glib/', '<gnome.h>'],
|
|
|
|
'libcerror': ['<libcerror.h>'],
|
|
|
|
'libcurl': ['<curl/curl.h>'],
|
|
|
|
'lua': ['<lua.h>', '"lua.h"'],
|
|
|
|
'microsoft_sal': ['<sal.h>'],
|
|
|
|
'motif': ['<X11/', '<Xm/'],
|
|
|
|
'nspr': ['<prtypes.h>', '"prtypes.h"'],
|
|
|
|
'opengl': ['<GL/gl.h>', '<GL/glu.h>', '<GL/glut.h>'],
|
|
|
|
'openmp': ['<omp.h>'],
|
|
|
|
'python': ['<Python.h>', '"Python.h"'],
|
2019-09-12 15:14:18 +02:00
|
|
|
'qt': ['<QApplication>', '<QList>', '<qlist.h>', '<QObject>', '<QString>', '<qstring.h>', '<QWidget>', '<QtWidgets>', '<QtGui'],
|
2019-09-09 13:42:57 +02:00
|
|
|
'ruby': ['<ruby.h>', '<ruby/', '"ruby.h"'],
|
|
|
|
'sdl': ['<SDL.h>', '<SDL/SDL.h>', '<SDL2/SDL.h>'],
|
|
|
|
'sqlite3': ['<sqlite3.h>', '"sqlite3.h"'],
|
|
|
|
'tinyxml2': ['<tinyxml2', '"tinyxml2'],
|
|
|
|
'wxwidgets': ['<wx/', '"wx/'],
|
|
|
|
'zlib': ['<zlib.h>'],
|
|
|
|
}
|
|
|
|
for library, includes in library_includes.items():
|
|
|
|
if has_include('temp', includes):
|
|
|
|
libraries.append(library)
|
|
|
|
return libraries
|
|
|
|
|
|
|
|
|
2018-08-31 14:28:42 +02:00
|
|
|
jobs = '-j1'
|
2019-06-30 12:43:05 +02:00
|
|
|
stop_time = None
|
|
|
|
work_path = os.path.expanduser('~/cppcheck-donate-cpu-workfolder')
|
|
|
|
package_url = None
|
2019-09-06 22:28:34 +02:00
|
|
|
server_address = ('cppcheck1.osuosl.org', 8000)
|
2019-01-18 21:36:03 +01:00
|
|
|
bandwidth_limit = None
|
2019-03-01 22:34:50 +01:00
|
|
|
max_packages = None
|
2019-03-28 15:49:20 +01:00
|
|
|
do_upload = True
|
2018-08-29 11:08:56 +02:00
|
|
|
for arg in sys.argv[1:]:
|
|
|
|
# --stop-time=12:00 => run until ~12:00 and then stop
|
|
|
|
if arg.startswith('--stop-time='):
|
2019-06-30 12:43:05 +02:00
|
|
|
stop_time = arg[-5:]
|
|
|
|
print('Stop time:' + stop_time)
|
2018-08-31 14:28:42 +02:00
|
|
|
elif arg.startswith('-j'):
|
|
|
|
jobs = arg
|
|
|
|
print('Jobs:' + jobs[2:])
|
2018-11-19 10:04:53 +01:00
|
|
|
elif arg.startswith('--package='):
|
2019-06-30 12:43:05 +02:00
|
|
|
package_url = arg[arg.find('=')+1:]
|
|
|
|
print('Package:' + package_url)
|
2018-08-31 14:28:42 +02:00
|
|
|
elif arg.startswith('--work-path='):
|
2019-06-30 12:43:05 +02:00
|
|
|
work_path = arg[arg.find('=')+1:]
|
|
|
|
print('work_path:' + work_path)
|
|
|
|
if not os.path.exists(work_path):
|
2018-08-31 14:28:42 +02:00
|
|
|
print('work path does not exist!')
|
|
|
|
sys.exit(1)
|
2019-01-03 13:42:33 +01:00
|
|
|
elif arg == '--test':
|
|
|
|
server_address = ('localhost', 8001)
|
2019-01-18 21:36:03 +01:00
|
|
|
elif arg.startswith('--bandwidth-limit='):
|
|
|
|
bandwidth_limit = arg[arg.find('=')+1:]
|
2019-03-01 22:34:50 +01:00
|
|
|
elif arg.startswith('--max-packages='):
|
|
|
|
arg_value = arg[arg.find('=')+1:]
|
|
|
|
try:
|
|
|
|
max_packages = int(arg_value)
|
|
|
|
except ValueError:
|
|
|
|
max_packages = None
|
|
|
|
if max_packages < 0:
|
|
|
|
max_packages = None
|
|
|
|
if max_packages is None:
|
|
|
|
print('Error: Max. packages value "{}" is invalid. Must be a positive number or 0.'.format(arg_value))
|
|
|
|
sys.exit(1)
|
|
|
|
# 0 means infinitely, no counting needed.
|
|
|
|
if max_packages == 0:
|
|
|
|
max_packages = None
|
2019-03-28 15:49:20 +01:00
|
|
|
elif arg.startswith('--no-upload'):
|
|
|
|
do_upload = False
|
2018-08-31 14:28:42 +02:00
|
|
|
elif arg == '--help':
|
|
|
|
print('Donate CPU to Cppcheck project')
|
|
|
|
print('')
|
2018-09-06 13:15:54 +02:00
|
|
|
print('Syntax: donate-cpu.py [-jN] [--stop-time=HH:MM] [--work-path=path]')
|
2018-08-31 14:28:42 +02:00
|
|
|
print(' -jN Use N threads in compilation/analysis. Default is 1.')
|
2018-12-20 21:15:42 +01:00
|
|
|
print(' --package=url Check a specific package and then stop. Can be useful if you want to reproduce')
|
|
|
|
print(' some warning/crash/exception/etc..')
|
2018-08-31 14:28:42 +02:00
|
|
|
print(' --stop-time=HH:MM Stop analysis when time has passed. Default is that you must terminate the script.')
|
2019-06-30 12:43:05 +02:00
|
|
|
print(' --work-path=path Work folder path. Default path is ' + work_path)
|
2019-01-18 21:36:03 +01:00
|
|
|
print(' --bandwidth-limit=limit Limit download rate for packages. Format for limit is the same that wget uses.')
|
|
|
|
print(' Examples: --bandwidth-limit=250k => max. 250 kilobytes per second')
|
|
|
|
print(' --bandwidth-limit=2m => max. 2 megabytes per second')
|
2019-03-01 22:34:50 +01:00
|
|
|
print(' --max-packages=N Process N packages and then exit. A value of 0 means infinitely.')
|
2019-03-28 15:49:20 +01:00
|
|
|
print(' --no-upload Do not upload anything. Defaults to False.')
|
2018-08-31 14:28:42 +02:00
|
|
|
print('')
|
|
|
|
print('Quick start: just run this script without any arguments')
|
|
|
|
sys.exit(0)
|
|
|
|
else:
|
|
|
|
print('Unhandled argument: ' + arg)
|
|
|
|
sys.exit(1)
|
|
|
|
|
2018-08-23 21:31:02 +02:00
|
|
|
print('Thank you!')
|
2019-06-30 12:43:05 +02:00
|
|
|
if not check_requirements():
|
2018-08-23 21:31:02 +02:00
|
|
|
sys.exit(1)
|
2019-01-19 12:45:08 +01:00
|
|
|
if bandwidth_limit and isinstance(bandwidth_limit, str):
|
2019-09-06 22:28:34 +02:00
|
|
|
if subprocess.call(['wget', '--limit-rate=' + bandwidth_limit, '-q', '--spider', 'cppcheck1.osuosl.org']) is 2:
|
2019-01-19 12:45:08 +01:00
|
|
|
print('Error: Bandwidth limit value "' + bandwidth_limit + '" is invalid.')
|
|
|
|
sys.exit(1)
|
|
|
|
else:
|
|
|
|
print('Bandwidth-limit: ' + bandwidth_limit)
|
2019-06-30 12:43:05 +02:00
|
|
|
if package_url:
|
2019-03-28 15:49:20 +01:00
|
|
|
max_packages = 1
|
2019-03-01 22:34:50 +01:00
|
|
|
if max_packages:
|
|
|
|
print('Maximum number of packages to download and analyze: {}'.format(max_packages))
|
2019-06-30 12:43:05 +02:00
|
|
|
if not os.path.exists(work_path):
|
|
|
|
os.mkdir(work_path)
|
|
|
|
cppcheck_path = os.path.join(work_path, 'cppcheck')
|
2019-03-01 22:34:50 +01:00
|
|
|
packages_processed = 0
|
2018-08-23 21:31:02 +02:00
|
|
|
while True:
|
2019-03-01 22:34:50 +01:00
|
|
|
if max_packages:
|
|
|
|
if packages_processed >= max_packages:
|
|
|
|
print('Processed the specified number of {} package(s). Exiting now.'.format(max_packages))
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
print('Processing package {} of the specified {} package(s).'.format(packages_processed + 1, max_packages))
|
|
|
|
packages_processed += 1
|
2019-06-30 12:43:05 +02:00
|
|
|
if stop_time:
|
|
|
|
print('stop_time:' + stop_time + '. Time:' + time.strftime('%H:%M') + '.')
|
|
|
|
if stop_time < time.strftime('%H:%M'):
|
2018-08-29 11:08:56 +02:00
|
|
|
print('Stopping. Thank you!')
|
|
|
|
sys.exit(0)
|
2019-08-27 06:48:00 +02:00
|
|
|
if not get_cppcheck(cppcheck_path, work_path):
|
2018-08-26 16:23:42 +02:00
|
|
|
print('Failed to clone Cppcheck, retry later')
|
|
|
|
sys.exit(1)
|
2019-06-30 12:43:05 +02:00
|
|
|
cppcheck_versions = get_cppcheck_versions(server_address)
|
|
|
|
if cppcheck_versions is None:
|
2018-12-08 11:39:44 +01:00
|
|
|
print('Failed to communicate with server, retry later')
|
|
|
|
sys.exit(1)
|
2019-06-30 12:43:05 +02:00
|
|
|
if len(cppcheck_versions) == 0:
|
2019-04-04 13:09:35 +02:00
|
|
|
print('Did not get any cppcheck versions from server, retry later')
|
|
|
|
sys.exit(1)
|
2019-06-30 12:43:05 +02:00
|
|
|
for ver in cppcheck_versions:
|
2018-11-29 21:19:45 +01:00
|
|
|
if ver == 'head':
|
2019-06-30 12:43:05 +02:00
|
|
|
if not compile(cppcheck_path, jobs):
|
2018-11-29 21:19:45 +01:00
|
|
|
print('Failed to compile Cppcheck, retry later')
|
|
|
|
sys.exit(1)
|
2019-06-30 12:43:05 +02:00
|
|
|
elif not compile_version(work_path, jobs, ver):
|
2018-11-29 21:19:45 +01:00
|
|
|
print('Failed to compile Cppcheck-{}, retry later'.format(ver))
|
|
|
|
sys.exit(1)
|
2019-06-30 12:43:05 +02:00
|
|
|
if package_url:
|
|
|
|
package = package_url
|
2018-11-19 10:04:53 +01:00
|
|
|
else:
|
2019-06-30 12:43:05 +02:00
|
|
|
package = get_package(server_address)
|
2018-11-19 10:04:53 +01:00
|
|
|
while len(package) == 0:
|
|
|
|
print("network or server might be temporarily down.. will try again in 30 seconds..")
|
|
|
|
time.sleep(30)
|
2019-06-30 12:43:05 +02:00
|
|
|
package = get_package(server_address)
|
|
|
|
tgz = download_package(work_path, package, bandwidth_limit)
|
2019-03-28 15:49:20 +01:00
|
|
|
if tgz is None:
|
|
|
|
print("No package downloaded")
|
|
|
|
continue
|
2019-06-30 12:43:05 +02:00
|
|
|
if not unpack_package(work_path, tgz):
|
2019-04-03 09:27:04 +02:00
|
|
|
print("No files to process")
|
|
|
|
continue
|
2018-08-29 22:07:48 +02:00
|
|
|
crash = False
|
2018-08-29 06:51:33 +02:00
|
|
|
count = ''
|
2019-06-30 12:43:05 +02:00
|
|
|
elapsed_time = ''
|
|
|
|
results_to_diff = []
|
2019-01-09 18:38:00 +01:00
|
|
|
cppcheck_options = ''
|
2019-01-22 15:27:13 +01:00
|
|
|
head_info_msg = ''
|
2019-09-05 09:08:45 +02:00
|
|
|
cppcheck_head_info = '';
|
2019-09-09 13:42:57 +02:00
|
|
|
libraries = get_libraries();
|
|
|
|
|
2019-06-30 12:43:05 +02:00
|
|
|
for ver in cppcheck_versions:
|
2018-11-29 21:19:45 +01:00
|
|
|
if ver == 'head':
|
2019-02-04 11:05:14 +01:00
|
|
|
current_cppcheck_dir = 'cppcheck'
|
2019-09-05 09:08:45 +02:00
|
|
|
cppcheck_head_info = get_cppcheck_info(work_path + '/cppcheck')
|
2018-11-29 21:19:45 +01:00
|
|
|
else:
|
2019-02-04 11:05:14 +01:00
|
|
|
current_cppcheck_dir = ver
|
2019-09-09 13:42:57 +02:00
|
|
|
c, errout, info, t, cppcheck_options = scan_package(work_path, current_cppcheck_dir, jobs, libraries)
|
2018-08-29 22:07:48 +02:00
|
|
|
if c < 0:
|
2019-07-26 10:15:09 +02:00
|
|
|
if c == -101 and 'error: could not find or open any of the paths given.' in errout:
|
|
|
|
# No sourcefile found (for example only headers present)
|
|
|
|
count += ' 0'
|
|
|
|
else:
|
|
|
|
crash = True
|
|
|
|
count += ' Crash!'
|
2018-08-29 22:07:48 +02:00
|
|
|
else:
|
|
|
|
count += ' ' + str(c)
|
2019-06-30 12:43:05 +02:00
|
|
|
elapsed_time += " {:.1f}".format(t)
|
|
|
|
results_to_diff.append(errout)
|
2019-01-22 15:27:13 +01:00
|
|
|
if ver == 'head':
|
|
|
|
head_info_msg = info
|
2019-02-09 22:05:12 +01:00
|
|
|
|
2019-01-09 18:38:00 +01:00
|
|
|
output = 'cppcheck-options: ' + cppcheck_options + '\n'
|
2019-01-11 20:07:11 +01:00
|
|
|
output += 'platform: ' + platform.platform() + '\n'
|
|
|
|
output += 'python: ' + platform.python_version() + '\n'
|
2019-02-09 09:27:54 +01:00
|
|
|
output += 'client-version: ' + CLIENT_VERSION + '\n'
|
2019-06-30 12:43:05 +02:00
|
|
|
output += 'cppcheck: ' + ' '.join(cppcheck_versions) + '\n'
|
2019-09-05 09:08:45 +02:00
|
|
|
output += 'head-info: ' + cppcheck_head_info + '\n'
|
2018-08-29 06:51:33 +02:00
|
|
|
output += 'count:' + count + '\n'
|
2019-06-30 12:43:05 +02:00
|
|
|
output += 'elapsed-time:' + elapsed_time + '\n'
|
2019-01-22 15:27:13 +01:00
|
|
|
info_output = output
|
|
|
|
info_output += 'info messages:\n' + head_info_msg
|
2019-06-30 12:43:05 +02:00
|
|
|
if 'head' in cppcheck_versions:
|
|
|
|
output += 'head results:\n' + results_to_diff[cppcheck_versions.index('head')]
|
2018-08-29 22:07:48 +02:00
|
|
|
if not crash:
|
2019-06-30 12:43:05 +02:00
|
|
|
output += 'diff:\n' + diff_results(work_path, cppcheck_versions[0], results_to_diff[0], cppcheck_versions[1], results_to_diff[1]) + '\n'
|
|
|
|
if package_url:
|
2018-11-19 10:04:53 +01:00
|
|
|
print('=========================================================')
|
|
|
|
print(output)
|
|
|
|
print('=========================================================')
|
2019-04-03 09:27:04 +02:00
|
|
|
print(info_output)
|
|
|
|
print('=========================================================')
|
2019-03-28 15:49:20 +01:00
|
|
|
if do_upload:
|
2019-06-30 12:43:05 +02:00
|
|
|
upload_results(package, output, server_address)
|
|
|
|
upload_info(package, info_output, server_address)
|
2019-03-01 22:34:50 +01:00
|
|
|
if not max_packages or packages_processed < max_packages:
|
|
|
|
print('Sleep 5 seconds..')
|
|
|
|
time.sleep(5)
|