diff options
Diffstat (limited to 'wscript')
-rw-r--r-- | wscript | 467 |
1 files changed, 467 insertions, 0 deletions
diff --git a/wscript b/wscript new file mode 100644 index 00000000..b38adf7e --- /dev/null +++ b/wscript @@ -0,0 +1,467 @@ +#!/usr/bin/env python + +import glob +import io +import os + +from waflib import 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 +SERD_VERSION = '0.30.1' +SERD_MAJOR_VERSION = '0' + +# Mandatory waf variables +APPNAME = 'serd' # Package name for waf dist +VERSION = SERD_VERSION # Package version for waf dist +top = '.' # Source directory +out = 'build' # Build directory + +def options(ctx): + ctx.load('compiler_c') + ctx.add_flags( + ctx.configuration_options(), + {'no-utils': 'do not build command line utilities', + 'stack-check': 'include runtime stack sanity checks', + 'static': 'build static library', + 'no-shared': 'do not build shared library', + 'static-progs': 'build programs as static binaries', + 'largefile': 'build with large file support on 32-bit systems', + 'no-posix': 'do not use POSIX functions, even if present'}) + +def configure(conf): + conf.load('compiler_c', cache=True) + conf.load('autowaf', cache=True) + autowaf.set_c_lang(conf, 'c99') + + conf.env.update({ + 'BUILD_UTILS': not Options.options.no_utils, + 'BUILD_SHARED': not Options.options.no_shared, + 'STATIC_PROGS': Options.options.static_progs, + 'BUILD_STATIC': Options.options.static or Options.options.static_progs}) + + if not conf.env.BUILD_SHARED and not conf.env.BUILD_STATIC: + conf.fatal('Neither a shared nor a static build requested') + + if Options.options.stack_check: + conf.define('SERD_STACK_CHECK', SERD_VERSION) + + if Options.options.largefile: + conf.env.append_unique('DEFINES', ['_FILE_OFFSET_BITS=64']) + + if not Options.options.no_posix: + for name, header in {'posix_memalign': 'stdlib.h', + 'posix_fadvise': 'fcntl.h', + 'fileno': 'stdio.h'}.items(): + autowaf.check_function(conf, 'c', name, + header_name = header, + define_name = 'HAVE_' + name.upper(), + defines = ['_POSIX_C_SOURCE=200809L'], + mandatory = False) + + autowaf.set_lib_env(conf, 'serd', SERD_VERSION) + conf.write_config_header('serd_config.h', remove=False) + + autowaf.display_summary( + conf, + {'Build static library': bool(conf.env['BUILD_STATIC']), + 'Build shared library': bool(conf.env['BUILD_SHARED']), + 'Build utilities': bool(conf.env['BUILD_UTILS']), + 'Build unit tests': conf.is_defined('HAVE_GL')}) + +lib_source = ['src/byte_source.c', + 'src/env.c', + 'src/n3.c', + 'src/node.c', + 'src/reader.c', + 'src/string.c', + 'src/uri.c', + 'src/writer.c'] + +def build(bld): + # C Headers + includedir = '${INCLUDEDIR}/serd-%s/serd' % SERD_MAJOR_VERSION + bld.install_files(includedir, bld.path.ant_glob('serd/*.h')) + + # Pkgconfig file + autowaf.build_pc(bld, 'SERD', SERD_VERSION, SERD_MAJOR_VERSION, [], + {'SERD_MAJOR_VERSION' : SERD_MAJOR_VERSION}) + + defines = [] + lib_args = {'export_includes': ['.'], + 'includes': ['.', './src'], + 'cflags': ['-fvisibility=hidden'], + 'lib': ['m'], + 'vnum': SERD_VERSION, + 'install_path': '${LIBDIR}'} + if bld.env.MSVC_COMPILER: + lib_args['cflags'] = [] + lib_args['lib'] = [] + defines = [] + + # Shared Library + if bld.env.BUILD_SHARED: + bld(features = 'c cshlib', + source = lib_source, + name = 'libserd', + target = 'serd-%s' % SERD_MAJOR_VERSION, + defines = defines + ['SERD_SHARED', 'SERD_INTERNAL'], + **lib_args) + + # Static library + if bld.env.BUILD_STATIC: + bld(features = 'c cstlib', + source = lib_source, + name = 'libserd_static', + target = 'serd-%s' % SERD_MAJOR_VERSION, + defines = defines + ['SERD_INTERNAL'], + **lib_args) + + if bld.env.BUILD_TESTS: + test_args = {'includes': ['.', './src'], + 'cflags': [''] if bld.env.NO_COVERAGE else ['--coverage'], + 'linkflags': [''] if bld.env.NO_COVERAGE else ['--coverage'], + 'lib': lib_args['lib'], + 'install_path': ''} + + # Profiled static library for test coverage + bld(features = 'c cstlib', + source = lib_source, + name = 'libserd_profiled', + target = 'serd_profiled', + defines = defines + ['SERD_INTERNAL'], + **test_args) + + # Test programs + for prog in [('serdi_static', 'src/serdi.c'), + ('serd_test', 'tests/serd_test.c')]: + bld(features = 'c cprogram', + source = prog[1], + use = 'libserd_profiled', + target = prog[0], + defines = defines, + **test_args) + + # Utilities + if bld.env.BUILD_UTILS: + obj = bld(features = 'c cprogram', + source = 'src/serdi.c', + target = 'serdi', + includes = ['.', './src'], + use = 'libserd', + lib = lib_args['lib'], + install_path = '${BINDIR}') + if not bld.env.BUILD_SHARED or bld.env.STATIC_PROGS: + obj.use = 'libserd_static' + if bld.env.STATIC_PROGS: + obj.env.SHLIB_MARKER = obj.env.STLIB_MARKER + obj.linkflags = ['-static'] + + # Documentation + autowaf.build_dox(bld, 'SERD', SERD_VERSION, top, out) + + # Man page + bld.install_files('${MANDIR}/man1', 'doc/serdi.1') + + bld.add_post_fun(autowaf.run_ldconfig) + if bld.env.DOCS: + bld.add_post_fun(lambda ctx: autowaf.make_simple_dox(APPNAME)) + +def lint(ctx): + "checks code for style issues" + import subprocess + cmd = ("clang-tidy -p=. -header-filter=.* -checks=\"*," + + "-bugprone-suspicious-string-compare," + + "-clang-analyzer-alpha.*," + + "-google-readability-todo," + + "-hicpp-signed-bitwise," + + "-llvm-header-guard," + + "-misc-unused-parameters," + + "-readability-else-after-return\" " + + "../src/*.c") + subprocess.call(cmd, cwd='build', shell=True) + +def amalgamate(ctx): + "builds single-file amalgamated source" + import shutil + shutil.copy('serd/serd.h', 'build/serd.h') + with open('build/serd.c', 'w') as amalgamation: + with open('src/serd_internal.h') as serd_internal_h: + for l in serd_internal_h: + amalgamation.write(l.replace('serd/serd.h', 'serd.h')) + + for f in lib_source: + with open(f) as fd: + amalgamation.write('\n/**\n @file %s\n*/' % f) + header = True + for l in fd: + if header: + if l == '*/\n': + header = False + else: + if l != '#include "serd_internal.h"\n': + amalgamation.write(l) + + for i in ['c', 'h']: + Logs.info('Wrote build/serd.%s' % i) + +def upload_docs(ctx): + os.system('rsync -ravz --delete -e ssh build/doc/html/ drobilla@drobilla.net:~/drobilla.net/docs/serd/') + for page in glob.glob('doc/*.[1-8]'): + os.system('soelim %s | pre-grohtml troff -man -wall -Thtml | post-grohtml > build/%s.html' % (page, page)) + os.system('rsync -avz --delete -e ssh build/%s.html drobilla@drobilla.net:~/drobilla.net/man/' % page) + +def earl_assertion(test, passed, asserter): + import datetime + + asserter_str = '' + if asserter is not None: + asserter_str = '\n\tearl:assertedBy <%s> ;' % asserter + + return ''' +[] + a earl:Assertion ;%s + earl:subject <http://drobilla.net/sw/serd> ; + earl:test <%s> ; + earl:result [ + a earl:TestResult ; + earl:outcome %s ; + dc:date "%s"^^xsd:dateTime + ] . +''' % (asserter_str, + test, + 'earl:passed' if passed else 'earl:failed', + datetime.datetime.now().replace(microsecond=0).isoformat()) + +serdi = './serdi_static' + +def test_thru(check, base, path, check_path, flags, isyntax, osyntax, opts=[]): + out_path = path + '.out' + out_cmd = [serdi] + opts + [f for sublist in flags for f in sublist] + [ + '-i', isyntax, + '-o', isyntax, + '-p', 'foo', + check.tst.src_path(path), base] + + thru_path = path + '.thru' + thru_cmd = [serdi] + opts + [ + '-i', isyntax, + '-o', osyntax, + '-c', 'foo', + out_path, + base] + + return (check(out_cmd, stdout=out_path, verbosity=0, name=out_path) and + check(thru_cmd, stdout=thru_path, verbosity=0, name=thru_path) and + check.file_equals(check_path, thru_path, verbosity=0)) + +def file_uri_to_path(uri): + try: + from urlparse import urlparse # Python 2 + except: + from urllib.parse import urlparse # Python 3 + + path = urlparse(uri).path + drive = os.path.splitdrive(path[1:])[0] + return path if not drive else path[1:] + +def _test_output_syntax(test_class): + if 'NTriples' in test_class or 'Turtle' in test_class: + return 'NTriples' + elif 'NQuads' in test_class or 'Trig' in test_class: + return 'NQuads' + raise Exception('Unknown test class <%s>' % test_class) + +def _load_rdf(filename): + "Load an RDF file into python dictionaries via serdi. Only supports URIs." + import subprocess + import re + + rdf_type = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' + model = {} + instances = {} + proc = subprocess.Popen(['./serdi_static', filename], stdout=subprocess.PIPE) + for line in proc.communicate()[0].splitlines(): + matches = re.match('<([^ ]*)> <([^ ]*)> <([^ ]*)> \.', line.decode('utf-8')) + if matches: + s, p, o = (matches.group(1), matches.group(2), matches.group(3)) + if s not in model: + model[s] = {p: [o]} + elif p not in model[s]: + model[s][p] = [o] + else: + model[s][p].append(o) + + if p == rdf_type: + if o not in instances: + instances[o] = set([s]) + else: + instances[o].update([s]) + + return model, instances + +def test_suite(ctx, base_uri, testdir, report, isyntax, options=[]): + import itertools + + srcdir = ctx.path.abspath() + + mf = 'http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#' + manifest_path = os.path.join(srcdir, 'tests', testdir, 'manifest.ttl') + model, instances = _load_rdf(manifest_path) + + asserter = '' + if os.getenv('USER') == 'drobilla': + asserter = 'http://drobilla.net/drobilla#me' + + def run_tests(test_class, tests, expected_return): + thru_flags = [['-e'], ['-f'], ['-b'], ['-r', 'http://example.org/']] + thru_options = [] + for n in range(len(thru_flags) + 1): + thru_options += list(itertools.combinations(thru_flags, n)) + thru_options_iter = itertools.cycle(thru_options) + + osyntax = _test_output_syntax(test_class) + tests_name = '%s.%s' % (testdir, test_class[test_class.find('#') + 1:]) + with ctx.group(tests_name) as check: + for test in sorted(tests): + action_node = model[test][mf + 'action'][0] + action = os.path.join('tests', testdir, os.path.basename(action_node)) + rel_action = os.path.join(os.path.relpath(srcdir), action) + uri = base_uri + os.path.basename(action) + command = [serdi] + options + ['-f', rel_action, uri] + + # Run strict test + if expected_return == 0: + result = check(command, stdout=action + '.out', name=action) + else: + result = check(command, + stdout=action + '.out', + stderr=autowaf.NONEMPTY, + expected=expected_return, + name=action) + + if result and ((mf + 'result') in model[test]): + # Check output against test suite + check_uri = model[test][mf + 'result'][0] + check_path = ctx.src_path(file_uri_to_path(check_uri)) + result = check.file_equals(action + '.out', check_path) + + # Run round-trip tests + if result: + test_thru(check, uri, action, check_path, + list(next(thru_options_iter)), + isyntax, osyntax, options) + + # Write test report entry + if report is not None: + report.write(earl_assertion(test, result, asserter)) + + # Run lax test + check([command[0]] + ['-l'] + command[1:], + expected=None, name=action + ' lax') + + ns_rdftest = 'http://www.w3.org/ns/rdftest#' + for test_class, instances in instances.items(): + if test_class.startswith(ns_rdftest): + expected = 1 if 'Negative' in test_class else 0 + run_tests(test_class, instances, expected) + +def test(tst): + import tempfile + + # Create test output directories + for i in ['bad', 'good', 'TurtleTests', 'NTriplesTests', 'NQuadsTests', 'TriGTests']: + try: + test_dir = os.path.join('tests', i) + os.makedirs(test_dir) + for i in glob.glob(test_dir + '/*.*'): + os.remove(i) + except: + pass + + srcdir = tst.path.abspath() + + with tst.group('Unit') as check: + check(['./serd_test']) + + def test_syntax_io(check, in_name, check_name, lang): + in_path = 'tests/good/%s' % in_name + out_path = in_path + '.out' + check_path = '%s/tests/good/%s' % (srcdir, check_name) + + check([serdi, '-o', lang, '%s/%s' % (srcdir, in_path), in_path], + stdout=out_path, name=in_name) + + check.file_equals(check_path, out_path) + + with tst.group('ThroughSyntax') as check: + test_syntax_io(check, 'base.ttl', 'base.ttl', 'turtle') + test_syntax_io(check, 'qualify-in.ttl', 'qualify-out.ttl', 'turtle') + + with tst.group('GoodCommands') as check: + check([serdi, '%s/tests/good/manifest.ttl' % srcdir]) + check([serdi, '-v']) + check([serdi, '-h']) + check([serdi, '-s', '<foo> a <#Thingie> .']) + check([serdi, os.devnull]) + with tempfile.TemporaryFile(mode='r') as stdin: + check([serdi, '-'], stdin=stdin) + + with tst.group('BadCommands', expected=1) as check: + check([serdi]) + check([serdi, '/no/such/file']) + check([serdi, 'ftp://example.org/unsupported.ttl']) + check([serdi, '-c']) + check([serdi, '-i', 'illegal']) + check([serdi, '-i', 'turtle']) + check([serdi, '-i']) + check([serdi, '-o', 'illegal']) + check([serdi, '-o']) + check([serdi, '-p']) + check([serdi, '-q', '%s/tests/bad/bad-id-clash.ttl' % srcdir]) + check([serdi, '-r']) + check([serdi, '-z']) + + with tst.group('IoErrors', expected=1) as check: + check([serdi, '-e', 'file://%s/' % srcdir], name='Read directory') + check([serdi, 'file://%s/' % srcdir], name='Bulk read directory') + if os.path.exists('/dev/full'): + check([serdi, 'file://%s/tests/good/manifest.ttl' % srcdir], + stdout='/dev/full', name='Write error') + + # Serd-specific test suites + serd_base = 'http://drobilla.net/sw/serd/tests/' + test_suite(tst, serd_base + 'good/', 'good', None, 'Turtle') + test_suite(tst, serd_base + 'bad/', 'bad', None, 'Turtle') + + # Standard test suites + with open('earl.ttl', 'w') as report: + report.write('@prefix earl: <http://www.w3.org/ns/earl#> .\n' + '@prefix dc: <http://purl.org/dc/elements/1.1/> .\n') + + with open(os.path.join(srcdir, 'serd.ttl')) as serd_ttl: + report.writelines(serd_ttl) + + w3c_base = 'http://www.w3.org/2013/' + test_suite(tst, w3c_base + 'TurtleTests/', + 'TurtleTests', report, 'Turtle') + test_suite(tst, w3c_base + 'NTriplesTests/', + 'NTriplesTests', report, 'NTriples') + test_suite(tst, w3c_base + 'NQuadsTests/', + 'NQuadsTests', report, 'NQuads') + test_suite(tst, w3c_base + 'TriGTests/', + 'TriGTests', report, 'Trig', ['-a']) + +def posts(ctx): + path = str(ctx.path.abspath()) + autowaf.news_to_posts( + os.path.join(path, 'NEWS'), + {'title' : 'Serd', + 'description' : autowaf.get_blurb(os.path.join(path, 'README.md')), + 'dist_pattern' : 'http://download.drobilla.net/serd-%s.tar.bz2'}, + { 'Author' : 'drobilla', + 'Tags' : 'Hacking, RDF, Serd' }, + os.path.join(out, 'posts')) |