project('yams', ['cpp', 'c'], version: '0.20.5', # x-release-please-version default_options: [ 'buildtype=release', 'b_pie=true', 'b_ndebug=if-release', 'warning_level=3', 'cpp_std=c++20', 'c_std=c11', ], ) fs = import('fs') cpp = meson.get_compiler('cpp') wasi_enabled = get_option('wasi') wasi_cpp_args = [] if wasi_enabled wasi_cpp_args += '-DYAMS_WASI=1' endif _yams_include_dir = join_paths(meson.current_source_dir(), 'include') if cpp.get_id() == 'msvc' or cpp.get_id() == 'clang-cl' add_project_arguments('/I' + _yams_include_dir, language: 'cpp') else add_project_arguments('-I' + _yams_include_dir, language: 'cpp') endif if host_machine.system() == 'darwin' add_global_link_arguments('-Wl,-no_warn_duplicate_libraries', language: 'cpp') endif # std::stacktrace is optional diagnostic sugar for assertion failures. Some # libstdc++ releases expose the header/feature macro but keep the implementation # in an extra experimental library; that compiles but fails final executable # links. Probe compile+link, not just header availability. _stacktrace_probe = ''' #include int main() { auto trace = std::stacktrace::current(); return static_cast(trace.size()); } ''' if cpp.links(_stacktrace_probe, name: 'std::stacktrace compile+link') add_project_arguments('-DYAMS_CORE_HAS_STACKTRACE=1', language: 'cpp') message('std::stacktrace diagnostics enabled') else add_project_arguments('-DYAMS_CORE_HAS_STACKTRACE=0', language: 'cpp') message('std::stacktrace diagnostics disabled: stacktrace did not link') endif if host_machine.system() == 'windows' add_project_arguments('-D_WIN32_WINNT=0x0A00', language: 'cpp') add_project_arguments('-D_CRT_SECURE_NO_WARNINGS', language: 'cpp') add_project_arguments('-D_SILENCE_CXX20_OLD_SHARED_PTR_ATOMIC_SUPPORT_DEPRECATION_WARNING', language: 'cpp') add_project_arguments('-DNOMINMAX', language: 'cpp') add_project_arguments('-DWIN32_LEAN_AND_MEAN', language: 'cpp') # Large source files like mcp_server.cpp exceed MSVC's default section limit add_project_arguments('/bigobj', language: 'cpp') endif # --- libc++ hardening mode --- # Apply hardening defines when using libc++ (Darwin always uses libc++, Linux when explicitly set) libcxx_hardening = get_option('libcxx-hardening') if libcxx_hardening != 'none' # Check if we're actually using libc++ using_libcxx = false if host_machine.system() == 'darwin' # macOS always uses libc++ using_libcxx = true elif cpp.get_id() == 'clang' or cpp.get_id() == 'clang-cl' # On non-Darwin, check if -stdlib=libc++ is in use # This is a heuristic; proper detection would check link flags has_libcxx = cpp.has_header('__config', prefix: '#include ') if has_libcxx using_libcxx = true endif endif if using_libcxx hardening_mode_map = { 'fast': '_LIBCPP_HARDENING_MODE_FAST', 'extensive': '_LIBCPP_HARDENING_MODE_EXTENSIVE', 'debug': '_LIBCPP_HARDENING_MODE_DEBUG', } hardening_define = hardening_mode_map.get(libcxx_hardening) if hardening_define != '' add_project_arguments('-D' + hardening_define, language: 'cpp') message('Enabling libc++ hardening mode: ' + libcxx_hardening + ' (' + hardening_define + ')') endif else warning('libc++ hardening requested but not using libc++ (detected: ' + cpp.get_id() + '). Hardening mode ignored.') endif endif # --- Sanitizers --- # Meson's b_sanitize is the single switch (e.g. -Db_sanitize=thread, # -Db_sanitize=address,undefined). These derived flags add the extra codegen # options each sanitizer lane wants and are reused by subdirs. _b_sanitize_str = '@0@'.format(get_option('b_sanitize')) yams_sanitize_thread = _b_sanitize_str.contains('thread') yams_sanitize_address = _b_sanitize_str.contains('address') yams_sanitize_undefined = _b_sanitize_str.contains('undefined') enable_tsan = yams_sanitize_thread enable_asan = yams_sanitize_address # Standalone UBSan lanes abort on the first report; with ASan it stays recoverable. enable_ubsan = yams_sanitize_undefined and not enable_asan and not enable_tsan enable_dcheck = get_option('enable-dcheck') foreach _alias : [['enable-tsan', yams_sanitize_thread, 'thread'], ['enable-asan', yams_sanitize_address, 'address'], ['enable-ubsan', yams_sanitize_undefined, 'undefined']] if get_option(_alias[0]) and not _alias[1] warning('-D@0@=true does not enable a sanitizer; configure -Db_sanitize=@1@'.format(_alias[0], _alias[2])) endif endforeach if enable_tsan buildtype = get_option('buildtype') # TSan is most useful in debug builds; warn if enabled in release if buildtype == 'release' or buildtype == 'plain' warning('ThreadSanitizer enabled in non-debug build. This will significantly impact performance.') endif # Check if compiler supports TSan if cpp.has_argument('-fsanitize=thread') # Use additional flags for better stack traces tsan_extra_flags = ['-fno-omit-frame-pointer'] add_project_arguments(tsan_extra_flags, language: 'cpp') add_project_arguments(tsan_extra_flags, language: 'c') message('ThreadSanitizer enabled - expect significant performance impact') message('Note: TSan requires all code (including dependencies) to be compiled with -fsanitize=thread') else error('ThreadSanitizer requested but compiler does not support -fsanitize=thread') endif endif if enable_asan buildtype = get_option('buildtype') # ASan is most useful in debug builds; warn if enabled in release if buildtype == 'release' or buildtype == 'plain' warning('AddressSanitizer enabled in non-debug build. This will significantly impact performance.') endif # Check if compiler supports ASan if cpp.has_argument('-fsanitize=address') # Use additional flags for better stack traces (sanitizer is set via b_sanitize option) asan_extra_flags = ['-fno-omit-frame-pointer'] add_project_arguments(asan_extra_flags, language: 'cpp') add_project_arguments(asan_extra_flags, language: 'c') message('AddressSanitizer enabled - memory errors will fail fast') else error('AddressSanitizer requested but compiler does not support -fsanitize=address') endif endif if enable_ubsan buildtype = get_option('buildtype') # UBSan is most useful in debug builds; warn if enabled in release if buildtype == 'release' or buildtype == 'plain' warning('UndefinedBehaviorSanitizer enabled in non-debug build. This will significantly impact performance.') endif # Check if compiler supports UBSan if cpp.has_argument('-fsanitize=undefined') # -fno-sanitize-recover=all: abort on first UB hit (turns UBSan into a hard gate, not just logging) ubsan_extra_flags = ['-fno-omit-frame-pointer', '-fno-sanitize-recover=all'] add_project_arguments(ubsan_extra_flags, language: 'cpp') add_project_arguments(ubsan_extra_flags, language: 'c') message('UndefinedBehaviorSanitizer enabled - undefined behavior will abort') else error('UBSan requested but compiler does not support -fsanitize=undefined') endif endif # Profiling builds are optimized; keep frame pointers so Tracy call stacks and # perf/Instruments unwinding stay accurate. if get_option('enable-profiling') and cpp.get_id() not in ['msvc', 'clang-cl'] _profiling_frame_args = cpp.get_supported_arguments('-fno-omit-frame-pointer', '-mno-omit-leaf-frame-pointer') add_project_arguments(_profiling_frame_args, language: ['cpp', 'c']) endif # Tracy for every yams library (optional, -Denable-profiling=true). Each library appends # yams_tracy_args to cpp_args and yams_tracy_deps to dependencies; both are empty when profiling # is off or Tracy is unavailable, and include/yams/profiling.h then compiles to nothing. yams_tracy_args = [] yams_tracy_deps = [] yams_tracy_enabled = false if get_option('enable-profiling') _tracy_dep = dependency('tracy', modules: ['Tracy::TracyClient'], required: false, static: true) if _tracy_dep.found() yams_tracy_deps = [_tracy_dep] _rocprof_opt = get_option('enable-rocprofiler-tracy') _want_rocprof = (not _rocprof_opt.disabled()) and (_rocprof_opt.enabled() or (_rocprof_opt.auto() and fs.exists('/opt/rocm'))) if _want_rocprof _rocprof_dep = dependency( 'rocprofiler-sdk', method: 'cmake', cmake_module_path: ['/opt/rocm/lib/cmake', '/opt/rocm/lib64/cmake'], required: _rocprof_opt.enabled(), ) if _rocprof_dep.found() yams_tracy_deps += _rocprof_dep else warning('Tracy present but rocprofiler-sdk missing; ROCm hooks unavailable') endif endif if cpp.links('int main(){ return 0; }', dependencies: yams_tracy_deps, name: 'tracy-link-check', required: false) yams_tracy_args = ['-DTRACY_ENABLE', '-DTRACY_ON_DEMAND'] yams_tracy_enabled = true message('Tracy profiling enabled for all yams libraries') else warning('Tracy dependency failed to link (likely missing rocprofiler-sdk); disabling Tracy for this build') yams_tracy_deps = [] endif else warning('enable-profiling set but Tracy was not found; zones will not emit') endif endif if enable_dcheck add_project_arguments('-DYAMS_ENABLE_DCHECK=1', language: 'cpp') message('YAMS_DCHECK enabled in this build (overrides NDEBUG for debug-only checks)') endif # --- General Hardening Flags --- hardening_cpp_args = [] hardening_c_args = [] hardening_link_args = [] if cpp.get_id() == 'msvc' or cpp.get_id() == 'clang-cl' # MSVC security checks: /GS (Stack Buffer Security Check) and /sdl (Additional Security Checks) hardening_cpp_args += ['/GS', '/sdl'] hardening_c_args += ['/GS', '/sdl'] # Control Flow Guard if cpp.has_argument('/guard:cf') hardening_cpp_args += '/guard:cf' hardening_c_args += '/guard:cf' hardening_link_args += '/guard:cf' endif else # GCC/Clang security checks # Bounds checking for libstdc++ hardening_cpp_args += '-D_GLIBCXX_ASSERTIONS' # Stack smashing protection if cpp.has_argument('-fstack-protector-strong') hardening_cpp_args += '-fstack-protector-strong' hardening_c_args += '-fstack-protector-strong' endif # Stack clash protection (probes large stack allocations), and on Linux the # architecture's control-flow protection: CET IBT/SHSTK notes on x86_64, BTI/PAC # on AArch64. get_supported_arguments() drops whatever the target rejects. _hardening_codegen = cpp.get_supported_arguments('-fstack-clash-protection') if host_machine.system() == 'linux' if host_machine.cpu_family() == 'x86_64' _hardening_codegen += cpp.get_supported_arguments('-fcf-protection=full') elif host_machine.cpu_family() == 'aarch64' _hardening_codegen += cpp.get_supported_arguments('-mbranch-protection=standard') endif endif hardening_cpp_args += _hardening_codegen hardening_c_args += _hardening_codegen # Fortify source (optimized builds only). Level 3 uses # __builtin_dynamic_object_size, so more calls with runtime-sized buffers # become __*_chk calls; glibc < 2.34 treats it as level 2. Undefine first so a # toolchain default (e.g. Ubuntu's) does not produce a redefinition warning. # FORTIFY only rewrites YAMS's own call sites: most libc calls in the binaries # come from the Conan static archives, which are built without it, so the # count of __*_chk imports stays small by construction. if get_option('optimization') not in ['0', 'g'] hardening_cpp_args += ['-U_FORTIFY_SOURCE', '-D_FORTIFY_SOURCE=3'] hardening_c_args += ['-U_FORTIFY_SOURCE', '-D_FORTIFY_SOURCE=3'] endif endif if host_machine.system() == 'linux' # Read-only relocations and immediate binding if cpp.has_link_argument('-Wl,-z,relro') hardening_link_args += '-Wl,-z,relro' endif if cpp.has_link_argument('-Wl,-z,now') hardening_link_args += '-Wl,-z,now' endif endif add_project_arguments(hardening_cpp_args, language: 'cpp') add_project_arguments(hardening_c_args, language: 'c') add_project_link_arguments(hardening_link_args, language: ['cpp', 'c']) # --- Release size: dead-section GC and packed relative relocations --- # Optimized builds put every function/object in its own section so the linker # can drop what nothing references (each executable links the full static # core, so most of it is unreachable from any one of them). DT_RELR packs the # relative relocations of the PIE binaries (~0.9 MiB .rela.dyn each); it needs # glibc >= 2.36 at runtime, below the GLIBC_2.38 floor the binaries already # carry, and the link probe drops it on toolchains that cannot emit it. # Debug/-O0 and coverage builds are left alone. yams_size_cflags = [] yams_size_ldflags = [] if get_option('optimization') not in ['0', 'g'] and not get_option('b_coverage') and cpp.get_id() not in ['msvc', 'clang-cl'] if host_machine.system() == 'darwin' yams_size_ldflags += cpp.get_supported_link_arguments('-Wl,-dead_strip') elif host_machine.system() != 'windows' yams_size_cflags += cpp.get_supported_arguments('-ffunction-sections', '-fdata-sections') yams_size_ldflags += cpp.get_supported_link_arguments('-Wl,--gc-sections', '-Wl,--build-id') if cpp.get_linker_id() == 'ld.lld' yams_size_ldflags += cpp.get_supported_link_arguments('-Wl,--icf=safe') endif if host_machine.system() == 'linux' yams_size_ldflags += cpp.get_supported_link_arguments('-Wl,-z,pack-relative-relocs') # Keep statically linked third-party code (Conan zstd, zlib, ...) out of # the dynamic symbol table. Otherwise an executable that also links a # system shared library depending on the same library (Arch: libmagic -> # libzstd/libz) exports its private copy and interposes it into that # library: ~600 exported ZSTD_/deflate/inflate symbols per binary. yams_size_ldflags += cpp.get_supported_link_arguments('-Wl,--exclude-libs,ALL') endif endif endif add_project_arguments(yams_size_cflags, language: ['cpp', 'c']) add_project_link_arguments(yams_size_ldflags, language: ['cpp', 'c']) # --- Fuzzing engine selection --- # AFL++ wrappers (afl-clang-fast++) instrument every object themselves, so an AFL build keeps # its historical flags. A plain clang build with -Dbuild-fuzzers=true selects libFuzzer: every # project object gets SanitizerCoverage (-fsanitize=fuzzer-no-link) so library code, not only the # harness entry points, guides the fuzzer, and ASan+UBSan are applied project-wide unless the # caller already chose sanitizers through b_sanitize. yams_fuzz_engine = 'none' if get_option('build-fuzzers') and not wasi_enabled _afl_compiler = cpp.compiles(''' #ifndef __AFL_COMPILER #error "Not AFL++" #endif int main() { return 0; } ''', name: 'AFL++ compiler check') if _afl_compiler yams_fuzz_engine = 'afl' elif cpp.get_id() == 'clang' and cpp.has_argument('-fsanitize=fuzzer-no-link') and cpp.links(''' #include #include extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t*, std::size_t) { return 0; } ''', args: ['-fsanitize=fuzzer'], name: 'libFuzzer runtime check') yams_fuzz_engine = 'libfuzzer' _fuzz_instrument_args = ['-fsanitize=fuzzer-no-link', '-fno-omit-frame-pointer'] if '@0@'.format(get_option('b_sanitize')) in ['none', '[]'] _fuzz_sanitizers = '-fsanitize=address,undefined' _fuzz_instrument_args += [_fuzz_sanitizers, '-fno-sanitize-recover=undefined'] add_project_link_arguments(_fuzz_sanitizers, language: ['cpp', 'c']) endif add_project_arguments(_fuzz_instrument_args, language: ['cpp', 'c']) if get_option('b_lundef') and host_machine.system() != 'darwin' warning('libFuzzer builds need -Db_lundef=false: shared libraries reference sanitizer ' + 'and coverage symbols that only the fuzzer executable provides') endif endif message('Fuzzing engine: ' + yams_fuzz_engine) endif if host_machine.system() == 'darwin' and cpp.get_id() == 'clang' # Apple clang + external fmt/spdlog can fail when fmt enables # consteval format-string checking inside spdlog headers. add_project_arguments('-DFMT_CONSTEVAL=', language: 'cpp') add_project_arguments('-Wno-macro-redefined', language: 'cpp') endif # --- C++20 Modules Support (Experimental) --- # Detect and configure C++20 module compilation when enabled yams_modules_enabled = get_option('enable-modules') yams_modules_supported = false yams_module_args = [] yams_module_flags = [] yams_use_modules_def = [] yams_module_cpp_args = [] if yams_modules_enabled # Include module detection and configuration subdir('meson') endif # --- Install prefix for plugin auto-trust --- # Pass the install prefix to C++ code so the daemon knows where system plugins are installed install_prefix = get_option('prefix') # Escape backslashes for Windows paths to avoid compiler warnings about unrecognized escape sequences install_prefix_escaped = install_prefix.replace('\\', '\\\\') add_project_arguments('-DYAMS_INSTALL_PREFIX="' + install_prefix_escaped + '"', language: 'cpp') message('YAMS install prefix: ' + install_prefix) # --- Shared Conan/CMake discovery paths --- conan_candidate_dirs = [] base_build = meson.project_build_root() default_candidates = [ base_build, join_paths(base_build, 'conan'), join_paths(base_build, 'conan', 'generators'), join_paths(base_build, 'generators'), join_paths(base_build, 'build-release', 'conan', 'generators'), join_paths(base_build, 'build-debug', 'conan', 'generators'), ] foreach variant : ['build-debug', 'build-release', 'debug', 'release'] variant_path = join_paths(base_build, variant) if fs.exists(variant_path) default_candidates += [variant_path] default_candidates += [join_paths(variant_path, 'conan')] default_candidates += [join_paths(variant_path, 'conan', 'generators')] default_candidates += [join_paths(variant_path, 'generators')] endif endforeach foreach cand : default_candidates if fs.exists(cand) conan_candidate_dirs += [cand] endif endforeach cli11_cmake_paths = [] foreach base : conan_candidate_dirs if fs.exists(base) cli11_cmake_paths += base foreach sub : ['CLI11', 'cli11'] cand = join_paths(base, sub) if fs.exists(cand) cli11_cmake_paths += cand endif endforeach endif endforeach onnx_cmake_paths = [] foreach base : conan_candidate_dirs if fs.exists(base) onnx_cmake_paths += base foreach sub : ['onnxruntime', 'onnxruntime/Release', 'onnxruntime/Debug'] cand = join_paths(base, sub) if fs.exists(cand) onnx_cmake_paths += cand endif endforeach endif endforeach # --- Shared dependency resolution --- cli11_resolved_dep = dependency('cli11', required: false, method: 'pkg-config') if not cli11_resolved_dep.found() cli11_resolved_dep = dependency('CLI11', required: false, method: 'pkg-config') endif if not cli11_resolved_dep.found() and cli11_cmake_paths.length() > 0 cli11_resolved_dep = dependency('cli11', method: 'cmake', cmake_module_path: cli11_cmake_paths, required: false) if not cli11_resolved_dep.found() cli11_resolved_dep = dependency('CLI11', method: 'cmake', cmake_module_path: cli11_cmake_paths, required: false) endif endif if not cli11_resolved_dep.found() cli11_resolved_dep = dependency('CLI11', method: 'cmake', required: false) endif # ONNX Runtime is deprecated and opt-in: skip discovery entirely when disabled so a stray # system/Homebrew onnxruntime cannot leak into default builds. onnxruntime_resolved_dep = dependency('', required: false) if not get_option('enable-onnx').disabled() onnxruntime_resolved_dep = dependency('onnxruntime', method: 'pkg-config', required: false) if not onnxruntime_resolved_dep.found() onnxruntime_resolved_dep = dependency('onnxruntime', method: 'cmake', cmake_module_path: onnx_cmake_paths, required: false) endif if not onnxruntime_resolved_dep.found() onnxruntime_resolved_dep = dependency('onnxruntime', required: false) endif if not onnxruntime_resolved_dep.found() onnxruntime_resolved_dep = dependency('libonnxruntime', required: false) endif endif # Directories that hold the ONNX Runtime shared library used for this build; the # runtime install (tools/yams-cli) bundles it privately from the first that does. # Only the dependency's own libdir counts (Conan/pkg-config/CMake): a Homebrew # onnxruntime links other Homebrew dylibs by absolute path and is not relocatable, # and the Homebrew formula uses Homebrew's onnxruntime directly anyway. onnxruntime_bundle_dirs = [] # Normalize ONNX Runtime linkage to the current library location instead of any stale # Homebrew copies that may remain under /opt/homebrew/lib after upgrades. if onnxruntime_resolved_dep.found() onnxruntime_link_dirs = [] onnxruntime_pkg_libdir = onnxruntime_resolved_dep.get_variable(pkgconfig: 'libdir', default_value: '') if onnxruntime_pkg_libdir != '' onnxruntime_link_dirs += [onnxruntime_pkg_libdir] endif onnxruntime_cmake_prefix = onnxruntime_resolved_dep.get_variable(cmake: 'PACKAGE_PREFIX_DIR', default_value: '') if onnxruntime_cmake_prefix != '' onnxruntime_link_dirs += [join_paths(onnxruntime_cmake_prefix, 'lib')] endif if host_machine.system() == 'darwin' foreach brew_onnx_prefix : ['/opt/homebrew/opt/onnxruntime/lib', '/usr/local/opt/onnxruntime/lib'] if fs.exists(brew_onnx_prefix) onnxruntime_link_dirs = [brew_onnx_prefix] + onnxruntime_link_dirs endif endforeach endif foreach _d : onnxruntime_link_dirs if not (_d.startswith('/opt/homebrew') or _d.startswith('/usr/local/opt') or _d.startswith('/usr/local/Cellar')) onnxruntime_bundle_dirs += [_d] endif endforeach onnxruntime_compile_dep = onnxruntime_resolved_dep.partial_dependency(compile_args: true, includes: true) onnxruntime_cpp = meson.get_compiler('cpp') onnxruntime_link_dep = onnxruntime_cpp.find_library('onnxruntime', dirs: onnxruntime_link_dirs, required: false) if not onnxruntime_link_dep.found() onnxruntime_link_dep = onnxruntime_cpp.find_library('onnxruntime', required: false) endif if onnxruntime_link_dep.found() onnxruntime_resolved_dep = declare_dependency( dependencies: [onnxruntime_compile_dep, onnxruntime_link_dep], ) message('ONNX Runtime linkage normalized via current library search path') endif endif tbb_resolved_dep = dependency('tbb', required: false, method: 'pkg-config') if not tbb_resolved_dep.found() tbb_resolved_dep = dependency('onetbb', required: false, method: 'pkg-config') endif if not tbb_resolved_dep.found() tbb_resolved_dep = dependency('tbb', required: false) endif if not tbb_resolved_dep.found() tbb_resolved_dep = dependency('onetbb', required: false) endif if host_machine.system() == 'darwin' and tbb_resolved_dep.found() tbb_normalized = false tbb_cpp = meson.get_compiler('cpp') foreach brew_tbb_prefix : ['/opt/homebrew/opt/tbb', '/usr/local/opt/tbb'] if not tbb_normalized brew_tbb_incdir = brew_tbb_prefix / 'include' brew_tbb_libdir = brew_tbb_prefix / 'lib' if fs.exists(brew_tbb_incdir) and fs.exists(brew_tbb_libdir) tbb_link_dep = tbb_cpp.find_library('tbb', dirs: [brew_tbb_libdir], required: false) if tbb_link_dep.found() tbb_resolved_dep = declare_dependency( include_directories: include_directories(brew_tbb_incdir), dependencies: [tbb_link_dep], ) tbb_normalized = true message('TBB linkage normalized via Homebrew opt prefix') endif endif endif endforeach endif # --- Windows DLL deployment --- # On Windows, DLLs must be in the same directory as the executable or in PATH. # We extract the DLL paths from the dependency's pkg-config `bindir` variable, # then use fs.copyfile() to deploy them to the build directory. # # This is the Meson-idiomatic approach that doesn't rely on Conan's runtime_deploy. windows_runtime_dlls = [] # List of [dll_name, dll_full_path] tuples if host_machine.system() == 'windows' # Get TBB dependency and extract bindir tbb_dep = tbb_resolved_dep if tbb_dep.found() tbb_prefix = tbb_dep.get_variable(pkgconfig: 'prefix', default_value: '') tbb_bindir = tbb_dep.get_variable(pkgconfig: 'bindir', default_value: '') if tbb_bindir != '' # Resolve ${prefix} placeholder if present tbb_bindir = tbb_bindir.replace('${prefix}', tbb_prefix) foreach dll_name : ['tbb12.dll', 'tbbbind_2_5.dll', 'tbbmalloc.dll', 'tbbmalloc_proxy.dll'] dll_path = tbb_bindir / dll_name if fs.exists(dll_path) windows_runtime_dlls += [[dll_name, dll_path]] endif endforeach endif endif # Get ONNX Runtime dependency and extract bindir (prefer Conan CMake config) onnx_dep_check = onnxruntime_resolved_dep onnx_bindir_candidates = [] if onnx_dep_check.found() onnx_prefix = onnx_dep_check.get_variable(pkgconfig: 'prefix', default_value: '') onnx_bindir = onnx_dep_check.get_variable(pkgconfig: 'bindir', default_value: '') if onnx_bindir != '' onnx_bindir = onnx_bindir.replace('${prefix}', onnx_prefix) onnx_bindir_candidates += [onnx_bindir] endif # CMake configs from Conan often expose PACKAGE_PREFIX_DIR; fall back to /bin cmake_prefix = onnx_dep_check.get_variable(cmake: 'PACKAGE_PREFIX_DIR', default_value: '') if cmake_prefix != '' onnx_bindir_candidates += [join_paths(cmake_prefix, 'bin')] elif onnx_prefix != '' onnx_bindir_candidates += [join_paths(onnx_prefix, 'bin')] endif endif # As a last resort, probe the Conan cache directly (~/.conan2/p/**/p/bin) via a tiny Python helper py_mod = import('python') if host_machine.system() == 'windows' python3 = py_mod.find_installation('python', required: false) if not python3.found() python3 = py_mod.find_installation('python3', required: false) endif else python3 = py_mod.find_installation('python3', required: false) if not python3.found() python3 = py_mod.find_installation('python', required: false) endif endif if python3.found() conan_probe = run_command(python3, ['-c', '''import pathlib, os root = pathlib.Path(os.path.expanduser("~/.conan2/p")) dirs = [] if root.exists(): for pattern in ("*/p/bin", "b/*/p/bin"): for bin_dir in root.glob(pattern): dll = bin_dir / "onnxruntime.dll" if dll.exists(): dirs.append(str(bin_dir)) print(";".join(dirs))'''], check: false) if conan_probe.returncode() == 0 conan_dirs = conan_probe.stdout().strip().split(';') foreach d : conan_dirs if d != '' onnx_bindir_candidates += [d] endif endforeach endif else message('Python not found (python3/python); skipping Conan cache probe for ONNX Runtime DLLs') endif foreach bindir : onnx_bindir_candidates foreach dll_name : ['onnxruntime.dll', 'onnxruntime_providers_shared.dll'] dll_path = bindir / dll_name if fs.exists(dll_path) windows_runtime_dlls += [[dll_name, dll_path]] endif endforeach endforeach # Deduplicate DLL entries by name to avoid duplicate targets dedup_map = {} deduped = [] foreach dll_info : windows_runtime_dlls name = dll_info[0] if not dedup_map.has_key(name) dedup_map += {name: true} deduped += [dll_info] endif endforeach windows_runtime_dlls = deduped # Log discovered DLLs foreach dll_info : windows_runtime_dlls message('Found runtime DLL: ' + dll_info[0] + ' at ' + dll_info[1]) endforeach endif if not wasi_enabled # Database backend selection (libsql or sqlite) db_backend = get_option('database-backend') # Try to find the selected database backend using_libsql = false if db_backend == 'libsql' # Try to find libsql (provides sqlite3-compatible API) # 1. First try system-installed libsql via pkg-config libsql_dep = dependency('libsql', required: false) if not libsql_dep.found() libsql_dep = dependency('libsql-sqlite3', required: false) endif # 2. If not found, try building from subproject (requires Rust toolchain) if not libsql_dep.found() cargo_prog = find_program('cargo', required: false) if cargo_prog.found() message('libSQL not found on system, building from subproject (requires Rust)...') libsql_subproj = subproject('libsql', required: false) if libsql_subproj.found() libsql_dep = libsql_subproj.get_variable('libsql_dep') endif endif endif if libsql_dep.found() sqlite_dep = libsql_dep using_libsql = true message('Using libSQL database backend (MVCC concurrent writes enabled)') else warning('libSQL not found and subproject build failed. Falling back to standard SQLite3.') warning('To use libSQL: install Rust toolchain (rustup) or system libsql package.') sqlite_dep = dependency('sqlite3', required: true) endif else sqlite_dep = dependency('sqlite3', required: true) message('Using standard SQLite3 database backend') endif # Add compile definition for backend detection if using_libsql add_project_arguments('-DYAMS_LIBSQL_BACKEND=1', language: 'cpp') else add_project_arguments('-DYAMS_LIBSQL_BACKEND=0', language: 'cpp') endif # Skip runtime test when cross-compiling or using subproject sqlite (internal dep can't be used with cpp.run in newer meson) if meson.is_cross_build() warning('Cross-compiling: skipping SQLite FTS5 runtime check. Ensure FTS5 is enabled in your target SQLite build.') elif sqlite_dep.type_name() == 'internal' message('Using subproject sqlite3: skipping FTS5 runtime check. Subproject sqlite3 has FTS5 enabled by default.') elif using_libsql message('Using libSQL: skipping FTS5 runtime check. libSQL includes FTS5 support.') else fts5_prog = cpp.run(files('meson/fts5_check.cpp'), dependencies: sqlite_dep, name: 'sqlite3 FTS5 check') if fts5_prog.returncode() != 0 error('SQLite3 built without FTS5 support. Enable FTS5 in your package manager or Conan (e.g., -o "sqlite3/*:fts5=True").') endif endif endif # Boost is a core requirement (e.g., Boost.Asio for daemon communication). # Resolution order: # 1. Per-library pkg-config probes against Conan's emitted boost_*.pc. # Required path on Windows: meson 1.4's built-in Boost finder rejects # Conan's MSVC layout (libs are named libboost_.lib, not the # auto-link form boost_-vc14x-mt-x64-1_85.lib that meson scans # for). pkg-config bypasses meson's Boost special-case entirely. # 2. System Boost auto-detect — Linux/macOS fallback when no .pc files # are on PKG_CONFIG_PATH (apt/brew Boost ships no pkg-config files). # NOTE: Conan PkgConfigDeps emits per-component .pc files using HYPHEN # separators (boost-system.pc, boost-thread.pc), not underscores. pkg-config # resolves dependency names against the .pc filename, so we must match. boost_system_dep = dependency('boost-system', method: 'pkg-config', required: false) boost_thread_dep = dependency('boost-thread', method: 'pkg-config', required: false) if boost_system_dep.found() and boost_thread_dep.found() boost_dep = declare_dependency( dependencies: [boost_system_dep, boost_thread_dep]) else boost_dep = dependency('boost', modules: ['system', 'thread'], required: true) endif meson.override_dependency('boost', boost_dep) # --- RE2 regex engine (optional, for high-performance grep) --- re2_opt = get_option('enable-re2') re2_dep = dependency('re2', required: re2_opt, include_type: 'system') if re2_dep.found() add_project_arguments('-DYAMS_HAS_RE2=1', language: 'cpp') message('RE2 regex engine enabled (grep will use RE2 with std::regex fallback)') else add_project_arguments('-DYAMS_HAS_RE2=0', language: 'cpp') message('RE2 not found; grep will use std::regex only') endif # --- Version header generation --- _override_version = get_option('yams-version') # Detect version from git tags if not overridden _git_version = '' _git_cmd = find_program('git', required: false) if _git_cmd.found() and _override_version == '' # Try to get the most recent semver tag (v*) _r = run_command(_git_cmd, ['describe', '--tags', '--match', 'v*', '--abbrev=0'], check: false) if _r.returncode() == 0 _tag = _r.stdout().strip() # Strip leading 'v' if present if _tag.startswith('v') _git_version = _tag.substring(1) else _git_version = _tag endif endif endif # Priority: command-line override > git tag > meson.project_version() if _override_version != '' _effective_version = _override_version elif _git_version != '' _effective_version = _git_version else _effective_version = meson.project_version() endif if host_machine.system() == 'windows' python_exe = find_program('python', 'python3') else python_exe = find_program('python3', 'python') endif _git_cmd = find_program('git', required: false) _git_exe = _git_cmd.found() ? _git_cmd.full_path() : 'git' version_header_template = files('include/yams/version_generated.h.in')[0] version_header_script = files('scripts/generate_version_header.py')[0] # Generate version header at build time so installed binaries pick up the # current commit even when source changes occur after the last Meson reconfigure. version_header = custom_target( 'version_generated_header', input: [version_header_template, version_header_script], output: 'version_generated.h', command: [ python_exe, version_header_script, '@INPUT0@', '@OUTPUT@', meson.project_source_root(), meson.project_version(), _override_version, _git_exe, ], build_always_stale: true, build_by_default: true, ) # Make builddir available as include path for generated headers # CLI will include as "version_generated.h" directly generated_inc = include_directories('.') version_generated_dep = declare_dependency( include_directories: generated_inc, sources: version_header, ) yams_build_cli = get_option('build-cli') and not wasi_enabled yams_build_mcp = get_option('build-mcp-server') # Install surface: the runtime set is executables, plugins, the private # lib/yams/ runtime libraries, share/yams data and the systemd unit. The C++ # libraries under src/ are internal static archives linked into those # executables, so neither they nor their headers or pkg-config files are # installed. The only devel payload is the mobile C ABI (src/mobile), tagged # 'devel'. scripts/ci/check_install_surface.py enforces this. # --- tl-expected global resolution --- # tl-expected is a header-only library. Homebrew installs it without pkgconfig. # Resolve it once here and override for all subdirs. _tl_expected_dep = dependency('tl-expected', method: 'cmake', required: false) if not _tl_expected_dep.found() _tl_expected_dep = dependency('tl-expected', method: 'pkg-config', required: false) endif if not _tl_expected_dep.found() # Manual fallback for homebrew installation. Probe both brew prefixes # (Apple Silicon + Intel) guarded by the header actually existing. foreach _brew_prefix : ['/opt/homebrew', '/usr/local'] if not _tl_expected_dep.found() and fs.exists(_brew_prefix / 'include' / 'tl' / 'expected.hpp') _tl_expected_inc = include_directories(_brew_prefix / 'include', is_system: true) _tl_expected_dep = declare_dependency(include_directories: _tl_expected_inc) endif endforeach endif meson.override_dependency('tl-expected', _tl_expected_dep) # --- tinyfsm global resolution --- # tinyfsm is a single-header FSM library vendored under third_party/. # Added as a project-wide include because daemon FSM headers are included # transitively by many targets via ServiceManager.h. _tinyfsm_inc = include_directories('third_party/tinyfsm/include', is_system: true) add_project_arguments( '-isystem', meson.project_source_root() / 'third_party' / 'tinyfsm' / 'include', language: 'cpp', ) _tinyfsm_dep = declare_dependency(include_directories: _tinyfsm_inc) meson.override_dependency('tinyfsm', _tinyfsm_dep) # --- libarchive global resolution --- # Homebrew on macOS installs libarchive as keg-only; pkg-config won't find it # without the Conan-generated pkg_config_path. Resolve once and override only # in the fallback path — overriding an already-resolved dependency is an error. _libarchive_dep = dependency('libarchive', required: false) if not _libarchive_dep.found() and host_machine.system() == 'darwin' foreach _brew_prefix : ['/opt/homebrew', '/usr/local'] if not _libarchive_dep.found() _brew_libarchive = _brew_prefix / 'opt' / 'libarchive' if fs.exists(_brew_libarchive / 'include' / 'archive.h') _libarchive_lib = cpp.find_library('archive', dirs: [_brew_libarchive / 'lib'], required: false) if _libarchive_lib.found() _libarchive_dep = declare_dependency( dependencies: _libarchive_lib, include_directories: include_directories(_brew_libarchive / 'include', is_system: true), ) meson.override_dependency('libarchive', _libarchive_dep) endif endif endif endforeach endif if wasi_enabled # WASI profile: compile a minimal subset (no daemon IPC, no plugins, no downloads). # Keep this intentionally small so cross-compilers don't need system libs like # OpenSSL/libcurl/sqlite/onnx. subdir('src/core') if get_option('build-mcp-server') subdir('src/mcp') subdir('tests/wasi') endif else # Core libraries (ordered by dependency) subdir('src/core') subdir('src/config') subdir('src/crypto') subdir('src/chunking') subdir('src/compression') subdir('src/storage') subdir('src/wal') subdir('src/memory_sync') subdir('src/manifest') subdir('src/metadata') subdir('src/integrity') subdir('src/extraction') subdir('src/detection') subdir('src/content') subdir('src/downloader') if get_option('build-plugins') subdir('src/plugins') endif subdir('src/daemon/tune_advisor') subdir('src/daemon/client') subdir('src/embedding_simeon') subdir('src/vector') subdir('src/topology') # Third-party libraries (must come before src/search which depends on symspell) subdir('third_party/symspell') subdir('src/search') subdir('src/api') subdir('src/ingest') subdir('src/repair') subdir('src/app/services') subdir('src/daemon') if get_option('enable-mobile-bindings') subdir('src/mobile') endif if get_option('build-benchmarks') subdir('src/benchmarks') endif if get_option('build-mcp-server') subdir('src/mcp') endif endif if get_option('build-plugins') and not wasi_enabled subdir('plugins') endif if get_option('build-cli') and not wasi_enabled subdir('src/cli') subdir('tools/yams-cli') if get_option('build-mcp-server') subdir('tools/yams-mcp') endif endif # Tests (enabled by default in Debug builds) _tests_opt = get_option('build-tests') _buildtype = get_option('buildtype') _tests_enabled = (not wasi_enabled) and (_tests_opt or (_buildtype == 'debug' and not get_option('build-fuzzers'))) if _tests_enabled subdir('tests') endif # Fuzzing harnesses (AFL++/libFuzzer) if get_option('build-fuzzers') and not wasi_enabled subdir('tools/fuzzing') endif sql_install_dir = join_paths(get_option('datadir'), 'yams', 'sql') install_data('sql/reference_schema.sql', install_dir: sql_install_dir, install_tag: 'runtime') # macOS: Meson installs dylibs with absolute install names; keep the private # libyams_onnx_resource @rpath-relative so staged/unzipped trees run in place. if host_machine.system() == 'darwin' and not wasi_enabled meson.add_install_script(files('scripts/macos-rpath-install-names.sh'), get_option('libdir'), install_tag: 'runtime') endif summary({ 'project': meson.project_name(), 'version': meson.project_version(), 'cpp_std': get_option('cpp_std'), 'build_cli': yams_build_cli, 'build_mcp_server': yams_build_mcp, }, section: 'YAMS Meson configuration', bool_yn: true) if get_option('b_coverage') gcovr = find_program('gcovr', required: false) if gcovr.found() _cpp_compiler = meson.get_compiler('cpp') _gcov_executable = 'gcov' if _cpp_compiler.get_id() == 'clang' _llvm_cov = find_program('llvm-cov', required: false) if _llvm_cov.found() _gcov_executable = 'llvm-cov gcov -p' endif endif coverage_report_target = custom_target('coverage-report', output: 'coverage.html', command: [ gcovr, '--root', meson.project_source_root(), '--gcov-executable', _gcov_executable, '--object-directory', meson.project_build_root(), '--merge-mode-functions', 'merge-use-line-min', '--filter', join_paths(meson.project_source_root(), 'src'), '--filter', join_paths(meson.project_source_root(), 'include'), '--exclude', join_paths(meson.project_source_root(), 'src', 'benchmarks'), '--html', '--html-details', '--output', '@OUTPUT@', meson.project_build_root(), ], build_by_default: false, build_always_stale: true, ) coverage_txt_target = custom_target('coverage-summary', output: 'coverage.txt', command: [ gcovr, '--root', meson.project_source_root(), '--gcov-executable', _gcov_executable, '--object-directory', meson.project_build_root(), '--merge-mode-functions', 'merge-use-line-min', '--filter', join_paths(meson.project_source_root(), 'src'), '--filter', join_paths(meson.project_source_root(), 'include'), '--exclude', join_paths(meson.project_source_root(), 'src', 'benchmarks'), '--print-summary', '--output', '@OUTPUT@', meson.project_build_root(), ], build_by_default: false, build_always_stale: true, ) message('Coverage reporting configured. Run tests then: ninja -C builddir coverage.txt && ninja -C builddir coverage.html') else warning('gcovr not found. Install gcovr to generate coverage reports.') endif endif # Note: Runtime DLLs are now deployed via Conan's runtime_deploy deployer during conan install # No custom meson install script needed