From 9af477e37ef2f5e1452c2920dc7434b5786b8611 Mon Sep 17 00:00:00 2001 From: Bob van der Linden Date: Fri, 10 Feb 2023 14:18:27 +0100 Subject: [PATCH] nix-profile-install: show helpful error upon package conflict Whenever a file conflict happens during "nix profile install" an error is shown that was previously thrown inside builtins.buildEnv. We catch BuildProfileConflictError here so that we can provide the user with more useful instructions on what to do next. Most notably, we give the user concrete commands to use with all parameters already filled in. This avoids the need for the user to look up these commands in manual pages. --- src/nix/profile.cc | 61 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/src/nix/profile.cc b/src/nix/profile.cc index 32364e720d7e..02dd184b11b1 100644 --- a/src/nix/profile.cc +++ b/src/nix/profile.cc @@ -329,7 +329,66 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile manifest.elements.push_back(std::move(element)); } - updateProfile(manifest.build(store)); + try { + updateProfile(manifest.build(store)); + } catch (BuildProfileConflictError & conflictError) { + auto findInstallableOfPkgPath = [&](const Path & pkgPath) { + std::string fallbackInstallable = "INSTALLABLE"; + auto maybeStorePath = store->maybeParseStorePath(pkgPath); + if (!maybeStorePath) { + printError("warning: unable to determine installable from package path (%1%)", pkgPath); + return fallbackInstallable; + } + auto storePath = *maybeStorePath; + auto manifestElement = find_if(manifest.elements.begin(), manifest.elements.end(), [&](const ProfileElement & currentProfileElement) { + return currentProfileElement.storePaths.contains(storePath); + }); + if (manifestElement == manifest.elements.end()) { + printError("warning: unable to find profile element for store path (%1%)", storePath.to_string()); + return fallbackInstallable; + } + if (!manifestElement->source) { + printError("warning: store path does not have a source (%1%)", storePath.to_string()); + return fallbackInstallable; + } + return manifestElement->source->originalRef.to_string(); + }; + std::string originalInstallable = findInstallableOfPkgPath(conflictError.originalPkgPath); + std::string conflictingInstallable = findInstallableOfPkgPath(conflictError.conflictingPkgPath); + + throw Error( + "An existing package already provides the following file:\n" + "\n" + " %1%\n" + "\n" + "This is the conflicting file from the new package:\n" + "\n" + " %3%\n" + "\n" + "To remove the existing package:\n" + "\n" + " nix profile remove %5%\n" + "\n" + "The new package can also be installed next to the existing one by assigning a different priority.\n" + "The conflicting packages have a priority of %7%.\n" + "To prioritise the new package:\n" + "\n" + " nix profile install %6% --priority %8%\n" + "\n" + "To prioritise the existing package:\n" + "\n" + " nix profile install %6% --priority %9%\n", + conflictError.originalFile, + conflictError.originalPkgPath, + conflictError.conflictingFile, + conflictError.conflictingPkgPath, + originalInstallable, + conflictingInstallable, + conflictError.priority, + conflictError.priority - 1, + conflictError.priority + 1 + ); + } } };