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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)。

## [0.0.32] — 2026-05-30

### 修复

- 修复 project-local `.xlings.json` 生成时未转义 JSON 字符串的问题,
避免 Windows 本地 index 路径中的反斜杠导致 xlings 跳过项目索引。

## [0.0.31] — 2026-05-30

### 修复
Expand Down
2 changes: 1 addition & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mcpp"
version = "0.0.31"
version = "0.0.32"
description = "Modern C++ build & package management tool"
license = "Apache-2.0"
authors = ["mcpp-community"]
Expand Down
2 changes: 1 addition & 1 deletion src/config.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,7 @@ bool ensure_project_index_dir(
if (spec.is_builtin()) continue;
if (spec.is_local()) {
auto source = resolve_project_index_path(projectDir, spec);
customRepos.emplace_back(name, source.string());
customRepos.emplace_back(name, source.generic_string());
continue;
}
customRepos.emplace_back(name, spec.url);
Expand Down
2 changes: 1 addition & 1 deletion src/toolchain/fingerprint.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import mcpp.toolchain.detect;

export namespace mcpp::toolchain {

inline constexpr std::string_view MCPP_VERSION = "0.0.31";
inline constexpr std::string_view MCPP_VERSION = "0.0.32";

struct FingerprintInputs {
Toolchain toolchain;
Expand Down
28 changes: 26 additions & 2 deletions src/xlings.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,29 @@ void write_file(const std::filesystem::path& p, std::string_view content) {
os << content;
}

std::string json_escape(std::string_view value) {
std::string out;
out.reserve(value.size());
for (unsigned char ch : value) {
switch (ch) {
case '"': out += "\\\""; break;
case '\\': out += "\\\\"; break;
case '\b': out += "\\b"; break;
case '\f': out += "\\f"; break;
case '\n': out += "\\n"; break;
case '\r': out += "\\r"; break;
case '\t': out += "\\t"; break;
default:
if (ch < 0x20) {
out += std::format("\\u{:04x}", static_cast<unsigned>(ch));
} else {
out.push_back(static_cast<char>(ch));
}
}
}
return out;
}

// LineScan: cheap field extraction for bootstrap install progress lines.
// Handles flat JSON; no nested array/object — the keys we extract are
// all leaves.
Expand Down Expand Up @@ -794,12 +817,13 @@ void seed_xlings_json(const Env& env,
json += " \"index_repos\": [\n";
for (std::size_t i = 0; i < repos.size(); ++i) {
json += std::format(" {{ \"name\": \"{}\", \"url\": \"{}\" }}{}\n",
repos[i].first, repos[i].second,
json_escape(repos[i].first),
json_escape(repos[i].second),
i + 1 == repos.size() ? "" : ",");
}
json += " ],\n";
json += " \"lang\": \"en\",\n";
json += std::format(" \"mirror\": \"{}\"\n", mirror);
json += std::format(" \"mirror\": \"{}\"\n", json_escape(mirror));
json += "}\n";
write_file(path, json);
}
Expand Down
37 changes: 37 additions & 0 deletions tests/unit/test_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,40 @@ TEST(Config, ResolveProjectIndexPathUsesProjectRootForRelativeLocalIndex) {
EXPECT_EQ(mcpp::config::resolve_project_index_path(project, spec),
(project / "mcpp").lexically_normal());
}

TEST(Config, ProjectIndexJsonEscapesLocalIndexPath) {
auto project = make_tempdir("mcpp-config-json-escape");
auto index = project / "local" / "index";
std::filesystem::create_directories(index / "pkgs");

mcpp::pm::IndexSpec local;
local.name = "local\"dev";
local.path = index;

mcpp::pm::IndexSpec remote;
remote.name = "remote";
remote.url = R"(https://example.com/a\b"c)";

std::map<std::string, mcpp::pm::IndexSpec> indices;
indices.emplace(local.name, local);
indices.emplace(remote.name, remote);

mcpp::config::GlobalConfig cfg;
ASSERT_TRUE(mcpp::config::ensure_project_index_dir(cfg, project, indices));

std::ifstream is(project / ".mcpp" / ".xlings.json");
ASSERT_TRUE(is);
std::stringstream ss;
ss << is.rdbuf();
auto content = ss.str();

EXPECT_NE(content.find(R"("name": "local\"dev")"), std::string::npos)
<< content;
EXPECT_NE(content.find(index.generic_string()), std::string::npos)
<< content;
EXPECT_NE(content.find(R"(https://example.com/a\\b\"c)"), std::string::npos)
<< content;

is.close();
std::filesystem::remove_all(project);
}
Loading