|
|
|
# @begin:license
|
|
|
|
#
|
|
|
|
# Copyright (c) 2015-2021, Ben Niemann <pink@odahoda.de>
|
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License along
|
|
|
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
#
|
|
|
|
# @end:license
|
|
|
|
|
|
|
|
import contextlib
|
|
|
|
import fnmatch
|
|
|
|
import logging
|
|
|
|
import os.path
|
|
|
|
import subprocess
|
|
|
|
from waflib.Build import BuildContext, CFG_FILES
|
|
|
|
from waflib.Configure import conf
|
|
|
|
from waflib.Errors import BuildError, ConfigurationError
|
|
|
|
from waflib import Logs
|
|
|
|
from waflib import Utils
|
|
|
|
|
|
|
|
top = '.'
|
|
|
|
out = 'build'
|
|
|
|
|
|
|
|
# Properly format command lines when using -v
|
|
|
|
os.environ['WAF_CMD_FORMAT'] = 'string'
|
|
|
|
|
|
|
|
|
|
|
|
@conf
|
|
|
|
def pkg_config(ctx, store, package, minver):
|
|
|
|
ctx.check_cfg(
|
|
|
|
package=package,
|
|
|
|
args=['%s >= %s' % (package, minver), '--cflags', '--libs'],
|
|
|
|
uselib_store=store,
|
|
|
|
msg="Checking for {} version >= {}".format(package, minver))
|
|
|
|
|
|
|
|
|
|
|
|
def options(ctx):
|
|
|
|
ctx.load('compiler_cxx')
|
|
|
|
ctx.load('qt5')
|
|
|
|
|
|
|
|
grp = ctx.add_option_group('Test options')
|
|
|
|
grp.parser.set_defaults(with_tests=True)
|
|
|
|
grp.add_option(
|
|
|
|
'--release',
|
|
|
|
action='store_true',
|
|
|
|
help="Build release version.")
|
|
|
|
grp.add_option(
|
|
|
|
'--with-tests',
|
|
|
|
action='store_true',
|
|
|
|
dest='with_tests',
|
|
|
|
help="Enable tests.")
|
|
|
|
grp.add_option(
|
|
|
|
'--without-tests',
|
|
|
|
action='store_false',
|
|
|
|
dest='with_tests',
|
|
|
|
help="Disable tests.")
|
|
|
|
grp.add_option(
|
|
|
|
'--with-coverage',
|
|
|
|
action='store_true',
|
|
|
|
default=False,
|
|
|
|
help="Enable code coverage.")
|
|
|
|
grp.add_option(
|
|
|
|
'--test-lint',
|
|
|
|
action='store_true',
|
|
|
|
default=False,
|
|
|
|
help="Run lint checks (mypy, pylint, ...).")
|
|
|
|
grp.add_option(
|
|
|
|
'--no-run-tests',
|
|
|
|
dest='run_tests',
|
|
|
|
action='store_false',
|
|
|
|
default=True,
|
|
|
|
help="Build, but do not run tests for './waf test'.")
|
|
|
|
grp.add_option(
|
|
|
|
'--test',
|
|
|
|
default=None,
|
|
|
|
help="Select specific test case to run (see pytest -k)")
|
|
|
|
|
|
|
|
|
|
|
|
def configure(ctx):
|
|
|
|
if ctx.options.with_coverage and not ctx.options.with_tests:
|
|
|
|
raise ConfigurationError("--with-coverage requires --with-tests")
|
|
|
|
|
|
|
|
ctx.load('compiler_cxx')
|
|
|
|
ctx.qt5_vars = ['Qt5Core', 'Qt5Gui', 'Qt5Widgets', 'Qt5Svg', 'Qt5Quick', 'Qt5QuickControls2', 'Qt5Qml'];
|
|
|
|
ctx.load('qt5')
|
|
|
|
ctx.load('python')
|
|
|
|
if not ctx.options.release:
|
|
|
|
ctx.load('build_utils.waf.local_rpath', tooldir='.')
|
|
|
|
ctx.load('build_utils.waf.static', tooldir='.')
|
|
|
|
ctx.load('build_utils.waf.qml', tooldir='.')
|
|
|
|
ctx.load('build_utils.waf.python', tooldir='.')
|
|
|
|
ctx.load('build_utils.waf.cython', tooldir='.')
|
|
|
|
ctx.load('build_utils.waf.flatbuffers', tooldir='.')
|
|
|
|
|
|
|
|
ctx.env.DATADIR = os.path.join(ctx.env.PREFIX, 'share', 'noisicaa')
|
|
|
|
ctx.env.LIBDIR = os.path.join(ctx.env.PREFIX, 'lib', 'noisicaa')
|
|
|
|
|
|
|
|
ctx.env.append_value('CXXFLAGS', ['-O2', '-std=c++17'])
|
|
|
|
ctx.env.append_value('CFLAGS', ['-O2'])
|
|
|
|
ctx.env.append_value('LDFLAGS', ['-lrt'])
|
|
|
|
ctx.env.append_value('INCLUDES', [ctx.srcnode.abspath(), ctx.bldnode.abspath()])
|
|
|
|
|
|
|
|
ctx.check_cfg(atleast_pkgconfig_version='0.29')
|
|
|
|
ctx.pkg_config('JACK', 'jack', '1.9')
|
|
|
|
ctx.pkg_config('FLATBUFFERS', 'flatbuffers', '2.0.0')
|
|
|
|
ctx.pkg_config('SWRESAMPLE', 'libswresample', '1.2')
|
|
|
|
ctx.pkg_config('AVUTIL', 'libavutil', '54')
|
|
|
|
ctx.pkg_config('FMT', 'fmt', '7.1.3')
|
|
|
|
ctx.pkg_config('PYTHON', 'python3', '3.8')
|
|
|
|
|
|
|
|
ctx.check_python_module('flatbuffers', condition="ver >= num(2, 0)")
|
|
|
|
ctx.check_python_module('eventfd') # has no __version__
|
|
|
|
|
|
|
|
ctx.find_program('faust')
|
|
|
|
ctx.find_program('rsvg-convert', var='RSVG')
|
|
|
|
|
|
|
|
if ctx.options.with_tests:
|
|
|
|
ctx.env['ENABLE_TEST'] = True
|
|
|
|
|
|
|
|
ctx.env.append_value('CXXFLAGS', ['-g', '-Wall'])
|
|
|
|
ctx.env.append_value('CFLAGS', ['-g'])
|
|
|
|
|
|
|
|
ctx.pkg_config('GTEST', 'gtest', '1.10')
|
|
|
|
ctx.pkg_config('GMOCK', 'gmock', '1.10')
|
|
|
|
ctx.check_python_module('pytest', condition="ver >= num(6, 2, 0)")
|
|
|
|
ctx.check_python_module('pytest_cpp') # has no __version__
|
|
|
|
ctx.check_python_module('pytest_mypy') # has no __version__
|
|
|
|
ctx.check_python_module('pytest_pylint') # has no __version__
|
|
|
|
ctx.check_python_module('pytest_asyncio', condition="ver >= num(0, 16, 0)")
|
|
|
|
ctx.check_python_module('pytestqt', condition="ver >= num(4, 0, 2)")
|
|
|
|
ctx.find_program('pytest')
|
|
|
|
|
|
|
|
else:
|
|
|
|
ctx.env['ENABLE_TEST'] = False
|
|
|
|
|
|
|
|
if ctx.options.with_coverage:
|
|
|
|
ctx.start_msg("Enable code coverage analysis")
|
|
|
|
ctx.env['ENABLE_COVERAGE'] = True
|
|
|
|
ctx.env.append_value('CXXFLAGS', ['-fprofile-arcs', '-ftest-coverage'])
|
|
|
|
ctx.env.append_value('CFLAGS', ['-fprofile-arcs', '-ftest-coverage'])
|
|
|
|
ctx.env.append_value('LINKFLAGS', ['-lgcov', '-coverage'])
|
|
|
|
ctx.end_msg('yes')
|
|
|
|
|
|
|
|
ctx.check_python_module('pytest_cov', condition="ver >= num(3, 0, 0)")
|
|
|
|
ctx.find_program('coverage-lcov')
|
|
|
|
ctx.find_program('lcov')
|
|
|
|
ctx.find_program('gcov')
|
|
|
|
ctx.find_program('genhtml')
|
|
|
|
|
|
|
|
else:
|
|
|
|
ctx.env['ENABLE_COVERAGE'] = False
|
|
|
|
|
|
|
|
ctx.recurse('3rdparty')
|
|
|
|
|
|
|
|
create_config_py(ctx)
|
|
|
|
|
|
|
|
|
|
|
|
def create_config_py(ctx):
|
|
|
|
ctx.start_msg("Create noisicaa/config.py")
|
|
|
|
node = ctx.bldnode.make_node('noisicaa/config.py')
|
|
|
|
node.parent.mkdir()
|
|
|
|
|
|
|
|
config = []
|
|
|
|
config.append("# Generated file, do not edit.")
|
|
|
|
config.append("")
|
|
|
|
|
|
|
|
with open(os.path.join(str(ctx.path), 'VERSION'), 'r') as fp:
|
|
|
|
config.append('VERSION = %r' % fp.readline().strip())
|
|
|
|
config.append('DATA_DIR = %r' % ctx.env.DATADIR)
|
|
|
|
config.append('LIB_DIR = %r' % ctx.env.LIBDIR)
|
|
|
|
config.append("")
|
|
|
|
|
|
|
|
defines = {}
|
|
|
|
for k in ctx.env.DEFINES:
|
|
|
|
a, _, b = k.partition('=')
|
|
|
|
defines[a] = b
|
|
|
|
|
|
|
|
for k in ctx.env['define_key']:
|
|
|
|
caption = ctx.get_define_comment(k)
|
|
|
|
if caption:
|
|
|
|
caption = ' # %s' % caption
|
|
|
|
try:
|
|
|
|
value = defines[k]
|
|
|
|
txt = '%s = %r%s' % (k, value, caption)
|
|
|
|
except KeyError:
|
|
|
|
txt = '# %s = UNSET%s' % (k, caption)
|
|
|
|
config.append(txt)
|
|
|
|
|
|
|
|
config.append("")
|
|
|
|
node.write('\n'.join(config))
|
|
|
|
|
|
|
|
# config files must not be removed on "waf clean"
|
|
|
|
ctx.env.append_unique(CFG_FILES, [node.abspath()])
|
|
|
|
ctx.end_msg('ok')
|
|
|
|
|
|
|
|
|
|
|
|
def clear_coverage_data(ctx):
|
|
|
|
if not ctx.env['ENABLE_COVERAGE']:
|
|
|
|
return
|
|
|
|
|
|
|
|
if os.path.exists(os.path.join(ctx.out_dir, '.coverage')):
|
|
|
|
os.unlink(os.path.join(ctx.out_dir, '.coverage'))
|
|
|
|
|
|
|
|
for dirpath, dirnames, filenames in os.walk(ctx.out_dir):
|
|
|
|
for filename in filenames:
|
|
|
|
if fnmatch.fnmatch(filename, '*.gcda'):
|
|
|
|
os.unlink(os.path.join(dirpath, filename))
|
|
|
|
|
|
|
|
def coverage_report(ctx):
|
|
|
|
if not ctx.env['ENABLE_COVERAGE']:
|
|
|
|
return
|
|
|
|
|
|
|
|
info_files = []
|
|
|
|
|
|
|
|
Logs.info("%sCollecting gcov data...", Logs.get_color('BLUE'))
|
|
|
|
argv = [
|
|
|
|
ctx.env.LCOV[0],
|
|
|
|
'--gcov-tool=' + ctx.env.GCOV[0],
|
|
|
|
'--exclude=*_test.cpp',
|
|
|
|
'--exclude=*_generated.h',
|
|
|
|
'--exclude=*.fb.cc',
|
|
|
|
'--exclude=*.fb.h',
|
|
|
|
'--exclude=*.pb.cc',
|
|
|
|
'--exclude=*.pb.h',
|
|
|
|
'--exclude=*.pyx.cpp',
|
|
|
|
'--no-external',
|
|
|
|
'--capture',
|
|
|
|
'--directory', ctx.top_dir,
|
|
|
|
'-o', os.path.join(ctx.out_dir, 'lcov-cpp.info'),
|
|
|
|
]
|
|
|
|
kw = {
|
|
|
|
'cwd': ctx.top_dir,
|
|
|
|
'stdout': subprocess.PIPE,
|
|
|
|
'stderr': subprocess.STDOUT,
|
|
|
|
}
|
|
|
|
ctx.log_command(argv, kw)
|
|
|
|
rc, stdout, _ = Utils.run_process(argv, kw)
|
|
|
|
if rc != 0:
|
|
|
|
print(stdout.decode('utf-8'))
|
|
|
|
raise BuildError()
|
|
|
|
info_files.append('lcov-cpp.info')
|
|
|
|
|
|
|
|
if os.path.exists(os.path.join(ctx.out_dir, '.coverage')):
|
|
|
|
Logs.info("%sConverting coverage.py data...", Logs.get_color('BLUE'))
|
|
|
|
argv = [
|
|
|
|
ctx.env.COVERAGE_LCOV[0],
|
|
|
|
'--relative_path',
|
|
|
|
'--data_file_path=' + os.path.join(ctx.out_dir, '.coverage'),
|
|
|
|
'--output_file_path=' + os.path.join(ctx.out_dir, 'lcov-py.info'),
|
|
|
|
]
|
|
|
|
kw = {
|
|
|
|
'cwd': ctx.out_dir,
|
|
|
|
'stdout': subprocess.PIPE,
|
|
|
|
'stderr': subprocess.STDOUT,
|
|
|
|
}
|
|
|
|
ctx.log_command(argv, kw)
|
|
|
|
rc, stdout, _ = Utils.run_process(argv, kw)
|
|
|
|
if rc != 0:
|
|
|
|
print(stdout.decode('utf-8'))
|
|
|
|
raise BuildError()
|
|
|
|
info_files.append('lcov-py.info')
|
|
|
|
|
|
|
|
Logs.info("%sMerging coverage data...", Logs.get_color('BLUE'))
|
|
|
|
argv = [ctx.env.LCOV[0]]
|
|
|
|
for f in info_files:
|
|
|
|
argv += ['-a', f]
|
|
|
|
argv += ['-o', 'lcov-merged.info']
|
|
|
|
kw = {
|
|
|
|
'cwd': ctx.out_dir,
|
|
|
|
'stdout': subprocess.PIPE,
|
|
|
|
'stderr': subprocess.STDOUT,
|
|
|
|
}
|
|
|
|
ctx.log_command(argv, kw)
|
|
|
|
rc, stdout, _ = Utils.run_process(argv, kw)
|
|
|
|
if rc != 0:
|
|
|
|
print(stdout.decode('utf-8'))
|
|
|
|
raise BuildError()
|
|
|
|
|
|
|
|
Logs.info("%sGenerating coverage report...", Logs.get_color('BLUE'))
|
|
|
|
report_dir = os.path.join(ctx.out_dir, 'coverage')
|
|
|
|
os.makedirs(report_dir, exist_ok=True)
|
|
|
|
|
|
|
|
argv = [
|
|
|
|
ctx.env.GENHTML[0],
|
|
|
|
os.path.join(ctx.out_dir, 'lcov-merged.info'),
|
|
|
|
'-o', report_dir,
|
|
|
|
]
|
|
|
|
kw = {
|
|
|
|
'cwd': ctx.out_dir,
|
|
|
|
'stdout': subprocess.PIPE,
|
|
|
|
'stderr': subprocess.STDOUT,
|
|
|
|
}
|
|
|
|
ctx.log_command(argv, kw)
|
|
|
|
rc, stdout, _ = Utils.run_process(argv, kw)
|
|
|
|
if rc != 0:
|
|
|
|
print(stdout.decode('utf-8'))
|
|
|
|
raise BuildError()
|
|
|
|
|
|
|
|
Logs.info("%sCoverage report: %sfile://%s/index.html", Logs.get_color('BLUE'), Logs.get_color('PINK'), report_dir)
|
|
|
|
|
|
|
|
@conf
|
|
|
|
@contextlib.contextmanager
|
|
|
|
def group(ctx, grp):
|
|
|
|
old_grp = ctx.current_group
|
|
|
|
ctx.set_group(grp)
|
|
|
|
try:
|
|
|
|
yield
|
|
|
|
finally:
|
|
|
|
ctx.set_group(old_grp)
|
|
|
|
|
|
|
|
@conf
|
|
|
|
def in_group(ctx, grp):
|
|
|
|
return ctx.get_group_name(ctx.current_group) == grp
|
|
|
|
|
|
|
|
|
|
|
|
def run_tests(ctx):
|
|
|
|
import pytest
|
|
|
|
os.environ["COLUMNS"] = "80"
|
|
|
|
|
|
|
|
argv = [
|
|
|
|
ctx.env.PYTEST[0],
|
|
|
|
'-c', os.path.join(ctx.top_dir, 'etc', 'pytest.ini'),
|
|
|
|
'-v',
|
|
|
|
]
|
|
|
|
if Logs.verbose:
|
|
|
|
argv += ['-s']
|
|
|
|
if ctx.options.test:
|
|
|
|
argv += ['-k', ctx.options.test]
|
|
|
|
if ctx.options.test_lint:
|
|
|
|
argv += ['--mypy', '--pylint', '--pylint-rcfile=' + os.path.join(ctx.top_dir, 'etc', 'pylintrc')]
|
|
|
|
if ctx.env.ENABLE_COVERAGE:
|
|
|
|
argv += ['--cov=noisicaa', '--cov-config=' + os.path.join(ctx.top_dir, 'etc', 'coveragerc'), '--cov-report=']
|
|
|
|
|
|
|
|
env = dict(os.environ)
|
|
|
|
# Get e.g. the pipewire jack emulation out of the way.
|
|
|
|
env['LD_LIBRARY_PATH'] = ''
|
|
|
|
|
|
|
|
kw = {
|
|
|
|
'cwd': ctx.out_dir,
|
|
|
|
'env': env,
|
|
|
|
}
|
|
|
|
ctx.log_command(argv, kw)
|
|
|
|
rc, _, _ = Utils.run_process(argv, kw)
|
|
|
|
if rc != 0:
|
|
|
|
raise BuildError()
|
|
|
|
|
|
|
|
coverage_report(ctx)
|
|
|
|
|
|
|
|
|
|
|
|
def build(ctx):
|
|
|
|
if ctx.cmd == 'test' and not ctx.env.ENABLE_TEST:
|
|
|
|
raise ConfigurationError("Not configured with --with-tests.")
|
|
|
|
|
|
|
|
ctx.GRP_BUILD_TOOLS = 'build:tools'
|
|
|
|
ctx.GRP_BUILD_GENERATED = 'build:generated'
|
|
|
|
ctx.GRP_BUILD_MAIN = 'build:main'
|
|
|
|
ctx.GRP_BUILD_TESTS = 'build:tests'
|
|
|
|
|
|
|
|
ctx.add_group(ctx.GRP_BUILD_TOOLS)
|
|
|
|
ctx.add_group(ctx.GRP_BUILD_GENERATED)
|
|
|
|
ctx.add_group(ctx.GRP_BUILD_MAIN)
|
|
|
|
ctx.add_group(ctx.GRP_BUILD_TESTS)
|
|
|
|
|
|
|
|
ctx.set_group(ctx.GRP_BUILD_MAIN)
|
|
|
|
|
|
|
|
ctx(rule='touch ${TGT}', target='.nobackup')
|
|
|
|
|
|
|
|
with ctx.group(ctx.GRP_BUILD_TOOLS):
|
|
|
|
ctx.recurse('build_utils')
|
|
|
|
|
|
|
|
ctx.recurse('3rdparty')
|
|
|
|
ctx.recurse('bin')
|
|
|
|
ctx.recurse('data')
|
|
|
|
ctx.recurse('noisicaa')
|
|
|
|
|
|
|
|
if ctx.cmd == 'test' and ctx.options.run_tests:
|
|
|
|
ctx.add_pre_fun(clear_coverage_data)
|
|
|
|
ctx.add_post_fun(run_tests)
|
|
|
|
|
|
|
|
|
|
|
|
class TestContext(BuildContext):
|
|
|
|
"""run unittests"""
|
|
|
|
|
|
|
|
cmd = 'test'
|