@@ -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));
0 commit comments