#!/usr/bin/env python import os import subprocess import sys from waflib import Build, Logs, Options from waflib.extras import autowaf # Library and package version (UNIX style major, minor, micro) # major increment <=> incompatible changes # minor increment <=> compatible changes (additions) # micro increment <=> no interface changes ZIX_VERSION = '0.0.2' ZIX_MAJOR_VERSION = '0' # Mandatory waf variables APPNAME = 'zix' # Package name for waf dist VERSION = ZIX_VERSION # Package version for waf dist top = '.' # Source directory out = 'build' # Build directory def options(ctx): ctx.load('compiler_c') opt = ctx.get_option_group('Configuration options') ctx.add_flags(opt, {'bench': 'build benchmarks', 'static': 'build static library'}) opt.add_option('--page-size', type='int', default=4096, dest='page_size', help='Page size for B-tree') def configure(conf): conf.load('compiler_c', cache=True) conf.load('autowaf', cache=True) autowaf.set_c_lang(conf, 'c99') conf.env.update({ 'BUILD_BENCH': Options.options.bench, 'BUILD_STATIC': Options.options.static}) if Options.options.strict: # Check for programs used by lint target conf.find_program("flake8", var="FLAKE8", mandatory=False) conf.find_program("clang-tidy", var="CLANG_TIDY", mandatory=False) conf.find_program("iwyu_tool", var="IWYU_TOOL", mandatory=False) if Options.options.ultra_strict: autowaf.add_compiler_flags(conf.env, '*', { 'clang': [ '-Wno-bad-function-cast', '-Wno-cast-align', '-Wno-padded', '-Wno-reserved-id-macro', '-Wno-sign-conversion', ], 'gcc': [ '-Wno-bad-function-cast', '-Wno-cast-align', '-Wno-conversion', '-Wno-inline', '-Wno-padded', '-Wno-suggest-attribute=const', '-Wno-suggest-attribute=pure', ], 'msvc': [ '/wd4191', # unsafe function conversion '/wd4200', # zero-sized array in struct/union '/wd4365', # signed/unsigned mismatch '/wd4514', # unreferenced inline function has been removed '/wd4706', # assignment within conditional expression '/wd4820', # padding added after construct '/wd5045', # will insert Spectre mitigation for memory load ], }) if 'mingw' in conf.env.CC[0]: conf.env.append_unique('CFLAGS', [ '-Wno-cast-function-type', '-Wno-format', ]) # Check for mlock conf.check_function('c', 'mlock', header_name='sys/mman.h', return_type='int', arg_types='const void*,size_t', define_name='HAVE_MLOCK', mandatory=False) if Options.options.bench: conf.check_pkg('glib-2.0 >= 2.0.0', uselib_store='GLIB', system=True, mandatory=False) if not conf.is_defined('HAVE_GLIB'): conf.fatal('Glib is required to build benchmarks') conf.define('ZIX_VERSION', ZIX_VERSION) conf.define('ZIX_BTREE_PAGE_SIZE', Options.options.page_size) conf.write_config_header('zix-config.h', remove=False) autowaf.display_summary( conf, {'Build unit tests': bool(conf.env.BUILD_TESTS), 'Build benchmarks': bool(conf.env.BUILD_BENCH), 'Page size': Options.options.page_size}) sources = [ 'zix/chunk.c', 'zix/digest.c', 'zix/hash.c', 'zix/patree.c', 'zix/trie.c', 'zix/ring.c', 'zix/sorted_array.c', 'zix/strindex.c', 'zix/tree.c', 'zix/btree.c', 'zix/bitset.c', 'zix/ampatree.c' ] tests = [ 'digest_test', 'hash_test', 'inline_test', 'patree_test', 'trie_test', 'ring_test', 'sem_test', 'sorted_array_test', 'strindex_test', 'tree_test', 'btree_test', 'bitset_test', 'ampatree_test' ] def build(bld): # C Headers bld.install_files('${INCLUDEDIR}/zix', bld.path.ant_glob('zix/*.h')) # Pkgconfig file autowaf.build_pc(bld, 'ZIX', ZIX_VERSION, ZIX_MAJOR_VERSION, [], {'ZIX_MAJOR_VERSION': ZIX_MAJOR_VERSION}) framework = '' if sys.platform == 'darwin': framework = ['CoreServices'] libflags = ['-fvisibility=hidden'] if bld.env.MSVC_COMPILER: libflags = [] # Library bld(features = 'c cshlib', export_includes = ['.'], source = sources, includes = ['.'], name = 'libzix', target = 'zix', vnum = ZIX_VERSION, install_path = '${LIBDIR}', framework = framework, cflags = libflags + ['-DZIX_SHARED', '-DZIX_INTERNAL']) if bld.env.BUILD_STATIC or bld.env.BUILD_BENCH: bld(features = 'c cstlib', export_includes = ['.'], source = sources, includes = ['.'], name = 'libzix_static', target = 'zix', vnum = ZIX_VERSION, install_path = None, framework = framework, cflags = libflags + ['-DZIX_SHARED', '-DZIX_INTERNAL']) if bld.env.BUILD_TESTS: test_cflags = [] test_linkflags = [] test_libs = ['pthread', 'dl'] if bld.env.MSVC_COMPILER: test_libs = [] elif bld.env.DEST_OS == 'win32': test_libs = 'pthread' if bld.is_defined('HAVE_GCOV'): test_cflags += ['--coverage'] test_linkflags += ['--coverage'] # Profiled static library (for unit test code coverage) bld(features = 'c cstlib', source = sources, includes = ['.'], lib = test_libs, name = 'libzix_profiled', target = 'zix_profiled', install_path = '', framework = framework, cflags = test_cflags + ['-DZIX_INTERNAL'], linkflags = test_linkflags) if bld.env.DEST_OS == 'win32': test_malloc = [] else: test_malloc = ['test/test_malloc.c'] # Unit test programs for i in tests: bld(features = 'c cprogram', source = ['test/%s.c' % i] + test_malloc, includes = ['.'], use = 'libzix_profiled', lib = test_libs, target = 'test/%s' % i, install_path = None, framework = framework, cflags = test_cflags, linkflags = test_linkflags) if bld.env.BUILD_BENCH: # Benchmark programs for i in ['tree_bench', 'dict_bench']: bld(features = 'c cprogram', source = 'benchmark/%s.c' % i, includes = ['.'], use = 'libzix_static', uselib = 'GLIB', lib = ['rt'], target = 'benchmark/%s' % i, framework = framework, install_path = '') # Documentation autowaf.build_dox(bld, 'ZIX', ZIX_VERSION, top, out) bld.add_post_fun(autowaf.run_ldconfig) class LintContext(Build.BuildContext): fun = cmd = 'lint' def lint(ctx): "checks code for style issues" import glob import subprocess st = 0 if "FLAKE8" in ctx.env: Logs.info("Running flake8") st = subprocess.call([ctx.env.FLAKE8[0], "wscript", "--ignore", "E101,E129,W191,E221,W504,E251,E241,E741"]) else: Logs.warn("Not running flake8") if "IWYU_TOOL" in ctx.env: Logs.info("Running include-what-you-use") cmd = [ctx.env.IWYU_TOOL[0], "-o", "clang", "-p", "build"] output = subprocess.check_output(cmd).decode('utf-8') if 'error: ' in output: sys.stdout.write(output) st += 1 else: Logs.warn("Not running include-what-you-use") if "CLANG_TIDY" in ctx.env and "clang" in ctx.env.CC[0]: Logs.info("Running clang-tidy") sources = glob.glob('zix/*.c') + glob.glob('test/*.c*') sources = list(map(os.path.abspath, sources)) procs = [] for source in sources: cmd = [ctx.env.CLANG_TIDY[0], "--quiet", "-p=.", source] procs += [subprocess.Popen(cmd, cwd="build")] for proc in procs: stdout, stderr = proc.communicate() st += proc.returncode else: Logs.warn("Not running clang-tidy") if st != 0: sys.exit(st) def build_dir(ctx, subdir): if autowaf.is_child(): return os.path.join('build', APPNAME, subdir) else: return os.path.join('build', subdir) def upload_docs(ctx): os.system('rsync -avz --delete -e ssh build/doc/html/*' ' drobilla@drobilla.net:~/drobilla.net/docs/zix') def test(tst): with tst.group('unit') as check: for test in tests: check([os.path.join('test', test)]) def bench(ctx): os.chdir('build') # Benchmark trees subprocess.call(['benchmark/tree_bench', '400000', '6400000']) subprocess.call(['../plot.py', 'tree_bench.svg', 'tree_insert.txt', 'tree_search.txt', 'tree_iterate.txt', 'tree_delete.txt']) # Benchmark dictionaries filename = 'gibberish.txt' if not os.path.exists(filename): Logs.info('Generating random text %s' % filename) import random out = open(filename, 'w') for i in range(1 << 20): wordlen = random.randrange(1, 64) word = '' for j in range(wordlen): word += chr(random.randrange(ord('A'), min(ord('Z'), ord('A') + j + 1))) out.write(word + ' ') out.close() subprocess.call(['benchmark/dict_bench', 'gibberish.txt']) subprocess.call(['../plot.py', 'dict_bench.svg', 'dict_insert.txt', 'dict_search.txt'])