Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions devel/0303.md
Original file line number Diff line number Diff line change
@@ -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`
171 changes: 85 additions & 86 deletions xmake.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 ]] ..
Expand All @@ -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
Expand Down Expand Up @@ -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")
Expand All @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
8 changes: 5 additions & 3 deletions xmake/tests.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -167,4 +169,4 @@ function add_target_integration_test(filepath, INSTALL_DIR, RUN_ENVS)
end
end)
end
end
end
Loading