####################################################################################################################################
# pgBackRest Project
####################################################################################################################################
project(
    'pgbackrest',
    ['c'],
    version: '2.59.0',
    license: 'MIT',
    meson_version: '>=0.47',
    default_options: [
        # Core options
        'buildtype=release',
        'warning_level=2',

        # Base options
        'b_ndebug=if-release',

        # Perform unity builds in a single file
        'unity_size=100000',

        # Compiler options
        'c_std=c11',
    ],
)

# Selected C compiler
cc = meson.get_compiler('c')

####################################################################################################################################
# Check for C11 compiler support (setting c_std has no effect -- works in 1.8.1 so likely a bug in our version)
####################################################################################################################################
c11_test = '''
#if !defined __STDC_VERSION__ || __STDC_VERSION__ < 201112L
# error "C compiler does not advertise C11 conformance"
#endif
'''

if not cc.compiles(c11_test)
    if cc.compiles(c11_test, args: '-std=c11')
        add_project_arguments('-std=c11', language: 'c')
    else
        error('C compiler does not support C11')
    endif
endif

####################################################################################################################################
# OS-specific settings
####################################################################################################################################
if host_machine.system() == 'linux'
    add_global_arguments('-D_POSIX_C_SOURCE=200809L', language: 'c')
elif host_machine.system() == 'darwin'
    add_global_arguments('-D_DARWIN_C_SOURCE', language: 'c')
endif

####################################################################################################################################
# Enable/disable warnings
####################################################################################################################################
# Enable as many additional warnings as possible to catch potential errors
warning_enable = [
    # Warn whenever a pointer is cast so as to remove a type qualifier from the target type
    '-Wcast-qual',

    # Warn for implicit conversions that may alter a value
    '-Wconversion',

    # Warn about duplicated conditions in an if-else-if chain
    '-Wduplicated-cond',

    # Warn when an if-else has identical branches
    '-Wduplicated-branches',

    # Warn if floating-point values are used in equality comparisons
    '-Wfloat-equal',

    # Enable -Wformat plus -Wnonnull, -Wformat-nonliteral, -Wformat-security, and -Wformat-y2k
    '-Wformat=2',

    # Warn about calls to formatted input/output functions such as sprintf and vsprintf that might overflow the destination buffer
    '-Wformat-overflow=2',

    # Warn if the format string requires an unsigned argument and the argument is signed and vice versa
    '-Wformat-signedness',

    # Warn about uninitialized variables which are initialized with themselves
    '-Winit-self',

    # Warn if a global function is defined without a previous prototype declaration
    '-Wmissing-prototypes',

    # Warn if a global variable is defined without a previous declaration
    '-Wmissing-variable-declarations',

    # Warn about anything that depends on the "size of" a function type or of void
    '-Wpointer-arith',

    # Warn if anything is declared more than once in the same scope, even when the extra declaration is valid and changes nothing
    '-Wredundant-decls',

    # Warn if a function is declared or defined without specifying the argument types
    '-Wstrict-prototypes',

    # Warn if a variable-length array is used
    '-Wvla',

    # Give string constants the type const char[length] so that copying the address of one into a non-const char * pointer produces
    # a warning
    '-Wwrite-strings',
]

# Disable various unhelpful warnings
warning_disable = [
    # Warn for variables that might be changed by longjmp or vfork. Disable because of constant false positives/negatives.
    '-Wno-clobbered',

    # Warn if a structure's initializer has some fields missing. Disable so we can initialize with {0}.
    '-Wno-missing-field-initializers',
]

add_project_arguments(cc.get_supported_arguments(warning_enable, warning_disable), language: 'c')

####################################################################################################################################
# Enable additional optimizations for appropriate levels
####################################################################################################################################
if get_option('optimization') in ['2', '3']
    optimization_enable = [
        # Unroll loops whose number of iterations can be determined at compile time or upon entry to the loop
        '-funroll-loops',

        # Perform loop vectorization on trees
        '-ftree-vectorize',
    ]

    add_project_arguments(cc.get_supported_arguments(optimization_enable), language: 'c')
endif

####################################################################################################################################
# Stop after the first error when error on warn enabled. Subsequent errors are often caused by the first error.
####################################################################################################################################
if get_option('fatal-errors')
    add_project_arguments(cc.get_supported_arguments('-Wfatal-errors'), language: 'c')
endif

####################################################################################################################################
# Generate -fmacro-prefix-map so the relative path to the source directory is not included in the __FILE__ macro. This provides
# reproducible builds and minimizes the file path in debug messages, just like an in-tree make build. For test source, prefix with
# test/ in case there are any module name collisions.
####################################################################################################################################
python = import('python').find_installation()

file_prefix = run_command(
    [
        python,
        '-c',
        'import sys, os; print(os.path.relpath(sys.argv[1], sys.argv[2]))',
        meson.current_source_dir(),
        meson.current_build_dir(),
    ],
    check: true,
).stdout().strip()

add_project_arguments(cc.get_supported_arguments('-fmacro-prefix-map=@0@/src/=' . format(file_prefix)), language: 'c')
add_project_arguments(cc.get_supported_arguments('-fmacro-prefix-map=@0@/test/src/=test/' . format(file_prefix)), language: 'c')

####################################################################################################################################
# Detect a distribution tarball, which is marked by the dist file written into the tarball root by meson dist. A distribution
# tarball ships the generated files, so the code generation tooling (build-code, the doc and test tools, libyaml) is not built. A
# normal source tree such as a git checkout has no marker and generates the files.
####################################################################################################################################
dist = run_command(
    [
        python,
        '-c',
        'import os, sys; sys.exit(0 if os.path.exists(os.path.join(sys.argv[1], "dist")) else 1)',
        meson.current_source_dir(),
    ],
    check: false,
).returncode() == 0

####################################################################################################################################
# Build configuration
####################################################################################################################################
configuration = configuration_data()

# Ensure the C compiler supports __builtin_clzl()
if not cc.links('''int main(void) {__builtin_clzl(1);}''')
    error('compiler must support __builtin_clzl()')
endif

# Ensure the C compiler supports __builtin_bswap64()
if not cc.links('''int main(void) {__builtin_bswap64(1);}''')
    error('compiler must support __builtin_bswap64()')
endif

# Check C compiler support for __builtin_types_compatible_p
if cc.compiles('''int main(void) {__builtin_types_compatible_p(char *, const char *);}''')
    configuration.set('HAVE_BUILTIN_TYPES_COMPATIBLE_P', true, description: 'Is __builtin_types_compatible_p present?')
endif

# Find optional backtrace library
lib_backtrace = cc.find_library('backtrace', required: false)

if lib_backtrace.found()
    configuration.set('HAVE_LIBBACKTRACE', true, description: 'Is libbacktrace present?')
endif

# Find required bz2 library
lib_bz2 = cc.find_library('bz2')

# Find required lz4 library
lib_lz4 = dependency('liblz4')

# Find required openssl library
lib_openssl = dependency('openssl', version : '>=1.1.1')

# Find required pq library
lib_pq = dependency('libpq')

# Find required xml library
lib_xml = dependency('libxml-2.0')

# Find yaml library (only used by the code generation tooling, which is not built from a distribution tarball)
if not dist
    lib_yaml = dependency('yaml-0.1')
endif

# Find required gz library
lib_z = dependency('zlib')

configuration.set('ZLIB_CONST', true, description: 'Require zlib const input buffer')

# Find optional systemd library
lib_systemd = dependency('libsystemd', required: get_option('libsystemd'))

if lib_systemd.found()
    configuration.set('HAVE_LIBSYSTEMD', true, description: 'Is libsystemd present?')
endif

# Find optional libssh2 library
lib_ssh2 = dependency('libssh2', required: get_option('libssh2'))

if lib_ssh2.found()
    configuration.set('HAVE_LIBSSH2', true, description: 'Is libssh2 present?')
endif

# Find optional zstd library
lib_zstd = dependency('libzstd', version: '>=1.0', required: get_option('libzstd'))

if lib_zstd.found()
    configuration.set('HAVE_LIBZST', true, description: 'Is libzstd present?')
endif

# Suppress -Wsign-conversion for C libraries (e.g. musl) that do no accept int without warning for FD_*() macros
if not cc.compiles(
        '''#include <sys/select.h>
        int main(void) {int fd = 1; fd_set s; FD_ZERO(&s); FD_SET(fd, &s); FD_ISSET(fd, &s);}''',
        args: ['-Wsign-conversion', '-Werror'])
    message('Disabling -Wsign-conversion because int is not accepted without warning by FD_*() macros')
    add_project_arguments(cc.get_supported_arguments('-Wno-sign-conversion'), language: 'c')
endif

# Enable debug code
if get_option('debug')
    configuration.set('DEBUG', true, description: 'Enable debug code')
endif

# Set configuration path
configuration.set_quoted('CFGOPTDEF_CONFIG_PATH', get_option('configdir'), description: 'Configuration path')

# Set VR_NON_STRING macro
if cc.compiles(
        '''int main(void) {__attribute__((nonstring)) static char test[3] = "ABC";}''',
        args: ['-Wattributes', '-Werror'])
    configuration.set(
        'VR_NON_STRING', '__attribute__((nonstring))',
        description: 'Indicate that the char array is intentionally not NULL terminated')
else
    configuration.set(
        'VR_NON_STRING', '', description: 'Indicate that the char array is intentionally not NULL terminated')
endif

####################################################################################################################################
# Include subdirs
####################################################################################################################################
subdir('src')

# The smoke test is shipped in the distribution so packagers can verify a build. It is in a subdir because unit test builds comment
# out all subdirs, which excludes the smoke test from them.
subdir('test')

# The doc and test tools generate code and are not built from a distribution tarball
if not dist
    subdir('doc/src')
    subdir('test/src')

    # Bake the generated files and the dist marker into the tarball so it builds without the code generation tooling
    meson.add_dist_script('src/build/dist.sh')
endif
