Skip to content

Commit 0d04a33

Browse files
authored
fix(index): install project index deps through xlings cli
1 parent 1cb54ff commit 0d04a33

7 files changed

Lines changed: 385 additions & 46 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@
33
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
44
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
55
6+
## [0.0.39] — 2026-06-01
7+
8+
### 修复
9+
10+
- 修复 project-local index 包安装时没有走项目 xlings 数据根的问题,本地 path
11+
索引现在通过 xlings CLI 直接安装到项目数据目录,避免 hook 查找不到同索引包。
12+
- 修复包 install hook 运行前 `mcpp.deps` 尚未安装的问题,库/头文件依赖可以继续
13+
留在 `mcpp.deps`,只有 hook 执行工具需要放入 xpm deps。
14+
615
## [0.0.38] — 2026-05-31
716

817
### 新增

mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mcpp"
3-
version = "0.0.38"
3+
version = "0.0.39"
44
description = "Modern C++ build & package management tool"
55
license = "Apache-2.0"
66
authors = ["mcpp-community"]

src/cli.cppm

Lines changed: 89 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1628,10 +1628,19 @@ prepare_build(bool print_fingerprint,
16281628
// 0.0.10+: loadVersionDep accepts structured (ns, shortName) for
16291629
// namespace-aware lookup. depName is the map key (qualified or bare),
16301630
// kept for install() target formatting and error messages.
1631-
auto loadVersionDep = [&](const std::string& depName,
1632-
const std::string& ns,
1633-
const std::string& shortName,
1634-
const std::string& version)
1631+
std::set<std::string> preinstallStack;
1632+
std::set<std::string> preinstallDone;
1633+
1634+
std::function<std::expected<LoadedDep, std::string>(
1635+
const std::string&,
1636+
const std::string&,
1637+
const std::string&,
1638+
const std::string&)> loadVersionDep;
1639+
1640+
loadVersionDep = [&](const std::string& depName,
1641+
const std::string& ns,
1642+
const std::string& shortName,
1643+
const std::string& version)
16351644
-> std::expected<LoadedDep, std::string>
16361645
{
16371646
auto cfg = get_cfg();
@@ -1641,24 +1650,29 @@ prepare_build(bool print_fingerprint,
16411650
// ─── Routing: check if this dep's namespace maps to a custom index ──
16421651
auto* idxSpec = findIndexForNs(ns);
16431652

1644-
// For local path indices, verify the xpkg.lua exists in the index.
1645-
// The local PATH index is for DISCOVERY only (finding the xpkg.lua
1646-
// descriptor); the actual package artifacts come from the URLs
1647-
// declared inside the lua, installed via global xlings. So we
1648-
// validate the lua exists, then fall through to the normal install
1649-
// flow below.
1650-
if (idxSpec && idxSpec->is_local()) {
1653+
const bool useProjectEnv = idxSpec && !idxSpec->is_builtin();
1654+
1655+
auto readLuaContent = [&]() -> std::optional<std::string> {
1656+
if (idxSpec && idxSpec->is_local()) {
1657+
auto indexPath = mcpp::config::resolve_project_index_path(*root, *idxSpec);
1658+
return mcpp::fetcher::Fetcher::read_xpkg_lua_from_path(
1659+
indexPath, ns, shortName);
1660+
}
1661+
if (idxSpec && !idxSpec->is_builtin()) {
1662+
return mcpp::fetcher::Fetcher::read_xpkg_lua_from_project_data(
1663+
*root, ns, shortName);
1664+
}
1665+
return fetcher.read_xpkg_lua(ns, shortName);
1666+
};
1667+
1668+
auto luaContent = readLuaContent();
1669+
if (idxSpec && idxSpec->is_local() && !luaContent) {
16511670
auto indexPath = mcpp::config::resolve_project_index_path(*root, *idxSpec);
1652-
auto luaCheck = mcpp::fetcher::Fetcher::read_xpkg_lua_from_path(
1653-
indexPath, ns, shortName);
1654-
if (!luaCheck) return std::unexpected(std::format(
1671+
return std::unexpected(std::format(
16551672
"dependency '{}': not found in local index at '{}'",
16561673
depName, indexPath.string()));
1657-
// lua found — fall through to normal install path resolution.
16581674
}
16591675

1660-
const bool useProjectEnv = idxSpec && !idxSpec->is_builtin();
1661-
16621676
// For custom indices, try project-level xlings data roots first.
16631677
std::optional<std::filesystem::path> installed;
16641678
if (useProjectEnv) {
@@ -1670,6 +1684,59 @@ prepare_build(bool print_fingerprint,
16701684
}
16711685

16721686
if (!installed) {
1687+
if (luaContent) {
1688+
auto field = mcpp::manifest::extract_mcpp_field(*luaContent);
1689+
if (field.kind == mcpp::manifest::McppField::TableBody) {
1690+
auto depManifest = mcpp::manifest::synthesize_from_xpkg_lua(
1691+
*luaContent, depName, version);
1692+
if (!depManifest) {
1693+
return std::unexpected(std::format(
1694+
"dependency '{}': {}", depName, depManifest.error().format()));
1695+
}
1696+
1697+
auto preinstallKey = std::format("{}:{}@{}", ns, shortName, version);
1698+
if (preinstallStack.contains(preinstallKey)) {
1699+
return std::unexpected(std::format(
1700+
"dependency '{}': cyclic mcpp.deps while preparing install hooks",
1701+
depName));
1702+
}
1703+
1704+
if (!preinstallDone.contains(preinstallKey)) {
1705+
preinstallStack.insert(preinstallKey);
1706+
for (auto [childName, childSpec] : depManifest->dependencies) {
1707+
mcpp::pm::compat::normalize_nested_namespace(
1708+
childSpec.namespace_,
1709+
childSpec.shortName,
1710+
childSpec.legacyDottedKey);
1711+
1712+
if (auto r = resolveSemver(childSpec, childName); !r) {
1713+
preinstallStack.erase(preinstallKey);
1714+
return std::unexpected(r.error());
1715+
}
1716+
1717+
if (!childSpec.isVersion()) continue;
1718+
1719+
ResolvedKey childKey{
1720+
childSpec.namespace_.empty()
1721+
? std::string{mcpp::manifest::kDefaultNamespace}
1722+
: childSpec.namespace_,
1723+
childSpec.shortName.empty() ? childName : childSpec.shortName,
1724+
};
1725+
if (auto child = loadVersionDep(
1726+
childName,
1727+
childKey.ns,
1728+
childKey.shortName,
1729+
childSpec.version); !child) {
1730+
preinstallStack.erase(preinstallKey);
1731+
return std::unexpected(child.error());
1732+
}
1733+
}
1734+
preinstallStack.erase(preinstallKey);
1735+
preinstallDone.insert(preinstallKey);
1736+
}
1737+
}
1738+
}
1739+
16731740
// xlings resolves packages by the full qualified name (ns.shortName)
16741741
// as it appears in the index's name field. Use fqname, not the
16751742
// map key (which may be a bare short name for default-ns deps).
@@ -1680,12 +1747,10 @@ prepare_build(bool print_fingerprint,
16801747
auto install_one = [&](std::string target) -> std::expected<mcpp::xlings::CallResult, mcpp::pm::CallError> {
16811748
if (useProjectEnv) {
16821749
auto projEnv = mcpp::config::make_project_xlings_env(**cfg, *root);
1683-
auto argsJson = std::format(
1684-
R"({{"targets":["{}"],"yes":true}})", target);
1685-
CliInstallProgress progress;
1686-
auto r = mcpp::xlings::call(projEnv, "install_packages", argsJson, &progress);
1687-
if (!r) return std::unexpected(mcpp::pm::CallError{r.error()});
1688-
return *r;
1750+
int directRc = mcpp::xlings::install_direct(projEnv, target, /*quiet=*/true);
1751+
mcpp::xlings::CallResult result;
1752+
result.exitCode = directRc;
1753+
return result;
16891754
}
16901755
std::vector<std::string> targets{ std::move(target) };
16911756
CliInstallProgress progress;
@@ -1730,17 +1795,8 @@ prepare_build(bool print_fingerprint,
17301795
std::filesystem::path verRoot = *installed;
17311796

17321797
// Route xpkg.lua reading through the appropriate index.
1733-
std::optional<std::string> luaContent;
1734-
if (idxSpec && idxSpec->is_local()) {
1735-
auto indexPath = mcpp::config::resolve_project_index_path(*root, *idxSpec);
1736-
luaContent = mcpp::fetcher::Fetcher::read_xpkg_lua_from_path(
1737-
indexPath, ns, shortName);
1738-
} else if (idxSpec && !idxSpec->is_builtin()) {
1739-
luaContent = mcpp::fetcher::Fetcher::read_xpkg_lua_from_project_data(
1740-
*root, ns, shortName);
1741-
}
17421798
if (!luaContent) {
1743-
luaContent = fetcher.read_xpkg_lua(ns, shortName);
1799+
luaContent = readLuaContent();
17441800
}
17451801
if (!luaContent) return std::unexpected(std::format(
17461802
"dependency '{}': index entry not found in local clone", depName));

src/config.cppm

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,42 @@ bool ensure_project_index_dir(
685685
env.home = dotMcpp;
686686
mcpp::xlings::seed_xlings_json(env, customRepos);
687687

688+
auto exposeLocalIndex = [&](const std::string& name,
689+
const std::filesystem::path& source,
690+
const std::filesystem::path& dataRoot)
691+
{
692+
std::error_code ec2;
693+
auto target = dataRoot / name;
694+
std::filesystem::create_directories(dataRoot, ec2);
695+
if (std::filesystem::exists(target / "pkgs", ec2)) {
696+
return;
697+
}
698+
ec2.clear();
699+
if (std::filesystem::exists(target, ec2) || std::filesystem::is_symlink(target, ec2)) {
700+
std::filesystem::remove_all(target, ec2);
701+
}
702+
ec2.clear();
703+
std::filesystem::create_directory_symlink(source, target, ec2);
704+
if (!ec2 && std::filesystem::exists(target / "pkgs", ec2)) {
705+
return;
706+
}
707+
ec2.clear();
708+
std::filesystem::copy(
709+
source,
710+
target,
711+
std::filesystem::copy_options::recursive
712+
| std::filesystem::copy_options::skip_existing,
713+
ec2);
714+
};
715+
716+
for (auto& [name, spec] : indices) {
717+
if (spec.is_builtin() || !spec.is_local()) continue;
718+
auto source = resolve_project_index_path(projectDir, spec);
719+
if (!std::filesystem::exists(source / "pkgs", ec)) continue;
720+
exposeLocalIndex(name, source, dotMcpp / ".xlings" / "data");
721+
exposeLocalIndex(name, source, dotMcpp / "data");
722+
}
723+
688724
// Project-scoped xlings installs custom-index packages in an additive
689725
// project data dir. Expose the global official xim index there too, so
690726
// package deps like `xim:python@latest` can resolve without falling back

src/toolchain/fingerprint.cppm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import mcpp.toolchain.detect;
1818

1919
export namespace mcpp::toolchain {
2020

21-
inline constexpr std::string_view MCPP_VERSION = "0.0.38";
21+
inline constexpr std::string_view MCPP_VERSION = "0.0.39";
2222

2323
struct FingerprintInputs {
2424
Toolchain toolchain;

tests/e2e/52_local_path_namespaced_index.sh

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ EOF
8787
mkdir -p "$TMP/fake-bin"
8888
FAKE_REGISTRY="$TMP/fake-registry"
8989
FAKE_LOG="$TMP/fake-xlings.log"
90+
FAKE_DIRECT_LOG="$TMP/fake-xlings-direct.log"
9091
mkdir -p "$FAKE_REGISTRY/data"
9192
if [[ -d "$USER_MCPP/registry/data/xpkgs" ]]; then
9293
ln -s "$USER_MCPP/registry/data/xpkgs" "$FAKE_REGISTRY/data/xpkgs"
@@ -114,7 +115,25 @@ if [[ "${1:-}" == "interface" && "${2:-}" == "install_packages" ]]; then
114115
fi
115116
shift
116117
done
117-
printf '{"kind":"result","exitCode":1}\n'
118+
printf '{"kind":"result","exitCode":0}\n'
119+
exit 0
120+
fi
121+
122+
if [[ "${1:-}" == "install" ]]; then
123+
printf '%s\n' "$*" > "${FAKE_XLINGS_DIRECT_LOG:?}"
124+
if [[ ! -d "${XLINGS_PROJECT_DIR:?}/.xlings/data/compat/pkgs" \
125+
&& ! -d "${XLINGS_PROJECT_DIR:?}/data/compat/pkgs" ]]; then
126+
echo "missing project local path index link" >&2
127+
find "${XLINGS_PROJECT_DIR:?}" -maxdepth 4 -type d -print >&2 2>/dev/null || true
128+
exit 23
129+
fi
130+
install_root="${XLINGS_PROJECT_DIR:?}/.xlings/data/xpkgs/compat-x-compat.cfg/1.0.0"
131+
mkdir -p "$install_root/src"
132+
cat > "$install_root/src/cfg.c" <<'SRC'
133+
int cfg_value(void) {
134+
return 42;
135+
}
136+
SRC
118137
exit 0
119138
fi
120139
@@ -147,10 +166,10 @@ EOF
147166
mkdir -p "$TMP/project/clean/src"
148167
cd "$TMP/project/clean"
149168

150-
cat > src/main.cpp <<'EOF'
169+
cat > src/clean.cpp <<'EOF'
151170
extern "C" int cfg_value(void);
152-
int main() {
153-
return cfg_value() == 42 ? 0 : 1;
171+
int clean_value(void) {
172+
return cfg_value();
154173
}
155174
EOF
156175

@@ -166,13 +185,15 @@ compat = { path = "$INDEX_DIR" }
166185
cfg = "1.0.0"
167186
168187
[targets.clean]
169-
kind = "bin"
170-
main = "src/main.cpp"
188+
kind = "lib"
171189
EOF
172190

173191
UPDATE_LOG="$TMP/fake-xlings-update.log"
174-
if FAKE_XLINGS_LOG="$FAKE_LOG" FAKE_XLINGS_UPDATE_LOG="$UPDATE_LOG" "$MCPP" build > fetch.log 2>&1; then
175-
echo "FAIL: clean local path dependency unexpectedly built without package install"
192+
if ! FAKE_XLINGS_LOG="$FAKE_LOG" \
193+
FAKE_XLINGS_DIRECT_LOG="$FAKE_DIRECT_LOG" \
194+
FAKE_XLINGS_UPDATE_LOG="$UPDATE_LOG" \
195+
"$MCPP" build > fetch.log 2>&1; then
196+
echo "FAIL: clean local path dependency should use direct xlings install"
176197
cat fetch.log
177198
exit 1
178199
fi
@@ -183,10 +204,17 @@ if [[ -f "$UPDATE_LOG" ]]; then
183204
exit 1
184205
fi
185206

186-
grep -Fq '"compat:compat.cfg@1.0.0"' "$FAKE_LOG" || {
187-
echo "FAIL: clean local path install target should use full package name"
207+
if [[ -f "$FAKE_LOG" ]]; then
208+
echo "FAIL: project local path install should use direct xlings install, not interface"
209+
cat "$FAKE_LOG"
210+
cat fetch.log
211+
exit 1
212+
fi
213+
214+
grep -Fq 'install compat:compat.cfg@1.0.0 -y' "$FAKE_DIRECT_LOG" || {
215+
echo "FAIL: clean local path install target should use direct xlings with full package name"
188216
echo "recorded:"
189-
cat "$FAKE_LOG" 2>/dev/null || true
217+
cat "$FAKE_DIRECT_LOG" 2>/dev/null || true
190218
echo "build log:"
191219
cat fetch.log
192220
exit 1

0 commit comments

Comments
 (0)