Fix the printing of the installables on nix profile install conflict

- If the element comes from a flake, print the full flakeref (with the
  fragment part) and not just the reference to the flake itself
- If the element doesn't come from a flake, print its store path(s)

This is a bit too verbose, but has the advantages of being correct (and
not crashing), so it's strictly better than the previous situation

Fix https://github.com/NixOS/nix/issues/8284
This commit is contained in:
Théophane Hufschmitt 2023-05-16 11:37:45 +02:00
parent 3d144e778e
commit e997512523
2 changed files with 36 additions and 10 deletions

View file

@ -31,6 +31,11 @@ struct ProfileElementSource
std::tuple(originalRef.to_string(), attrPath, outputs) < std::tuple(originalRef.to_string(), attrPath, outputs) <
std::tuple(other.originalRef.to_string(), other.attrPath, other.outputs); std::tuple(other.originalRef.to_string(), other.attrPath, other.outputs);
} }
std::string to_string() const
{
return fmt("%s#%s%s", originalRef, attrPath, outputs.to_string());
}
}; };
const int defaultPriority = 5; const int defaultPriority = 5;
@ -45,13 +50,27 @@ struct ProfileElement
std::string identifier() const std::string identifier() const
{ {
if (source) if (source)
return fmt("%s#%s%s", source->originalRef, source->attrPath, source->outputs.to_string()); return source->to_string();
StringSet names; StringSet names;
for (auto & path : storePaths) for (auto & path : storePaths)
names.insert(DrvName(path.name()).name); names.insert(DrvName(path.name()).name);
return concatStringsSep(", ", names); return concatStringsSep(", ", names);
} }
/**
* Return a string representing an installable corresponding to the current
* element, either a flakeref or a plain store path
*/
std::set<std::string> toInstallables(Store & store)
{
if (source)
return {source->to_string()};
StringSet rawPaths;
for (auto & path : storePaths)
rawPaths.insert(store.printStorePath(path));
return rawPaths;
}
std::string versions() const std::string versions() const
{ {
StringSet versions; StringSet versions;
@ -363,10 +382,10 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
auto profileElement = *it; auto profileElement = *it;
for (auto & storePath : profileElement.storePaths) { for (auto & storePath : profileElement.storePaths) {
if (conflictError.fileA.starts_with(store->printStorePath(storePath))) { if (conflictError.fileA.starts_with(store->printStorePath(storePath))) {
return std::pair(conflictError.fileA, profileElement.source->originalRef); return std::pair(conflictError.fileA, profileElement.toInstallables(*store));
} }
if (conflictError.fileB.starts_with(store->printStorePath(storePath))) { if (conflictError.fileB.starts_with(store->printStorePath(storePath))) {
return std::pair(conflictError.fileB, profileElement.source->originalRef); return std::pair(conflictError.fileB, profileElement.toInstallables(*store));
} }
} }
} }
@ -375,9 +394,9 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
// There are 2 conflicting files. We need to find out which one is from the already installed package and // 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. // 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). // The first matching package is the one that was already installed (original).
auto [originalConflictingFilePath, originalConflictingRef] = findRefByFilePath(manifest.elements.begin(), manifest.elements.end()); auto [originalConflictingFilePath, originalConflictingRefs] = findRefByFilePath(manifest.elements.begin(), manifest.elements.end());
// The last matching package is the one that was going to be installed (new). // The last matching package is the one that was going to be installed (new).
auto [newConflictingFilePath, newConflictingRef] = findRefByFilePath(manifest.elements.rbegin(), manifest.elements.rend()); auto [newConflictingFilePath, newConflictingRefs] = findRefByFilePath(manifest.elements.rbegin(), manifest.elements.rend());
throw Error( throw Error(
"An existing package already provides the following file:\n" "An existing package already provides the following file:\n"
@ -403,8 +422,8 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
" nix profile install %4% --priority %7%\n", " nix profile install %4% --priority %7%\n",
originalConflictingFilePath, originalConflictingFilePath,
newConflictingFilePath, newConflictingFilePath,
originalConflictingRef.to_string(), concatStringsSep(" ", originalConflictingRefs),
newConflictingRef.to_string(), concatStringsSep(" ", newConflictingRefs),
conflictError.priority, conflictError.priority,
conflictError.priority - 1, conflictError.priority - 1,
conflictError.priority + 1 conflictError.priority + 1

View file

@ -157,17 +157,17 @@ error: An existing package already provides the following file:
To remove the existing package: To remove the existing package:
nix profile remove path:${flake1Dir} nix profile remove path:${flake1Dir}#packages.${system}.default
The new package can also be installed next to the existing one by assigning a different priority. The new package can also be installed next to the existing one by assigning a different priority.
The conflicting packages have a priority of 5. The conflicting packages have a priority of 5.
To prioritise the new package: To prioritise the new package:
nix profile install path:${flake2Dir} --priority 4 nix profile install path:${flake2Dir}#packages.${system}.default --priority 4
To prioritise the existing package: To prioritise the existing package:
nix profile install path:${flake2Dir} --priority 6 nix profile install path:${flake2Dir}#packages.${system}.default --priority 6
EOF EOF
) )
[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello World" ]] [[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello World" ]]
@ -177,3 +177,10 @@ nix profile install $flake2Dir --priority 0
[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello World2" ]] [[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello World2" ]]
# nix profile install $flake1Dir --priority 100 # nix profile install $flake1Dir --priority 100
# [[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello World" ]] # [[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello World" ]]
# Ensure that conflicts are handled properly even when the installables aren't
# flake references.
# Regression test for https://github.com/NixOS/nix/issues/8284
clearProfiles
nix profile install $(nix build $flake1Dir --no-link --print-out-paths)
expect 1 nix profile install --impure --expr "(builtins.getFlake ''$flake2Dir'').packages.$system.default"