diff --git a/devel/4453.md b/devel/4453.md new file mode 100644 index 0000000000..1eeb222ed6 --- /dev/null +++ b/devel/4453.md @@ -0,0 +1,50 @@ +# 4453 带路径包名的 use-package 找不到 $TEXMACS_HOME_PATH 下的用户包 + +## 背景 + +用户包放在 `$TEXMACS_HOME_PATH/packages/TST-core/` 下,自己用 +`` 级联加载子包。 +GNU TeXmacs 2.1.5 与 2026_2.6 正常,2026_3.2 起子包不再被加载, +宏原样显示成 `⟨big-unicode-table|...⟩`。把三个包一起加进文档的 +style/package 列表则正常,只有包体里的级联失效。 + +## 定位 + +1200(`17e6bab45`)给 `exec_use_package` 加了带路径包名的直查快路径, +包名含 `/` 时只查 `$TEXMACS_PATH/packages` 和 +`$TEXMACS_PATH/plugins/<首段>/packages`,不查 `$TEXMACS_HOME_PATH`。 +1200 之前是拿 `$TEXMACS_STYLE_PATH` 去 resolve,那个变量由 +`search_sub_dirs (style_root | package_root)` 生成,含 +`$TEXMACS_HOME_PATH/packages` 及其全部子目录,所以用户包能找到。 + +手动加进文档包列表之所以正常:文档自己的列表先过 `preprocess_style` +(`new_style.cpp:118`)解析成绝对路径再交给 `exec`,而 +`url::operator*` 遇到 rooted 右操作数直接返回右操作数 +(`lolly/System/Classes/url.cpp:645`),于是绝对路径仍能命中。 +包体里的相对名没有这一步兜底。 + +## 改动 + +`resolve_dotted_package` 改为接收 `base_file_name`,在 +`$TEXMACS_PACKAGE_ROOT`、`$TEXMACS_STYLE_ROOT`、文档所在目录 +(本地文档沿祖先目录上溯)里依次直查。两个根变量条目数是 +「2 + 插件个数」量级,不是递归展开全部子目录的 `$TEXMACS_STYLE_PATH`, +1200 的性能优化基本保留;`plugin_path` 的 base 是 +`$TEXMACS_HOME_PATH:$TEXMACS_PATH`,已覆盖原来的插件规则。 + +`resolve_pack_in` 增加 or-url 分支逐根遍历,让「`.stem` 优先 `.ts`」 +只在同一个根内生效,靠前的根(用户包)才能覆盖靠后的根(内置包)。 + +定位一个内置包的 stat 次数:1200 之前 8 次,1200 为 1 次,现在 3 次。 + +**涉及文件**:`src/Typeset/Env/env_exec.cpp` + +## 回归测试 + +`tests/Typeset/Env/use_package_resolve_test.cpp`,在临时 +`$TEXMACS_HOME_PATH` 下铺出 issue 里的两层包结构,只 use-package +入口包,断言子包的宏确实进了环境。另覆盖文档相对定位、 +home 插件目录、style 根、两个根的先后顺序、裸包名不回归、 +找不到的包不崩。改动前 4 passed / 5 failed,改动后 9 passed。 + +**涉及文件**:`tests/Typeset/Env/use_package_resolve_test.cpp` diff --git a/src/Typeset/Env/env_exec.cpp b/src/Typeset/Env/env_exec.cpp index 8310a060ce..24f9c684f2 100644 --- a/src/Typeset/Env/env_exec.cpp +++ b/src/Typeset/Env/env_exec.cpp @@ -1208,13 +1208,19 @@ filter_style (tree t) { } /** - * @brief 在指定根目录下按相对路径定位包文件,优先 .stem 后 .ts - * @param root 包根目录 - * @param pi 带路径的包名(如 a/b/c),可显式带 .ts/.stem 后缀 - * @return 解析到的文件 url,未找到返回 url_none + * @brief 在根目录下定位包文件,优先 .stem 后 .ts + * @param root 包根目录,可以是 or-url,按先后顺序逐个尝试 + * @param pi 带路径的包名,可显式带 .ts/.stem 后缀 */ static url resolve_pack_in (url root, string pi) { + // 逐根尝试而不是把 or-url 整个交给 resolve,让「.stem 优先 .ts」只在同一个 + // 根内生效,靠前的根里的 .ts 才能覆盖靠后的根里的同名 .stem + if (is_or (root)) { + url name= resolve_pack_in (root[1], pi); + if (!is_none (name)) return name; + return resolve_pack_in (root[2], pi); + } if (ends (pi, ".ts") || ends (pi, ".stem")) return resolve (root * pi); url name= resolve (root * (pi * string (".stem"))); if (is_none (name)) name= resolve (root * (pi * string (".ts"))); @@ -1222,18 +1228,26 @@ resolve_pack_in (url root, string pi) { } /** - * @brief 带路径包名(含 /)的直查解析,避免 resolve 对 $TEXMACS_STYLE_PATH - * 全部子目录逐一扫描 - * @note 依次尝试 $TEXMACS_PATH/packages 与 - * $TEXMACS_PATH/plugins/<首段>/packages + * @brief 带路径包名的搜索根:package 根、style 根、文档所在目录 + * @note 两个根变量由 init_env_vars 设置,条目数是「2 + 插件个数」量级, + * 不是 $TEXMACS_STYLE_PATH 那种递归展开出全部子目录的路径 + */ +static url +package_search_roots (url base_file_name) { + url roots= url ("$TEXMACS_PACKAGE_ROOT") | url ("$TEXMACS_STYLE_ROOT"); + if (is_none (base_file_name)) return roots; + // 本地文档沿祖先目录上溯,与不带路径包名的分支一致 + if (is_rooted (base_file_name, "default")) + return roots | ::expand (head (base_file_name) * url_ancestor ()); + return roots | head (base_file_name); +} + +/** + * @brief 带路径包名(含 /)的直查解析,避免逐一扫描 $TEXMACS_STYLE_PATH */ static url -resolve_dotted_package (string pi) { - url name= resolve_pack_in (url ("$TEXMACS_PATH/packages"), pi); - if (!is_none (name)) return name; - int pos= search_forwards ("/", 0, pi); - return resolve_pack_in ( - url ("$TEXMACS_PATH/plugins") * pi (0, pos) * "packages", pi); +resolve_dotted_package (string pi, url base_file_name) { + return resolve_pack_in (package_search_roots (base_file_name), pi); } tree @@ -1245,7 +1259,7 @@ edit_env_rep::exec_use_package (tree t) { string pi = as_string (t[i]); string task= "use-package " * pi; bench_start (task); - if (occurs ("/", pi)) name= resolve_dotted_package (pi); + if (occurs ("/", pi)) name= resolve_dotted_package (pi, base_file_name); else { if (is_rooted (base_file_name, "default")) styp= styp | ::expand (head (base_file_name) * url_ancestor ()); diff --git a/tests/Typeset/Env/use_package_resolve_test.cpp b/tests/Typeset/Env/use_package_resolve_test.cpp new file mode 100644 index 0000000000..571056e520 --- /dev/null +++ b/tests/Typeset/Env/use_package_resolve_test.cpp @@ -0,0 +1,221 @@ +/****************************************************************************** + * MODULE : use_package_resolve_test.cpp + * DESCRIPTION: 带路径包名 的解析回归测试 + * COPYRIGHT : (C) 2026 Darcy Shen + ******************************************************************************* + * This software falls under the GNU general public license version 3 or later. + * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE + * in the root directory or . + ******************************************************************************/ + +#include "Metafont/load_tex.hpp" +#include "base.hpp" +#include "data_cache.hpp" +#include "env.hpp" +#include "file.hpp" +#include "sys_utils.hpp" +#include "tm_sys_utils.hpp" +#include +#include +#include + +using namespace moebius; // USE_PACKAGE +using moebius::drd::init_std_drd; +using moebius::drd::std_drd; + +// edit_env 按引用持有 drd 和各 hashmap,必须与 env 同生命周期 +struct test_env { + drd_info drd; + hashmap lref, gref, laux, gaux, latt, gatt; + edit_env env; + + test_env (url base_file_name) + : drd ("test", std_drd), + env (drd, base_file_name, lref, gref, laux, gaux, latt, gatt) {} +}; + +static url the_home= url_none (); +static url the_doc = url_none (); + +static void +write_pack (url file, string body) { + make_dir (head (file)); + string doc= string ("\n\n\n\n<\\body>\n ") * + body * string ("\n\n"); + QVERIFY (!save_string (file, doc, false)); +} + +static void +set_roots (url package_root, url style_root) { + set_env ("TEXMACS_PACKAGE_ROOT", as_string (package_root)); + set_env ("TEXMACS_STYLE_ROOT", as_string (style_root)); +} + +class TestUsePackageResolve : public QObject { + Q_OBJECT + +private slots: + void initTestCase (); + void init (); + void cleanupTestCase (); + + void nested_user_package (); + void document_relative_package (); + void home_plugin_package (); + void style_root_package (); + void package_root_wins_over_style_root (); + void bare_package_name_still_works (); + void missing_package_is_silent (); +}; + +void +TestUsePackageResolve::initTestCase () { + // edit_env 的构造会走到 update_font,字体栈没起来会抛异常, + // 所以先初始化 TeX/字体环境,之后再改写 TEXMACS_HOME_PATH + init_lolly (); + init_texmacs_home_path (); + cache_initialize (); + init_tex (); + init_std_drd (); + + the_home= url_temp (); + the_doc = url_temp (); + + // issue #4453 复现用的用户包:入口包在 TST-core/,两个子包在 + // TST-core/unicode/ + url packages= the_home * "packages"; + write_pack (packages * "TST-core" * "unicode" * "unicode-figures.ts", + ">"); + write_pack (packages * "TST-core" * "unicode" * "unicode-environments.ts", + ">"); + write_pack (packages * "TST-core" * "unicode-core.ts", + "\n" + " \n" + " >"); + + // 不带路径的包名,走未改动的 $TEXMACS_STYLE_PATH 分支 + write_pack (packages * "solo.ts", ">"); + + // package 根与 style 根同名同路径,用于验证根的先后顺序 + write_pack (packages * "over" * "pack.ts", + ">"); + write_pack (the_home * "styles" * "over" * "pack.ts", + ">"); + write_pack (the_home * "styles" * "TST-style" * "style-pack.ts", + ">"); + + // $TEXMACS_HOME_PATH 下的插件包 + write_pack (the_home * "plugins" * "tstplug" * "packages" * "tstplug" * + "deep" * "plug-pack.ts", + ">"); + + // 文档旁边的包,文档本身放在下一层,同时覆盖祖先目录上溯 + write_pack (the_doc * "relative" * "rel-pack.ts", + ">"); + + set_env ("TEXMACS_HOME_PATH", as_string (the_home)); +} + +void +TestUsePackageResolve::init () { + // 每个用例自己声明搜索根,避免相互影响 + set_env ("TEXMACS_PACKAGE_ROOT", ""); + set_env ("TEXMACS_STYLE_ROOT", ""); + set_env ("TEXMACS_STYLE_PATH", ""); +} + +void +TestUsePackageResolve::cleanupTestCase () { + if (!is_none (the_home)) rmdir (the_home); + if (!is_none (the_doc)) rmdir (the_doc); +} + +// issue #4453:只加 unicode-core,两个子包必须级联加载 +void +TestUsePackageResolve::nested_user_package () { + set_roots (url ("$TEXMACS_HOME_PATH/packages"), + url ("$TEXMACS_HOME_PATH/styles")); + + test_env te (url ("$PWD/none")); + te.env->exec (tree (USE_PACKAGE, string ("TST-core/unicode-core"))); + + QVERIFY (te.env->provides ("unicode-core-marker")); + QVERIFY (te.env->provides ("unicode-figures-marker")); + QVERIFY (te.env->provides ("unicode-environments-marker")); +} + +// 文档在 /sub/,包在 /relative/,靠祖先目录上溯找到 +void +TestUsePackageResolve::document_relative_package () { + set_roots (url ("$TEXMACS_HOME_PATH/packages"), + url ("$TEXMACS_HOME_PATH/styles")); + + test_env te (the_doc * "sub" * "doc.tm"); + te.env->exec (tree (USE_PACKAGE, string ("relative/rel-pack"))); + + QVERIFY (te.env->provides ("rel-pack-marker")); +} + +// $TEXMACS_HOME_PATH/plugins/<插件>/packages 下的包 +void +TestUsePackageResolve::home_plugin_package () { + set_roots (url ("$TEXMACS_HOME_PATH/packages") | + url ("$TEXMACS_HOME_PATH/plugins/tstplug/packages"), + url ("$TEXMACS_HOME_PATH/styles")); + + test_env te (url ("$PWD/none")); + te.env->exec (tree (USE_PACKAGE, string ("tstplug/deep/plug-pack"))); + + QVERIFY (te.env->provides ("plug-pack-marker")); +} + +// 带路径包名也要能落到 style 根上 +void +TestUsePackageResolve::style_root_package () { + set_roots (url ("$TEXMACS_HOME_PATH/packages"), + url ("$TEXMACS_HOME_PATH/styles")); + + test_env te (url ("$PWD/none")); + te.env->exec (tree (USE_PACKAGE, string ("TST-style/style-pack"))); + + QVERIFY (te.env->provides ("style-pack-marker")); +} + +// 同名包同时在两个根里时,靠前的根胜出 +void +TestUsePackageResolve::package_root_wins_over_style_root () { + set_roots (url ("$TEXMACS_HOME_PATH/packages"), + url ("$TEXMACS_HOME_PATH/styles")); + + test_env te (url ("$PWD/none")); + te.env->exec (tree (USE_PACKAGE, string ("over/pack"))); + + QVERIFY (te.env->provides ("over-from-packages")); + QVERIFY (!te.env->provides ("over-from-styles")); +} + +// 不含 / 的包名仍走 $TEXMACS_STYLE_PATH,防回归 +void +TestUsePackageResolve::bare_package_name_still_works () { + set_env ("TEXMACS_STYLE_PATH", as_string (the_home * "packages")); + + test_env te (url ("$PWD/none")); + te.env->exec (tree (USE_PACKAGE, string ("solo"))); + + QVERIFY (te.env->provides ("solo-marker")); +} + +// 找不到的包不崩、不定义任何东西 +void +TestUsePackageResolve::missing_package_is_silent () { + set_roots (url ("$TEXMACS_HOME_PATH/packages"), + url ("$TEXMACS_HOME_PATH/styles")); + + test_env te (url ("$PWD/none")); + te.env->exec (tree (USE_PACKAGE, string ("no/such/package"))); + + QVERIFY (!te.env->provides ("unicode-figures-marker")); +} + +QTEST_MAIN (TestUsePackageResolve) +#include "use_package_resolve_test.moc"