diff options
Diffstat (limited to 'wscript')
| -rw-r--r-- | wscript | 869 |
1 files changed, 543 insertions, 326 deletions
@@ -1,52 +1,86 @@ #!/usr/bin/env python -import glob + import os import re -import shutil -import subprocess import sys +from waflib import Context, Logs, Options, Scripting, Utils from waflib.extras import autowaf as autowaf -import waflib.Context as Context -import waflib.Logs as Logs -import waflib.Options as Options -import waflib.Scripting as Scripting - -# Variables for 'waf dist' -APPNAME = 'lv2' -VERSION = '1.6.1' - -# Mandatory variables -top = '.' -out = 'build' - -def options(opt): - opt.load('compiler_c') - autowaf.set_options(opt) - opt.add_option('--test', action='store_true', dest='build_tests', - help='Build unit tests') - opt.add_option('--online-docs', action='store_true', dest='online_docs', - help='Build documentation for web hosting') - opt.add_option('--no-plugins', action='store_true', dest='no_plugins', - help='Do not build example plugins') - opt.add_option('--copy-headers', action='store_true', dest='copy_headers', - help='Copy headers instead of linking to bundle') - opt.recurse('lv2/lv2plug.in/ns/lv2core') + +# Mandatory waf variables +APPNAME = 'lv2' # Package name for waf dist +VERSION = '1.18.0' # Package version for waf dist +top = '.' # Source directory +out = 'build' # Build directory + +# Release variables +title = 'LV2' +uri = 'http://lv2plug.in/ns/lv2' +dist_pattern = 'http://lv2plug.in/spec/lv2-%d.%d.%d.tar.bz2' +post_tags = [] + +# Map of specification base name to old URI-style include path +spec_map = { + 'atom' : 'lv2/lv2plug.in/ns/ext/atom', + 'buf-size' : 'lv2/lv2plug.in/ns/ext/buf-size', + 'core' : 'lv2/lv2plug.in/ns/lv2core', + 'data-access' : 'lv2/lv2plug.in/ns/ext/data-access', + 'dynmanifest' : 'lv2/lv2plug.in/ns/ext/dynmanifest', + 'event' : 'lv2/lv2plug.in/ns/ext/event', + 'instance-access' : 'lv2/lv2plug.in/ns/ext/instance-access', + 'log' : 'lv2/lv2plug.in/ns/ext/log', + 'midi' : 'lv2/lv2plug.in/ns/ext/midi', + 'morph' : 'lv2/lv2plug.in/ns/ext/morph', + 'options' : 'lv2/lv2plug.in/ns/ext/options', + 'parameters' : 'lv2/lv2plug.in/ns/ext/parameters', + 'patch' : 'lv2/lv2plug.in/ns/ext/patch', + 'port-groups' : 'lv2/lv2plug.in/ns/ext/port-groups', + 'port-props' : 'lv2/lv2plug.in/ns/ext/port-props', + 'presets' : 'lv2/lv2plug.in/ns/ext/presets', + 'resize-port' : 'lv2/lv2plug.in/ns/ext/resize-port', + 'state' : 'lv2/lv2plug.in/ns/ext/state', + 'time' : 'lv2/lv2plug.in/ns/ext/time', + 'ui' : 'lv2/lv2plug.in/ns/extensions/ui', + 'units' : 'lv2/lv2plug.in/ns/extensions/units', + 'uri-map' : 'lv2/lv2plug.in/ns/ext/uri-map', + 'urid' : 'lv2/lv2plug.in/ns/ext/urid', + 'worker' : 'lv2/lv2plug.in/ns/ext/worker'} + +def options(ctx): + ctx.load('compiler_c') + ctx.load('compiler_cxx') + ctx.load('lv2') + ctx.add_flags( + ctx.configuration_options(), + {'no-coverage': 'Do not use gcov for code coverage', + 'online-docs': 'Build documentation for web hosting', + 'no-check-links': 'Do not check documentation for broken links', + 'no-plugins': 'Do not build example plugins', + 'copy-headers': 'Copy headers instead of linking to bundle'}) def configure(conf): try: - conf.load('compiler_c') + conf.load('compiler_c', cache=True) except: Options.options.build_tests = False Options.options.no_plugins = True + try: + conf.load('compiler_cxx', cache=True) + except Exception: + pass + if Options.options.online_docs: Options.options.docs = True - autowaf.configure(conf) - autowaf.set_c99_mode(conf) + conf.load('lv2', cache=True) + conf.load('autowaf', cache=True) + autowaf.set_c_lang(conf, 'c99') - if Options.platform == 'win32' or not hasattr(os.path, 'relpath'): + if Options.options.ultra_strict and not conf.env.MSVC_COMPILER: + conf.env.append_value('CFLAGS', ['-Wconversion']) + + if conf.env.DEST_OS == 'win32' or not hasattr(os.path, 'relpath'): Logs.warn('System does not support linking headers, copying') Options.options.copy_headers = True @@ -62,153 +96,55 @@ def configure(conf): except: Logs.warn('Asciidoc not found, book will not be built') + if not Options.options.no_check_links: + if not conf.find_program('linkchecker', + var='LINKCHECKER', mandatory=False): + Logs.warn('Documentation will not be checked for broken links') + # Check for gcov library (for test coverage) - if conf.env.BUILD_TESTS and not conf.is_defined('HAVE_GCOV'): + if (conf.env.BUILD_TESTS + and not Options.options.no_coverage + and not conf.is_defined('HAVE_GCOV')): conf.check_cc(lib='gcov', define_name='HAVE_GCOV', mandatory=False) - autowaf.set_recursive() + if conf.env.BUILD_TESTS: + conf.find_program('serdi', mandatory=False) + conf.find_program('sord_validate', mandatory=False) + + autowaf.set_lib_env(conf, 'lv2', VERSION, has_objects=False) + autowaf.set_local_lib(conf, 'lv2', has_objects=False) - conf.recurse('lv2/lv2plug.in/ns/lv2core') + conf.run_env.append_unique('LV2_PATH', + [os.path.join(conf.path.abspath(), 'lv2')]) - conf.env.LV2_BUILD = ['lv2/lv2plug.in/ns/lv2core'] if conf.env.BUILD_PLUGINS: - for i in conf.path.ant_glob('plugins/*', src=False, dir=True): + for i in ['eg-amp.lv2', + 'eg-fifths.lv2', + 'eg-metro.lv2', + 'eg-midigate.lv2', + 'eg-params.lv2', + 'eg-sampler.lv2', + 'eg-scope.lv2']: try: - conf.recurse(i.srcpath()) - conf.env.LV2_BUILD += [i.srcpath()] - except: - Logs.warn('Configuration failed, %s will not be built\n' % i) - - autowaf.configure(conf) - autowaf.display_header('LV2 Configuration') - autowaf.display_msg(conf, 'Bundle directory', conf.env.LV2DIR) - autowaf.display_msg(conf, 'Copy (not link) headers', conf.env.COPY_HEADERS) - autowaf.display_msg(conf, 'Version', VERSION) + path = os.path.join('plugins', i) + conf.recurse(path) + conf.env.LV2_BUILD += [path] + conf.run_env.append_unique( + 'LV2_PATH', [conf.build_path('plugins/%s/lv2' % i)]) + except Exception as e: + Logs.warn('Configuration failed, not building %s (%s)' % (i, e)) + + autowaf.display_summary( + conf, + {'Bundle directory': conf.env.LV2DIR, + 'Copy (not link) headers': bool(conf.env.COPY_HEADERS), + 'Version': VERSION}) def chop_lv2_prefix(s): if s.startswith('lv2/lv2plug.in/'): return s[len('lv2/lv2plug.in/'):] return s -# Rule for calling lv2specgen on a spec bundle -def specgen(task): - import rdflib - doap = rdflib.Namespace('http://usefulinc.com/ns/doap#') - lv2 = rdflib.Namespace('http://lv2plug.in/ns/lv2core#') - owl = rdflib.Namespace('http://www.w3.org/2002/07/owl#') - rdf = rdflib.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#') - - sys.path.append('./lv2specgen') - import lv2specgen - - spec = task.inputs[0] - path = os.path.dirname(spec.srcpath()) - outdir = os.path.abspath(os.path.join(out, chop_lv2_prefix(path))) - - bundle = str(outdir) - b = os.path.basename(outdir) - - if not os.access(spec.abspath(), os.R_OK): - print('warning: extension %s has no %s.ttl file' % (b, b)) - return - - try: - model = rdflib.ConjunctiveGraph() - for i in glob.glob('%s/*.ttl' % bundle): - model.parse(i, format='n3') - except: - e = sys.exc_info()[1] - print('error parsing %s: %s' % (bundle, str(e))) - return - - # Get extension URI - ext_node = model.value(None, rdf.type, lv2.Specification) - if not ext_node: - print('no extension found in %s' % bundle) - return - - ext = str(ext_node) - - # Get version - minor = 0 - micro = 0 - try: - minor = int(model.value(ext_node, lv2.minorVersion, None)) - micro = int(model.value(ext_node, lv2.microVersion, None)) - except: - e = sys.exc_info()[1] - print('warning: %s: failed to find version for %s' % (bundle, ext)) - - # Get date - date = None - for r in model.triples([ext_node, doap.release, None]): - revision = model.value(r[2], doap.revision, None) - if revision == ('%d.%d' % (minor, micro)): - date = model.value(r[2], doap.created, None) - break - - # Verify that this date is the latest - for r in model.triples([ext_node, doap.release, None]): - revision = model.value(r[2], doap.revision, None) - this_date = model.value(r[2], doap.created, None) - if this_date > date: - print('warning: %s revision %d.%d (%s) is not the latest release' % ( - ext_node, minor, micro, date)) - break - - # Get short description - shortdesc = model.value(ext_node, doap.shortdesc, None) - - SPECGENDIR = 'lv2specgen' - STYLEPATH = 'build/aux/style.css' - TAGFILE = 'build/tags' - - specdoc = lv2specgen.specgen( - spec.abspath(), - SPECGENDIR, - os.path.relpath(STYLEPATH, bundle), - os.path.relpath('build/doc/html', bundle), - TAGFILE, - instances=True, - offline=(not task.env.ONLINE_DOCS)) - - lv2specgen.save(task.outputs[0].abspath(), specdoc) - - # Name (comment is to act as a sort key) - target = path[len('lv2/lv2plug.in/ns/'):] - if not task.env.ONLINE_DOCS: - target += '/%s.html' % b - row = '<tr><!-- %s --><td><a rel="rdfs:seeAlso" href="%s">%s</a></td>' % ( - b, target, b) - - # Description - if shortdesc: - row += '<td>' + str(shortdesc) + '</td>' - else: - row += '<td></td>' - - # Version - version_str = '%s.%s' % (minor, micro) - if minor == 0 or (micro % 2 != 0): - row += '<td><span style="color: red">' + version_str + '</span></td>' - else: - row += '<td>' + version_str + '</td>' - - # Status - deprecated = model.value(ext_node, owl.deprecated, None) - if minor == 0: - row += '<td><span class="error">Experimental</span></td>' - elif deprecated and str(deprecated[2]) != "false": - row += '<td><span class="warning">Deprecated</span></td>' - elif micro % 2 == 0: - row += '<td><span class="success">Stable</span></td>' - - row += '</tr>' - - index = open(os.path.join('build', 'index_rows', b), 'w') - index.write(row) - index.close() - def subst_file(template, output, dict): i = open(template, 'r') o = open(output, 'w') @@ -219,9 +155,54 @@ def subst_file(template, output, dict): i.close() o.close() +def specdirs(path): + return (path.ant_glob('lv2/*', dir=True) + + path.ant_glob('plugins/*.lv2', dir=True)) + +def ttl_files(path, specdir): + def abspath(node): + return node.abspath() + + return map(abspath, + path.ant_glob(specdir.path_from(path) + '/*.ttl')) + +def load_ttl(files, exclude = []): + import rdflib + model = rdflib.ConjunctiveGraph() + for f in files: + if f not in exclude: + model.parse(f, format='n3') + return model + # Task to build extension index def build_index(task): - global index_lines + src_dir = task.inputs[0].parent.parent + sys.path.append(str(src_dir.find_node('lv2specgen'))) + import rdflib + import lv2specgen + + doap = rdflib.Namespace('http://usefulinc.com/ns/doap#') + rdf = rdflib.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#') + + model = load_ttl([str(src_dir.find_node('lv2/core/meta.ttl')), + str(src_dir.find_node('lv2/core/people.ttl'))]) + + # Get date for this version, and list of all LV2 distributions + proj = rdflib.URIRef('http://lv2plug.in/ns/lv2') + date = None + dists = [] + for r in model.triples([proj, doap.release, None]): + revision = model.value(r[2], doap.revision, None) + created = model.value(r[2], doap.created, None) + if str(revision) == VERSION: + date = created + + dist = model.value(r[2], doap['file-release'], None) + if dist and created: + dists += [(created, dist)] + else: + print('warning: %s has no file release\n' % proj) + rows = [] for f in task.inputs: if not f.abspath().endswith('index.html.in'): @@ -229,50 +210,44 @@ def build_index(task): rows += rowfile.readlines() rowfile.close() - subst_file(task.inputs[0].abspath(), task.outputs[0].abspath(), - { '@ROWS@': ''.join(rows), - '@LV2_VERSION@': VERSION}) - -# Task for making a link in the build directory to a source file -def link(task): - if hasattr(os, 'symlink'): - func = os.symlink - else: - func = shutil.copy # Symlinks unavailable, make a copy - - try: - os.remove(task.outputs[0].abspath()) # Remove old target - except: - pass # No old target, whatever - - func(task.inputs[0].abspath(), task.outputs[0].abspath()) + if date is None: + import datetime + import time + now = int(os.environ.get('SOURCE_DATE_EPOCH', time.time())) + date = datetime.datetime.utcfromtimestamp(now).strftime('%F') -def build_ext(bld, path): - name = os.path.basename(path) - bundle_dir = os.path.join(bld.env.LV2DIR, name + '.lv2') - include_dir = os.path.join(bld.env.INCLUDEDIR, path) + subst_file(task.inputs[0].abspath(), task.outputs[0].abspath(), + {'@ROWS@': ''.join(rows), + '@LV2_VERSION@': VERSION, + '@DATE@': date}) - # Copy headers to URI-style include paths in build directory - for i in bld.path.ant_glob(path + '/*.h'): - bld(rule = link, - source = i, - target = bld.path.get_bld().make_node('%s/%s' % (path, i))) +def build_spec(bld, path): + name = os.path.basename(path) + bundle_dir = os.path.join(bld.env.LV2DIR, name + '.lv2') + include_dir = os.path.join(bld.env.INCLUDEDIR, path) + old_include_dir = os.path.join(bld.env.INCLUDEDIR, spec_map[name]) # Build test program if applicable - if bld.env.BUILD_TESTS and bld.path.find_node(path + '/%s-test.c' % name): - test_lib = [] - test_cflags = [''] + for test in bld.path.ant_glob(os.path.join(path, '*-test.c')): + test_lib = [] + test_cflags = [''] + test_linkflags = [''] if bld.is_defined('HAVE_GCOV'): - test_lib += ['gcov'] - test_cflags += ['-fprofile-arcs', '-ftest-coverage'] + test_lib += ['gcov'] + test_cflags += ['--coverage'] + test_linkflags += ['--coverage'] + if bld.env.DEST_OS not in ['darwin', 'win32']: + test_lib += ['rt'] # Unit test program bld(features = 'c cprogram', - source = path + '/%s-test.c' % name, + source = test, lib = test_lib, - target = path + '/%s-test' % name, + uselib = 'LV2', + target = os.path.splitext(str(test.get_bld()))[0], install_path = None, - cflags = test_cflags) + cflags = test_cflags, + linkflags = test_linkflags) # Install bundle bld.install_files(bundle_dir, @@ -281,22 +256,22 @@ def build_ext(bld, path): # Install URI-like includes headers = bld.path.ant_glob(path + '/*.h') if headers: - if bld.env.COPY_HEADERS: - bld.install_files(include_dir, headers) - else: - bld.symlink_as(include_dir, - os.path.relpath(bundle_dir, - os.path.dirname(include_dir))) + for d in [include_dir, old_include_dir]: + if bld.env.COPY_HEADERS: + bld.install_files(d, headers) + else: + bld.symlink_as(d, + os.path.relpath(bundle_dir, os.path.dirname(d))) def build(bld): - exts = (bld.path.ant_glob('lv2/lv2plug.in/ns/ext/*', dir=True) + - bld.path.ant_glob('lv2/lv2plug.in/ns/extensions/*', dir=True)) + specs = (bld.path.ant_glob('lv2/*', dir=True)) - # Copy lv2.h to URI-style include path in build directory - lv2_h_path = 'lv2/lv2plug.in/ns/lv2core/lv2.h' - bld(rule = link, - source = bld.path.find_node(lv2_h_path), - target = bld.path.get_bld().make_node(lv2_h_path)) + # Copy lv2.h to include directory for backwards compatibility + old_lv2_h_path = os.path.join(bld.env.INCLUDEDIR, 'lv2.h') + if bld.env.COPY_HEADERS: + bld.install_files(os.path.dirname(old_lv2_h_path), 'lv2/core/lv2.h') + else: + bld.symlink_as(old_lv2_h_path, 'lv2/core/lv2.h') # LV2 pkgconfig file bld(features = 'subst', @@ -307,96 +282,138 @@ def build(bld): INCLUDEDIR = bld.env.INCLUDEDIR, VERSION = VERSION) + # Validator + bld(features = 'subst', + source = 'util/lv2_validate.in', + target = 'lv2_validate', + chmod = Utils.O755, + install_path = '${BINDIR}', + LV2DIR = bld.env.LV2DIR) + # Build extensions - for i in exts: - build_ext(bld, i.srcpath()) + for spec in specs: + build_spec(bld, spec.path_from(bld.path)) # Build plugins - for i in bld.env.LV2_BUILD: - bld.recurse(i) - - if bld.env.BUILD_BOOK: - bld.recurse('plugins') + for plugin in bld.env.LV2_BUILD: + bld.recurse(plugin) + + # Install lv2specgen + bld.install_files('${DATADIR}/lv2specgen/', + ['lv2specgen/style.css', + 'lv2specgen/template.html']) + bld.install_files('${DATADIR}/lv2specgen/DTD/', + bld.path.ant_glob('lv2specgen/DTD/*')) + bld.install_files('${BINDIR}', 'lv2specgen/lv2specgen.py', + chmod=Utils.O755) + + # Install schema bundle + bld.install_files('${LV2DIR}/schemas.lv2/', + bld.path.ant_glob('schemas.lv2/*.ttl')) + + if bld.env.ONLINE_DOCS: + # Generate .htaccess files + for d in ('ns', 'ns/ext', 'ns/extensions'): + path = os.path.join(str(bld.path.get_bld()), d) + bld(features = 'subst', + source = 'doc/htaccess.in', + target = os.path.join(path, '.htaccess'), + install_path = None, + BASE = '/' + d) if bld.env.DOCS or bld.env.ONLINE_DOCS: - # Build Doxygen documentation (and tags file) - autowaf.build_dox(bld, 'LV2', VERSION, top, out, 'lv2plug.in/doc', False) + # Copy spec files to build dir + for spec in specs: + srcpath = spec.path_from(bld.path) + basename = os.path.basename(srcpath) + full_path = spec_map[basename] + name = 'lv2core' if basename == 'core' else basename + path = chop_lv2_prefix(full_path) + spec_path = os.path.join(path[3:], name + '.ttl') + + bld(features = 'subst', + is_copy = True, + source = os.path.join(srcpath, name + '.ttl'), + target = path + '.ttl') + + # Copy stylesheets to build directory + for i in ['style.css', 'pygments.css']: + bld(features = 'subst', + is_copy = True, + name = 'copy', + source = 'doc/%s' % i, + target = 'aux/%s' % i) - # Copy stylesheet to build directory - bld(features = 'subst', - is_copy = True, - name = 'copy', - source = 'doc/style.css', - target = 'aux/style.css') + # Build Doxygen documentation (and tags file) + autowaf.build_dox(bld, 'LV2', VERSION, top, out, 'doc', False) + bld.add_group() index_files = [] - - # Prepare spec output directories - specs = exts + [bld.path.find_node('lv2/lv2plug.in/ns/lv2core')] - for i in specs: - # Copy spec files to build dir - for f in bld.path.ant_glob(i.srcpath() + '/*.*'): - bld(features = 'subst', - is_copy = True, - name = 'copy', - source = f, - target = chop_lv2_prefix(f.srcpath())) - - base = i.srcpath()[len('lv2/lv2plug.in'):] - name = os.path.basename(i.srcpath()) - - # Generate .htaccess file - if bld.env.ONLINE_DOCS: - bld(features = 'subst', - source = 'doc/htaccess.in', - target = os.path.join(base, '.htaccess'), - install_path = None, - NAME = name, - BASE = base) - - # Call lv2specgen for each spec - for i in specs: - name = os.path.basename(i.srcpath()) - index_file = os.path.join('index_rows', name) - index_files += [index_file] - - bld.add_group() # Barrier (don't call lv2specgen in parallel) - + for spec in specs: # Call lv2specgen to generate spec docs - bld(rule = specgen, - name = 'specgen', - source = os.path.join(i.srcpath(), name + '.ttl'), - target = ['%s/%s.html' % (chop_lv2_prefix(i.srcpath()), name), - index_file]) + srcpath = spec.path_from(bld.path) + basename = os.path.basename(srcpath) + full_path = spec_map[basename] + name = 'lv2core' if basename == 'core' else basename + ttl_name = name + '.ttl' + index_file = bld.path.get_bld().make_node('index_rows/' + name) + index_files += [index_file] + chopped_path = chop_lv2_prefix(full_path) + + assert chopped_path.startswith('ns/') + root_path = os.path.relpath('/', os.path.dirname(chopped_path[2:])) + html_path = '%s.html' % chopped_path + out_dir = os.path.dirname(html_path) + + cmd = (str(bld.path.find_node('lv2specgen/lv2specgen.py')) + + ' --root-uri=http://lv2plug.in/ns/ --root-path=' + root_path + + ' --list-email=devel@lists.lv2plug.in' + ' --list-page=http://lists.lv2plug.in/listinfo.cgi/devel-lv2plug.in' + ' --style-uri=' + os.path.relpath('aux/style.css', out_dir) + + ' --docdir=' + os.path.relpath('doc/html', out_dir) + + ' --tags=%s' % bld.path.get_bld().make_node('doc/tags') + + ' --index=' + str(index_file) + + ' ${SRC} ${TGT}') + + bld(rule = cmd, + source = os.path.join(srcpath, ttl_name), + target = [html_path, index_file], + shell = False) # Install documentation - if not bld.env.ONLINE_DOCS: - base = chop_lv2_prefix(i.srcpath()) - bld.install_files('${DOCDIR}/' + i.srcpath(), - bld.path.get_bld().ant_glob(base + '/*.html')) + base = chop_lv2_prefix(srcpath) + bld.install_files(os.path.join('${DOCDIR}', 'lv2', os.path.dirname(html_path)), + html_path) - index_files.sort() - bld.add_group() # Barrier (wait for lv2specgen to build index) + index_files.sort(key=lambda x: x.path_from(bld.path)) + bld.add_group() # Build extension index bld(rule = build_index, name = 'index', - source = ['lv2/lv2plug.in/ns/index.html.in'] + index_files, + source = ['doc/index.html.in'] + index_files, target = 'ns/index.html') # Install main documentation files - if not bld.env.ONLINE_DOCS: - bld.install_files('${DOCDIR}/lv2/lv2plug.in/aux/', 'aux/style.css') - bld.install_files('${DOCDIR}/lv2/lv2plug.in/ns/', 'ns/index.html') + bld.install_files('${DOCDIR}/lv2/aux/', 'aux/style.css') + bld.install_files('${DOCDIR}/lv2/ns/', 'ns/index.html') + + def check_links(ctx): + import subprocess + if ctx.env.LINKCHECKER: + if subprocess.call([ctx.env.LINKCHECKER[0], '--no-status', out]): + ctx.fatal('Documentation contains broken links') + + if bld.cmd == 'build': + bld.add_post_fun(check_links) if bld.env.BUILD_TESTS: - # Generate a compile test .c file that includes all headers + # Generate a compile test file that includes all headers def gen_build_test(task): - out = open(task.outputs[0].abspath(), 'w') - for i in task.inputs: - out.write('#include "%s"\n' % i.bldpath()) - out.write('int main(void) { return 0; }\n') - out.close() + with open(task.outputs[0].abspath(), 'w') as out: + for i in task.inputs: + out.write('#include "%s"\n' % i.bldpath()) + out.write('int main(void) { return 0; }\n') bld(rule = gen_build_test, source = bld.path.ant_glob('lv2/**/*.h'), @@ -406,20 +423,180 @@ def build(bld): bld(features = 'c cprogram', source = bld.path.get_bld().make_node('build-test.c'), target = 'build-test', + includes = '.', + uselib = 'LV2', install_path = None) + if 'COMPILER_CXX' in bld.env: + bld(rule = gen_build_test, + source = bld.path.ant_glob('lv2/**/*.h'), + target = 'build-test.cpp', + install_path = None) + + bld(features = 'cxx cxxprogram', + source = bld.path.get_bld().make_node('build-test.cpp'), + target = 'build-test-cpp', + includes = '.', + uselib = 'LV2', + install_path = None) + + if bld.env.BUILD_BOOK: + # Build "Programming LV2 Plugins" book from plugin examples + bld.recurse('plugins') + def lint(ctx): - for i in ctx.path.ant_glob('lv2/**/*.h'): - subprocess.call('cpplint.py --filter=+whitespace/comments,-whitespace/tab,-whitespace/braces,-whitespace/labels,-build/header_guard,-readability/casting,-build/include,-runtime/sizeof ' + i.abspath(), shell=True) - -def test(ctx): - autowaf.pre_test(ctx, APPNAME, dirs=['.']) - for i in ctx.path.ant_glob('**/*-test'): - name = os.path.basename(i.abspath()) - os.environ['PATH'] = '.' + os.pathsep + os.getenv('PATH') - autowaf.run_tests( - ctx, name, [i.path_from(ctx.path.find_node('build'))], dirs=['.']) - autowaf.post_test(ctx, APPNAME, dirs=['.']) + "checks code for style issues" + import subprocess + + subprocess.call("flake8 --ignore E203,E221,W503,W504,E302,E305,E251,E241,E722 " + "wscript lv2specgen/lv2docgen.py lv2specgen/lv2specgen.py " + "plugins/literasc.py", + shell=True) + + cmd = ("clang-tidy -p=. -header-filter=.* -checks=\"*," + + "-hicpp-signed-bitwise," + + "-llvm-header-guard," + + "-misc-unused-parameters," + + "-readability-else-after-return\" " + + "build-test.c") + subprocess.call(cmd, cwd='build', shell=True) + + +def test_vocabularies(check, specs, files): + import rdflib + + foaf = rdflib.Namespace('http://xmlns.com/foaf/0.1/') + lv2 = rdflib.Namespace('http://lv2plug.in/ns/lv2core#') + owl = rdflib.Namespace('http://www.w3.org/2002/07/owl#') + rdf = rdflib.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#') + rdfs = rdflib.Namespace('http://www.w3.org/2000/01/rdf-schema#') + + # Check if this is a stable LV2 release to enable additional tests + version_tuple = tuple(map(int, VERSION.split("."))) + is_stable = version_tuple[1] % 2 == 0 and version_tuple[2] % 2 == 0 + + # Check that extended documentation is not in main specification file + for spec in specs: + path = str(spec.abspath()) + name = os.path.basename(path) + name = 'lv2core' if name == 'core' else name + vocab = os.path.join(path, name + '.ttl') + + spec_model = rdflib.ConjunctiveGraph() + spec_model.parse(vocab, format='n3') + + def has_statement(s, p, o): + for t in spec_model.triples([s, p, o]): + return True + + return False + + check(lambda: not has_statement(None, lv2.documentation, None), + name = name + ".ttl does not contain lv2:documentation") + + # Check specification manifests + for spec in specs: + path = str(spec.abspath()) + manifest_path = os.path.join(path, 'manifest.ttl') + manifest_model = rdflib.ConjunctiveGraph() + manifest_model.parse(manifest_path, format='n3') + + uri = manifest_model.value(None, rdf.type, lv2.Specification) + minor = manifest_model.value(uri, lv2.minorVersion, None) + micro = manifest_model.value(uri, lv2.microVersion, None) + check(lambda: uri is not None, + name = manifest_path + " has a lv2:Specification") + check(lambda: minor is not None, + name = manifest_path + " has a lv2:minorVersion") + check(lambda: micro is not None, + name = manifest_path + " has a lv2:microVersion") + + if is_stable: + check(lambda: int(minor) > 0, + name = manifest_path + " has even non-zero minor version") + check(lambda: int(micro) % 2 == 0, + name = manifest_path + " has even micro version") + + # Load everything into one big model + model = rdflib.ConjunctiveGraph() + for f in files: + model.parse(f, format='n3') + + # Check that all named and typed resources have labels and comments + for r in sorted(model.triples([None, rdf.type, None])): + subject = r[0] + if (type(subject) == rdflib.term.BNode or + foaf.Person in model.objects(subject, rdf.type)): + continue + + def has_property(subject, prop): + return model.value(subject, prop, None) is not None + + check(lambda: has_property(subject, rdfs.label), + name = '%s has rdfs:label' % subject) + + if check(lambda: has_property(subject, rdfs.comment), + name = '%s has rdfs:comment' % subject): + comment = str(model.value(subject, rdfs.comment, None)) + + check(lambda: comment.endswith('.'), + name = "%s comment ends in '.'" % subject) + check(lambda: comment.find('\n') == -1, + name = "%s comment contains no newlines" % subject) + check(lambda: comment == comment.strip(), + name = "%s comment has stripped whitespace" % subject) + + # Check that lv2:documentation, if present, is proper Markdown + documentation = model.value(subject, lv2.documentation, None) + if documentation is not None: + check(lambda: documentation.datatype == lv2.Markdown, + name = "%s documentation is explicitly Markdown" % subject) + check(lambda: str(documentation).startswith('\n\n'), + name = "%s documentation starts with blank line" % subject) + check(lambda: str(documentation).endswith('\n\n'), + name = "%s documentation ends with blank line" % subject) + + # Check that all properties are either datatype or object properties + for r in sorted(model.triples([None, rdf.type, rdf.Property])): + subject = r[0] + + check(lambda: ((owl.DatatypeProperty in model.objects(subject, rdf.type)) or + (owl.ObjectProperty in model.objects(subject, rdf.type)) or + (owl.AnnotationProperty in model.objects(subject, rdf.type))), + name = "%s is a Datatype/Object/Annotation property" % subject) + + +def test(tst): + import tempfile + + with tst.group("Data") as check: + specs = (tst.path.ant_glob('lv2/*', dir=True)) + schemas = list(map(str, tst.path.ant_glob("schemas.lv2/*.ttl"))) + spec_files = list(map(str, tst.path.ant_glob("lv2/**/*.ttl"))) + plugin_files = list(map(str, tst.path.ant_glob("plugins/**/*.ttl"))) + bld_files = list(map(str, tst.path.get_bld().ant_glob("**/*.ttl"))) + + if "SERDI" in tst.env and sys.platform != 'win32': + for f in spec_files: + with tempfile.NamedTemporaryFile(mode="w") as tmp: + base_dir = os.path.dirname(f) + cmd = tst.env.SERDI + ["-o", "turtle", f, base_dir] + check(cmd, stdout=tmp.name) + check.file_equals(f, tmp.name) + + if "SORD_VALIDATE" in tst.env: + all_files = schemas + spec_files + plugin_files + bld_files + check(tst.env.SORD_VALIDATE + all_files) + + try: + test_vocabularies(check, specs, spec_files) + except ImportError as e: + Logs.warn('Not running vocabulary tests (%s)' % e) + + with tst.group('Unit') as check: + pattern = tst.env.cprogram_PATTERN % '**/*-test' + for test in tst.path.get_bld().ant_glob(pattern): + check([str(test)]) class Dist(Scripting.Dist): def execute(self): @@ -434,42 +611,82 @@ class DistCheck(Dist, Scripting.DistCheck): def execute(self): Dist.execute(self) self.check() - + def archive(self): Dist.archive(self) +def _get_news_entries(ctx): + from waflib.extras import autoship + + # Get project-level news entries + lv2_entries = autoship.read_ttl_news('lv2', + ['lv2/core/meta.ttl', + 'lv2/core/people.ttl'], + dist_pattern = dist_pattern) + + release_pattern = r'http://lv2plug.in/spec/lv2-([0-9\.]*).tar.bz2' + current_version = sorted(lv2_entries.keys(), reverse=True)[0] + + # Add items from every specification + for specdir in specdirs(ctx.path): + name = os.path.basename(specdir.abspath()) + files = list(ttl_files(ctx.path, specdir)) + if name == "core": + files = [f for f in files if (not f.endswith('/meta.ttl') and + not f.endswith('/people.ttl') and + not f.endswith('/manifest.ttl'))] + + entries = autoship.read_ttl_news(name, files) + + def add_items(lv2_version, name, items): + for item in items: + lv2_entries[lv2_version]["items"] += ["%s: %s" % (name, item)] + + if entries: + latest_revision = sorted(entries.keys(), reverse=True)[0] + for revision, entry in entries.items(): + if "dist" in entry: + match = re.match(release_pattern, entry["dist"]) + if match: + # Append news items to corresponding LV2 version + version = tuple(map(int, match.group(1).split('.'))) + add_items(version, name, entry["items"]) + + elif revision == latest_revision: + # Dev version that isn't in a release yet, append to current + add_items(current_version, name, entry["items"]) + + # Sort news items in each versions + for revision, entry in lv2_entries.items(): + entry["items"].sort() + + return lv2_entries + +def posts(ctx): + "generates news posts in Pelican Markdown format" + + from waflib.extras import autoship + + try: + os.mkdir(os.path.join(out, 'posts')) + except: + pass + + autoship.write_posts(_get_news_entries(ctx), + os.path.join(out, 'posts'), + {'Author': 'drobilla'}) + +def news(ctx): + """write an amalgamated NEWS file to the source directory""" + + from waflib.extras import autoship + + autoship.write_news(_get_news_entries(ctx), 'NEWS') + def dist(ctx): - subdirs = ([ctx.path.find_node('lv2/lv2plug.in/ns/lv2core')] + - ctx.path.ant_glob('plugins/*', dir=True) + - ctx.path.ant_glob('lv2/lv2plug.in/ns/ext/*', dir=True) + - ctx.path.ant_glob('lv2/lv2plug.in/ns/extensions/*', dir=True)) - - # Write NEWS files in source directory - top_entries = {} - for i in subdirs: - def abspath(node): - return node.abspath() - in_files = map(abspath, - ctx.path.ant_glob(i.path_from(ctx.path) + '/*.ttl')) - autowaf.write_news(os.path.basename(i.abspath()), - in_files, - i.abspath() + '/NEWS', - top_entries) - - # Write top level amalgamated NEWS file - autowaf.write_news('lv2', - ['lv2/lv2plug.in/ns/meta/meta.ttl'], - 'NEWS', - None, - top_entries) - - # Build archive + news(ctx) ctx.archive() - # Delete generated NEWS files from source directory - for i in subdirs + [ctx.path]: - try: - os.remove(os.path.join(i.abspath(), 'NEWS')) - except: - pass - +def distcheck(ctx): + news(ctx) + ctx.archive() |