#! /usr/bin/env python
# encoding: utf-8

import sys, os, re, shutil
from waflib import Context, Build, Utils, Options, Logs, Configure, Scripting, Task
from waflib.Errors import WafError, ConfigurationError
from waflib.TaskGen import extension, taskgen_method, feature, before_method, after_method

# Waf version check, for global waf installs

if Context.WAFVERSION[:3] != '2.0':
    print('Incompatible Waf, use 2.0')
    sys.exit (1)

# these variables are mandatory ('/' are converted automatically)
srcdir = '.'
blddir = 'build'

# used by waf dist and waf build
VERSION='0.47.0'
APPNAME='guitarix'

Options.OptionsContext.gxload = \
    Configure.ConfigurationContext.gxload = \
        Build.BuildContext.gxload = \
            lambda ctx, tool: ctx.load(tool, tooldir='waftools')

check_in_faust_version = '2.79.3'  # this Faust should be used for checked in generated files
other_faust_versions = ['2.37.3', '2.33.1', '2.30.5','2.40.0', '2.54.9', '2.60.3','2.68.1','2.69.3','2.70.3', '2.72.14', '2.75.7'] # other Faust versions known to work

g_maxlen = 40 # maximum width of message for 2-column display

fontmsg = """
RobotoCondensed font not found: please install the fonts-roboto package from your distribution (recommended)
or use --install-roboto-font to install the included roboto font."""

################################################################
# options defined for waf configure
################################################################

def options(opt):

    def add_enable_option (option, desc, group=None, disable=False):
        if group == None:
            group = opt
        option_ = option.replace ('-', '_')
        group.add_option ('--enable-' + option, action='store_true',
            default=False, help='Enable ' + desc, dest='enable_' + option_)
        group.add_option ('--disable-' + option, action='store_true',
            default=disable, help='Disable ' + desc, dest='disable_' + option_)

    opt.add_option('--with-gcov',action='store_true',help='Use gcov for profiling',default=False)
    opt.load('intltool')

    opt.add_option('--no-standalone',
                    action='store_false',
                    dest='standalone',
                    default=True,
                    help=("Don't build standalone (JACK) application"))

    group = opt.add_option_group ('Localization and documentation', '')
    add_enable_option ('nls', 'native language support', group)

    opt.load('compiler_cxx')

    comp = opt.get_option_group("Configuration options")

    opt.gxload('cpu_optimization')

    comp.add_option('--enable-lfs',
                    action='store_const',
                    default=False,
                    const=True,
                    help='enable Large File Support (LFS)  ')

    comp.add_option('--no-desktop-update',
                    action='store_const',
                    default=False, #'-fomit-frame-pointer -ffast-math -fstrength-reduce -pipe',
                    const=True,
                    help='dont automatically update desktop database')


    opt.recurse('libgxw/gxw')
    opt.recurse('libgxwmm/gxwmm')

    opt.gxload('zita-resampler')
    opt.gxload('zita-convolver')

    comp.add_option('--libdir',
                   type=str,
                   default='',
                   dest='libdir',
                   help=('library install path '),
                   )

    opt.gxload('faust')

    opt.gxload('lv2')
    opt.gxload('robotofont')

    opt.recurse('pygxw')
    opt.recurse('glade-gxw')

    opt.load('compiler_c') # for pygxw and glade-gxw

    opt.add_option('--dist-tree',
                   type='string',
                   dest='dist_tree',
                   help=("option for 'dist' command: git tree-ish to build"
                         " the archive from [Default: %s]" % 'V%s' % VERSION))

    opt.recurse('src/gx_head')
    opt.recurse('src/NAM')
    opt.recurse('src/RTNeural')


################################################################
# waf configure
################################################################

################################################################
# configuration display helper functions
#

def print_msg(msg, nl=True):
    s = sys.stdout
    if nl:
        t = "\n"
    else:
        t = " "
    s.write(msg+t)

# a bit of waf display formatting
@Configure.conf
def display_msg(msg, status = None, color = None):
    sr = msg
    global g_maxlen
    g_maxlen = max(g_maxlen, len(msg))
    if status is not None:
        print_msg("%s :" % msg.ljust(g_maxlen),False)
        Logs.pprint(color, status)
    else:
        print_msg("%s" % msg.ljust(g_maxlen))

@Configure.conf
def display_msg_1(self, msg, status = None, color = None):
    sr = msg
    global g_maxlen
    g_maxlen = max(g_maxlen, len(msg))
    if status is not None:
        print_msg("%s :" % msg.ljust(g_maxlen),False)
        Logs.pprint(color, status)
    else:
        print_msg("%s" % msg.ljust(g_maxlen))

def error_msg(msg):
    Utils.eprint('RED', msg)

def display_feature(msg, build):
    if build:
        display_msg(msg, "yes", 'CYAN')
    else:
        display_msg(msg, "no", 'YELLOW')

################################################################
# configuration utility functions
#

def option_enabled (option):
    if getattr (Options.options, 'enable_' + option):
        return True
    if getattr (Options.options, 'disable_' + option):
        return False
    return True

@Configure.conf
def define_with_env(self, varname, value):
    self.env[varname] = value
    self.define(varname, value)

################################################################
# configuration helper functions
#
def check_boost(conf):
    code="""
    #include <boost/system/error_code.hpp>
    int main() { boost::system::error_code c; }
    """
    msg = "Checking for boost-system "
    conf.check_cxx(msg = msg, fragment=code, lib="boost_system", uselib_store="BOOST_SYSTEM", mandatory=1,includes='/opt/local/include', libpath='/opt/local/lib')
    # some boost (>1.49) versions depend on boost-system so we will check for it first
    # and later link against boost_system were boost headers are included.
    boost_atleast_version = 104200
    boost_atleast_vermsg = "1.42"
    code="""
    #include <boost/version.hpp>
    #include <boost/format.hpp>
    #include <boost/thread.hpp>
    #if BOOST_VERSION < %d
    #error
    #endif
    int main(){ return 0; }
    """ % boost_atleast_version
    msg = "Checking for boost >= %s" % boost_atleast_vermsg
    conf.check_cxx(msg = msg, fragment=code, lib="boost_system", mandatory=1)
    #conf.check_cxx(msg = msg, fragment=code, lib="boost_system-mt", mandatory=1,includes='/opt/local/include', libpath='/opt/local/lib')

    msg = "Checking for boost_iostreams >= %s" % boost_atleast_vermsg
    conf.check_cxx(msg = msg, fragment=code, lib=["boost_iostreams","boost_system"], uselib_store="BOOST_IOSTREAMS", mandatory=1)
    #conf.check_cxx(msg = msg, fragment=code, lib=["boost_iostreams","boost_system-mt"], uselib_store="BOOST_IOSTREAMS", mandatory=1, includes='/opt/local/include', libpath='/opt/local/lib')
 
    #try:
    #    conf.check_cxx(
    #        msg = msg % "", fragment=code, lib="boost_thread", uselib_store="BOOST_THREAD",
    #        errmsg="not found, trying boost_thread-mt", mandatory=1)
    #except ConfigurationError:
    #    conf.check_cxx(
    #        msg = msg % "-mt",fragment=code, lib="boost_thread-mt",
    #        uselib_store="BOOST_THREAD", mandatory=1)
    
    # not needed: with boost 1.42 Guitarix doesn't generate code which
    #             references the boost threading lib
    #msg = "Checking for boost_thread%%s >= %s" % boost_atleast_vermsg
    #try:
    #    conf.check_cxx(
    #        msg = msg % "", fragment=code, lib="boost_thread", uselib_store="BOOST_THREAD",
    #        errmsg="not found, trying boost_thread-mt", mandatory=1)
    #except ConfigurationError:
    #    conf.check_cxx(
    #        msg = msg % "-mt",fragment=code, lib="boost_thread-mt",
    #        uselib_store="BOOST_THREAD", mandatory=1)


################################################################
# guitarix waf configuration
#

def configure(conf):
    conf.gxload('submodule')
    conf.gxload('bugfixes')
    opt = Options.options

    if opt.disable_sse and opt.optimization:
        display_msg("configuration error", " cannot use --disable-sse and --optimization together", 'RED')
        sys.exit(1)

    conf.env['SHAREDIR'] = conf.env['PREFIX'] + '/share'

    if 'LINGUAS' in os.environ:
        conf.env['LINGUAS'] = os.environ['LINGUAS']

    platform = Utils.unversioned_sys_platform()
    conf.env['IS_MACOSX'] = platform == 'darwin'
    conf.env['IS_LINUX'] = platform == 'linux' or platform == 'posix'
    conf.env['IS_SUN'] = platform == 'sunos'
    conf.env['IS_MSWIN'] = platform == 'win32'

    if conf.env['IS_LINUX']:
        conf.define ('IS_LINUX', platform )

    if conf.env['IS_MACOSX']:
        conf.define ('IS_MACOSX', platform )

    if conf.env['IS_SUN']:
        conf.define ('IS_SUN', platform )

    if conf.env['IS_MSWIN']:
        conf.define ('IS_MSWIN', platform )

    conf.env['OS'] = platform

    if not opt.disable_nls and opt.standalone :
        conf.load('intltool')
        conf.gxload('intltool_extras')
        if conf.env['INTLTOOL'] and conf.env['MSGFMT']:
            nls = 'yes'
        else:
            if opt.enable_nls:
                Logs.pprint ('RED', 'localization N/A')
                sys.exit (1)
            nls = 'N/A'
            conf.define_with_env('DISABLE_NLS', 1)
    else:
        nls = 'no '
        conf.define_with_env('DISABLE_NLS', 1)
    conf.define ('GETTEXT_PACKAGE', APPNAME)

    nls_flag = int(nls == 'yes')
    conf.define_with_env('ENABLE_NLS', nls_flag)
    # compiler
    conf.load('compiler_cxx')
    conf.gxload('compile-extra')
    if opt.with_gcov:
        conf.find_program('gcov')
        conf.env['CXXFLAGS'] = conf.env['CFLAGS'] = ['-fprofile-arcs', '-ftest-coverage']
        conf.env['LDFLAGS'] = ['-lgcov', '-coverage']
    if opt.python_wrapper or opt.glade_support:
        conf.load('compiler_c')
    # linker flags
    conf.env.append_value('LDFLAGS', opt.ldflags.split())
    conf.env['LINKFLAGS'] = conf.env['LDFLAGS']

    # os specific: common settings
    conf.env['OS_LV2_CXXFLAGS']      = ['-fvisibility=hidden']
    if any('clang' not in s for s in conf.env["CXX"]):
        conf.env.append_value('OS_LV2_CXXFLAGS',['-Wl,--exclude-libs,ALL'])
    conf.env['OS_SNDFILE_CFGFLAGS']  = ['--cflags','--libs','sndfile >= 1.0.17']
    conf.env['OS_CAIRO_CFGFLAGS']    = '--cflags --libs'
    conf.env['OS_RESOURCES_LDFLAGS'] = '-z noexecstack'
    if conf.env['OS'] != 'win32':
        if any('clang' not in s for s in conf.env["CXX"]):
            conf.env.append_value('OS_LV2_CXXFLAGS', ['-Wl,-z,relro,-z,now'])
    else:
        # MSWin changes
        conf.env.prepend_value('OS_SNDFILE_CFGFLAGS', ['--static'])
        conf.env['OS_CAIRO_CFGFLAGS']    = '--static %s' % conf.env['OS_CAIRO_CFGFLAGS']
        conf.env['OS_RESOURCES_LDFLAGS'] = '' # no -z options

        conf.env.append_value('DEFINES', ['_USE_MATH_DEFINES=1'])
        # waf always(!) adds "-Wl,-Bdynamic" (SHLIB_MARKER) to the commandline, even for an empty list of dynamic libs.
        # MinGW-gcc automatically links libstdc++ and libgcc_seh dynamically, if "-Wl,-Bdynamic" is present.
        # As a workaround, that marker is replaced by an empty string:
        conf.env['SHLIB_MARKER'] = ''
        # "-shared -static" builds a dynamic library which itself is statically linked
        conf.env['LINKFLAGS_cshlib']=['-shared','-static']
        conf.env['LINKFLAGS_cxxshlib']=['-shared','-static']
        conf.check(features='cxx cxxshlib', lib=['pthread'], uselib_store='PTHREAD')

    # needed libraries
    conf.check_cfg(package='sndfile', args=conf.env['OS_SNDFILE_CFGFLAGS'], uselib_store='SNDFILE', mandatory=1)
    conf.check(header_name='fftw3.h', mandatory=1)
    conf.check_cfg(package='fftw3f', args=['--cflags','--libs','fftw3f >= 3.3.8'], uselib_store='FFTW3', mandatory=1)

    if opt.standalone:
        try:
            conf.check_cfg(package='jack', args=['--cflags','--libs','jack >= 0.116.2','jack <= 1.8.0'], uselib_store='JACK', msg="Checking for 'jackd1'", mandatory=1)
        except ConfigurationError:
            conf.check_cfg(package='jack', args=['--cflags','--libs','jack >= 1.9.2'], uselib_store='JACK', msg="Checking for 'jackd2'", mandatory=1)
        # jack_session support
        if opt.jack_session:
            code = "#include <jack/session.h>\nint main(){return JackSessionID != 0;}"
            conf.check_cxx(fragment=code, msg="Checking for jack session support", define_name='HAVE_JACK_SESSION')
    if opt.standalone:
        conf.env['STANDALONE'] = True
        conf.check_cfg(package='gmodule-export-2.0', args='--cflags --libs', uselib_store='GMODULE_EXPORT', mandatory=1)
        conf.check_cfg(package='libcurl', args=['--cflags','--libs','libcurl >= 7.26.0'], uselib_store='CURL', mandatory=1)
        conf.gxload('robotofont')
        conf.gxload('sassc')
    else:
        conf.env['NO_MAIN'] = True
        opt.no_desktop_update = True

    if opt.standalone:
        conf.check_cfg(package='gthread-2.0', args=['--cflags','--libs','gthread-2.0 >= 2.24'], uselib_store='GTHREAD', mandatory=1)
        conf.check_cfg(package='glibmm-2.4', args=['--cflags','--libs','glibmm-2.4 >= 2.56'], uselib_store='GLIBMM', mandatory=1)
        conf.check_cfg(package='gtk+-3.0', args=['--cflags','--libs','gtk+-3.0 >= 3.22'], uselib_store='GTK2', mandatory=1)
        conf.check_cfg(package='gtkmm-3.0', args=['--cflags','--libs','gtkmm-3.0 >= 3.22'], uselib_store='GTKMM', mandatory=1)
        conf.check_cfg(package='giomm-2.4', args=['--cflags','--libs','giomm-2.4 >= 2.24'], uselib_store='GIOMM', mandatory=1)

    if opt.standalone:
        conf.check(header_name='ladspa.h', errmsg="ladspa.h not found, using included version now", mandatory=0)
        conf.check_cfg(package='lrdf', args='--cflags --libs', uselib_store='LRDF', mandatory=1)
        conf.check_cfg(package='lilv-0', args='--cflags --libs', uselib_store='LILV', mandatory=1)
        check_boost(conf)

    conf.gxload('zita-convolver')
    conf.gxload('zita-resampler')
    conf.find_program("gperf", var='HAVE_GPERF', mandatory=False)
    conf.gxload('faust')

    if Options.options.generate_resources:
        conf.find_program('glib-compile-resources', var='HAVE_GLIB')

    # defines for compilation
    gxsharedir = os.path.normpath(os.path.join(conf.env['SHAREDIR'], 'gx_head'))
    gx_style_dir = os.path.join(gxsharedir,'skins')
    conf.define_with_env('GX_STYLE_DIR', gx_style_dir)
    conf.define_with_env('GX_FACTORY_DIR', os.path.join(gxsharedir,'factorysettings'))
    gx_sound_dir = os.path.join(gxsharedir,'sounds')
    conf.define_with_env('GX_SOUND_DIR', gx_sound_dir)
    conf.define_with_env('GX_SOUND_BPB_DIR', os.path.join(gx_sound_dir,'bands'))
    conf.define_with_env('GX_SOUND_BPA_DIR', os.path.join(gx_sound_dir,'amps'))
    conf.define_with_env('GX_BUILDER_DIR', os.path.join(gxsharedir,'builder'))
    conf.define_with_env('GX_ICON_DIR',
                         os.path.normpath(os.path.join(conf.env['SHAREDIR'], 'guitarix','icons')))
    conf.define_with_env('GX_PIXMAPS_DIR',
                         os.path.normpath(os.path.join(conf.env['SHAREDIR'], 'pixmaps')))
    conf.define_with_env('GX_METAINFO_DIR',
                         os.path.normpath(os.path.join(conf.env['SHAREDIR'], 'metainfo')))

    conf.define('GX_VERSION', VERSION)

    if conf.env.DEST_CPU=='x86_64' or conf.env.DEST_CPU=='aarch64':
        conf.define('OS_64_BIT', 1)
    else:
        conf.define('OS_32_BIT', 1)

    if not opt.libdir:
        conf.env['LIBDIR'] = os.path.normpath(os.path.join(conf.env['PREFIX'], 'lib'))
    else:
        conf.env['LIBDIR'] = opt.libdir

    conf.env['BINDIR'] = os.path.normpath(os.path.join(conf.env['PREFIX'], 'bin'))
    conf.env['DESKAPPS_DIR'] = os.path.normpath(os.path.join(conf.env['SHAREDIR'], 'applications'))
    conf.env['BIN_NAME'] = APPNAME
    if opt.lv2:
        conf.gxload('strip')
        conf.gxload('lv2')

    conf.gxload('cpu_optimization')

    if opt.no_desktop_update:
        conf.env["NO_UPDATE_DESKTOP_DATA_BASE"] = True

    if conf.env['STANDALONE']:
        conf.recurse('src/gx_head');
    if conf.env['FAUST']:
        conf.recurse('src/faust');
    conf.recurse('src/plugins');
    if conf.env["LV2"]:
        #opt.shared_lib = True;
        conf.recurse('src/LV2');

    # config subdirs
    if conf.env['STANDALONE'] or conf.env.GX_PYTHON_WRAPPER or conf.env.GX_GLADE_SUPPORT:
        conf.recurse('pygxw')
        conf.recurse('glade-gxw')
        conf.recurse('libgxwmm')
        conf.recurse('libgxw/gxw')
        conf.recurse('rcstyles')

    if opt.jobs:
        conf.env['JOBS'] = str(opt.jobs)

    # writing config.h
    conf.write_config_header('config.h', remove=False)

    # some output
    print("")
    display_msg("==================")
    version_msg = "GUITARIX II " + VERSION
    print_msg(version_msg)

    print_msg("")
    display_msg("OS ", conf.env['OS'], 'CYAN')
    if conf.cpu_model:
        display_msg("CPU version" , "%s" % conf.cpu_model, "CYAN")
    if not conf.env['NOOPT'] and opt.optimization:
        display_msg("package libcloog-ppl0 not found", "loop optimization is disabled", "RED")
    display_msg("C++ flags", " ".join(conf.env['CXXFLAGS']), 'CYAN')
    display_msg("Link flags", " ".join(conf.env['LINKFLAGS']), 'CYAN')
    display_msg("Compiler %s version" %conf.env["CXX"], "%s" % ".".join(conf.env["CC_VERSION"]), "CYAN")
    if opt.jobs:
        display_msg("Parallel build jobs", conf.env['JOBS'], 'CYAN')
    display_feature("Use prebuild faust files", not conf.env['FAUST'])
    if not opt.faust_float:
        display_msg("Use faust precision", "double", 'CYAN')
    else:
        display_msg("Use faust precision", "single", 'CYAN')
    if opt.faust_vectorize:
        display_msg("call faust with --vectorize", conf.env['FAUST_VECTORIZE'], 'CYAN')
    if opt.faust_vectorize_float:
        display_msg("faust vectorize float prec.", conf.env['FAUST_VECTORIZE_FLOAT'], 'CYAN')
    if opt.faust_options:
        display_msg("Additional faust options", conf.env['FAUST_OPTIONS'], 'CYAN')
    display_feature("Use prebuild gperf files", not conf.env["HAVE_GPERF"])
    display_feature("Use prebuild gresouce file", not conf.env["HAVE_GLIB"])
    display_feature("Avahi service discovery", conf.env["HAVE_AVAHI"])
    display_feature("Bluetooth rfcomm", conf.env["HAVE_BLUEZ"])
    display_feature("Build with NSM support", conf.env["HAVE_LIBLO"])
    display_feature("Use internal zita-resampler", not conf.env['HAVE_ZITA_RESAMPLER'])
    if conf.env['HAVE_CONVOLVER_FFMPEG']:
        display_feature("Use zita-convolver-ffmpeg", True)
    else:
        display_feature("Use internal zita-convolver", not conf.env['HAVE_ZITA_CONVOLVER'])

    display_feature("Build standalone application", conf.env['STANDALONE'])
    display_feature("Jack Session Support", conf.env['HAVE_JACK_SESSION'])
    display_feature("Build LV2 plugins", conf.env['LV2'])
    display_feature("Build MOD LV2 plugin GUI's", conf.env['MODGUI'])
    if opt.lv2:
        display_feature("SSE2 support found", conf.env['SSE2'])
        display_feature("SSE2 support disabled", conf.env['NOSSE'])
    display_feature("skip Python Library Wrapper", not conf.env['GX_PYTHON_WRAPPER'])
    display_feature("use prebuild C++ Library Wrapper", conf.env['USE_GENERATED_CPP'])
    display_feature("skip building shared lib", not conf.env['GX_LIB_SHARED'])
    display_feature("skip install lib-dev", not conf.env['GX_LIB_DEV'])
    display_feature("run ldconfig tool", not conf.env['NO_LDCONFIG'])
    display_feature("update desktop database", not conf.env['NO_UPDATE_DESKTOP_DATA_BASE'])
    display_feature("Localization  (intltool)", conf.env['ENABLE_NLS'])
    display_feature("skip glade support", not conf.env['GX_GLADE_SUPPORT'])
    if conf.env['GX_GLADE_SUPPORT']:
        display_msg("glade catalog dir", conf.env.GLADEUI_catalogdir, 'CYAN')
        display_msg("glade modules dir", conf.env.GLADEUI_moduledir, 'CYAN')
    display_msg("Install prefix", conf.env['PREFIX'], 'CYAN')
    if conf.env['STANDALONE']:
        display_msg("Install standalone binary", conf.env['BINDIR'], 'CYAN')
        display_msg("Guitarix shared files directory", gxsharedir, 'CYAN')
        display_msg("Guitarix pixmaps directory", conf.env['GX_PIXMAPS_DIR'], 'CYAN')
    display_msg("Install library", conf.env['LIBDIR'], 'CYAN')
    if conf.env['FONT']:
        display_msg("Install font roboto condensed", conf.env['GX_FONTS_DIR'], 'CYAN')
        display_feature("Update Font config cache", not conf.env["NO_UPDATE_FONT_CACHE"])
    if opt.lv2:
        display_msg("Install lv2", conf.env['LV2DIR'], 'CYAN')
    if conf.env['g++']:
       error_msg("ERROR       !!! Compiler version is too old !!!")
    if conf.env['FONTMSG']:
        display_msg('Font config',fontmsg,'RED')
    print_msg("")


################################################################
# Build / Install
################################################################

################################################################
# Task features / LV2


################################################################
# build

@Configure.conf
def add_post_fun_ignore_errors(bld, fun):
    def wrapper(ctx):
        try:
            fun(ctx)
        except Exception as e:
            Logs.warn(e.msg)
    bld.add_post_fun(wrapper)

def sub_file(task):
    src_fname = task.inputs[0].abspath()
    dst_fname = task.outputs[0].abspath()
    lst = task.generator.sub_list

    with open(src_fname, 'r') as f:
        txt = f.read()
    for (key, val) in lst:
        re_pat = re.compile(key, re.M)
        txt = re_pat.sub(val, txt)
    with open(dst_fname, 'w') as f:
        f.write(txt)

def build(bld):

    if bld.env['JOBS'] and Options.options.jobs == bld.jobs:
        Options.options.jobs = int(bld.env['JOBS'])
        bld.jobs = int(bld.env['JOBS'])

    if bld.env['INTLTOOL']:
        bld(name='i18n', features='intltool_po', appname=APPNAME, podir='po')

    if any('clang' not in s for s in bld.env["CXX"]):
        gccflags = ['-Wl,--exclude-libs,ALL']
    else:
        gccflags = []

    if bld.env['HAVE_CONVOLVER_FFMPEG']:
        bld.objects(name='ZITA_CONVOLVER',
                    source = 'src/zita-convolver-ffmpeg/zita-convolver.cc',
                    includes = 'src/headers',
                    export_includes = 'src/zita-convolver-ffmpeg',
                    use = ['LIBAVCODEC','LIBAVUTIL'],
                    cxxflags = bld.env.CXXFLAGS_cxxshlib + ['-fvisibility=hidden'] + gccflags,
        )
    elif not bld.env['HAVE_ZITA_CONVOLVER']:
        bld.objects(name='ZITA_CONVOLVER',
                    source='src/zita-convolver/zita-convolver.cc',
                    export_includes = 'src/zita-convolver',
                    cxxflags = bld.env.CXXFLAGS_cxxshlib + ['-fvisibility=hidden'] + gccflags,
                    use = ['FFTW3','PTHREAD'],
        )
    if not bld.env['HAVE_ZITA_RESAMPLER']:
        bld.objects(name='ZITA_RESAMPLER',
                    source=[
                        'src/zita-resampler-1.1.0/resampler.cc',
                        'src/zita-resampler-1.1.0/resampler-table.cc',
                    ],
                    includes = 'src/zita-resampler-1.1.0',
                    export_includes = 'src/zita-resampler-1.1.0',
                    use=['PTHREAD'],
                    cxxflags = bld.env.CXXFLAGS_cxxshlib + ['-fvisibility=hidden'] + gccflags,
        )
    # process subfolders from here
    bld.recurse('libgxw/gxw')
    bld.recurse('libgxwmm')
    bld.recurse('glade-gxw')
    bld.recurse('pygxw')
    bld.recurse('src/NAM')
    bld.recurse('src/RTNeural')
    bld.recurse('src/faust')
    bld.recurse('src/plugins')
    bld.recurse('rcstyles')
    bld.recurse('pixmaps')
    bld.recurse('src/gx_head')
    bld.recurse('src/LV2')
    bld.recurse('documentation')

    if bld.env['STANDALONE']:
        if bld.env['FONT']:
            bld.install_files(os.path.join(bld.env['GX_FONTS_DIR'], 'robotocondensed'),
                              bld.srcnode.ant_glob('fonts/*'), chmod=0o644)
            if bld.is_install and not bld.env["NO_UPDATE_FONT_CACHE"] \
               and not Options.options.no_font_cache_update:
                bld.add_post_fun_ignore_errors(
                    lambda ctx: ctx.exec_command("fc-cache -fv"))

        bld.install_files(bld.env['GX_FACTORY_DIR'], 'factorysettings/dirlist.js', chmod=0o664)
        bld.install_files(bld.env['GX_FACTORY_DIR'], 'factorysettings/Musiclab.gx', chmod=0o664)
        bld.install_files(bld.env['GX_FACTORY_DIR'], 'factorysettings/univibe', chmod=0o664)
        bld.install_files(bld.env['GX_FACTORY_DIR'], 'factorysettings/univibe_mono', chmod=0o664)
        bld.install_files(bld.env['GX_FACTORY_DIR'], 'factorysettings/flanger_mono_gx', chmod=0o664)
        bld.install_files(bld.env['GX_FACTORY_DIR'], 'factorysettings/jconv', chmod=0o664)
        bld.install_files(bld.env['GX_FACTORY_DIR'], 'factorysettings/mbc', chmod=0o664)
        bld.install_files(bld.env['GX_FACTORY_DIR'], 'factorysettings/mbcs', chmod=0o664)
        bld.install_files(bld.env['GX_FACTORY_DIR'], 'factorysettings/graphiceq', chmod=0o664)
        bld.install_files(bld.env['GX_FACTORY_DIR'], 'factorysettings/jconv_mono', chmod=0o664)
        # "greathall.wav" is referenced in the source (keep in sync when changing)
        bld.install_files(bld.env['GX_SOUND_DIR'], './IR/greathall.wav', chmod=0o664)
        bld.install_files(bld.env['GX_SOUND_BPB_DIR'], bld.path.ant_glob('IR/BestPlugins_Bands/*'), chmod=0o664)
        bld.install_files(bld.env['GX_SOUND_BPA_DIR'], bld.path.ant_glob('IR/BestPlugins_Amps/*'), chmod=0o664)
        bld.install_files(bld.env['GX_METAINFO_DIR'], 'org.guitarix.guitarix.metainfo.xml', chmod=0o664)

        if not bld.env['DISABLE_NLS']:
            if bld.env['HAVE_LIBLO']:
                gen_desktop = bld(
                    name      = 'nls',
                    features  = "intltool_in",
                    podir     = "po",
                    flags     = ["-d", "-q", "-u"],
                    source    = 'guitarix.desktop.in_nsm',
                    target    = 'guitarix.desktop',
                    install_path = "${DESKAPPS_DIR}/",
                )
            else:
                gen_desktop = bld(
                    name      = 'nls',
                    features  = "intltool_in",
                    podir     = "po",
                    flags     = ["-d", "-q", "-u"],
                    source    = 'guitarix.desktop.in',
                    target    = 'guitarix.desktop',
                    install_path = "${DESKAPPS_DIR}/",
                )
        else:
            if bld.env['HAVE_LIBLO']:
                bld(name     = "guitarix_desktop",
                    rule     = sub_file,
                    source   = 'guitarix.desktop.in_nsm',
                    target   = 'guitarix.desktop',
                    sub_list = [('^_', '%s' % "")],
                    install_path = "${DESKAPPS_DIR}/",
                )
            else:
                bld(name     = "guitarix_desktop",
                    rule     = sub_file,
                    source   = 'guitarix.desktop.in',
                    target   = 'guitarix.desktop',
                    sub_list = [('^_', '%s' % "")],
                    install_path = "${DESKAPPS_DIR}/",
                )
        if bld.is_install and not Options.options.destdir \
           and not bld.env["NO_UPDATE_DESKTOP_DATA_BASE"] \
           and not Options.options.no_desktop_update:
            bld.add_post_fun_ignore_errors(
                lambda bld: bld.exec_command(
                    ["update-desktop-database",
                     Utils.subst_vars("${DESKAPPS_DIR}", bld.env)])
            )

    if bld.env["GX_LIB_DEV"]:
        for fname in 'libgxw/gxw.pc', 'libgxwmm/gxwmm.pc':
            bld(rule = sub_file,
                source = fname+'.in',
                target = fname,
                sub_list = [('prefix=/path', 'prefix={env[PREFIX]}'.format(env=bld.env)),
                            ('libdir=/path','libdir={env[LIBDIR]}'.format(env=bld.env)),
                            ],
                )
            bld.install_files('${LIBDIR}/pkgconfig', fname)
        bld.install_files('${PREFIX}/include/gxw',bld.srcnode.ant_glob('libgxw/gxw/*.h'))
        bld.install_files('${PREFIX}/include','libgxw/gxw.h')
        bld.install_files('${PREFIX}/include/gxwmm',bld.srcnode.ant_glob('libgxwmm/gxwmm-generated/*.h'))
        bld.install_files('${PREFIX}/include/gxwmm',bld.srcnode.ant_glob('libgxwmm/gxwmm/*.h'))
        bld.install_files('${PREFIX}/include','libgxwmm/gxwmm.h')

################################################################
# other commands
################################################################

def etags(ctx):
    "Create an Emacs ETAGS file for the src directory"
    cmd = ("etags -o TAGS libgxw/gxw/*.cpp libgxwmm/gxwmm/*.ccg libgxwmm/gxwmm/*.hg"
           " libgxw/gxw/*.h src/gx_head/engine/*.cpp src/gx_head/engine/*.cc"
           " src/gx_head/gui/*.cpp src/headers/*.h")
    Logs.debug("runner: system command -> %s" % cmd)
    ctx.exec_command(cmd)

def update_pot(ctx):
    "extract all text for localization into po/untitled.pot"
    ctx.exec_command("cd po && intltool-update -p")


# redefine the standard commands dist and distcheck

dir_prefix = "guitarix"
tar_prefix = "guitarix2"
tar_ext = ".tar.xz"

class Archive(Context.Context):
    # If cmd is set to another value, like 'archive', a new command
    # would be defined (and Archive.__doc__ text will be used as help
    # text).
    "makes a tarball for redistributing the sources"
    cmd='dist'
    def execute(self):
        self.exec_command(
            "git ls-files --recurse-submodules | tar c -T- --transform 's,^,%(d)s-%(v)s/,' | xz "
            " > %(p)s-%(v)s%(e)s" % self.make_dict())

    def create_and_unpack(self, d, path):
        self.exec_command(
            "git ls-files --recurse-submodules | tar c -T- | tar -xf - -C %s" % path)

    def make_dict(self):
        t = Options.options.dist_tree
        if not t:
            t = 'V%s' % VERSION
        d = dict(t=t, d=dir_prefix, p=tar_prefix, e=tar_ext)
        if re.match(r"V\d", t):
            d["v"] = t[1:]
            if self.exec_command("git show-ref --verify --quiet --tags refs/tags/'%(t)s'" % d) != 0:
                raise WafError(
                    "tag '%(t)s' not found: please create revision tag" % d)
        else:
            d["v"] = t[1:]
            try:
                s = self.cmd_and_log("git show-ref --hash=8 '%(t)s' | sort -u" % d,
                                     quiet=True)
                if not s:
                    raise ValueError
                s = s.split()
                if len(s) > 1:
                    raise WafError(
                        "git tree-ish '%(t)s' is ambiguous" % d)
                s = s[0]
            except ValueError:
                raise WafError(
                    "git tree-ish '%(t)s' not found" % d)
            d["v"] = "git-" + s.strip()
        return d



class ArchiveCheck(Archive):
    cmd='distcheck'
    def execute(self):
        '''checks if the sources compile (tarball from 'dist')'''
        import tempfile
        waf = [sys.executable, sys.argv[0]]
        d = self.make_dict()
        path = "%(d)s-%(v)s" % d
        if os.path.exists(path):
            prompt = input
            if sys.version_info.major < 3:
                prompt = raw_input
            try:
                prompt("%s exists, press enter to remove (stop with Ctrl-C)\n" % path)
            except KeyboardInterrupt:
                print("")
                raise WafError('distcheck cancelled')
                return
            else:
                print("removing %s" % path)
            shutil.rmtree(path)
        print("creating tarball and extracting files into %s" % path)
        os.mkdir(path)
        self.create_and_unpack(d, path)
        build_path = path
        instdir = tempfile.mkdtemp('.inst', path)
        cmdargs = waf + ['configure','build','install','uninstall', '--destdir='+instdir]
        print('starting "%s"' % (" ".join(cmdargs)))
        print("")
        ret = Utils.subprocess.Popen(cmdargs, cwd=build_path).wait()
        if ret:
            raise WafError('distcheck failed with code %i'%ret)
        if os.path.exists(instdir):
            raise WafError('distcheck succeeded, but files were left in %s'%instdir)
        shutil.rmtree(path)

class DistClean(Context.Context):
    # redefine distclean
    cmd='distclean'
    def execute(self):
        Context.g_module.distclean(self)
        html_path = 'html'
        if os.path.exists(html_path):
            shutil.rmtree(html_path)
