project('libfprint', [ 'c', 'cpp' ],
    version: '1.94.100',
    license: 'LGPLv2.1+',
    default_options: [
        'buildtype=debugoptimized',
        'warning_level=1',
        'c_std=gnu99',
    ],
    meson_version: '>= 0.62.0')

fs = import('fs')
gnome = import('gnome')

python3 = find_program('python3')

libfprint_conf = configuration_data()
libfprint_conf.set_quoted('LIBFPRINT_VERSION', meson.project_version())

prefix = get_option('prefix')
libdir = prefix / get_option('libdir')
libexecdir = prefix / get_option('libexecdir')
datadir = prefix / get_option('datadir')

cc = meson.get_compiler('c')
cpp = meson.get_compiler('cpp')
host_system = host_machine.system()

libfprint_sanitizers = get_option('b_sanitize').split(',')
if libfprint_sanitizers == ['none']
  libfprint_sanitizers = []
endif

glib_min_version = '2.68'

glib_version_def = 'GLIB_VERSION_@0@_@1@'.format(
    glib_min_version.split('.')[0], glib_min_version.split('.')[1])
common_cflags = cc.get_supported_arguments([
    '-Wcast-align',
    '-Wformat-nonliteral',
    '-Wformat-security',
    '-Wformat=2',
    '-Wignored-qualifiers',
    '-Wlogical-op',
    '-Wmissing-declarations',
    '-Wmissing-format-attribute',
    '-Wmissing-include-dirs',
    '-Wmissing-noreturn',
    '-Wpointer-arith',
    '-Wshadow',
    '-Wswitch-enum',
    '-Wtype-limits',
    '-Wundef',
    '-Wunused',
    '-Werror=address',
    '-Werror=array-bounds',
    '-Werror=empty-body',
    '-Werror=init-self',
    '-Werror=int-to-pointer-cast',
    '-Werror=main',
    '-Werror=missing-braces',
    '-Werror=nonnull',
    '-Werror=redundant-decls',
    '-Werror=return-type',
    '-Werror=sequence-point',
    '-Werror=trigraphs',
    '-Werror=write-strings',
    '-fno-strict-aliasing',
    '-DGLIB_VERSION_MIN_REQUIRED=' + glib_version_def,
    '-DGLIB_VERSION_MAX_ALLOWED=' + glib_version_def,
    '-D_GNU_SOURCE',
    '-DG_LOG_DOMAIN="@0@"'.format(meson.project_name()),
])
c_cflags = cc.get_supported_arguments([
    '-Wimplicit-function-declaration',
    '-Wmissing-prototypes',
    '-Wnested-externs',
    '-Wold-style-definition',
    '-Wstrict-prototypes',
    '-Werror=implicit',
    '-Werror=pointer-to-int-cast',
])
add_project_arguments(common_cflags + c_cflags, language: 'c')
add_project_arguments(common_cflags, language: 'cpp')

# maintaining compatibility with the previous libtool versioning
# current = binary - interface
# revision = interface
soversion = 2
current = 0
revision = 0
libversion = '@0@.@1@.@2@'.format(soversion, current, revision)
versioned_libname = meson.project_name() + '-' + soversion.to_string()

# Dependencies
glib_dep = dependency('glib-2.0', version: '>=' + glib_min_version)
gio_dep = dependency('gio-unix-2.0', version: '>=' + glib_min_version)
gobject_dep = dependency('gobject-2.0', version: '>=' + glib_min_version)
gmodule_dep = dependency('gmodule-2.0', version: '>=' + glib_min_version)
gusb_dep = dependency('gusb', version: '>= 0.2.0')
mathlib_dep = cc.find_library('m', required: false)

# The following dependencies are only used for tests
sh = find_program('sh', required: true)
cairo_dep = dependency('cairo', required: false)

sync_unsupported_files = files('scripts' / 'sync-unsupported-devices.py')

# introspection scanning and Gio-2.0.gir
gobject_introspection = dependency('gobject-introspection-1.0', required: get_option('introspection'))

# SPI
have_spi = host_machine.system() == 'linux'

# Drivers
drivers = get_option('drivers').split(',')

drivers_info = {
    'upektc_img': {},
    'vfs5011': { 'endian_dependent': true },
    'vfs7552': {},
    'aes3500': { 'helper': ['aeslib', 'aes3k', 'pixman'] },
    'aes4000': { 'helper': ['aeslib', 'aes3k', 'pixman'] },
    'aes1610': { 'helper': ['aeslib'] },
    'aes1660': { 'helper': ['aeslib', 'aesx660'] },
    'aes2660': { 'helper': ['aeslib', 'aesx660'] },
    'aes2501': { 'helper': ['aeslib'] },
    'aes2550': { 'helper': ['aeslib'] },
    'vfs101': {},
    'vfs301': { 'endian_dependent': true },
    'vfs0050': { 'endian_dependent': true },
    'etes603': {},
    'egis0570': {},
    'egismoc': {},
    'egis_etu905': {},
    'vcom5s': {},
    'synaptics': {},
    'elan': { 'endian_dependent': true },
    'elanmoc': {},
    'elanmoc2': {},
    'uru4000': { 'helper': ['openssl'] },
    'upektc': {},
    'upeksonly': {},
    'upekts': {},
    'goodixmoc': {},
    'nb1010': {},
    'fpcmoc': {},
    'realtek': {},
    'focaltech_moc': {},
    'mafpmoc': {},
    'secugen': {},

    # SPI driver (non-optional if SPI is available)
    'elanspi': { 'spi': true, 'helper': ['udev'], 'optional': not have_spi },

    # Virtual drivers (test-only, optional)
    'virtual_image': { 'virtual': true, 'helper': ['virtual'], 'optional': true },
    'virtual_device': { 'virtual': true, 'helper': ['virtual'], 'optional': true },
    'virtual_device_storage': { 'virtual': true, 'helper': ['virtual'], 'optional': true },
}

default_drivers = []
virtual_drivers = []
foreach name, info : drivers_info
    if not info.get('optional', false)
        default_drivers += name
    endif
    if info.get('virtual', false)
        virtual_drivers += name
    endif
endforeach

all_drivers = default_drivers + virtual_drivers

if drivers == [ 'all' ]
    drivers = all_drivers
endif

if drivers == [ 'default' ]
    drivers = default_drivers
endif

if not have_spi
    foreach driver: drivers
        if drivers_info[driver].get('spi', false)
            error('SPI driver @0@ is not supported'.format(driver))
        endif
    endforeach
endif

driver_helpers = []
foreach driver : drivers
    if driver in drivers_info
        foreach helper : drivers_info[driver].get('helper', [])
            if helper not in driver_helpers
                driver_helpers += helper
            endif
        endforeach
    endif
endforeach


if drivers.length() == 0 or drivers[0] == ''
    error('Cannot build libfprint without drivers, please specify a valid value for the drivers option')
endif

if drivers == all_drivers or drivers == default_drivers
    default_drivers_are_enabled = true
else
    default_drivers_are_enabled = true
    foreach driver: default_drivers
        if driver not in drivers
            default_drivers_are_enabled = false
            break
        endif
    endforeach
endif

udev_rules = get_option('udev_rules')
install_udev_rules = udev_rules.enabled()

optional_deps = []

# Resolve extra dependencies
foreach i : driver_helpers
    foreach name, info : drivers_info
        if i in info.get('helper', [])
            driver = name
            break
        endif
    endforeach

    if i == 'pixman'
        imaging_dep = dependency('pixman-1', required: false)
        if not imaging_dep.found()
            error('pixman is required for @0@ and possibly others'.format(driver))
        endif

        libfprint_conf.set10('HAVE_PIXMAN', true)
        optional_deps += imaging_dep
    elif i == 'openssl'
        openssl_dep = dependency('openssl', version: '>= 3.0', required: false)
        if not openssl_dep.found()
            error('OpenSSL is required for @0@ and possibly others'.format(driver))
        endif

        optional_deps += openssl_dep
    elif i == 'udev'
        install_udev_rules = true

        gudev_dep = dependency('gudev-1.0', required: false)
        if not gudev_dep.found()
            error('udev is required for SPI support')
        endif

        libfprint_conf.set10('HAVE_UDEV', true)

        optional_deps += gudev_dep
    endif
endforeach

if udev_rules.disabled()
    install_udev_rules = false
endif

if install_udev_rules
    udev_rules_dir = get_option('udev_rules_dir')
    if udev_rules_dir == 'auto'
        udev_dep = dependency('udev')
        udev_rules_dir = udev_dep.get_variable(pkgconfig: 'udevdir') + '/rules.d'
    endif
endif

# FIXME: All the drivers should be fixed by adjusting the byte order.
# See https://gitlab.freedesktop.org/libfprint/libfprint/-/issues/236
supported_drivers = []
foreach driver: drivers
    if build_machine.endian() == 'little' or not drivers_info[driver].get('endian_dependent', false)
        supported_drivers += driver
    else
        warning('Driver @0@ is not supported by big endian cpu @1@. Please, fix it!'.format(
            driver, build_machine.cpu()))
    endif
endforeach

if default_drivers_are_enabled and supported_drivers != drivers
    default_drivers_are_enabled = false
endif

root_inc = include_directories('.')

udev_hwdb = get_option('udev_hwdb')
if not udev_hwdb.disabled()
    # systemd v248 includes our autosuspend rules
    udev_versioned_dep = dependency('udev', version: '>= 248', required: false)
    udev_hwdb_dir = get_option('udev_hwdb_dir')
    if udev_hwdb.auto()
        if udev_versioned_dep.found()
            udev_hwdb_dir = ''
        endif
    else
        if udev_versioned_dep.found()
            warning('udev hwdb will be installed by both systemd and libfprint')
        endif
    endif

    if udev_hwdb_dir == 'auto'
        udev_dep = dependency('udev')
        udev_hwdb_dir = udev_dep.get_variable(pkgconfig: 'udevdir') + '/hwdb.d'
    endif
else
    udev_hwdb_dir = ''
endif

if get_option('gtk-examples')
    gtk_dep = dependency('gtk+-3.0', required: false)
    if not gtk_dep.found()
        error('GTK+ 3.x is required for GTK+ examples')
    endif
endif

# Some dependency resolving happens inside here
subdir('libfprint')

configure_file(output: 'config.h', configuration: libfprint_conf)

if get_option('doc')
    subdir('doc')
endif
if get_option('gtk-examples')
    subdir('demo')
endif

subdir('data')
subdir('tests')

subdir('examples')

meson.add_dist_script(sync_unsupported_files, '--check', udev_hwdb_list_file)

pkgconfig = import('pkgconfig')
pkgconfig.generate(
    name: versioned_libname,
    description: 'Generic C API for fingerprint reader access',
    version: meson.project_version(),
    libraries: libfprint,
    requires: [gio_dep, gobject_dep],
    subdirs: versioned_libname,
    filebase: versioned_libname,
)

summary({'Drivers': drivers, }, section: 'Drivers')
