Skip to content

Commit e22fee6

Browse files
committed
fix: E0006 Upgrade 提示按安装布局给出指引(AUR 检测 + 推荐默认 xlings),避免误导用户装出第二份(#394
1 parent 8625c45 commit e22fee6

2 files changed

Lines changed: 86 additions & 5 deletions

File tree

src/pm/index_contract.cppm

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export module mcpp.pm.index_contract;
2121

2222
import std;
2323
import mcpp.libs.toml;
24+
import mcpp.platform.fs; // self_exe_path (distro-layout detection)
2425
import mcpp.version_req;
2526
import mcpp.version; // MCPP_VERSION (leaf — see that module)
2627

@@ -44,6 +45,13 @@ read_index_contract(const std::filesystem::path& indexRoot);
4445
std::optional<std::string>
4546
floor_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+
121149
std::optional<std::string>
122150
floor_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

138192
bool 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

tests/unit/test_index_contract.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,31 @@ TEST(IndexContract, FloorViolationOrdering) {
1414
EXPECT_NE(v->find("0.0.85"), std::string::npos);
1515
}
1616

17+
// The Upgrade advice must tell the user which install method THEY have. A
18+
// single hardcoded install.sh one-liner misleads distro (AUR) users into
19+
// installing a second copy that does not update the running binary.
20+
TEST(IndexContract, E0006MessageSwitchesUpgradeAdviceByLayout) {
21+
using mcpp::pm::floor_violation;
22+
using mcpp::pm::e0006_message;
23+
auto violation = floor_violation("2026.8.3.3", "2026.7.28.2");
24+
ASSERT_TRUE(violation.has_value());
25+
26+
// Non-distro (install.sh / xlings) layout keeps the install.sh one-liner
27+
// and always appends the recommended-installer note.
28+
auto script = e0006_message(*violation, /*distroManaged=*/false);
29+
EXPECT_NE(script.find("install.sh"), std::string::npos);
30+
EXPECT_EQ(script.find("distro-managed"), std::string::npos);
31+
EXPECT_NE(script.find("xlings update mcpp"), std::string::npos);
32+
EXPECT_NE(script.find("E0006"), std::string::npos);
33+
34+
// Distro (AUR) layout swaps in the package-manager advice.
35+
auto distro = e0006_message(*violation, /*distroManaged=*/true);
36+
EXPECT_NE(distro.find("distro-managed"), std::string::npos);
37+
EXPECT_EQ(distro.find("re-run the install.sh one-liner"), std::string::npos);
38+
EXPECT_NE(distro.find("xlings update mcpp"), std::string::npos);
39+
EXPECT_NE(distro.find("E0006"), std::string::npos);
40+
}
41+
1742
// The floor compares mcpp's OWN version, so the date scheme (YYYY.M.D.N) has
1843
// to be ordered on all four segments. While parse_version truncated at three,
1944
// every release of a given day compared equal and the floor let a too-old mcpp

0 commit comments

Comments
 (0)