You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
noisicaa/wscript

396 lines
12 KiB
Plaintext

# @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)")
2 years ago
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'