#!/usr/bin/env python import datetime import glob import os import shutil import subprocess import sys 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.0.7' # Mandatory variables top = '.' out = 'build' def options(opt): opt.load('compiler_cc') opt.load('compiler_cxx') autowaf.set_options(opt) opt.add_option('--test', action='store_true', dest='build_tests', help='Build unit tests') 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') def configure(conf): try: conf.load('compiler_c') conf.load('compiler_cxx') except: Options.options.build_tests = False Options.options.no_plugins = True autowaf.configure(conf) autowaf.set_c99_mode(conf) if Options.platform == 'win32' or not hasattr(os.path, 'relpath'): Logs.warn('System does not support linking headers, copying') Options.options.copy_headers = True conf.env.BUILD_TESTS = Options.options.build_tests conf.env.BUILD_PLUGINS = not Options.options.no_plugins conf.env.COPY_HEADERS = Options.options.copy_headers # Check for gcov library (for test coverage) if conf.env.BUILD_TESTS and not conf.is_defined('HAVE_GCOV'): conf.check_cc(lib='gcov', define_name='HAVE_GCOV', mandatory=False) autowaf.set_recursive() conf.recurse('lv2/lv2plug.in/ns/lv2core') conf.env.LV2_BUILD = ['lv2/lv2plug.in/ns/lv2core'] if conf.env.BUILD_PLUGINS: for i in conf.path.ant_glob('plugins/*', dir=True): 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) 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) lv2specgen.save(task.outputs[0].abspath(), specdoc) # Name (comment is to act as a sort key) row = '%s' % ( b, path[len('lv2/lv2plug.in/ns/'):], b) # Description if shortdesc: row += '' + str(shortdesc) + '' else: row += '' # Version version_str = '%s.%s' % (minor, micro) if minor == 0 or (micro % 2 != 0): row += '' + version_str + '' else: row += '' + version_str + '' # Status deprecated = model.value(ext_node, owl.deprecated, None) if minor == 0: row += 'Experimental' elif deprecated and str(deprecated[2]) != "false": row += 'Deprecated' elif micro % 2 == 0: row += 'Stable' row += '' 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') for line in i: for key in dict: line = line.replace(key, dict[key]) o.write(line) i.close() o.close() # Task to build extension index def build_index(task): global index_lines rows = [] for f in task.inputs: if not f.abspath().endswith('index.html.in'): rowfile = open(f.abspath(), 'r') rows += rowfile.readlines() rowfile.close() subst_file(task.inputs[0].abspath(), task.outputs[0].abspath(), { '@ROWS@': ''.join(rows), '@TIME@': datetime.datetime.utcnow().strftime('%F %H:%M UTC') }) # 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()) 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) # 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))) # Build test program if applicable if bld.env.BUILD_TESTS and bld.path.find_node(path + '/%s-test.c' % name): test_lib = [] test_cflags = [''] if bld.is_defined('HAVE_GCOV'): test_lib += ['gcov'] test_cflags += ['-fprofile-arcs', '-ftest-coverage'] # Unit test program bld(features = 'c cprogram', source = path + '/%s-test.c' % name, lib = test_lib, target = path + '/%s-test' % name, install_path = None, cflags = test_cflags) # Install bundle bld.install_files(bundle_dir, bld.path.ant_glob(path + '/?*.*', excl='*.in')) # 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))) 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)) # 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)) # LV2 pkgconfig file bld(features = 'subst', source = 'lv2.pc.in', target = 'lv2.pc', install_path = '${LIBDIR}/pkgconfig', PREFIX = bld.env.PREFIX, INCLUDEDIR = bld.env.INCLUDEDIR, VERSION = VERSION) # Build extensions for i in exts: build_ext(bld, i.srcpath()) # Build plugins for i in bld.env.LV2_BUILD: bld.recurse(i) if bld.env.DOCS: # Build Doxygen documentation (and tags file) autowaf.build_dox(bld, 'LV2', VERSION, top, out) # Copy stylesheet to build directory bld(features = 'subst', is_copy = True, name = 'copy', source = 'doc/style.css', target = 'aux/style.css') 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 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) # 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]) index_files.sort() bld.add_group() # Barrier (wait for lv2specgen to build index) # Build extension index bld(rule = build_index, name = 'index', source = ['lv2/lv2plug.in/ns/index.html.in'] + index_files, target = 'ns/index.html') if bld.env.BUILD_TESTS: # Generate a compile test .c 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() bld(rule = gen_build_test, source = bld.path.ant_glob('lv2/**/*.h'), target = 'build-test.c', install_path = None) bld(features = 'c cprogram', source = bld.path.get_bld().make_node('build-test.c'), target = 'build-test', install_path = None) 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=['.']) class Dist(Scripting.Dist): def execute(self): 'Execute but do not call archive() since dist() has already done so.' self.recurse([os.path.dirname(Context.g_module.root_path)]) def get_tar_path(self, node): 'Resolve symbolic links to avoid broken links in tarball.' return os.path.realpath(node.abspath()) class DistCheck(Dist, Scripting.DistCheck): def execute(self): Dist.execute(self) self.check() def archive(self): Dist.archive(self) 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/extension/*', dir=True)) # Write NEWS files in source directory for i in subdirs: print '* ' + i.path_from(ctx.path) print ctx.path.ant_glob(i.path_from(ctx.path) + '/*.ttl') 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') # Build archive ctx.archive() # Delete generated NEWS files from source directory for i in subdirs: try: os.remove(os.path.join(i.abspath(), 'NEWS')) except: pass