-- Copyright Epic Games, Inc. All Rights Reserved. set_configvar("ZEN_SCHEMA_VERSION", 5) -- force state wipe after 0.2.31 causing bad data (dan.engelbrecht) set_configvar("ZEN_DATA_FORCE_SCRUB_VERSION", 0) set_allowedplats("windows", "linux", "macosx") set_allowedarchs("windows|x64", "linux|x86_64", "macosx|x86_64", "macosx|arm64") -- Returns true when building for Windows with native MSVC (not clang-cl cross-compilation) function is_native_msvc() local tc = get_config("toolchain") or "" return is_plat("windows") and tc ~= "clang-cl" end -------------------------------------------------------------------------- -- We support debug and release modes. On Windows we use static CRT to -- minimize dependencies. set_allowedmodes("debug", "release") add_rules("mode.debug", "mode.release") -- Disable xmake's run.autobuild (on by default in xmake 3.x). Auto-rebuild -- before run is dangerous: if the current config differs from the build that -- produced the binary (e.g. ASAN was toggled), xmake can rebuild with wrong -- flags, causing linker errors (e.g. MSVC annotate_string mismatch). Users -- are expected to run `xmake build` explicitly before `xmake run`. set_policy("run.autobuild", false) if is_plat("windows") then if false then -- DLL runtime if is_mode("debug") then set_runtimes("MDd") else set_runtimes("MD") end else -- static runtime if is_mode("debug") then set_runtimes("MTd") else set_runtimes("MT") end end end -- Sanitizers -- -- https://xmake.io/api/description/builtin-policies.html#build-sanitizer-address -- -- Each sanitizer has a .supp file (asan.supp, tsan.supp, msan.supp). A single -- sanitizer.options rule handles all three in one before_run callback. -- -- asan.supp and msan.supp contain key=value runtime options (no native -- "options from file" mechanism exists for either sanitizer on any platform). -- -- tsan.supp is a mixed-format file: key=value runtime options (parsed by xmake -- and set in TSAN_OPTIONS) plus native TSAN suppression patterns (race:..., -- mutex:...) that are passed via TSAN_OPTIONS=suppressions= on Linux/Mac. -- The TSAN runtime silently skips key=value lines when parsing the suppression -- file (they don't match type:pattern; TSAN emits a verbose-level warning). -- -- IMPORTANT: The env vars are set by xmake's before_run hook. If you run a -- binary directly (not via xmake run / xmake test) you must set them manually. -------------------------------------------------------------------------- -- AddressSanitizer is supported on Windows (MSVC 2019+), Linux, and MacOS. -- Enable with: `xmake config --asan=y` or `xmake test --asan` -- (automatically disables mimalloc and sentry, which are incompatible with ASAN) -- -- Options live in asan.supp. For manual runs: -- Linux/Mac: ASAN_OPTIONS="new_delete_type_mismatch=0" ./zenserver -- Windows: set ASAN_OPTIONS=new_delete_type_mismatch=0 option("asan") set_default(false) set_showmenu(true) set_description("Enable AddressSanitizer (disables mimalloc and sentry)") option_end() -- Global flag: read by sub-target xmake.lua files (like enable_unity) use_asan = has_config("asan") if use_asan then set_policy("build.sanitizer.address", true) -- On Windows/MSVC, explicitly define __SANITIZE_ADDRESS__ so that -- IntelliSense in VS sees it (XmakeDefines -> PreprocessorDefinitions). -- MSVC sets it implicitly at compile time via /fsanitize=address, but -- vsxmake IntelliSense needs it in the explicit defines list. -- Not needed on Clang/GCC: they set it implicitly and adding it via -D -- would cause a macro-redefinition warning (error with -Werror). if is_os("windows") then add_defines("__SANITIZE_ADDRESS__=1") end end -------------------------------------------------------------------------- -- ThreadSanitizer is supported on Linux and MacOS only. -- Enable with: `xmake config --tsan=y` or `xmake test --tsan` -- -- Key=value options and suppression patterns both live in tsan.supp. For -- manual runs: TSAN_OPTIONS="detect_deadlocks=0:suppressions=$(pwd)/tsan.supp" option("tsan") set_default(false) set_showmenu(true) set_description("Enable ThreadSanitizer (Linux/Mac only)") option_end() use_tsan = has_config("tsan") if use_tsan then if is_os("windows") then raise("TSAN is not supported on Windows. Use Linux or macOS.") end set_policy("build.sanitizer.thread", true) end -------------------------------------------------------------------------- -- MemorySanitizer is supported on Linux only. In practice it rarely works -- because all dependencies must also be compiled with MSan instrumentation. -- Enable with: `xmake config --msan=y` or `xmake test --msan` -- -- Options live in msan.supp. For manual runs: set MSAN_OPTIONS manually. option("msan") set_default(false) set_showmenu(true) set_description("Enable MemorySanitizer (Linux only, requires all deps instrumented)") option_end() use_msan = has_config("msan") if use_msan then set_policy("build.sanitizer.memory", true) end -------------------------------------------------------------------------- -- Single rule handles all three sanitizer env vars. parse_supp_opts is defined -- once inside before_run (scripting scope) where io is a valid global. rule("sanitizer.options") before_run(function(target) -- Parses key=value lines from a .supp file. Blank lines and lines whose -- first non-whitespace character is '#' are ignored. Suppression-pattern -- lines (race:..., mutex:...) don't match ^[%w_]+= and are skipped. local function parse_supp_opts(filepath) local opts = {} local content = os.isfile(filepath) and io.readfile(filepath) or "" for line in content:gmatch("[^\r\n]+") do line = line:match("^%s*(.-)%s*$") if line ~= "" and not line:match("^#") and line:match("^[%w_]+=") then table.insert(opts, line) end end return opts end if target:policy("build.sanitizer.address") and not os.getenv("ASAN_OPTIONS") then local opts = parse_supp_opts(path.join(os.projectdir(), "asan.supp")) if #opts > 0 then os.setenv("ASAN_OPTIONS", table.concat(opts, ":")) end end if target:policy("build.sanitizer.thread") and not os.getenv("TSAN_OPTIONS") then local suppfile = path.join(os.projectdir(), "tsan.supp") local opts = parse_supp_opts(suppfile) -- Also pass the suppression patterns via the native file mechanism when -- both conditions hold: -- 1. Not clang-cl: file path support requires the LLVM/GCC TSAN runtime. -- 2. Platform is Linux or macOS: TSAN is unavailable on Windows -- (config-time raise() above prevents reaching here on Windows). local tc_name = get_config("toolchain") or "" local is_not_clang_cl = tc_name ~= "clang-cl" if os.isfile(suppfile) and is_not_clang_cl and (target:is_plat("linux") or target:is_plat("macosx")) then table.insert(opts, "suppressions=" .. suppfile) end if #opts > 0 then os.setenv("TSAN_OPTIONS", table.concat(opts, ":")) end end if target:policy("build.sanitizer.memory") and not os.getenv("MSAN_OPTIONS") then local opts = parse_supp_opts(path.join(os.projectdir(), "msan.supp")) if #opts > 0 then os.setenv("MSAN_OPTIONS", table.concat(opts, ":")) end end end) rule_end() add_rules("sanitizer.options") --set_policy("build.sanitizer.leak", true) --set_policy("build.sanitizer.undefined", true) -------------------------------------------------------------------------- -- Dependencies add_repositories("zen-repo repo") set_policy("build.ccache", false) set_policy("package.precompiled", false) add_defines("gsl_FEATURE_GSL_COMPATIBILITY_MODE=1") add_requires("gsl-lite", {system = false}) add_requires("http_parser", {system = false}) add_requires("json11", {system = false}) add_requires("lua", {system = false}) add_requires("lz4", {system = false}) add_requires("xxhash", {system = false}) add_requires("zlib", {system = false}) add_defines("EASTL_STD_ITERATOR_CATEGORY_ENABLED", "EASTL_DEPRECATIONS_FOR_2024_APRIL=EA_DISABLED") add_requires("eastl", {system = false}) add_requires("consul", {system = false}) -- for hub tests add_requires("nomad", {system = false}) -- for nomad provisioner tests add_requires("oidctoken", {system = false}) if has_config("zenmimalloc") and not use_asan then add_requires("mimalloc", {system = false}) end -------------------------------------------------------------------------- -- Crypto configuration. For reasons unknown each platform needs a -- different package if is_plat("windows") then -- we use schannel on Windows add_defines("ZEN_USE_OPENSSL=0") add_requires("libcurl", {system = false}) elseif is_plat("linux", "macosx") then add_requires("openssl3", {system = false}) add_defines("ZEN_USE_OPENSSL=1") add_requires("libcurl", {system = false, configs = {openssl3 = true}}) end -------------------------------------------------------------------------- if is_plat("windows") then -- for bundling, Linux tries to compile from source which fails with UE toolchain, -- fallback is regular zip add_requires("7z") end -- When using the UE clang toolchain, statically link the toolchain's libc++ and -- libc++abi to avoid ABI mismatches with system libraries at runtime. -- These are project-level flags (not in the toolchain definition) so they don't -- propagate into cmake package builds. if is_plat("linux") and get_config("toolchain") == "ue-clang" then add_ldflags("-static-libstdc++", {force = true}) add_ldflags("$(projectdir)/thirdparty/ue-libcxx/lib64/libc++.a", {force = true}) add_ldflags("$(projectdir)/thirdparty/ue-libcxx/lib64/libc++abi.a", {force = true}) add_ldflags("-lpthread", {force = true}) end if has_config("zensentry") and not use_asan then if is_plat("linux") then add_requires("sentry-native 0.12.1", {configs = {backend = "crashpad"}}) elseif is_plat("windows") then add_requires("sentry-native 0.12.1", {debug = is_mode("debug"), configs = {backend = "crashpad"}}) else add_requires("sentry-native 0.12.1", {configs = {backend = "crashpad"}}) end end enable_unity = false --add_rules("c++.unity_build") if is_mode("release") then -- LTO does not appear to work with the current Linux UE toolchain -- Also, disabled LTO on Mac to reduce time spent building openssl tests -- Disabled for cross-compilation (clang-cl on Linux) due to cmake package compat issues local is_cross_win = is_plat("windows") and is_host("linux") if not is_plat("linux", "macosx") and not is_cross_win then set_policy("build.optimization.lto", true) end set_optimize("fastest") end if is_mode("debug") then add_defines("DEBUG") end if is_mode("debug") then add_defines("ZEN_WITH_TESTS=1") else add_defines("ZEN_WITH_TESTS=0") end -- fmt 11+ requires utf-8 when using unicode if is_os("windows") then set_encodings("utf-8") else set_encodings("source:utf-8", "target:utf-8") end -- When cross-compiling with clang-cl, the xwin SDK may ship a newer MSVC STL -- than the host clang version supports. Bypass the version gate. if is_plat("windows") and not is_native_msvc() then add_defines("_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH") end if is_os("windows") then add_defines( "_CRT_SECURE_NO_WARNINGS", "_UNICODE", "UNICODE", "_CONSOLE", "NOMINMAX", -- stop Windows SDK defining 'min' and 'max' "NOGDI", -- otherwise Windows.h defines 'GetObject' "WIN32_LEAN_AND_MEAN", -- cut down Windows.h "_WIN32_WINNT=0x0A00", "_WINSOCK_DEPRECATED_NO_WARNINGS" -- let us use the ANSI functions ) -- Make builds more deterministic and portable (MSVC-only flags) if is_native_msvc() then add_cxxflags("/d1trimfile:$(curdir)\\") -- eliminates the base path from __FILE__ paths add_cxxflags("/experimental:deterministic") -- (more) deterministic compiler output add_ldflags("/PDBALTPATH:%_PDB%") -- deterministic pdb reference in exe add_cxxflags("/Zc:u8EscapeEncoding") -- Enable UTF-8 encoding for u8 string literals (clang does this by default) add_cxxflags("/Zc:preprocessor") -- Enable preprocessor conformance mode add_cxxflags("/Zc:inline") -- Enforce inline semantics end -- add_ldflags("/MAP") end -- Clang warning suppressions (native clang on Linux/Mac, or clang-cl cross-compile) if is_os("linux") or is_os("macosx") or not is_native_msvc() then -- Silence warnings about unrecognized -Wno-* flags on older clang versions add_cxxflags("-Wno-unknown-warning-option", {force = true}) add_cxxflags("-Wno-delete-non-abstract-non-virtual-dtor", {force = true}) add_cxxflags("-Wno-format", {force = true}) add_cxxflags("-Wno-implicit-fallthrough", {force = true}) add_cxxflags("-Wno-inconsistent-missing-override", {force = true}) add_cxxflags("-Wno-missing-field-initializers", {force = true}) add_cxxflags("-Wno-nonportable-include-path", {force = true}) add_cxxflags("-Wno-sign-compare", {force = true}) add_cxxflags("-Wno-strict-aliasing", {force = true}) add_cxxflags("-Wno-switch", {force = true}) add_cxxflags("-Wno-unused-lambda-capture", {force = true}) add_cxxflags("-Wno-unused-private-field", {force = true}) add_cxxflags("-Wno-unused-value", {force = true}) add_cxxflags("-Wno-unused-variable", {force = true}) add_cxxflags("-Wno-vla-cxx-extension", {force = true}) -- GCC false positive: constinit static locals used by reference are reported as unused-but-set add_cxxflags("-Wno-unused-but-set-variable", {tools = "gcc"}) end -- Additional suppressions specific to clang-cl cross-compilation if get_config("toolchain") == "clang-cl" then add_cxxflags("-Wno-cast-function-type-mismatch", {force = true}) add_cxxflags("-Wno-parentheses-equality", {force = true}) add_cxxflags("-Wno-reorder-ctor", {force = true}) add_cxxflags("-Wno-unused-but-set-variable", {force = true}) add_cxxflags("-Wno-unused-parameter", {force = true}) add_cflags("-Wno-unknown-warning-option", {force = true}) add_cflags("-Wno-unused-command-line-argument", {force = true}) end if is_os("linux") then add_defines("_GNU_SOURCE") end -- Turn use of undefined cpp macros into errors if is_os("windows") then add_cxxflags("/we4668") else add_cxxflags("-Wundef") end function add_define_by_config(define, config_name) local value = has_config(config_name) and 1 or 0 add_defines(define.."="..value) end option("zensentry") set_default(true) set_showmenu(true) set_description("Enables Sentry support (incompatible with ASAN)") option_end() add_defines("ZEN_USE_SENTRY=" .. ((has_config("zensentry") and not use_asan) and "1" or "0")) option("zenmimalloc") set_default(true) set_showmenu(true) set_description("Use MiMalloc for faster memory management (incompatible with ASAN)") option_end() add_defines("ZEN_USE_MIMALLOC=" .. ((has_config("zenmimalloc") and not use_asan) and "1" or "0")) option("zenrpmalloc") set_default(true) set_showmenu(true) set_description("Use rpmalloc for faster memory management") option_end() add_define_by_config("ZEN_USE_RPMALLOC", "zenrpmalloc") if is_os("windows") then option("httpsys") set_default(true) set_showmenu(true) set_description("Enable http.sys server") option_end() add_define_by_config("ZEN_WITH_HTTPSYS", "httpsys") else add_defines("ZEN_WITH_HTTPSYS=0") end local compute_default = true option("zencompute") set_default(compute_default) set_showmenu(true) set_description("Enable compute services endpoint") option_end() add_define_by_config("ZEN_WITH_COMPUTE_SERVICES", "zencompute") option("zenhorde") set_default(compute_default) set_showmenu(true) set_description("Enable Horde worker provisioning") option_end() add_define_by_config("ZEN_WITH_HORDE", "zenhorde") option("zennomad") set_default(compute_default) set_showmenu(true) set_description("Enable Nomad worker provisioning") option_end() add_define_by_config("ZEN_WITH_NOMAD", "zennomad") if is_os("windows") then add_defines("UE_MEMORY_TRACE_AVAILABLE=1") option("zenmemtrack") set_default(true) set_showmenu(true) set_description("Enable UE's Memory Trace support") option_end() add_define_by_config("ZEN_WITH_MEMTRACK", "zenmemtrack") else add_defines("ZEN_WITH_MEMTRACK=0") end option("zentrace") set_default(true) set_showmenu(true) set_description("Enable UE's Trace support") option_end() add_define_by_config("ZEN_WITH_TRACE", "zentrace") option("zencpr") set_default(true) set_showmenu(true) set_description("Enable CPR HTTP client backend") option_end() add_define_by_config("ZEN_WITH_CPR", "zencpr") set_warnings("allextra", "error") set_languages("cxx20") -- always generate debug information set_symbols("debug") includes("toolchains") -- Auto-select the UE clang toolchain on Linux when the SDK is available if is_plat("linux") and not get_config("toolchain") then local ue_sdk = os.getenv("UE_TOOLCHAIN_DIR") if not ue_sdk or ue_sdk == "" then local home = os.getenv("HOME") if home then local default_path = path.join(home, ".ue-toolchain") if os.isdir(default_path) then ue_sdk = default_path end end end if ue_sdk and ue_sdk ~= "" and os.isdir(ue_sdk) then set_toolchains("ue-clang") end end includes("thirdparty") includes("src/transports") includes("src/zenbase") includes("src/zencore", "src/zencore-test") includes("src/zenhttp", "src/zenhttp-test") includes("src/zennet", "src/zennet-test") includes("src/zenremotestore", "src/zenremotestore-test") includes("src/zencompute", "src/zencompute-test") if has_config("zenhorde") then includes("src/zenhorde") end if has_config("zennomad") then includes("src/zennomad") end includes("src/zenstore", "src/zenstore-test") includes("src/zentelemetry", "src/zentelemetry-test") includes("src/zenutil", "src/zenutil-test") if is_plat("windows") then includes("src/zenvfs") end includes("src/zenserver", "src/zenserver-test") includes("src/zen") includes("src/zentest-appstub") -------------------------------------------------------------------------- task("bundle") set_menu { usage = "xmake bundle", description = "Create Zip bundle from binaries", options = { {nil, "withtrace", "k", nil, "Compiles with trace support"}, {nil, "codesignidentity", "v", nil, "Code signing identity"}, } } on_run(function () import("scripts.bundle") bundle() end) task("docker") set_menu { usage = "xmake docker [--push] [--no-wine] [--win-binary PATH] [--tag TAG] [--registry REGISTRY]", description = "Build Docker image for zenserver compute workers", options = { {nil, "push", "k", nil, "Push the image after building"}, {nil, "no-wine", "k", nil, "Build without Wine (smaller image, Linux-only workers)"}, {nil, "win-binary", "v", nil, "Path to Windows zenserver.exe to include in image"}, {nil, "tag", "v", nil, "Override image tag (default: version from VERSION.txt)"}, {nil, "registry", "v", nil, "Registry prefix (e.g. ghcr.io/epicgames)"}, } } on_run(function () import("scripts.docker") docker() end) task("kill") set_menu { usage = "xmake kill", description = "Terminate any running zenserver instances", } on_run(function () local ok = try { function() os.execv("xmake", {"run", "zen", "down", "--all"}) end } if ok then return end if is_host("windows") then ok = try { function() os.runv("taskkill", {"/F", "/IM", "zenserver.exe"}) end } if ok then print("zenserver terminated") end else ok = try { function() os.runv("pkill", {"-f", "zenserver"}) end } if ok then print("zenserver terminated") end end end) task("precommit") set_menu { usage = "xmake precommit", description = "Run required pre-commit steps (clang-format, etc)", } on_run(function () print(os.exec("pre-commit run --all-files")) end) task("sln") set_menu { usage = "xmake sln [--open] [--vscode]", description = "Generate IDE project files", options = { {'o', "open", "k", nil, "Open the generated project in the IDE after generation"}, {nil, "vscode", "k", nil, "Generate compile_commands.json for VS Code / clangd"}, } } on_run(function () import("core.base.option") if option.get("vscode") then os.exec("xmake project --yes --kind=compile_commands --lsp=clangd") printf("generated compile_commands.json\n") return end if is_os("windows") then os.exec("xmake project --yes --kind=vsxmake2022 -m release,debug -a x64") if option.get("open") then local sln = path.join(os.projectdir(), "vsxmake2022", path.filename(os.projectdir()) .. ".sln") printf("opening %s\n", sln) try { function() os.execv("explorer", {sln}) end, catch { function() end } } end elseif is_os("macosx") then os.exec("xmake project --yes --kind=xcode -m release,debug -a x64,arm64") if option.get("open") then local xcproj = path.join(os.projectdir(), path.filename(os.projectdir()) .. ".xcodeproj") printf("opening %s\n", xcproj) os.exec("open \"%s\"", xcproj) end end end) task("test") set_menu { usage = "xmake test --run=[name|all] [-- extra-args...] (use --list to see available tests)", description = "Run Zen tests", options = { {'r', "run", "kv", "all", "Run test(s) - comma-separated"}, {'l', "list", "k", nil, "List available test names"}, {'j', "junit", "k", nil, "Enable junit report output"}, {'n', "noskip", "k", nil, "Run skipped tests (passes --no-skip to doctest)"}, {nil, "repeat", "kv", nil, "Repeat tests N times (stops on first failure)"}, {'V', "output", "k", nil, "Route child process output to stdout (zenserver-test)"}, {nil, "asan", "k", nil, "Enable AddressSanitizer (disables mimalloc and sentry)"}, {nil, "tsan", "k", nil, "Enable ThreadSanitizer (Linux/Mac only)"}, {nil, "msan", "k", nil, "Enable MemorySanitizer (Linux only, requires all deps instrumented)"}, {nil, "arguments", "vs", nil, "Extra arguments passed to test runners (after --)"} } } on_run(function() import("scripts.test") test() end)