diff --git a/devel/0303.md b/devel/0303.md new file mode 100644 index 0000000000..85480fce94 --- /dev/null +++ b/devel/0303.md @@ -0,0 +1,55 @@ +# [0133] 优化 xmake.lua 避免不必要的重新编译 + +## 1 相关文档 +- [dddd.md](dddd.md) - 任务文档模板 + +## 2 任务相关的代码文件 +- `xmake.lua` +- `xmake/vars.lua` +- `xmake/stem.lua` + +## 3 如何测试 + +### 3.1 确定性测试(单元测试) +```bash +xmake b stem +xmake b stem # 第二次构建不应该重新编译任何文件 +``` + +### 3.2 非确定性测试(文档验证) +```bash +# 检查 build/include/QWKCore/ 下的头文件时间戳是否在两次构建之间被修改 +ls -la build/include/QWKCore/windowagentbase.h +xmake b stem +ls -la build/include/QWKCore/windowagentbase.h +``` + +## 4 如何提交 + +提交前执行以下最少步骤: + +```bash +xmake b stem +xmake b stem # 确认第二次构建没有重新编译 +``` + +## 5 What +优化 xmake.lua,解决 `xmake b stem` 每次都会重新编译部分文件的问题。 + +1. 修复 QWKCore/QWKWidgets 的 `before_build` 中 `os.vcp` 无条件更新头文件时间戳的问题 +2. 移除全局和 target 级别重复的 `add_configfiles("src/System/config.h.xmake")` +3. 将 `before_build` 中动态添加的 `forceincludes` 和 `includedirs` 移至 `on_load` + +## 6 Why +每次构建都重新编译部分文件(如 `src/Plugins/Qt/*.cpp`、`src/Scheme/L5/init_glue_l5.cpp`、`src/System/Boot/init_texmacs.cpp` 等),严重影响开发效率。 + +根因分析: +- QWKCore/QWKWidgets 的 `before_build` 使用 `os.vcp` 复制头文件,`os.vcp` 会无条件更新目标文件时间戳,导致依赖这些头文件的所有源文件被重新编译 +- `libmogan` target 内部和全局作用域各有一份 `add_configfiles("src/System/config.h.xmake")`,导致 config.h 被生成两次 +- `before_build` 中通过 `target:add()` 动态添加编译标志,属于不良实践,可能引发编译缓存失效 + +## 7 How +1. 将 `os.vcp` 替换为自定义的 `safe_copy` 函数:仅在目标文件不存在或内容变化时才写入,避免更新时间戳 +2. 移除全局作用域的 `add_configfiles("src/System/config.h.xmake")`,因为 `libmogan` target 内部已包含完整的配置生成逻辑 +3. 将 QWKCore `before_build` 中的 `target:add("includedirs", ...)` 移至 `on_load` +4. 将 `libmogan` 和 `stem` 的 `before_build` 中的 `target:add("forceincludes", ...)` 移至 `on_load` diff --git a/xmake.lua b/xmake.lua index cb4a4628b4..98568eb76a 100644 --- a/xmake.lua +++ b/xmake.lua @@ -60,44 +60,6 @@ option_end() set_config("debug_with_timestamp", true) --- Generate build/config.h from template -add_configfiles("src/System/config.h.xmake", { - filename = "config.h", - variables = { - SIZEOF_VOID_P = 8, - VERSION = XMACS_VERSION, - USE_FREETYPE = 1, - USE_ICONV = true, - USE_PLUGIN_GS = true, - USE_PLUGIN_BIBTEX = true, - USE_PLUGIN_TEX = true, - USE_PLUGIN_ISPELL = true, - USE_PLUGIN_PDF = true, - USE_PLUGIN_SPARKLE = false, - USE_PLUGIN_HTML = true, - OS_MACOS = is_plat("macosx"), - MACOSX_EXTENSIONS = is_plat("macosx"), - QTTEXMACS = true, - SANITY_CHECKS = true, - OS_MINGW = is_plat("mingw"), - OS_GNU_LINUX = is_plat("linux"), - OS_WIN = is_plat("windows"), - OS_WASM = is_plat("wasm"), - NOMINMAX = true, - QTPIPES = true, - USE_QT_PRINTER = true, - TM_DYNAMIC_LINKING = true, - USE_FONTCONFIG = true, - PDFHUMMUS_NO_TIFF = true, - USE_MUPDF_RENDERER = has_config("mupdf"), - USE_STARTUP_TAB = has_config("startup_tab"), - USE_TEXT_TOOLBAR = has_config("text_toolbar"), - USE_TUTORIAL = enable_tutorial, - IS_COMMUNITY = has_config("is_community"), - DEBUG_WITH_TIMESTAMP = has_config("debug_with_timestamp"), - } -}) - -- because this cpp project use variant length arrays which is not supported by -- msvc, this project will not support windows env. -- because some package is not ported to cygwin env, this project will not @@ -355,18 +317,44 @@ target("QWKCore") add_frameworks("QtCore", "QtGui", "QtWidgets") end + on_load(function (target) + local private_paths = {} + local qt_package = get_config("qt") + local qt_version = get_config("qt_sdkver") + + local modules = {"QtCore", "QtGui"} + for _, module in ipairs(modules) do + local headers_path = "" + if is_plat("macosx") then + headers_path= path.join(qt_package, "lib", module .. ".framework", "Headers") + table.insert(private_paths, path.join(headers_path, qt_version, module, "private")) + table.insert(private_paths, path.join(headers_path, qt_version, module)) + table.insert(private_paths, path.join(headers_path, qt_version)) + else + headers_path= path.join(qt_package, "include") + table.insert(private_paths, path.join(headers_path, module, qt_version, module, "private")) + table.insert(private_paths, path.join(headers_path, module, qt_version, module)) + table.insert(private_paths, path.join(headers_path, module, qt_version)) + end + end + if is_plat("windows") then + table.insert(private_paths, path.join(qt_package, "mkspecs", "win32-msvc")) + end + target:add("includedirs", private_paths, {public = true}) + end) + -- Generate config header before build before_build(function (target) -- Create build directories os.mkdir("$(buildir)/include/QWKCore") os.mkdir("$(buildir)/include/QWKCore/private") - + -- Generate qwkconfig.h local config_content = [[ #ifndef QWKCONFIG_H #define QWKCONFIG_H -#define QWINDOWKIT_ENABLE_QT_WINDOW_CONTEXT ]] .. +#define QWINDOWKIT_ENABLE_QT_WINDOW_CONTEXT ]] .. "-1" .. [[ #define QWINDOWKIT_ENABLE_STYLE_AGENT ]] .. @@ -386,43 +374,35 @@ target("QWKCore") if existing_content ~= config_content then io.writefile(config_path, config_content) end - + -- Copy header files without bumping timestamps when unchanged - os.vcp("3rdparty/qwindowkitty/src/core/*.h", "$(buildir)/include/QWKCore/") - os.vcp("3rdparty/qwindowkitty/src/core/*_p.h", "$(buildir)/include/QWKCore/private/") - os.vcp("3rdparty/qwindowkitty/src/core/contexts/*_p.h", "$(buildir)/include/QWKCore/private/") - os.vcp("3rdparty/qwindowkitty/src/core/contexts/*.h", "$(buildir)/include/QWKCore/private/") - os.vcp("3rdparty/qwindowkitty/src/core/kernel/*_p.h", "$(buildir)/include/QWKCore/private/") - os.vcp("3rdparty/qwindowkitty/src/core/shared/*_p.h", "$(buildir)/include/QWKCore/private/") - - if has_config("style_agent") then - os.vcp("3rdparty/qwindowkitty/src/core/style/*_p.h", "$(buildir)/include/QWKCore/private/") - os.vcp("3rdparty/qwindowkitty/src/core/style/styleagent.h", "$(buildir)/include/QWKCore/styleagent.h") + local function safe_cp(src, dst) + local src_content = io.readfile(src) + local dst_content = nil + if os.isfile(dst) then + dst_content = io.readfile(dst) + end + if src_content ~= dst_content then + os.cp(src, dst) + end end - - local private_paths = {} - local qt_package = get_config("qt") - local qt_version = get_config("qt_sdkver") - - local modules = {"QtCore", "QtGui"} - for _, module in ipairs(modules) do - local headers_path = "" - if is_plat("macosx") then - headers_path= path.join(qt_package, "lib", module .. ".framework", "Headers") - table.insert(private_paths, path.join(headers_path, qt_version, module, "private")) - table.insert(private_paths, path.join(headers_path, qt_version, module)) - table.insert(private_paths, path.join(headers_path, qt_version)) - else - headers_path= path.join(qt_package, "include") - table.insert(private_paths, path.join(headers_path, module, qt_version, module, "private")) - table.insert(private_paths, path.join(headers_path, module, qt_version, module)) - table.insert(private_paths, path.join(headers_path, module, qt_version)) + local function safe_vcp(src_pattern, dst_dir) + for _, filepath in ipairs(os.files(src_pattern)) do + local dst = path.join(dst_dir, path.filename(filepath)) + safe_cp(filepath, dst) end end - if is_plat("windows") then - table.insert(private_paths, path.join(qt_package, "mkspecs", "win32-msvc")) + safe_vcp("3rdparty/qwindowkitty/src/core/*.h", "$(buildir)/include/QWKCore/") + safe_vcp("3rdparty/qwindowkitty/src/core/*_p.h", "$(buildir)/include/QWKCore/private/") + safe_vcp("3rdparty/qwindowkitty/src/core/contexts/*_p.h", "$(buildir)/include/QWKCore/private/") + safe_vcp("3rdparty/qwindowkitty/src/core/contexts/*.h", "$(buildir)/include/QWKCore/private/") + safe_vcp("3rdparty/qwindowkitty/src/core/kernel/*_p.h", "$(buildir)/include/QWKCore/private/") + safe_vcp("3rdparty/qwindowkitty/src/core/shared/*_p.h", "$(buildir)/include/QWKCore/private/") + + if has_config("style_agent") then + safe_vcp("3rdparty/qwindowkitty/src/core/style/*_p.h", "$(buildir)/include/QWKCore/private/") + safe_vcp("3rdparty/qwindowkitty/src/core/style/styleagent.h", "$(buildir)/include/QWKCore/styleagent.h") end - target:add("includedirs", private_paths, {public = true}) end) -- Include directories @@ -531,13 +511,7 @@ target("QWKWidgets") -- Enable RCC generation for Qt resources used by this target add_rules("qt.qrc") - -- Generate config header and copy headers before build - before_build(function (target) - os.mkdir("$(buildir)/include/QWKWidgets") - os.mkdir("$(buildir)/include/QWKWidgets/ui/widgetframe") - os.vcp("3rdparty/qwindowkitty/src/widgets/*.h", "$(buildir)/include/QWKWidgets/") - os.vcp("3rdparty/qwindowkitty/src/ui/widgetframe/*.h", "$(buildir)/include/QWKWidgets/ui/widgetframe/") - + on_load(function (target) local private_paths = {} local qt_package = get_config("qt") local qt_version = get_config("qt_sdkver") @@ -563,6 +537,30 @@ target("QWKWidgets") target:add("includedirs", private_paths, {public = true}) end) + -- Generate config header and copy headers before build + before_build(function (target) + os.mkdir("$(buildir)/include/QWKWidgets") + os.mkdir("$(buildir)/include/QWKWidgets/ui/widgetframe") + local function safe_cp(src, dst) + local src_content = io.readfile(src) + local dst_content = nil + if os.isfile(dst) then + dst_content = io.readfile(dst) + end + if src_content ~= dst_content then + os.cp(src, dst) + end + end + local function safe_vcp(src_pattern, dst_dir) + for _, filepath in ipairs(os.files(src_pattern)) do + local dst = path.join(dst_dir, path.filename(filepath)) + safe_cp(filepath, dst) + end + end + safe_vcp("3rdparty/qwindowkitty/src/widgets/*.h", "$(buildir)/include/QWKWidgets/") + safe_vcp("3rdparty/qwindowkitty/src/ui/widgetframe/*.h", "$(buildir)/include/QWKWidgets/ui/widgetframe/") + end) + -- Include directories add_includedirs("$(buildir)/include", {public = true}) add_includedirs("3rdparty/qwindowkitty/src/widgets", "3rdparty/qwindowkitty/src") @@ -649,7 +647,7 @@ target("libmogan") do -- * https://github.com/xmake-io/xmake/issues/320 -- * https://github.com/xmake-io/xmake/issues/342 --------------------------------------------------------------------------- - set_configdir("src/System") + set_configdir("$(buildir)") -- check for dl library -- configvar_check_cxxfuncs("TM_DYNAMIC_LINKING","dlopen") configvar_check_cxxincludes("HAVE_INTTYPES_H", "inttypes.h") @@ -721,6 +719,7 @@ target("libmogan") do --------------------------------------------------------------------------- -- add source and header files --------------------------------------------------------------------------- + add_includedirs("$(buildir)", {public = true}) add_includedirs(moe_includedirs) add_includedirs({ "src/Data/Convert", @@ -849,9 +848,9 @@ target("libmogan") do end add_mxflags("-fno-objc-arc") - before_build(function (target) - target:add("forceincludes", path.absolute("src/System/config.h")) - target:add("forceincludes", path.absolute("src/System/tm_configure.hpp")) + on_load(function (target) + target:add("forceincludes", path.absolute("$(buildir)/config.h")) + target:add("forceincludes", path.absolute("$(buildir)/tm_configure.hpp")) end) end @@ -1082,9 +1081,9 @@ target("stem") do end end) - before_build(function (target) - target:add("forceincludes", path.absolute("src/System/config.h")) - target:add("forceincludes", path.absolute("src/System/tm_configure.hpp")) + on_load(function (target) + target:add("forceincludes", path.absolute("$(buildir)/config.h")) + target:add("forceincludes", path.absolute("$(buildir)/tm_configure.hpp")) end) -- After install callback for Linux to rename MIME icon files diff --git a/xmake/tests.lua b/xmake/tests.lua index a26a0b7dcc..1d9d8ddb45 100644 --- a/xmake/tests.lua +++ b/xmake/tests.lua @@ -40,8 +40,9 @@ function add_target_cpp_test(filepath, dep1, dep2) add_files("tests/Base/base.cpp") add_files(filepath) add_files(filepath, {rules = "qt.moc"}) - before_build(function (target) + on_load(function (target) target:add("forceincludes", path.absolute("$(buildir)/config.h")) + target:add("forceincludes", path.absolute("$(buildir)/tm_configure.hpp")) end) if is_plat("wasm") then @@ -89,8 +90,9 @@ function add_target_cpp_bench(filepath, dep) add_files("tests/Base/base.cpp") add_files(filepath) add_files(filepath, {rules = "qt.moc"}) - before_build(function (target) + on_load(function (target) target:add("forceincludes", path.absolute("$(buildir)/config.h")) + target:add("forceincludes", path.absolute("$(buildir)/tm_configure.hpp")) end) end end @@ -167,4 +169,4 @@ function add_target_integration_test(filepath, INSTALL_DIR, RUN_ENVS) end end) end -end \ No newline at end of file +end