aboutsummaryrefslogtreecommitdiff
path: root/xmake.lua
diff options
context:
space:
mode:
Diffstat (limited to 'xmake.lua')
-rw-r--r--xmake.lua532
1 files changed, 99 insertions, 433 deletions
diff --git a/xmake.lua b/xmake.lua
index e9bed93aa..846bc8f1d 100644
--- a/xmake.lua
+++ b/xmake.lua
@@ -6,6 +6,12 @@ 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.
@@ -48,12 +54,27 @@ set_policy("build.sanitizer.address", use_asan)
-- ThreadSanitizer, MemorySanitizer, LeakSanitizer, and UndefinedBehaviorSanitizer
-- are supported on Linux and MacOS only.
+--
+-- You can enable these by editing the xmake.lua directly, or by passing the
+-- appropriate flags on the command line:
+--
+-- `xmake --policies=build.sanitizer.thread:y` for ThreadSanitizer,
+-- `xmake --policies=build.sanitizer.memory:y` for MemorySanitizer, etc.
---set_policy("build.sanitizer.thread", true)
---set_policy("build.sanitizer.memory", true)
+-- When using TSAN you will want to also use the suppression tile to silence
+-- known benign races. You do this by ensuring the the TSAN_OPTIONS environment
+-- vriable is set to something like `TSAN_OPTIONS="suppressions=$(projectdir)/tsan.supp"`
+--
+-- `prompt> TSAN_OPTIONS="detect_deadlocks=0 suppressions=$(projectdir)/tsan.supp" xmake run zenserver`
+
+--set_policy("build.sanitizer.thread", true)
--set_policy("build.sanitizer.leak", true)
--set_policy("build.sanitizer.undefined", true)
+-- In practice, this does not work because of the difficulty of compiling
+-- dependencies with MemorySanitizer.
+--set_policy("build.sanitizer.memory", true)
+
--------------------------------------------------------------------------
-- Dependencies
@@ -131,7 +152,9 @@ enable_unity = false
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
- if not is_plat("linux", "macosx") then
+ -- 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")
@@ -154,6 +177,12 @@ 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",
@@ -166,33 +195,49 @@ if is_os("windows") then
"_WIN32_WINNT=0x0A00",
"_WINSOCK_DEPRECATED_NO_WARNINGS" -- let us use the ANSI functions
)
- -- Make builds more deterministic and portable
- 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:preprocessor") -- Enable preprocessor conformance mode
- add_cxxflags("/Zc:u8EscapeEncoding") -- Enable UTF-8 encoding for u8 string literals
- add_cxxflags("/Zc:inline") -- Enforce inline semantics
+ -- 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
-if is_os("linux") or is_os("macosx") then
- add_cxxflags("-Wno-implicit-fallthrough")
- add_cxxflags("-Wno-missing-field-initializers")
- add_cxxflags("-Wno-strict-aliasing")
- add_cxxflags("-Wno-switch")
- add_cxxflags("-Wno-unused-lambda-capture")
- add_cxxflags("-Wno-unused-private-field")
- add_cxxflags("-Wno-unused-value")
- add_cxxflags("-Wno-unused-variable")
- add_cxxflags("-Wno-vla-cxx-extension")
+-- 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
-if is_os("macosx") then
- -- silence warnings about -Wno-vla-cxx-extension since to my knowledge we can't
- -- detect the clang version used in Xcode and only recent versions contain this flag
- add_cxxflags("-Wno-unknown-warning-option")
+-- 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
@@ -286,6 +331,13 @@ option("zentrace")
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")
@@ -328,7 +380,9 @@ end
includes("src/zenstore", "src/zenstore-test")
includes("src/zentelemetry", "src/zentelemetry-test")
includes("src/zenutil", "src/zenutil-test")
-includes("src/zenvfs")
+if is_plat("windows") then
+ includes("src/zenvfs")
+end
includes("src/zenserver", "src/zenserver-test")
includes("src/zen")
includes("src/zentest-appstub")
@@ -349,6 +403,23 @@ task("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",
@@ -366,16 +437,6 @@ task("kill")
end
end)
-task("updatefrontend")
- set_menu {
- usage = "xmake updatefrontend",
- description = "Create Zip of the frontend/html folder for bundling with zenserver executable",
- }
- on_run(function()
- import("scripts.updatefrontend")
- updatefrontend()
- end)
-
task("precommit")
set_menu {
usage = "xmake precommit",
@@ -430,401 +491,6 @@ task("test")
}
}
on_run(function()
- import("core.base.option")
- import("core.project.config")
- import("core.project.project")
-
- config.load()
-
- -- Override table: target name -> short name (for targets that don't follow convention)
- local short_name_overrides = {
- ["zenserver-test"] = "integration",
- }
-
- -- Build test list from targets in the "tests" group
- local available_tests = {}
- for name, target in pairs(project.targets()) do
- if target:get("group") == "tests" and name:endswith("-test") then
- local short = short_name_overrides[name]
- if not short then
- -- Derive short name: "zencore-test" -> "core"
- short = name
- if short:startswith("zen") then short = short:sub(4) end
- if short:endswith("-test") then short = short:sub(1, -6) end
- end
- table.insert(available_tests, {short, name})
- end
- end
-
- -- Add non-test-group entries that have a test subcommand
- table.insert(available_tests, {"server", "zenserver"})
-
- table.sort(available_tests, function(a, b) return a[1] < b[1] end)
-
- -- Handle --list: print discovered test names and exit
- if option.get("list") then
- printf("Available tests:\n")
- for _, entry in ipairs(available_tests) do
- printf(" %-16s -> %s\n", entry[1], entry[2])
- end
- return
- end
-
- local testname = option.get("run")
-
- -- Parse comma-separated test names into a set
- local requested = {}
- for token in testname:gmatch("[^,]+") do
- requested[token:match("^%s*(.-)%s*$")] = true
- end
-
- -- Filter to requested test(s)
- local tests = {}
- local matched = {}
-
- for _, entry in ipairs(available_tests) do
- local name, target = entry[1], entry[2]
- if requested["all"] or requested[name] then
- table.insert(tests, {name = name, target = target})
- matched[name] = true
- end
- end
-
- -- Check for unknown test names
- if not requested["all"] then
- for name, _ in pairs(requested) do
- if not matched[name] then
- raise("no tests match specification: '%s'", name)
- end
- end
- end
-
- if #tests == 0 then
- raise("no tests match specification: '%s'", testname)
- end
-
- local plat, arch
- if is_host("windows") then
- plat = "windows"
- arch = "x64"
- elseif is_host("macosx") then
- plat = "macosx"
- arch = is_arch("arm64") and "arm64" or "x86_64"
- else
- plat = "linux"
- arch = "x86_64"
- end
-
- -- Only reconfigure if current config doesn't already match
- if config.get("mode") ~= "debug" or config.get("plat") ~= plat or config.get("arch") ~= arch then
- local toolchain_flag = config.get("toolchain") and ("--toolchain=" .. config.get("toolchain")) or ""
- local sdk_flag = config.get("sdk") and ("--sdk=" .. config.get("sdk")) or ""
- os.exec("xmake config -c -m debug -p %s -a %s %s %s", plat, arch, toolchain_flag, sdk_flag)
- end
-
- -- Build targets we're going to run
- if requested["all"] then
- os.exec("xmake build -y")
- else
- for _, entry in ipairs(tests) do
- os.exec("xmake build -y %s", entry.target)
- end
- end
-
- local use_junit_reporting = option.get("junit")
- local use_noskip = option.get("noskip")
- local use_verbose = option.get("verbose")
- local repeat_count = tonumber(option.get("repeat")) or 1
- local extra_args = option.get("arguments") or {}
- local junit_report_files = {}
-
- local junit_report_dir
- if use_junit_reporting then
- junit_report_dir = path.join(os.projectdir(), config.get("buildir"), "reports")
- os.mkdir(junit_report_dir)
- end
-
- -- Results collection for summary table
- local results = {}
- local any_failed = false
-
- -- Format a number with thousands separators (e.g. 31103 -> "31,103")
- local function format_number(n)
- local s = tostring(n)
- local pos = #s % 3
- if pos == 0 then pos = 3 end
- local result = s:sub(1, pos)
- for i = pos + 1, #s, 3 do
- result = result .. "," .. s:sub(i, i + 2)
- end
- return result
- end
-
- -- Center a string within a given width
- local function center_str(s, width)
- local pad = width - #s
- local lpad = math.floor(pad / 2)
- local rpad = pad - lpad
- return string.rep(" ", lpad) .. s .. string.rep(" ", rpad)
- end
-
- -- Left-align a string within a given width (with 1-space left margin)
- local function left_align_str(s, width)
- return " " .. s .. string.rep(" ", width - #s - 1)
- end
-
- -- Right-align a string within a given width (with 1-space right margin)
- local function right_align_str(s, width)
- return string.rep(" ", width - #s - 1) .. s .. " "
- end
-
- -- Format elapsed seconds as a human-readable string
- local function format_time(seconds)
- if seconds >= 60 then
- local mins = math.floor(seconds / 60)
- local secs = seconds - mins * 60
- return string.format("%dm %04.1fs", mins, secs)
- else
- return string.format("%.1fs", seconds)
- end
- end
-
- -- Parse test summary file written by TestListener
- local function parse_summary_file(filepath)
- if not os.isfile(filepath) then return nil end
- local content = io.readfile(filepath)
- if not content then return nil end
- local ct = content:match("cases_total=(%d+)")
- local cp = content:match("cases_passed=(%d+)")
- local at = content:match("assertions_total=(%d+)")
- local ap = content:match("assertions_passed=(%d+)")
- if ct then
- local failures = {}
- for name, file, line in content:gmatch("failed=([^|\n]+)|([^|\n]+)|(%d+)") do
- table.insert(failures, {name = name, file = file, line = tonumber(line)})
- end
- local es = content:match("elapsed_seconds=([%d%.]+)")
- return {
- cases_total = tonumber(ct),
- cases_passed = tonumber(cp) or 0,
- asserts_total = tonumber(at) or 0,
- asserts_passed = tonumber(ap) or 0,
- elapsed_seconds = tonumber(es) or 0,
- failures = failures
- }
- end
- return nil
- end
-
- -- Temp directory for summary files
- local summary_dir = path.join(os.tmpdir(), "zen-test-summary")
- os.mkdir(summary_dir)
-
- -- Run each test suite and collect results
- for iteration = 1, repeat_count do
- if repeat_count > 1 then
- printf("\n*** Iteration %d/%d ***\n", iteration, repeat_count)
- end
-
- for _, entry in ipairs(tests) do
- local name, target = entry.name, entry.target
- printf("=== %s ===\n", target)
-
- local suite_name = target
- if name == "server" then
- suite_name = "zenserver (test)"
- end
-
- local cmd = string.format("xmake run %s", target)
- if name == "server" then
- cmd = string.format("xmake run %s test", target)
- end
- cmd = string.format("%s --duration=true", cmd)
-
- if use_junit_reporting then
- local junit_report_file = path.join(junit_report_dir, string.format("junit-%s-%s-%s.xml", config.plat(), arch, target))
- junit_report_files[target] = junit_report_file
- cmd = string.format("%s --reporters=junit --out=%s", cmd, junit_report_file)
- end
- if use_noskip then
- cmd = string.format("%s --no-skip", cmd)
- end
- if use_verbose and name == "integration" then
- cmd = string.format("%s --verbose", cmd)
- end
- for _, arg in ipairs(extra_args) do
- cmd = string.format("%s %s", cmd, arg)
- end
-
- -- Tell TestListener where to write the summary
- local summary_file = path.join(summary_dir, target .. ".txt")
- os.setenv("ZEN_TEST_SUMMARY_FILE", summary_file)
-
- -- Run test with real-time streaming output
- local test_ok = true
- try {
- function()
- os.exec(cmd)
- end,
- catch {
- function(errors)
- test_ok = false
- end
- }
- }
-
- -- Read summary written by TestListener
- local summary = parse_summary_file(summary_file)
- os.tryrm(summary_file)
-
- if not test_ok then
- any_failed = true
- end
-
- table.insert(results, {
- suite = suite_name,
- cases_passed = summary and summary.cases_passed or 0,
- cases_total = summary and summary.cases_total or 0,
- asserts_passed = summary and summary.asserts_passed or 0,
- asserts_total = summary and summary.asserts_total or 0,
- elapsed_seconds = summary and summary.elapsed_seconds or 0,
- failures = summary and summary.failures or {},
- passed = test_ok
- })
- end
-
- if any_failed then
- if repeat_count > 1 then
- printf("\n*** Failure detected on iteration %d, stopping ***\n", iteration)
- end
- break
- end
- end
-
- -- Clean up
- os.setenv("ZEN_TEST_SUMMARY_FILE", "")
- os.tryrm(summary_dir)
-
- -- Print JUnit reports if requested
- for test, junit_report_file in pairs(junit_report_files) do
- printf("=== report - %s ===\n", test)
- if os.isfile(junit_report_file) then
- local data = io.readfile(junit_report_file)
- if data then
- print(data)
- end
- end
- end
-
- -- Print summary table
- if #results > 0 then
- -- Calculate column widths based on content
- local col_suite = #("Suite")
- local col_cases = #("Cases")
- local col_asserts = #("Assertions")
- local col_time = #("Time")
- local col_status = #("Status")
-
- -- Compute totals
- local total_cases_passed = 0
- local total_cases_total = 0
- local total_asserts_passed = 0
- local total_asserts_total = 0
- local total_elapsed = 0
-
- for _, r in ipairs(results) do
- col_suite = math.max(col_suite, #r.suite)
- local cases_str = format_number(r.cases_passed) .. "/" .. format_number(r.cases_total)
- col_cases = math.max(col_cases, #cases_str)
- local asserts_str = format_number(r.asserts_passed) .. "/" .. format_number(r.asserts_total)
- col_asserts = math.max(col_asserts, #asserts_str)
- col_time = math.max(col_time, #format_time(r.elapsed_seconds))
- local status_str = r.passed and "SUCCESS" or "FAILED"
- col_status = math.max(col_status, #status_str)
-
- total_cases_passed = total_cases_passed + r.cases_passed
- total_cases_total = total_cases_total + r.cases_total
- total_asserts_passed = total_asserts_passed + r.asserts_passed
- total_asserts_total = total_asserts_total + r.asserts_total
- total_elapsed = total_elapsed + r.elapsed_seconds
- end
-
- -- Account for totals row in column widths
- col_suite = math.max(col_suite, #("Total"))
- col_cases = math.max(col_cases, #(format_number(total_cases_passed) .. "/" .. format_number(total_cases_total)))
- col_asserts = math.max(col_asserts, #(format_number(total_asserts_passed) .. "/" .. format_number(total_asserts_total)))
- col_time = math.max(col_time, #format_time(total_elapsed))
-
- -- Add padding (1 space each side)
- col_suite = col_suite + 2
- col_cases = col_cases + 2
- col_asserts = col_asserts + 2
- col_time = col_time + 2
- col_status = col_status + 2
-
- -- Build horizontal border segments
- local h_suite = string.rep("-", col_suite)
- local h_cases = string.rep("-", col_cases)
- local h_asserts = string.rep("-", col_asserts)
- local h_time = string.rep("-", col_time)
- local h_status = string.rep("-", col_status)
-
- local top = "+" .. h_suite .. "+" .. h_cases .. "+" .. h_asserts .. "+" .. h_time .. "+" .. h_status .. "+"
- local mid = "+" .. h_suite .. "+" .. h_cases .. "+" .. h_asserts .. "+" .. h_time .. "+" .. h_status .. "+"
- local bottom = "+" .. h_suite .. "+" .. h_cases .. "+" .. h_asserts .. "+" .. h_time .. "+" .. h_status .. "+"
- local vbar = "|"
-
- local header_msg = any_failed and "Some tests failed:" or "All tests passed:"
- printf("\n* %s\n", header_msg)
- printf(" %s\n", top)
- printf(" %s%s%s%s%s%s%s%s%s%s%s\n", vbar, center_str("Suite", col_suite), vbar, center_str("Cases", col_cases), vbar, center_str("Assertions", col_asserts), vbar, center_str("Time", col_time), vbar, center_str("Status", col_status), vbar)
-
- for _, r in ipairs(results) do
- printf(" %s\n", mid)
- local cases_str = format_number(r.cases_passed) .. "/" .. format_number(r.cases_total)
- local asserts_str = format_number(r.asserts_passed) .. "/" .. format_number(r.asserts_total)
- local time_str = format_time(r.elapsed_seconds)
- local status_str = r.passed and "SUCCESS" or "FAILED"
- printf(" %s%s%s%s%s%s%s%s%s%s%s\n", vbar, left_align_str(r.suite, col_suite), vbar, right_align_str(cases_str, col_cases), vbar, right_align_str(asserts_str, col_asserts), vbar, right_align_str(time_str, col_time), vbar, right_align_str(status_str, col_status), vbar)
- end
-
- -- Totals row
- if #results > 1 then
- local h_suite_eq = string.rep("=", col_suite)
- local h_cases_eq = string.rep("=", col_cases)
- local h_asserts_eq = string.rep("=", col_asserts)
- local h_time_eq = string.rep("=", col_time)
- local h_status_eq = string.rep("=", col_status)
- local totals_sep = "+" .. h_suite_eq .. "+" .. h_cases_eq .. "+" .. h_asserts_eq .. "+" .. h_time_eq .. "+" .. h_status_eq .. "+"
- printf(" %s\n", totals_sep)
-
- local total_cases_str = format_number(total_cases_passed) .. "/" .. format_number(total_cases_total)
- local total_asserts_str = format_number(total_asserts_passed) .. "/" .. format_number(total_asserts_total)
- local total_time_str = format_time(total_elapsed)
- local total_status_str = any_failed and "FAILED" or "SUCCESS"
- printf(" %s%s%s%s%s%s%s%s%s%s%s\n", vbar, left_align_str("Total", col_suite), vbar, right_align_str(total_cases_str, col_cases), vbar, right_align_str(total_asserts_str, col_asserts), vbar, right_align_str(total_time_str, col_time), vbar, right_align_str(total_status_str, col_status), vbar)
- end
-
- printf(" %s\n", bottom)
- end
-
- -- Print list of individual failing tests
- if any_failed then
- printf("\n Failures:\n")
- for _, r in ipairs(results) do
- if #r.failures > 0 then
- printf(" -- %s --\n", r.suite)
- for _, f in ipairs(r.failures) do
- printf(" FAILED: %s (%s:%d)\n", f.name, f.file, f.line)
- end
- elseif not r.passed then
- printf(" -- %s --\n", r.suite)
- printf(" (test binary exited with error, no failure details available)\n")
- end
- end
- end
-
- if any_failed then
- raise("one or more test suites failed")
- end
+ import("scripts.test")
+ test()
end)