Skip to content

Commit b7e14dd

Browse files
committed
fix(index): install project index deps through xlings cli
1 parent 1cb54ff commit b7e14dd

4 files changed

Lines changed: 376 additions & 39 deletions

File tree

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

tests/e2e/52_local_path_namespaced_index.sh

Lines changed: 35 additions & 6 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
@@ -171,8 +190,11 @@ main = "src/main.cpp"
171190
EOF
172191

173192
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"
193+
if ! FAKE_XLINGS_LOG="$FAKE_LOG" \
194+
FAKE_XLINGS_DIRECT_LOG="$FAKE_DIRECT_LOG" \
195+
FAKE_XLINGS_UPDATE_LOG="$UPDATE_LOG" \
196+
"$MCPP" build > fetch.log 2>&1; then
197+
echo "FAIL: clean local path dependency should use direct xlings install"
176198
cat fetch.log
177199
exit 1
178200
fi
@@ -183,10 +205,17 @@ if [[ -f "$UPDATE_LOG" ]]; then
183205
exit 1
184206
fi
185207

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

0 commit comments

Comments
 (0)