aboutsummaryrefslogtreecommitdiffstats
path: root/wscript
diff options
context:
space:
mode:
Diffstat (limited to 'wscript')
-rw-r--r--wscript869
1 files changed, 543 insertions, 326 deletions
diff --git a/wscript b/wscript
index 08803e1..7c7161f 100644
--- a/wscript
+++ b/wscript
@@ -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()