Files
crossdesk/apps/desktop/xmake/rules/slint.lua
T

142 lines
6.6 KiB
Lua

rule("slint")
set_extensions(".slint")
after_load(function(target)
-- Consumers inline generated C++ bindings. They must not check header
-- timestamps or compile until generation finishes, otherwise one build
-- can link the new UI layout with callers compiled against the old one.
target:set("policy", "build.fence", true)
local outputdir = path.join(target:autogendir(), "rules", "slint")
target:add("includedirs", outputdir, {public = true})
local sourcebatch = target:sourcebatches()["slint"]
local outputs = {}
for _, sourcefile in ipairs(sourcebatch and sourcebatch.sourcefiles or {}) do
local header = path.join(outputdir, path.basename(sourcefile) .. ".h")
local sources = {}
-- Slint supports moving definitions out of the header and splitting
-- them into independently compiled translation units.
for index = 1, 4 do
local cppfile = path.join(outputdir,
path.basename(sourcefile) .. "_" .. index .. ".cpp")
table.insert(sources, cppfile)
target:add("files", cppfile, {always_added = true})
end
outputs[sourcefile] = {header = header, sources = sources}
end
target:data_set("slint.outputs", outputs)
end)
on_config(function(target)
if target:rule("c++.build") then
local cpp_rule = target:rule("c++.build"):clone()
cpp_rule:add("deps", "slint", {order = true})
target:rule_add(cpp_rule)
end
end)
before_build_file(function(target, sourcefile, opt)
import("core.project.depend")
import("core.project.project")
import("core.tools.gcc.parse_deps", {alias = "parse_deps"})
import("utils.progress")
local package = assert(target:pkg("slint"), "the slint package is required by this target")
local compiler = path.join(package:installdir(), "bin",
is_host("windows") and "slint-compiler.exe" or "slint-compiler")
local outputs = target:data("slint.outputs")[sourcefile]
local depfile = outputs.header .. ".d"
-- Slint writes the -o path literally into each generated #include.
-- The custom UI uses fixed light backgrounds. Keep default text and
-- standard widgets light as well, regardless of the system theme.
local argv = {sourcefile, "-f", "cpp", "-o", path.absolute(outputs.header),
"--depfile", depfile, "--style", "fluent-light",
"--embed-resources=embed-files", "--cpp-namespace", "crossdesk::ui"}
local missing_output = not os.isfile(outputs.header)
for _, cppfile in ipairs(outputs.sources) do
table.insert(argv, "--cpp-file")
table.insert(argv, path.absolute(cppfile))
missing_output = missing_output or not os.isfile(cppfile)
end
local function dependencies()
local files = {sourcefile, compiler}
if os.isfile(depfile) then
-- Track imported components AND embedded images, including
-- resources outside the UI directory, using the compiler output.
table.join2(files, parse_deps(io.readfile(depfile, {continuation = "\\"})))
end
files = table.unique(files)
table.sort(files)
return files
end
local function input_hashes(files)
-- Rapid edits can share an mtime with the last generation too.
local hashes = {}
for _, file in ipairs(files) do
if file ~= compiler then
table.insert(hashes, os.isfile(file) and hash.sha256(file) or "missing")
end
end
return hashes
end
local function invalidate_consumers(changed_files)
for _, consumer in pairs(project.targets()) do
local uses_target = consumer == target
for _, dependency in ipairs(consumer:orderdeps()) do
uses_target = uses_target or dependency == target
end
if uses_target then
for _, dependfile in ipairs(consumer:dependfiles()) do
local info = depend.load(dependfile, {target = consumer})
for _, file in ipairs(info and info.files or {}) do
if changed_files[path.absolute(file)] then
os.tryrm(dependfile)
break
end
end
end
-- Archiving/linking uses the same coarse mtime comparison.
if consumer:targetfile() then
os.tryrm(consumer:dependfile())
end
end
end
end
local dependfile = target:dependfile(outputs.header)
local previous = depend.load(dependfile) or {}
local files = dependencies()
local depopts = {dependfile = dependfile,
values = {argv, input_hashes(files)}, files = files,
changed = target:is_rebuilt() or missing_output or not os.isfile(depfile)}
-- Slint preserves timestamps of unchanged outputs. Use the dependency
-- cache's successful-generation time, not the oldest output's time.
depend.on_changed(function()
progress.show(opt.progress, "${color.build.object}generating.slint %s", sourcefile)
os.mkdir(path.directory(outputs.header))
os.vrunv(compiler, argv)
-- Xmake compares mtimes in whole seconds. A generated header can
-- change in the same second as a consumer's last compilation, so
-- ordering alone cannot prevent mixing incompatible UI bindings.
local generated, changed_files = {}, {}
for _, file in ipairs(table.join({outputs.header}, outputs.sources)) do
local absolute = path.absolute(file)
generated[absolute] = hash.sha256(absolute)
if generated[absolute] ~= (previous.generated or {})[absolute] then
changed_files[absolute] = true
end
end
if not table.empty(changed_files) then
invalidate_consumers(changed_files)
end
-- Save the freshly discovered dependencies on the first build too.
depopts.files = dependencies()
depopts.values = {argv, input_hashes(depopts.files)}
return {generated = generated}
end, depopts)
end)
rule_end()