project(
    'anastasis',
    'c',
    license: 'AGPLv3',
    meson_version: '>=1.1.0',
    version: run_command('sh', 'scripts/get_version.sh', check: true).stdout().strip(),
    # PIE is a built-in option, so it has to be set here rather than under the
    # 'hardening' option below; -Db_pie=false still turns it off.
    default_options: ['b_pie=true'],
)

cc = meson.get_compiler('c')
incdir = include_directories('src/include')

# Used to populate gnunet_private_config.h
private_config = configuration_data()


plugindir = get_option('libdir') / 'anastasis'
pkgdatadir = get_option('datadir') / 'anastasis'
pkgcfgdir = pkgdatadir / 'config.d'
docdir = get_option('datadir') / 'doc' / 'anastasis'

if get_option('install-rpath')
    rpath_option = get_option('prefix') / get_option('libdir')
else
    rpath_option = ''
endif

install_emptydir(docdir)
install_data('README', 'COPYING', install_dir: docdir)

gnunet_user = false
dpkg_architecture_bin = find_program(
    'dpkg-architecture',
    '/usr/bin/dpkg-architecture',
    required: false,
)
if dpkg_architecture_bin.found()
    private_config.set(
        'MULTIARCH',
        dpkg_architecture_bin.full_path() + ' -qDEB_HOST_MULTIARCH',
    )
endif

TALER_PLUGIN_LDFLAGS = [
    '-export-dynamic',
    '-avoid-version',
    '-module',
    '--no-undefined',
]

cdata = configuration_data()
if not get_option('only-doc')
    add_project_arguments(
        '-Wall',
        '-Wno-address-of-packed-member',
        language: 'c',
    )

    # Source builds get no hardening from anywhere else: distribution packaging
    # passes its own flags, but ./configure && make does not.  For a key backup
    # tool that is worth having on by default.
    if get_option('hardening')
        hardening_cflags = cc.get_supported_arguments(
            '-fstack-protector-strong',
            '-fstack-clash-protection',
        )
        # _FORTIFY_SOURCE needs optimisation to do anything and warns without
        # it, so only set it when the build is actually optimised.  'plain' is
        # excluded as well: it means the packager supplies the flags, and
        # distributions that pass their own -D_FORTIFY_SOURCE would then get a
        # "redefined" warning in every translation unit.
        if get_option('optimization') not in ['0', 'g', 'plain']
            # The obvious test, '#if _FORTIFY_SOURCE < 3', can never fail: it
            # reads back the value just put on the command line.  glibc does
            # not lower _FORTIFY_SOURCE when it cannot honour the level, it
            # clamps __USE_FORTIFY_LEVEL and emits a #warning -- hence both the
            # macro tested here and the -Werror that turns that warning into
            # the failure it should be.  A libc that defines neither is treated
            # as not supporting level 3.
            if cc.compiles(
                '''#include <string.h>
                   #if !defined(__USE_FORTIFY_LEVEL) || __USE_FORTIFY_LEVEL < 3
                   #error no level 3
                   #endif
                   int main(void) { return 0; }''',
                args: ['-O2', '-Werror', '-D_FORTIFY_SOURCE=3'],
                name: '_FORTIFY_SOURCE=3',
            )
                hardening_cflags += '-D_FORTIFY_SOURCE=3'
            else
                hardening_cflags += '-D_FORTIFY_SOURCE=2'
            endif
        endif
        add_project_arguments(hardening_cflags, language: 'c')
        add_project_link_arguments(
            cc.get_supported_link_arguments('-Wl,-z,relro', '-Wl,-z,now'),
            language: 'c',
        )
    endif
    taler_lib_ldflags = '-export-dynamic -no-undefined'

    check_headers = ['stdint.h', 'stdlib.h', 'string.h', 'unistd.h']

    foreach h : check_headers
        if cc.check_header(h)
            define = 'HAVE_' + h.underscorify().to_upper()
            message(define)
            private_config.set(define, 1)
        endif
    endforeach

    # Gettext
    i18n = import('i18n')

    gettext_package = 'anastasis'
    add_project_arguments('-DGETTEXT_PACKAGE=' + gettext_package, language: 'c')
    add_project_arguments('-DENABLE_NLS=1', language: 'c')

    subdir('po')

    zlib_dep = dependency('zlib', required: false)
    if not zlib_dep.found()
        zlib_dep = cc.find_library('z', required: true)
    endif
    m_dep = cc.find_library('m', required: false)
    if m_dep.found()
        private_config.set('HAVE_LIBM', 1)
    endif


    mhd_dep = dependency('libmicrohttpd', required: false)
    if not mhd_dep.found()
        mhd_dep = cc.find_library('microhttpd', required: true)
    endif

    mhd2_dep = dependency('libmicrohttpd2', required: false)
    if not mhd2_dep.found()
        mhd2_dep = cc.find_library('microhttpd2', required: false)
    endif

    json_dep = dependency('jansson', required: false)
    if not json_dep.found()
        json_dep = cc.find_library('jansson', required: true)
    endif

    gcrypt_dep = dependency('libgcrypt', required: false)
    if not gcrypt_dep.found()
        gcrypt_dep = cc.find_library('gcrypt', required: true)
    endif

    private_config.set_quoted('NEED_LIBGCRYPT_VERSION', '1.6.1')

    gnunetutil_dep = dependency('gnunetutil', required: false)
    if not gnunetutil_dep.found()
        gnunetutil_dep = cc.find_library('gnunetutil', required: true)
    endif

    cc.has_header_symbol(
        'gnunet/gnunet_util_lib.h',
        'GNUNET_TIME_round_up',
        dependencies: [gnunetutil_dep],
        required: true,
    )

    gnunetjson_dep = dependency('gnunetjson', required: false)
    if not gnunetjson_dep.found()
        gnunetjson_dep = cc.find_library('gnunetjson', required: true)
    endif
    sodium_dep = dependency('libsodium', required: false, version: '>=1.0.18')
    if not sodium_dep.found()
        sodium_dep = cc.find_library('sodium', required: true)
        sodium_version_check = '''#include <sodium.h>
  int main(int argc, char **argv) {
    #if !((SODIUM_LIBRARY_VERSION_MAJOR > 10) || \
         ((SODIUM_LIBRARY_VERSION_MAJOR == 10) && \
         (SODIUM_LIBRARY_VERSION_MINOR >= 3)))
      #error "libsodium version >= 1.0.18 required"
    #endif
    return 0
    }
  '''
        if not cc.compiles(
            sodium_version_check,
            name: 'sodium version check',
            dependencies: sodium_dep,
        )
            error('libsodium version >=1.0.18 required')
        endif
    endif

    curl_dep = dependency('libcurl', version: '>=7.34.0', required: false)
    if not curl_dep.found()
        curl_dep = cc.find_library('curl', required: true)
        curl_version_check = '''#include <curl/curl.h>
  int main(int argc, char **argv) {
    #if LIBCURL_VERSION_NUM < 0x073400
      #error "cURL version >= 7.34.0 required"
    #endif
    return 0;
    }
  '''
        if not cc.compiles(
            curl_version_check,
            name: 'cURL version check',
            dependencies: curl_dep,
        )
            error('cURL version >=7.34.0 required')
        endif
    endif

    gnunetcurl_dep = dependency('gnunetcurl', required: false)
    if not gnunetcurl_dep.found()
        gnunetcurl_dep = cc.find_library('gnunetcurl', required: true)
    endif
    cc.has_header_symbol(
        'gnunet/gnunet_curl_lib.h',
        'GNUNET_CURL_get_select_info',
        dependencies: [gnunetcurl_dep],
        required: true,
    )
    pq_dep = dependency('libpq', required: false)
    if not pq_dep.found()
        pq_dep = cc.find_library('pq', required: true)
    endif

    gnunetpq_dep = dependency('gnunetpq', required: false)
    if not gnunetpq_dep.found()
        gnunetpq_dep = cc.find_library('gnunetpq', required: true)
    endif
    cc.has_header_symbol(
        'gnunet/gnunet_pq_lib.h',
        'GNUNET_PQ_query_param_blind_sign_priv',
        required: true,
        dependencies: [pq_dep, gnunetpq_dep],
    )
    private_config.set10('HAVE_GNUNETPQ', gnunetpq_dep.found())


    talerutil_dep = dependency('talerutil', required: false)
    if not talerutil_dep.found()
        talerutil_dep = cc.find_library('talerutil', required: true)
    endif
    cc.has_header_symbol(
        'taler/taler_util.h',
        'TALER_merchant_instance_auth_hash_with_salt',
        required: true,
        dependencies: [talerutil_dep],
    )
    private_config.set10('HAVE_TALERUTIL', talerutil_dep.found())

    talerexchange_dep = dependency('talerexchange', required: false)
    if not talerexchange_dep.found()
        talerexchange_dep = cc.find_library('talerexchange', required: true)
    endif
    private_config.set10('HAVE_TALEREXCHANGE', talerexchange_dep.found())

    talercurl_dep = dependency('talercurl', required: false)
    if not talercurl_dep.found()
        talercurl_dep = cc.find_library('talercurl', required: true)
    endif
    talertesting_dep = dependency('talertesting', required: false)
    if not talertesting_dep.found()
        talertesting_dep = cc.find_library('talertesting', required: true)
    endif

    talermhd_dep = dependency('talermhd', required: false)
    if not talermhd_dep.found()
        talermhd_dep = cc.find_library('talermhd', required: true)
    endif
    cc.has_header_symbol(
        'taler/taler_mhd_lib.h',
        'TALER_MHD_parse_request_arg_rel_time',
        required: true,
        dependencies: [talermhd_dep],
    )
    private_config.set10('HAVE_TALERMHD', talermhd_dep.found())


    talermerchant_dep = dependency('talermerchant', required: false)
    if not talermerchant_dep.found()
        talermerchant_dep = cc.find_library('talermerchant', required: true)
    endif
    cc.has_header_symbol(
        'taler/taler_merchant_service.h',
        'TALER_MERCHANT_parse_pay_uri',
        required: true,
        dependencies: [talermerchant_dep],
    )
    private_config.set10('HAVE_TALERMERCHANT', talermerchant_dep.found())
    talermerchanttesting_dep = dependency(
        'talermerchanttesting',
        required: false,
    )
    if not talermerchanttesting_dep.found()
        talermerchanttesting_dep = cc.find_library(
            'talermerchanttesting',
            required: true,
        )
    endif

    talerjson_dep = dependency('talerjson', required: false)
    if not talerjson_dep.found()
        talerjson_dep = cc.find_library('talerjson', required: true)
    endif
    cc.has_header_symbol(
        'taler/taler_json_lib.h',
        'TALER_JSON_currency_specs_to_json',
        required: true,
        dependencies: [talerjson_dep],
    )
    private_config.set10('HAVE_TALERJSON', talerjson_dep.found())


    talerpq_dep = dependency('talerpq', required: false)
    if not talerpq_dep.found()
        talerpq_dep = cc.find_library('talerpq', required: true)
    endif
    cc.has_header_symbol(
        'taler/taler_pq_lib.h',
        'TALER_PQ_query_param_array_blinded_denom_sig',
        required: true,
        dependencies: [talerpq_dep, pq_dep],
    )
    private_config.set10('HAVE_TALERPQ', talerpq_dep.found())

    logging_opt = get_option('logging')
    logging_verbosity = 0

    if logging_opt == 'yes'
        logging_verbosity = 1
    endif
    if logging_opt == 'no'
        add_project_arguments('-DGNUNET_CULL_LOGGING=1', language: 'c')
    endif
    if logging_opt == 'verbose'
        logging_verbosity = 2
    endif
    if logging_opt == 'veryverbose'
        logging_verbosity = 3
    endif

    #add_project_arguments('-DGNUNET_EXTRA_LOGGING=@0@'.format(logging_verbosity), language: 'c')


    # todo gcov has meson builtin

    # Used to populate configuration file and script templates


    libltversions = [
        ['libanastasis', '0:0:0'],
        ['libanastasisutil', '1:0:0'],
        ['libanastasisauthorization', '0:0:0'],
        ['libanastasiseufin', '0:0:0'],
        ['libanastasisdb', '4:0:0'],
        ['libanastasisrest', '0:0:0'],
        ['libanastasistesting', '0:0:0'],
        ['libanastasisredux', '0:0:0'],
    ]

    solibversions = {}

    foreach libversion : libltversions
        ltversion = libversion[1].split(':')
        current = ltversion[0].to_int()
        revision = ltversion[1].to_int()
        age = ltversion[2].to_int()
        soversion_str = '@0@'.format(current - age)
        ltversion_str = '@0@.@1@.@2@'.format(current - age, age, revision)
        solibversions = solibversions + {
            libversion[0]: {
                'soversion': soversion_str,
                'version': ltversion_str,
            },
        }
    endforeach

    private_config.set_quoted('PACKAGE', meson.project_name())
    private_config.set_quoted('PACKAGE_VERSION', meson.project_version())
    # Compatibility. Used in source.
    private_config.set_quoted('VERSION', meson.project_version())
    private_config.set_quoted('PACKAGE_BUGREPORT', 'taler@gnu.org')
    configure_file(output: 'anastasis_config.h', configuration: private_config)
    configuration_inc = include_directories('.')

    cdata.merge_from(private_config)
    add_project_arguments('-DHAVE_CONFIG_H', language: 'c')

    pkg = import('pkgconfig')
    subdir('contrib')
    subdir('src')
    if not get_option('disable-doc')
        subdir('doc')
    endif

    taler_prefix = get_option('prefix') / get_option('libdir')

    add_test_setup(
        'default',
        env: ['ANASTASIS_PREFIX=' + taler_prefix],
        exclude_suites: ['perf', 'installcheck', 'integrationtests'],
        is_default: true,
    )
else
    subdir('contrib')
    if not get_option('disable-doc')
        subdir('doc')
    endif
endif

meson.add_dist_script('meson-dist-script')

