@@ -21,6 +21,7 @@ export module mcpp.pm.index_contract;
2121
2222import std;
2323import mcpp.libs.toml;
24+ import mcpp.platform.fs; // self_exe_path (distro-layout detection)
2425import mcpp.version_req;
2526import mcpp.version; // MCPP_VERSION (leaf — see that module)
2627
@@ -44,6 +45,13 @@ read_index_contract(const std::filesystem::path& indexRoot);
4445std::optional<std::string>
4546floor_violation (std::string_view minMcpp, std::string_view ownVersion);
4647
48+ // Pure: E0006 message with the Upgrade advice suited to the install layout
49+ // (`distroManaged` is detected once by the caller via self_exe_path). The
50+ // plain one-liner misleads users of the other install methods — an AUR install
51+ // plus the install.sh command installs a second copy that does not update the
52+ // running binary.
53+ std::string e0006_message (std::string violation, bool distroManaged);
54+
4755// Pure predicate — no reporting, no registration, no dedup. For callers that
4856// need to ask "would this tree be usable?" without the side effects of
4957// check_index_floor (the refresh guard asks it twice per refresh).
@@ -118,6 +126,26 @@ read_index_contract(const std::filesystem::path& indexRoot)
118126 return c;
119127}
120128
129+ // Upgrade advice embedded in the E0006 message. mcpp supports several install
130+ // methods (xlings is the recommended default; install.sh / AUR / Homebrew are
131+ // alternatives), and each lands in a different place. A single hardcoded
132+ // one-liner misleads users of the other methods — e.g. an AUR install plus the
133+ // install.sh command installs a SECOND copy that does not update the running
134+ // binary. So the message names the method and how to upgrade IT.
135+ namespace {
136+ constexpr std::string_view kInstallShUpgrade =
137+ " Upgrade: re-run the install.sh one-liner\n "
138+ " curl -fsSL https://github.com/mcpp-community/mcpp/"
139+ " releases/latest/download/install.sh | bash\n " ;
140+ constexpr std::string_view kDistroUpgrade =
141+ " Upgrade: this is a distro-managed install (AUR) — update the package\n "
142+ " with your AUR helper (e.g. 'paru -Syu mcpp-bin' or\n "
143+ " 'yay -Syu mcpp-bin'). The install.sh one-liner installs a\n "
144+ " separate copy and will NOT update this one.\n " ;
145+ constexpr std::string_view kXlingsUpgrade =
146+ " Upgrade: 'xlings update mcpp' (recommended default installer)\n " ;
147+ } // namespace
148+
121149std::optional<std::string>
122150floor_violation (std::string_view minMcpp, std::string_view ownVersion)
123151{
@@ -128,11 +156,37 @@ floor_violation(std::string_view minMcpp, std::string_view ownVersion)
128156 if (*have >= *need) return std::nullopt ;
129157 return std::format (
130158 " index requires mcpp >= {} but this is mcpp {} [E0006]\n "
131- " Upgrade: curl -fsSL https://github.com/mcpp-community/mcpp/"
132- " releases/latest/download/install.sh | bash\n "
159+ " {}"
133160 " Details: mcpp explain E0006 "
134161 " (override for debugging: MCPP_INDEX_FLOOR=ignore)" ,
135- minMcpp, ownVersion);
162+ minMcpp, ownVersion, kInstallShUpgrade );
163+ }
164+
165+ // Pure: swap the Upgrade advice for the install layout the caller detected.
166+ std::string e0006_message (std::string violation, bool distroManaged)
167+ {
168+ if (distroManaged) {
169+ auto pos = violation.find (kInstallShUpgrade );
170+ if (pos != std::string::npos)
171+ violation.replace (pos, kInstallShUpgrade .size (), kDistroUpgrade );
172+ }
173+ // Append the recommended installer note to every layout.
174+ violation += kXlingsUpgrade ;
175+ return violation;
176+ }
177+
178+ // Impure (reads /proc/self/exe): true when the running binary sits under the
179+ // distro-managed tree (/opt/mcpp — the AUR layout). install.sh / xlings
180+ // installs keep the binary inside $MCPP_HOME (~/.mcpp) instead.
181+ bool distro_managed_install ()
182+ {
183+ #if defined(_WIN32) || defined(__APPLE__)
184+ return false ;
185+ #else
186+ std::error_code ec;
187+ auto self = mcpp::platform::fs::self_exe_path ();
188+ return self.string ().find (" /opt/mcpp/" ) != std::string::npos;
189+ #endif
136190}
137191
138192bool index_usable (const std::filesystem::path& indexRoot)
@@ -192,15 +246,17 @@ check_index_floor(const std::filesystem::path& indexRoot)
192246 auto violation = floor_violation (c->minMcpp , mcpp::MCPP_VERSION );
193247 if (!violation) return std::nullopt ;
194248
249+ auto message = e0006_message (*violation, distro_managed_install ());
250+
195251 // Record BEFORE the dedup return: the fact must be queryable no matter how
196252 // many times this root is opened, while the message is printed only once.
197253 // Deriving "was anything unusable?" from "did we print?" is what made the
198254 // second and later reads indistinguishable from an ordinary miss.
199255 if (!index_marked_unusable (indexRoot))
200- unusable_registry ().push_back ({indexRoot, *violation });
256+ unusable_registry ().push_back ({indexRoot, message });
201257 else
202258 return std::nullopt ; // already reported — stay quiet
203- return violation ;
259+ return message ;
204260}
205261
206262} // namespace mcpp::pm
0 commit comments