@@ -268,7 +268,12 @@ std::filesystem::path glob_literal_prefix(std::string_view glob) {
268268 ? glob : glob.substr (0 , wildcard);
269269 auto slash = literal.find_last_of (' /' );
270270 if (slash == std::string_view::npos) return {};
271- return std::filesystem::path (literal.substr (0 , slash));
271+ // Native separators, not the raw generic form: MSVC keeps the input's
272+ // `/` verbatim, and `root / p` plus the directory walk then propagate a
273+ // MIXED `root\generated/modules` into every downstream path — which is
274+ // what `compile_commands.json`'s `file` field showed on Windows for
275+ // multi-segment globs. See mcpp::modgraph::native_path_from_generic.
276+ return native_path_from_generic (literal.substr (0 , slash));
272277}
273278
274279// mcpp#228: `{a,b}` alternation, recursively. Finds the first top-level `{`,
@@ -442,7 +447,9 @@ std::vector<std::filesystem::path> expand_dir_glob(const std::filesystem::path&
442447 // expand_glob) — include_dirs entries are meant to name one literal
443448 // directory each; a caller wanting alternatives lists multiple entries.
444449 if (glob.find (' *' ) == std::string_view::npos) {
445- auto p = root / std::filesystem::path (glob);
450+ // Native spelling (see native_path_from_generic — a raw `a/b` would
451+ // come back mixed from .string() on MSVC).
452+ auto p = root / native_path_from_generic (glob);
446453 if (std::filesystem::is_directory (p, ec)) out.push_back (p);
447454 return out;
448455 }
@@ -494,10 +501,24 @@ namespace {
494501
495502// has_root_path: leave absolute AND root-relative ("/x" on Windows)
496503// spellings alone — only genuinely root-less paths are project-relative.
504+ // Both branches normalize to NATIVE separators: a `-Ithird_party/inc` cxxflag
505+ // would otherwise come back as `C:\proj\third_party/inc` on MSVC (path keeps
506+ // the input `/` verbatim) and reach the CDB's arguments via packageCxxflags.
497507std::string rewrite_rel_copy (const std::string& p, const std::filesystem::path& root) {
498508 std::filesystem::path fp (p);
499- if (fp.has_root_path ()) return p;
500- return (root / fp).string ();
509+ if (fp.has_root_path ()) {
510+ // Nothing to re-spell → hand back the ORIGINAL bytes rather than
511+ // round-tripping them through path's narrow conversion, which throws
512+ // std::system_error for names the ANSI codepage cannot express
513+ // (mcpp#230 — see path_matches_glob). A rooted path with no '/' is
514+ // already native on both platform families.
515+ if (p.find (' /' ) == std::string::npos) return p;
516+ fp.make_preferred ();
517+ return fp.string ();
518+ }
519+ auto joined = root / fp;
520+ joined.make_preferred ();
521+ return joined.string ();
501522}
502523
503524void rewrite_rel (std::string& p, const std::filesystem::path& root) {
@@ -682,7 +703,14 @@ local_include_dirs_for(const std::filesystem::path& root,
682703 std::vector<std::filesystem::path> dirs;
683704 for (auto const & inc : manifest.buildConfig .includeDirs ) {
684705 if (inc.is_absolute ()) {
685- dirs.push_back (inc);
706+ // A TOML value like `C:/SDL2/include` keeps its `/` on MSVC —
707+ // normalize so the CDB's -I comes out native (mixed separators
708+ // break CLion). Direct make_preferred, no generic_string round
709+ // trip: the narrow conversion can throw for names the ANSI
710+ // codepage cannot spell (mcpp#230).
711+ auto n = inc;
712+ n.make_preferred ();
713+ dirs.push_back (std::move (n));
686714 continue ;
687715 }
688716 for (auto & d : expand_dir_glob (root, inc.generic_string ())) {
@@ -701,7 +729,9 @@ local_include_dirs_after_for(const std::filesystem::path& root,
701729 std::vector<std::filesystem::path> dirs;
702730 for (auto const & inc : manifest.buildConfig .includeDirsAfter ) {
703731 if (inc.is_absolute ()) {
704- dirs.push_back (inc);
732+ auto n = inc;
733+ n.make_preferred ();
734+ dirs.push_back (std::move (n));
705735 continue ;
706736 }
707737 for (auto & d : expand_dir_glob (root, inc.generic_string ())) {
@@ -738,7 +768,10 @@ void scan_one_into(ScanResult& result,
738768 // Literal absolute entry — e.g. a dependency build.mcpp's OUT_DIR
739769 // generated source, which lives OUTSIDE the (possibly read-only)
740770 // package root. No glob expansion; taken as-is when it exists.
741- if (std::filesystem::path gp (g); gp.is_absolute ()) {
771+ // Native spelling: a raw `C:/abs/x.cppm` would stay mixed on MSVC
772+ // (see native_path_from_generic) and leak into the CDB.
773+ auto gp = native_path_from_generic (g);
774+ if (gp.is_absolute ()) {
742775 std::error_code aec;
743776 if (std::filesystem::is_regular_file (gp, aec)) all_files.insert (gp);
744777 continue ;
0 commit comments