aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBob van der Linden <bobvanderlinden@gmail.com>2023-02-10 14:18:27 +0100
committerBob van der Linden <bobvanderlinden@gmail.com>2023-02-28 09:28:05 +0100
commit872cdb4346f72186ae683f90ac97b8a7d9bddfd4 (patch)
treea473d7c653e6c779987068830a742b08ee73d140
parent3113b13df9afaab918792c725742c6bc3fcec88b (diff)
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.
-rw-r--r--src/nix/profile.cc56
1 files changed, 55 insertions, 1 deletions
diff --git a/src/nix/profile.cc b/src/nix/profile.cc
index 32364e720..3464a977d 100644
--- a/src/nix/profile.cc
+++ b/src/nix/profile.cc
@@ -329,7 +329,61 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
manifest.elements.push_back(std::move(element));
}
- updateProfile(manifest.build(store));
+ try {
+ updateProfile(manifest.build(store));
+ } catch (BuildEnvFileConflictError & conflictError) {
+ auto findRefByFilePath = [&]<typename Iterator>(Iterator begin, Iterator end) {
+ for (auto it = begin; it != end; it++) {
+ auto profileElement = *it;
+ for (auto & storePath : profileElement.storePaths) {
+ if (conflictError.fileA.starts_with(store->printStorePath(storePath))) {
+ return std::pair(conflictError.fileA, profileElement.source->originalRef);
+ }
+ if (conflictError.fileB.starts_with(store->printStorePath(storePath))) {
+ return std::pair(conflictError.fileB, profileElement.source->originalRef);
+ }
+ }
+ }
+ throw conflictError;
+ };
+ // There are 2 conflicting files. We need to find out which one is from the already installed package and
+ // which one is the package that is the new package that is being installed.
+ // The first matching package is the one that was already installed (original).
+ auto [originalConflictingFilePath, originalConflictingRef] = findRefByFilePath(manifest.elements.begin(), manifest.elements.end());
+ // The last matching package is the one that was going to be installed (new).
+ auto [newConflictingFilePath, newConflictingRef] = findRefByFilePath(manifest.elements.rbegin(), manifest.elements.rend());
+
+ 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"
+ " %2%\n"
+ "\n"
+ "To remove the existing package:\n"
+ "\n"
+ " nix profile remove %3%\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 %5%.\n"
+ "To prioritise the new package:\n"
+ "\n"
+ " nix profile install %4% --priority %6%\n"
+ "\n"
+ "To prioritise the existing package:\n"
+ "\n"
+ " nix profile install %4% --priority %7%\n",
+ originalConflictingFilePath,
+ newConflictingFilePath,
+ originalConflictingRef.to_string(),
+ newConflictingRef.to_string(),
+ conflictError.priority,
+ conflictError.priority - 1,
+ conflictError.priority + 1
+ );
+ }
}
};