Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/cli.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -795,7 +795,14 @@ int run(int argc, char** argv) {
"Default build target <triple> (omit = host)")))
.subcommand(cl::App("remove")
.description("Uninstall a toolchain")
.arg(cl::Arg("spec").help("<family>@<version>").required())
// Same dual-form as `default`: `gcc@16.1.0` or `gcc 16.1.0`.
// NOT partial versions: `default` resolves `gcc 15` to the
// highest installed 15.x.y, but a removal must name the one
// payload it deletes, so the version here is exact.
.arg(cl::Arg("spec")
.help("<family>[@<version>] (exact version)")
.required())
.arg(cl::Arg("version").help("(optional, alternative to @-form)"))
.option(cl::Option("target").takes_value().help(
"Remove the payload for <triple> instead of the host one")))
.action(wrap_rc(cmd_toolchain)))
Expand Down
3 changes: 2 additions & 1 deletion src/cli/cmd_toolchain.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ export int cmd_toolchain(const mcpplibs::cmdline::ParsedArgs& parsed) {
return mcpp::toolchain::toolchain_set_default(
*cfg, sub_parsed.positional(0), sub_parsed.positional(1), targetArg);
if (subname == "remove")
return mcpp::toolchain::toolchain_remove(*cfg, sub_parsed.positional(0), targetArg);
return mcpp::toolchain::toolchain_remove(
*cfg, sub_parsed.positional(0), sub_parsed.positional(1), targetArg);

mcpp::ui::error(std::format("unknown toolchain subcommand '{}'", subname));
return 2;
Expand Down
20 changes: 16 additions & 4 deletions src/toolchain/lifecycle.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -1269,19 +1269,31 @@ export int toolchain_set_default(const mcpp::config::GlobalConfig& cfg,
// `mcpp toolchain remove <spec> [--target <triple>]` — uninstall a payload.
export int toolchain_remove(const mcpp::config::GlobalConfig& cfg,
const std::string& pos0,
const std::string& pos1,
const std::string& targetArg = {}) {
auto xlEnv = mcpp::config::make_xlings_env(cfg);
auto parsedSpec = mcpp::toolchain::parse_toolchain_spec(pos0);
if (parsedSpec && mcpp::toolchain::is_system_toolchain(*parsedSpec)) {
auto parsedSpec = mcpp::toolchain::parse_toolchain_spec(pos0, pos1);
if (!parsedSpec) {
mcpp::ui::error(std::format(
"{}; e.g. `mcpp toolchain remove gcc 16.1.0`", parsedSpec.error()));
return 2;
}
if (mcpp::toolchain::is_system_toolchain(*parsedSpec)) {
mcpp::ui::error(
"msvc@system is the machine's own Visual Studio, managed by the "
"Visual Studio Installer — mcpp cannot remove it.\n"
" A toolset mcpp installed can be removed by naming it:\n"
" mcpp toolchain remove msvc@<toolset>");
return 1;
}
if (!parsedSpec || parsedSpec->version.empty()) {
mcpp::ui::error(std::format("invalid spec '{}'", pos0));
// A family alone is a well-formed spec that names no payload. Say
// that, rather than "invalid spec 'gcc'", which reads as if the family
// itself were wrong (#284).
if (parsedSpec->version.empty()) {
mcpp::ui::error(std::format(
"`toolchain remove` needs the exact version to uninstall; "
"e.g. `mcpp toolchain remove {0} <version>` or `{0}@<version>`",
mcpp::toolchain::family_name(parsedSpec->family)));
return 2;
}
mcpp::toolchain::print_compat_hint(*parsedSpec);
Expand Down
29 changes: 27 additions & 2 deletions tests/e2e/26_toolchain_management.sh
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ echo "$out" | grep -q '16.1.0' || { echo "FAIL: list missing version: $out"; exi
grep -q 'Default' /tmp/_def.log || { cat /tmp/_def.log; echo "FAIL: no Default msg"; exit 1; }
out=$("$MCPP" toolchain list 2>&1)
echo "$out" | grep -q '\*' || { echo "FAIL: default mark missing: $out"; exit 1; }
"$MCPP" toolchain default gcc 16.1.0 > /tmp/_def_split.log 2>&1 || {
cat /tmp/_def_split.log; echo "FAIL: space-separated default failed"; exit 1; }
grep -q 'Default' /tmp/_def_split.log || {
cat /tmp/_def_split.log; echo "FAIL: no Default msg for space-separated form"; exit 1; }

# 6) Build now succeeds (project has no [toolchain]; uses global default)
"$MCPP" build > /tmp/_build.log 2>&1 || { cat /tmp/_build.log; echo "FAIL: build failed"; exit 1; }
Expand Down Expand Up @@ -98,8 +102,29 @@ echo "$out" | grep -q 'default toolchain = gcc@16.1.0' || {

# 9) Remove
rm -rf target
"$MCPP" toolchain remove gcc@16.1.0 > /tmp/_rm.log 2>&1
grep -q 'Removed' /tmp/_rm.log || { cat /tmp/_rm.log; echo "FAIL: no Removed msg"; exit 1; }
# A family alone names no payload: the error says the version is missing,
# not that the spec is invalid (which reads as a wrong family name, #284).
rc=0
"$MCPP" toolchain remove gcc > /tmp/_rm_nover.log 2>&1 || rc=$?
[[ "$rc" -eq 2 ]] || { cat /tmp/_rm_nover.log; echo "FAIL: remove without a version should exit 2 (got $rc)"; exit 1; }
grep -q 'needs the exact version' /tmp/_rm_nover.log || {
cat /tmp/_rm_nover.log; echo "FAIL: remove without a version did not name the missing version"; exit 1; }
"$MCPP" toolchain remove gcc 16.1.0 > /tmp/_rm.log 2>&1
grep -q 'Removed' /tmp/_rm.log || { cat /tmp/_rm.log; echo "FAIL: no Removed msg for space-separated form"; exit 1; }
# The issue's cross-target spelling should parse too and reach the payload
# lookup, even when that target is not installed in this test workspace.
rc=0
"$MCPP" toolchain remove gcc 16.1.0 --target x86_64-linux-musl > /tmp/_rm_target.log 2>&1 || rc=$?
[[ "$rc" -ne 0 ]] || { echo "FAIL: removing an absent target toolchain should fail"; exit 1; }
grep -q 'is not installed' /tmp/_rm_target.log || {
cat /tmp/_rm_target.log; echo "FAIL: split form with --target did not reach installed-payload check"; exit 1; }
# The compact spelling remains valid too; after removal it should reach the
# installed-payload check rather than fail spec parsing.
rc=0
"$MCPP" toolchain remove gcc@16.1.0 > /tmp/_rm_at.log 2>&1 || rc=$?
[[ "$rc" -ne 0 ]] || { echo "FAIL: removing an absent toolchain should fail"; exit 1; }
grep -q 'is not installed' /tmp/_rm_at.log || {
cat /tmp/_rm_at.log; echo "FAIL: @-form remove did not reach installed-payload check"; exit 1; }

# 10) After removing, the toolchain dir is gone, but config.toml still
# points to it as default. Verify list shows nothing for that compiler now.
Expand Down
Loading