6
Botan is released under the Simplified BSD License (see license.txt)
15
import optparse # pylint: disable=deprecated-module
17
def get_concurrency():
19
Get default concurrency level of build
24
import multiprocessing
25
return max(def_concurrency, multiprocessing.cpu_count())
27
return def_concurrency
29
def determine_flags(target, target_os, target_cpu, target_cc, cc_bin, ccache, root_dir, pkcs11_lib):
30
# pylint: disable=too-many-branches,too-many-statements,too-many-arguments,too-many-locals
33
Return the configure.py flags as well as make/test running prefixes
35
is_cross_target = target.startswith('cross-')
37
if target_os not in ['linux', 'osx', 'windows']:
38
print('Error unknown OS %s' % (target_os))
39
return (None, None, None)
42
if target_os == 'osx':
44
elif target == 'cross-win32':
49
test_cmd = [os.path.join(root_dir, 'botan-test')]
51
fast_tests = ['block', 'aead', 'hash', 'stream', 'mac', 'modes',
52
'hmac_drbg', 'hmac_drbg_unit',
54
'rsa_sign', 'rsa_verify', 'dh_kat', 'ecdsa_sign', 'curve25519_scalar',
55
'simd_32', 'os_utils', 'util', 'util_dates']
57
install_prefix = os.path.join(tempfile.gettempdir(), 'botan-install')
58
flags = ['--prefix=%s' % (install_prefix),
59
'--cc=%s' % (target_cc),
60
'--os=%s' % (target_os)]
62
if target_cpu != None:
63
flags += ['--cpu=%s' % (target_cpu)]
65
if target in ['shared', 'mini-shared']:
66
flags += ['--disable-static']
68
if target in ['static', 'mini-static', 'fuzzers'] or target_os in ['ios', 'mingw']:
69
flags += ['--disable-shared']
71
if target in ['mini-static', 'mini-shared']:
72
flags += ['--minimized-build', '--enable-modules=system_rng,sha2_32,sha2_64,aes']
74
if target == 'shared' and target_os != 'osx':
75
# Enabling amalgamation build for shared is somewhat arbitrary, but we want to test it
76
# somewhere. In addition the majority of the Windows builds are shared, and MSVC is
77
# much faster compiling via the amalgamation than individual files.
78
flags += ['--amalgamation']
80
if target in ['bsi', 'nist']:
81
# Arbitrarily test disable static on module policy builds
82
flags += ['--module-policy=%s' % (target), '--disable-static']
85
flags += ['--with-doxygen', '--with-sphinx', '--with-rst2man']
88
if target == 'coverage':
89
flags += ['--with-coverage-info']
90
if target == 'valgrind':
91
flags += ['--with-valgrind']
92
test_prefix = ['valgrind', '--error-exitcode=9', '-v', '--leak-check=full', '--show-reachable=yes']
93
test_cmd += fast_tests
94
if target == 'fuzzers':
95
flags += ['--unsafe-fuzzer-mode']
97
if target in ['fuzzers', 'coverage', 'valgrind']:
98
flags += ['--with-debug-info']
99
if target in ['fuzzers', 'coverage']:
100
flags += ['--build-fuzzers=test']
101
if target in ['fuzzers', 'sanitizer']:
103
# On VC iterator debugging comes from generic debug mode
104
if target_cc == 'msvc':
105
flags += ['--with-debug-info']
107
flags += ['--with-sanitizers']
108
if target in ['valgrind', 'sanitizer', 'fuzzers']:
109
flags += ['--disable-modules=locking_allocator']
111
if target == 'parallel':
112
flags += ['--with-openmp']
114
if target == 'sonar':
115
if target_os != 'linux' or target_cc != 'clang':
116
raise Exception('Only Linux/clang supported in Sonar target currently')
118
flags += ['--cc-abi-flags=-fprofile-instr-generate -fcoverage-mapping',
121
make_prefix = [os.path.join(root_dir, 'build-wrapper-linux-x86/build-wrapper-linux-x86-64'),
122
'--out-dir', 'bw-outputs']
125
if target_os == 'ios':
126
make_prefix = ['xcrun', '--sdk', 'iphoneos']
128
if target == 'cross-arm32':
129
flags += ['--cpu=armv7', '--cc-abi-flags=-arch armv7 -arch armv7s -stdlib=libc++']
130
elif target == 'cross-arm64':
131
flags += ['--cpu=armv8-a', '--cc-abi-flags=-arch arm64 -stdlib=libc++']
133
raise Exception("Unknown cross target '%s' for iOS" % (target))
134
elif target == 'cross-win32':
135
cc_bin = 'i686-w64-mingw32-g++'
136
flags += ['--cpu=x86_32', '--cc-abi-flags=-static', '--ar-command=i686-w64-mingw32-ar']
137
test_cmd = [os.path.join(root_dir, 'botan-test.exe')]
138
# No runtime prefix required for Wine
140
# Build everything but restrict what is run
141
test_cmd += fast_tests
143
if target == 'cross-arm32':
144
flags += ['--cpu=armv7']
145
cc_bin = 'arm-linux-gnueabihf-g++'
146
test_prefix = ['qemu-arm', '-L', '/usr/arm-linux-gnueabihf/']
147
elif target == 'cross-arm64':
148
flags += ['--cpu=armv8-a']
149
cc_bin = 'aarch64-linux-gnu-g++'
150
test_prefix = ['qemu-aarch64', '-L', '/usr/aarch64-linux-gnu/']
151
elif target == 'cross-ppc32':
152
flags += ['--cpu=ppc32']
153
cc_bin = 'powerpc-linux-gnu-g++'
154
test_prefix = ['qemu-ppc', '-L', '/usr/powerpc-linux-gnu/']
155
elif target == 'cross-ppc64':
156
flags += ['--cpu=ppc64', '--with-endian=little']
157
cc_bin = 'powerpc64le-linux-gnu-g++'
158
test_prefix = ['qemu-ppc64le', '-L', '/usr/powerpc64le-linux-gnu/']
160
raise Exception("Unknown cross target '%s' for Linux" % (target))
162
# Flags specific to native targets
164
if target_os in ['osx', 'linux']:
165
flags += ['--with-bzip2', '--with-sqlite', '--with-zlib']
167
if target_os == 'osx':
169
flags += ['--with-boost']
170
elif target_os == 'linux':
171
flags += ['--with-lzma']
173
if target_os == 'linux':
174
if target not in ['sanitizer', 'valgrind', 'mini-shared', 'mini-static']:
175
# Avoid OpenSSL when using dynamic checkers, or on OS X where it sporadically
176
# is not installed on the CI image
177
flags += ['--with-openssl']
179
if target in ['sonar', 'coverage']:
180
flags += ['--with-tpm']
181
test_cmd += ['--run-long-tests', '--run-online-tests']
182
if pkcs11_lib and os.access(pkcs11_lib, os.R_OK):
183
test_cmd += ['--pkcs11-lib=%s' % (pkcs11_lib)]
186
flags += ['--cc-bin=%s' % (cc_bin)]
187
elif ccache == 'clcache':
188
flags += ['--cc-bin=%s' % (ccache)]
190
flags += ['--cc-bin=%s %s' % (ccache, cc_bin)]
193
run_test_command = None
195
run_test_command = test_prefix + test_cmd
197
return flags, run_test_command, make_prefix
199
def run_cmd(cmd, root_dir):
201
Execute a command, die if it failed
203
print("Running '%s' ..." % (' '.join(cmd)))
208
cmd = [os.path.expandvars(elem) for elem in cmd]
209
sub_env = os.environ.copy()
210
sub_env['LD_LIBRARY_PATH'] = root_dir
212
redirect_stdout = None
213
if len(cmd) > 3 and cmd[-2] == '>':
214
redirect_stdout = open(cmd[-1], 'w')
216
proc = subprocess.Popen(cmd, close_fds=True, env=sub_env, stdout=redirect_stdout)
219
time_taken = int(time.time() - start)
222
print("Ran for %d seconds" % (time_taken))
224
if proc.returncode != 0:
225
print("Command failed with error code %d" % (proc.returncode))
226
sys.exit(proc.returncode)
228
def parse_args(args):
232
parser = optparse.OptionParser()
234
parser.add_option('--os', default=platform.system().lower(),
235
help='Set the target os (default %default)')
236
parser.add_option('--cc', default='gcc',
237
help='Set the target compiler type (default %default)')
238
parser.add_option('--cc-bin', default=None,
239
help='Set path to compiler')
240
parser.add_option('--root-dir', metavar='D', default='.',
241
help='Set directory to execute from (default %default)')
243
parser.add_option('--make-tool', metavar='TOOL', default='make',
244
help='Specify tool to run to build source (default %default)')
246
parser.add_option('--cpu', default=None,
247
help='Specify a target CPU platform')
249
parser.add_option('--with-debug', action='store_true', default=False,
250
help='Include debug information')
251
parser.add_option('--amalgamation', action='store_true', default=False,
252
help='Build via amalgamation')
253
parser.add_option('--disable-shared', action='store_true', default=False,
254
help='Disable building shared libraries')
256
parser.add_option('--branch', metavar='B', default=None,
257
help='Specify branch being built')
259
parser.add_option('--add-travis-folds', action='store_true', default=False,
260
help='Add fold markers for Travis UI')
262
parser.add_option('--dry-run', action='store_true', default=False,
263
help='Just show commands to be executed')
264
parser.add_option('--build-jobs', metavar='J', default=get_concurrency(),
265
help='Set number of jobs to run in parallel (default %default)')
267
parser.add_option('--compiler-cache', default=None,
268
help='Set a compiler cache to use (ccache, clcache)')
270
parser.add_option('--pkcs11-lib', default=None,
271
help='Set PKCS11 lib to use for testing')
273
parser.add_option('--with-python3', dest='use_python3', action='store_true', default=None,
274
help='Enable using python3')
275
parser.add_option('--without-python3', dest='use_python3', action='store_false',
276
help='Disable using python3')
278
return parser.parse_args(args)
282
Check if some named program exists in the path
284
for path in os.environ['PATH'].split(os.pathsep):
285
exe_file = os.path.join(path, prog)
286
if os.path.exists(exe_file) and os.access(exe_file, os.X_OK):
291
# pylint: disable=too-many-branches,too-many-statements,too-many-locals,too-many-return-statements
293
Parse options, do the things
296
if os.getenv('COVERITY_SCAN_BRANCH') == '1':
297
print('Skipping build COVERITY_SCAN_BRANCH set in environment')
300
(options, args) = parse_args(args or sys.argv)
303
print('Usage: %s [options] target' % (args[0]))
310
use_python2 = have_prog('python2')
312
if options.use_python3 is None:
313
use_python3 = have_prog('python3')
315
use_python3 = options.use_python3
317
py_interp = 'python3'
319
if options.cc_bin is None:
320
if options.cc == 'gcc':
321
options.cc_bin = 'g++'
322
elif options.cc == 'clang':
323
options.cc_bin = 'clang++'
324
elif options.cc == 'msvc':
325
options.cc_bin = 'cl'
327
print('Error unknown compiler %s' % (options.cc))
330
if options.compiler_cache is None and options.cc != 'msvc':
331
# Autodetect ccache, unless using clang profiling - ccache seems to misbehave there
332
if have_prog('ccache') and target not in ['sonar']:
333
options.compiler_cache = 'ccache'
335
if options.compiler_cache == 'clcache' and target in ['sanitizer']:
336
# clcache doesn't support /Zi so using it just adds overhead with
338
options.compiler_cache = None
340
if target == 'sonar' and os.getenv('SONAR_TOKEN') is None:
341
print('Skipping Sonar scan due to missing SONAR_TOKEN env variable')
344
root_dir = options.root_dir
346
if os.access(root_dir, os.R_OK) != True:
347
raise Exception('Bad root dir setting, dir %s not readable' % (root_dir))
353
if not use_python2 and not use_python3:
354
raise Exception('No python interpreters found cannot lint')
356
pylint_rc = '--rcfile=%s' % (os.path.join(root_dir, 'src/configs/pylint.rc'))
357
pylint_flags = [pylint_rc, '--reports=no', '--score=no']
359
# Some disabled rules specific to Python2
360
# superfluous-parens: needed for Python3 compatible print statements
361
# too-many-locals: variable counting differs from pylint3
362
py2_flags = '--disable=superfluous-parens,too-many-locals'
366
'src/python/botan2.py',
367
'src/scripts/ci_build.py',
368
'src/scripts/install.py',
369
'src/scripts/dist.py',
370
'src/scripts/cleanup.py',
371
'src/scripts/build_docs.py',
372
'src/scripts/website.py',
373
'src/scripts/bench.py',
374
'src/scripts/python_unittests.py',
375
'src/scripts/python_unittests_unix.py']
377
for target in py_scripts:
378
target_path = os.path.join(root_dir, target)
381
cmds.append(['python2', '-m', 'pylint'] + pylint_flags + [py2_flags, target_path])
384
cmds.append(['python3', '-m', 'pylint'] + pylint_flags + [target_path])
387
config_flags, run_test_command, make_prefix = determine_flags(
388
target, options.os, options.cpu, options.cc,
389
options.cc_bin, options.compiler_cache, root_dir,
392
cmds.append([py_interp, os.path.join(root_dir, 'configure.py')] + config_flags)
394
make_cmd = [options.make_tool]
396
make_cmd += ['-C', root_dir]
397
if options.build_jobs > 1:
398
make_cmd += ['-j%d' % (options.build_jobs)]
402
cmds.append(make_cmd + ['docs'])
404
if options.compiler_cache == 'ccache':
405
cmds.append(['ccache', '--show-stats'])
406
elif options.compiler_cache == 'clcache':
407
cmds.append(['clcache', '-s'])
409
make_targets = ['libs', 'cli', 'tests']
410
if target in ['coverage', 'fuzzers']:
411
make_targets += ['fuzzers', 'fuzzer_corpus_zip']
413
cmds.append(make_prefix + make_cmd + make_targets)
415
if options.compiler_cache == 'ccache':
416
cmds.append(['ccache', '--show-stats'])
417
elif options.compiler_cache == 'clcache':
418
cmds.append(['clcache', '-s'])
420
if run_test_command != None:
421
cmds.append(run_test_command)
423
if target in ['coverage', 'fuzzers']:
424
cmds.append([py_interp, os.path.join(root_dir, 'src/scripts/test_fuzzers.py'),
425
os.path.join(root_dir, 'fuzzer_corpus'),
426
os.path.join(root_dir, 'build/fuzzer')])
428
if target in ['static', 'shared'] and options.os != 'windows':
429
botan_exe = os.path.join(root_dir, 'botan-cli.exe' if options.os == 'windows' else 'botan')
430
cmds.append([py_interp,
431
os.path.join(root_dir, 'src/scripts/cli_tests.py'),
434
botan_py = os.path.join(root_dir, 'src/python/botan2.py')
436
if target in ['shared', 'coverage']:
439
cmds.append(['python2', botan_py])
441
cmds.append(['python3', botan_py])
443
if target in ['shared', 'static', 'bsi', 'nist']:
444
cmds.append(make_cmd + ['install'])
446
if target in ['sonar']:
448
cmds.append(['llvm-profdata', 'merge', '-sparse', 'default.profraw', '-o', 'botan.profdata'])
449
cmds.append(['llvm-cov', 'show', './botan-test',
450
'-instr-profile=botan.profdata',
451
'>', 'build/cov_report.txt'])
452
sonar_config = os.path.join(root_dir, os.path.join(root_dir, 'src/build-data/sonar-project.properties'))
453
cmds.append(['sonar-scanner',
454
'-Dproject.settings=%s' % (sonar_config),
455
'-Dsonar.login=$SONAR_TOKEN'])
457
if target in ['coverage']:
459
if not have_prog('lcov'):
460
print('Error: lcov not found in PATH (%s)' % (os.getenv('PATH')))
463
if not have_prog('gcov'):
464
print('Error: gcov not found in PATH (%s)' % (os.getenv('PATH')))
467
cov_file = 'coverage.info'
468
raw_cov_file = 'coverage.info.raw'
470
cmds.append(['lcov', '--capture', '--directory', options.root_dir,
471
'--output-file', raw_cov_file])
472
cmds.append(['lcov', '--remove', raw_cov_file, '/usr/*', '--output-file', cov_file])
473
cmds.append(['lcov', '--list', cov_file])
475
if have_prog('coverage'):
476
cmds.append(['coverage', 'run', '--branch',
477
'--rcfile', os.path.join(root_dir, 'src/configs/coverage.rc'),
480
if have_prog('codecov'):
481
# If codecov exists assume we are on Travis and report to codecov.io
482
cmds.append(['codecov'])
484
# Otherwise generate a local HTML report
485
cmds.append(['genhtml', cov_file, '--output-directory', 'lcov-out'])
487
cmds.append(make_cmd + ['clean'])
488
cmds.append(make_cmd + ['distclean'])
492
print('$ ' + ' '.join(cmd))
494
run_cmd(cmd, root_dir)
498
if __name__ == '__main__':