Skip to content

Commit 2dbb0f9

Browse files
committed
refactor(build): architecture-review pass — rsp path portability, one directive-fold owner, type/rule unification
Consolidated fixes from the three-lens pre-PR review (correctness / cross-platform / architecture): - #247 P0: escape_ninja_path emits generic (forward-slash) node names. rspfile_content copies node names verbatim into a response file that gcc/clang/GNU ar tokenize GNU-style, where backslash is an ESCAPE — obj\cli.o would arrive as objcli.o and break every Windows driver-style link. All Windows consumers accept forward slashes; POSIX output is byte-identical. - link/archive/shared rules: ONE link_rule emitter for both dialect branches (useRsp = separateLinker || is_windows); msvc rule text byte-identical to the previous hand-written stanzas. - prepare.cppm: markDirectiveTail/foldDirectiveTailIntoPrivateBuild — a single owner of 'which compile-visible channels a build.mcpp directive lands in', shared by the dep loop and the root call site (kills the #242 two-derivations shape the parallel tracks had reintroduced). Link/source/fingerprint residues stay at the call sites where they genuinely differ. - BuildConfig::includeDirsAfter aligned to filesystem::path (was string), deleting per-consumer conversions. - local_include_flags derives the NASM degradation from cu.source itself instead of a third parameter threaded by every caller. - Windows shell fallback: cd /d (cmd.exe cd does not switch drives). - docs: source= joins the cache-invalidation list; 0.0.100+ tags on the new directives/env rows; NASM -I degradation documented (en+zh).
1 parent 622ab61 commit 2dbb0f9

10 files changed

Lines changed: 165 additions & 146 deletions

File tree

docs/05-mcpp-toml.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,9 @@ macos_deployment_target = "14.0" # Minimum supported OS version for macOS arti
149149

150150
`include_dirs_after` (#249) lists header directories that are searched **after**
151151
the toolchain's system directories (emitted as `-idirafter` on GCC/Clang, as
152-
trailing `/I` under the MSVC dialect, which has no equivalent). Use it instead of
152+
trailing `/I` under the MSVC dialect, and as plain `-I` for NASM
153+
assembly units — neither has an equivalent, and neither has a system-header
154+
chain to protect). Use it instead of
153155
`include_dirs` when the directory is an extracted source-tarball root that
154156
contains files whose names collide with standard headers — e.g. ffmpeg's
155157
top-level `VERSION` file shadows libc++'s `<version>` on case-insensitive macOS

docs/07-build-mcpp.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ is ignored, so you can freely log diagnostics.
4949
| `mcpp:link-search=<dir>` | add a library search dir (`-L`; relative dirs resolve against the project root) |
5050
| `mcpp:cfg=<name>` | define `-D<name>` for both C and C++ |
5151
| `mcpp:generated=<path>` | add a generated source (relative to the project root) to the build |
52-
| `mcpp:source=<path>` | select a **pre-existing** source file into the build (absolute, or relative to the package root). Same downstream effect as `generated=`; use it for files the program *chose* (payload/vendored tree) rather than wrote — e.g. a per-target source selection over a large tarball |
53-
| `mcpp:include-dir=<dir>` | add a **private** include directory (`-I`) for this package's own TUs (absolute, or relative to the package root; normalized). Replaces the `cxxflag=-I` + `cflag=-I` double emission |
54-
| `mcpp:include-dir-after=<dir>` | like `include-dir`, but searched **after** the system directories (`-idirafter`) — for payload trees that shadow system headers |
52+
| `mcpp:source=<path>` *(0.0.100+)* | select a **pre-existing** source file into the build (absolute, or relative to the package root). Same downstream effect as `generated=`; use it for files the program *chose* (payload/vendored tree) rather than wrote — e.g. a per-target source selection over a large tarball |
53+
| `mcpp:include-dir=<dir>` *(0.0.100+)* | add a **private** include directory (`-I`) for this package's own TUs (absolute, or relative to the package root; normalized). Replaces the `cxxflag=-I` + `cflag=-I` double emission |
54+
| `mcpp:include-dir-after=<dir>` *(0.0.100+)* | like `include-dir`, but searched **after** the system directories (`-idirafter`) — for payload trees that shadow system headers |
5555
| `mcpp:rerun-if-changed=<path>` | re-run `build.mcpp` when this file changes |
5656
| `mcpp:rerun-if-env-changed=<VAR>` | re-run `build.mcpp` when this env var changes |
5757

@@ -111,16 +111,16 @@ The running program receives the build context as `MCPP_*` variables
111111
| Variable | Typed reader | Value |
112112
|---|---|---|
113113
| `MCPP_TARGET` | `mcpp::target()` | resolved canonical triple (the `--target` triple under cross; the host triple natively) |
114-
| `MCPP_TARGET_OS` | `mcpp::target_os()` | the target's OS segment (`linux`/`macos`/`windows`) — no need to hand-split `MCPP_TARGET` |
115-
| `MCPP_TARGET_ARCH` | `mcpp::target_arch()` | the target's arch segment (GNU spelling: `x86_64`, `aarch64`, …) |
116-
| `MCPP_TARGET_ENV` | `mcpp::target_env()` | the target's env segment (`gnu`/`musl`/`msvc`); empty string when the triple has none (macOS) |
114+
| `MCPP_TARGET_OS` *(0.0.100+)* | `mcpp::target_os()` | the target's OS segment (`linux`/`macos`/`windows`) — no need to hand-split `MCPP_TARGET` |
115+
| `MCPP_TARGET_ARCH` *(0.0.100+)* | `mcpp::target_arch()` | the target's arch segment (GNU spelling: `x86_64`, `aarch64`, …) |
116+
| `MCPP_TARGET_ENV` *(0.0.100+)* | `mcpp::target_env()` | the target's env segment (`gnu`/`musl`/`msvc`); empty string when the triple has none (macOS) |
117117
| `MCPP_HOST` | `mcpp::host()` | the host triple |
118118
| `MCPP_PROFILE` | `mcpp::profile()` | effective profile name (`dev`/`release`/…) |
119119
| `MCPP_OUT_DIR` | `mcpp::out_dir()` | a writable scratch/output dir owned by mcpp |
120120
| `MCPP_MANIFEST_DIR` | `mcpp::manifest_dir()` | the package root (= CWD) |
121121
| `MCPP_FEATURE_<NAME>` | `mcpp::has_feature("name")` | set to `1` per active feature (same `<NAME>` sanitization as the `MCPP_FEATURE_` compile macro) |
122122
| `MCPP_FEATURES` || comma-separated active feature list |
123-
| `MCPP_DEP_<NAME>_DIR` | `mcpp::dep_dir("name")` | the resolved install dir of each declared dependency (canonical **and** namespace-stripped name spellings; same `<NAME>` sanitization as `MCPP_FEATURE_`). Received by dependencies' build.mcpp **and** the root project's (the root runs after dependency resolution) |
123+
| `MCPP_DEP_<NAME>_DIR` | `mcpp::dep_dir("name")` | the resolved install dir of each declared dependency (canonical **and** namespace-stripped name spellings; same `<NAME>` sanitization as `MCPP_FEATURE_`). Received by dependencies' build.mcpp **and** the root project's (the root runs after dependency resolution, 0.0.100+) |
124124

125125
These values are folded into the re-run key **unconditionally** — changing the
126126
target, profile, or feature set re-runs the program without any
@@ -147,7 +147,7 @@ directives and re-runs only when something it depends on changed:
147147
- the toolchain,
148148
- any file you declared with `rerun-if-changed`,
149149
- any env var you declared with `rerun-if-env-changed`,
150-
- (or a `generated` output went missing).
150+
- (or a `generated` output / `source=` selection went missing).
151151

152152
So **declare your inputs**: if your program reads `config.h` or the `USE_FAST`
153153
variable, emit `mcpp:rerun-if-changed=config.h` / `mcpp:rerun-if-env-changed=USE_FAST`.

docs/zh/05-mcpp-toml.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,8 @@ macos_deployment_target = "14.0" # macOS 产物的最低支持系统版本(仅
141141
```
142142

143143
`include_dirs_after`(#249)列出**排在工具链系统目录之后**搜索的头文件目录
144-
(GCC/Clang 发射为 `-idirafter`;MSVC 方言无对应能力,退化为排在末尾的
145-
`/I`)。当目录是解压后的源码 tarball 根目录、且其中的文件名会与标准头冲突时,
144+
(GCC/Clang 发射为 `-idirafter`;MSVC 方言退化为排在末尾的 `/I`,NASM 汇编
145+
单元退化为普通 `-I`——两者都没有对应 flag,也都没有需要保护的系统头搜索链)。当目录是解压后的源码 tarball 根目录、且其中的文件名会与标准头冲突时,
146146
用它代替 `include_dirs` —— 例如 ffmpeg 根目录的 `VERSION` 文件在大小写不敏感
147147
的 macOS 文件系统上会把 libc++ 的 `<version>` 遮蔽(若该根目录挂在 `-I` 上)。
148148
使用 `include_dirs_after` 时系统头永远优先,而包自己的真实头文件

docs/zh/07-build-mcpp.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ mcpp build # 编译 + 运行 build.mcpp,然后构建工程
4646
| `mcpp:link-search=<dir>` | 增加库搜索目录(`-L`;相对路径按工程根目录解析) |
4747
| `mcpp:cfg=<name>` | 为 C 与 C++ 同时定义 `-D<name>` |
4848
| `mcpp:generated=<path>` | 把生成的源码(相对工程根目录)加入构建 |
49-
| `mcpp:source=<path>` | 把一份**既有**源文件选入构建(绝对路径,或相对包根)。下游效果与 `generated=` 相同;语义区别在于文件是程序*选中*的(tarball payload / vendored 源树)而非程序写出的——例如对大型源码包做 per-target 源选择 |
50-
| `mcpp:include-dir=<dir>` | 为本包自身 TU 增加一个**私有** include 目录(`-I`;绝对路径或相对包根,自动规范化)。取代过去 `cxxflag=-I` + `cflag=-I` 的双重裸发 |
51-
| `mcpp:include-dir-after=<dir>` |`include-dir`,但排在系统目录**之后**搜索(`-idirafter`)——用于会遮蔽系统头的 payload 源树 |
49+
| `mcpp:source=<path>` *(0.0.100+)* | 把一份**既有**源文件选入构建(绝对路径,或相对包根)。下游效果与 `generated=` 相同;语义区别在于文件是程序*选中*的(tarball payload / vendored 源树)而非程序写出的——例如对大型源码包做 per-target 源选择 |
50+
| `mcpp:include-dir=<dir>` *(0.0.100+)* | 为本包自身 TU 增加一个**私有** include 目录(`-I`;绝对路径或相对包根,自动规范化)。取代过去 `cxxflag=-I` + `cflag=-I` 的双重裸发 |
51+
| `mcpp:include-dir-after=<dir>` *(0.0.100+)* |`include-dir`,但排在系统目录**之后**搜索(`-idirafter`)——用于会遮蔽系统头的 payload 源树 |
5252
| `mcpp:rerun-if-changed=<path>` | 该文件变化时重跑 `build.mcpp` |
5353
| `mcpp:rerun-if-env-changed=<VAR>` | 该环境变量变化时重跑 `build.mcpp` |
5454

@@ -103,16 +103,16 @@ int main() {
103103
| 变量 | 类型化读取 ||
104104
|---|---|---|
105105
| `MCPP_TARGET` | `mcpp::target()` | 解析后的 canonical 三元组(交叉构建下是 `--target` 三元组,原生构建是宿主) |
106-
| `MCPP_TARGET_OS` | `mcpp::target_os()` | 目标的 OS 段(`linux`/`macos`/`windows`)——不必再手撕 `MCPP_TARGET` |
107-
| `MCPP_TARGET_ARCH` | `mcpp::target_arch()` | 目标的 arch 段(GNU 拼写:`x86_64``aarch64`…) |
108-
| `MCPP_TARGET_ENV` | `mcpp::target_env()` | 目标的 env 段(`gnu`/`musl`/`msvc`);三元组无 env 段(macOS)时为空串 |
106+
| `MCPP_TARGET_OS` *(0.0.100+)* | `mcpp::target_os()` | 目标的 OS 段(`linux`/`macos`/`windows`)——不必再手撕 `MCPP_TARGET` |
107+
| `MCPP_TARGET_ARCH` *(0.0.100+)* | `mcpp::target_arch()` | 目标的 arch 段(GNU 拼写:`x86_64``aarch64`…) |
108+
| `MCPP_TARGET_ENV` *(0.0.100+)* | `mcpp::target_env()` | 目标的 env 段(`gnu`/`musl`/`msvc`);三元组无 env 段(macOS)时为空串 |
109109
| `MCPP_HOST` | `mcpp::host()` | 宿主三元组 |
110110
| `MCPP_PROFILE` | `mcpp::profile()` | 生效 profile 名(`dev`/`release`/…) |
111111
| `MCPP_OUT_DIR` | `mcpp::out_dir()` | mcpp 提供的可写输出/暂存目录 |
112112
| `MCPP_MANIFEST_DIR` | `mcpp::manifest_dir()` | 包根(= CWD) |
113113
| `MCPP_FEATURE_<NAME>` | `mcpp::has_feature("name")` | 每个活跃 feature 置 `1`(`<NAME>` 消毒规则与 `MCPP_FEATURE_` 编译宏一致) |
114114
| `MCPP_FEATURES` || 活跃 feature 逗号列表 |
115-
| `MCPP_DEP_<NAME>_DIR` | `mcpp::dep_dir("name")` | 每个已声明依赖解析后的安装目录(canonical 名与去命名空间短名两种拼写都可用;`<NAME>` 消毒规则同 `MCPP_FEATURE_`)。依赖包的 build.mcpp ****根工程的 build.mcpp 都能拿到(根工程的 build.mcpp 在依赖解析之后运行) |
115+
| `MCPP_DEP_<NAME>_DIR` | `mcpp::dep_dir("name")` | 每个已声明依赖解析后的安装目录(canonical 名与去命名空间短名两种拼写都可用;`<NAME>` 消毒规则同 `MCPP_FEATURE_`)。依赖包的 build.mcpp ****根工程的 build.mcpp 都能拿到(根工程的 build.mcpp 在依赖解析之后运行,0.0.100+) |
116116

117117
这些契约值**无条件**折入重跑键——换 target、换 profile、开关 feature 都会触发重跑,
118118
不需要任何 `rerun-if-env-changed` 声明。
@@ -135,7 +135,7 @@ mcpp **不会**每次构建都重跑 `build.mcpp`。它会缓存程序产出的
135135
- 工具链,
136136
- 任何用 `rerun-if-changed` 声明的文件,
137137
- 任何用 `rerun-if-env-changed` 声明的环境变量,
138-
- (或某个 `generated` 产物丢失了)。
138+
- (或某个 `generated` 产物 / `source=` 选中的文件丢失了)。
139139

140140
所以请**声明你的输入**:如果程序读了 `config.h``USE_FAST` 变量,就分别 emit
141141
`mcpp:rerun-if-changed=config.h` / `mcpp:rerun-if-env-changed=USE_FAST`。这用一份明确的

src/build/ninja_backend.cppm

Lines changed: 53 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,15 @@ namespace {
6060
std::string escape_ninja_path(const std::filesystem::path& p) {
6161
// Ninja escapes: $ → $$, : → $:, space → $ (with leading space).
6262
// For simplicity we wrap in case-by-case.
63-
std::string s = p.string();
63+
//
64+
// generic_string(): ninja node names must be forward-slash even on
65+
// Windows (#247). rspfile_content = $in copies node names verbatim into
66+
// a response file that gcc/clang/GNU ar tokenize GNU-style, where
67+
// backslash is an ESCAPE character — `obj\cli.o` would arrive as
68+
// `objcli.o`. Every Windows consumer of these strings (CreateProcess
69+
// path resolution, cl.exe/link.exe, PowerShell Copy-Item, ninja itself)
70+
// accepts forward slashes; POSIX output is byte-identical.
71+
std::string s = p.generic_string();
6472
std::string out;
6573
for (char c : s) {
6674
if (c == '$')
@@ -87,8 +95,12 @@ std::string escape_flag_path(const std::filesystem::path& p) {
8795
return out;
8896
}
8997

90-
std::string local_include_flags(const CompileUnit& cu, bool msvcDialect,
91-
bool nasmUnit) {
98+
bool is_nasm_source(const std::filesystem::path& src) {
99+
return src.extension() == ".asm";
100+
}
101+
102+
std::string local_include_flags(const CompileUnit& cu, bool msvcDialect) {
103+
const bool nasmUnit = is_nasm_source(cu.source);
92104
std::string flags;
93105
for (auto const& inc : cu.localIncludeDirs) {
94106
flags += " -I";
@@ -197,10 +209,6 @@ bool is_gas_source(const std::filesystem::path& src) {
197209
return ext == ".S" || ext == ".s";
198210
}
199211

200-
bool is_nasm_source(const std::filesystem::path& src) {
201-
return src.extension() == ".asm";
202-
}
203-
204212
// TUs the P1689 module scan must skip: C-family and assembly units cannot
205213
// contain `import`/`module` declarations, and feeding them to the scanner
206214
// would route them through the C++ frontend.
@@ -580,39 +588,25 @@ std::string emit_ninja_string(const BuildPlan& plan) {
580588
}
581589

582590
// Link/archive/shared: driver-style (g++/clang++ are the linker) vs the
583-
// msvc dialect's separate link.exe/lib.exe. The msvc commands go through
584-
// response files — object lists exceed cmd.exe's 8191-char limit fast.
585-
if (separateLinker) {
586-
append("rule cxx_link\n");
587-
append(" command = $ld /nologo /OUT:$out @$out.rsp $ldflags $unit_ldflags\n");
588-
append(" rspfile = $out.rsp\n");
589-
append(" rspfile_content = $in\n");
590-
append(" description = LINK $out\n\n");
591-
592-
append("rule cxx_archive\n");
593-
append(" command = $ar /nologo /OUT:$out @$out.rsp\n");
594-
append(" rspfile = $out.rsp\n");
595-
append(" rspfile_content = $in\n");
596-
append(" description = AR $out\n\n");
597-
598-
append("rule cxx_shared\n");
599-
append(" command = $ld /nologo /DLL /OUT:$out /IMPLIB:$out.lib "
600-
"@$out.rsp $ldflags $unit_ldflags\n");
601-
append(" rspfile = $out.rsp\n");
602-
append(" rspfile_content = $in\n");
603-
append(" description = SHARED $out\n\n");
604-
} else {
605-
// Driver-style toolchains (g++/clang++ as the link driver). On
606-
// Windows these still spawn through CreateProcess (32 KiB command
607-
// line ceiling), and large source packages (ffmpeg/opencv-class)
608-
// link thousands of objects — an inlined $in overflows it (#247).
609-
// gcc/clang drivers and GNU/llvm ar all accept @rspfile, so route
610-
// $in through one there. POSIX keeps the inline form byte-identical:
611-
// ARG_MAX is ample and the plain command is easier to reproduce.
612-
auto driver_rule = [&](std::string_view name, std::string cmd,
613-
std::string_view desc) {
591+
// msvc dialect's separate link.exe/lib.exe. One emitter owns the rule
592+
// shape; `useRsp` decides whether $in is inlined or routed through a
593+
// response file (`$in → @$out.rsp` — the only `$i…` variable in any
594+
// link/archive command; revisit the first-match replace if a dialect
595+
// ever grows another).
596+
//
597+
// rsp is used when the command spawns through CreateProcess (32 KiB
598+
// command-line ceiling): always for the separate-linker msvc dialect,
599+
// and on Windows for driver-style too (#247 — ffmpeg/opencv-class
600+
// packages link thousands of objects; clang/gcc drivers and GNU/llvm ar
601+
// all accept @rspfile). POSIX driver-style keeps the inline form
602+
// byte-identical: ARG_MAX is ample and the plain command is easier to
603+
// reproduce by hand.
604+
{
605+
const bool useRsp = separateLinker || mcpp::platform::is_windows;
606+
auto link_rule = [&](std::string_view name, std::string cmd,
607+
std::string_view desc) {
614608
append(std::format("rule {}\n", name));
615-
if constexpr (mcpp::platform::is_windows) {
609+
if (useRsp) {
616610
if (auto pos = cmd.find("$in"); pos != std::string::npos)
617611
cmd.replace(pos, 3, "@$out.rsp");
618612
append(std::format(" command = {}\n", cmd));
@@ -623,12 +617,23 @@ std::string emit_ninja_string(const BuildPlan& plan) {
623617
}
624618
append(std::format(" description = {} $out\n\n", desc));
625619
};
626-
driver_rule("cxx_link",
627-
"$cxx $in -o $out $ldflags $unit_ldflags", "LINK");
628-
driver_rule("cxx_archive", std::string(dial.archiveCmd), "AR");
629-
driver_rule("cxx_shared",
630-
"$cxx -shared $in -o $out $ldflags $soname_flag $unit_ldflags",
631-
"SHARED");
620+
if (separateLinker) {
621+
link_rule("cxx_link",
622+
"$ld /nologo /OUT:$out $in $ldflags $unit_ldflags",
623+
"LINK");
624+
link_rule("cxx_archive", std::string(dial.archiveCmd), "AR");
625+
link_rule("cxx_shared",
626+
"$ld /nologo /DLL /OUT:$out /IMPLIB:$out.lib "
627+
"$in $ldflags $unit_ldflags",
628+
"SHARED");
629+
} else {
630+
link_rule("cxx_link",
631+
"$cxx $in -o $out $ldflags $unit_ldflags", "LINK");
632+
link_rule("cxx_archive", std::string(dial.archiveCmd), "AR");
633+
link_rule("cxx_shared",
634+
"$cxx -shared $in -o $out $ldflags $soname_flag $unit_ldflags",
635+
"SHARED");
636+
}
632637
}
633638

634639
append("rule runtime_alias\n");
@@ -750,7 +755,7 @@ std::string emit_ninja_string(const BuildPlan& plan) {
750755
append(std::format("build {} : cxx_scan {}\n", escape_ninja_path(ddi),
751756
escape_ninja_path(cu.source)));
752757
append(std::format(" compile_target = {}\n", escape_ninja_path(cu.object)));
753-
if (auto includes = local_include_flags(cu, msvcDeps, is_nasm_source(cu.source)); !includes.empty())
758+
if (auto includes = local_include_flags(cu, msvcDeps); !includes.empty())
754759
append(std::format(" local_includes ={}\n", includes));
755760
if (auto flags = join_flags(cu.packageCxxflags); !flags.empty())
756761
append(std::format(" unit_cxxflags ={}\n", flags));
@@ -829,7 +834,7 @@ std::string emit_ninja_string(const BuildPlan& plan) {
829834
} else {
830835
out_line += "\n";
831836
}
832-
if (auto includes = local_include_flags(cu, msvcDeps, is_nasm_source(cu.source)); !includes.empty())
837+
if (auto includes = local_include_flags(cu, msvcDeps); !includes.empty())
833838
out_line += " local_includes =" + includes + "\n";
834839
if (is_gas_source(cu.source) || is_nasm_source(cu.source)) {
835840
if (auto flags = join_flags(asm_unit_flags(cu)); !flags.empty())
@@ -879,7 +884,7 @@ std::string emit_ninja_string(const BuildPlan& plan) {
879884
if (!implicit.empty())
880885
out_line += " |" + implicit;
881886
out_line += "\n";
882-
if (auto includes = local_include_flags(cu, msvcDeps, is_nasm_source(cu.source)); !includes.empty())
887+
if (auto includes = local_include_flags(cu, msvcDeps); !includes.empty())
883888
out_line += " local_includes =" + includes + "\n";
884889
if (is_gas_source(cu.source) || is_nasm_source(cu.source)) {
885890
if (auto flags = join_flags(asm_unit_flags(cu)); !flags.empty())

src/build/plan.cppm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ local_include_dirs_after_for_manifest(const std::filesystem::path& root,
280280
{
281281
std::vector<std::filesystem::path> dirs;
282282
for (auto const& inc : manifest.buildConfig.includeDirsAfter) {
283-
for (auto& d : expand_manifest_include_entry(root, std::filesystem::path(inc)))
283+
for (auto& d : expand_manifest_include_entry(root, inc))
284284
dirs.push_back(std::move(d));
285285
}
286286
return dirs;

0 commit comments

Comments
 (0)