forked from lix-project/lix
Merge changes from topic "profile-v3" into main
* changes: nix3-profile: remove check "name" attr in manifests Add profile migration test nix3-profile: make element names stable getNameFromURL(): Support uppercase characters in attribute names nix3-profile: remove indices nix3-profile: allow using human-readable names to select packages implement parsing human-readable names from URLs
This commit is contained in:
commit
076dfd30c6
9
doc/manual/rl-next/nix-profile-names.md
Normal file
9
doc/manual/rl-next/nix-profile-names.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
synopsis: "`nix profile` now allows referring to elements by human-readable name, and no longer accepts indices"
|
||||||
|
prs: 8678
|
||||||
|
cls: [978, 980]
|
||||||
|
---
|
||||||
|
|
||||||
|
[`nix profile`](@docroot@/command-ref/new-cli/nix3-profile.md) now uses names to refer to installed packages when running [`list`](@docroot@/command-ref/new-cli/nix3-profile-list.md), [`remove`](@docroot@/command-ref/new-cli/nix3-profile-remove.md) or [`upgrade`](@docroot@/command-ref/new-cli/nix3-profile-upgrade.md) as opposed to indices. Indices have been removed. Profile element names are generated when a package is installed and remain the same until the package is removed.
|
||||||
|
|
||||||
|
**Warning**: The `manifest.nix` file used to record the contents of profiles has changed. Nix will automatically upgrade profiles to the new version when you modify the profile. After that, the profile can no longer be used by older versions of Nix.
|
|
@ -1,8 +1,12 @@
|
||||||
|
#include <set>
|
||||||
|
|
||||||
#include "cmd-profiles.hh"
|
#include "cmd-profiles.hh"
|
||||||
#include "built-path.hh"
|
#include "built-path.hh"
|
||||||
#include "builtins/buildenv.hh"
|
#include "builtins/buildenv.hh"
|
||||||
|
#include "logging.hh"
|
||||||
#include "names.hh"
|
#include "names.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
#include "url-name.hh"
|
||||||
|
|
||||||
namespace nix
|
namespace nix
|
||||||
{
|
{
|
||||||
|
@ -116,6 +120,8 @@ ProfileManifest::ProfileManifest(EvalState & state, const Path & profile)
|
||||||
sOriginalUrl = "originalUri";
|
sOriginalUrl = "originalUri";
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
|
[[fallthrough]];
|
||||||
|
case 3:
|
||||||
sUrl = "url";
|
sUrl = "url";
|
||||||
sOriginalUrl = "originalUrl";
|
sOriginalUrl = "originalUrl";
|
||||||
break;
|
break;
|
||||||
|
@ -123,7 +129,10 @@ ProfileManifest::ProfileManifest(EvalState & state, const Path & profile)
|
||||||
throw Error("profile manifest '%s' has unsupported version %d", manifestPath, version);
|
throw Error("profile manifest '%s' has unsupported version %d", manifestPath, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto & e : json["elements"]) {
|
auto elems = json["elements"];
|
||||||
|
|
||||||
|
for (auto & elem : elems.items()) {
|
||||||
|
auto & e = elem.value();
|
||||||
ProfileElement element;
|
ProfileElement element;
|
||||||
for (auto & p : e["storePaths"]) {
|
for (auto & p : e["storePaths"]) {
|
||||||
element.storePaths.insert(state.store->parseStorePath((std::string) p));
|
element.storePaths.insert(state.store->parseStorePath((std::string) p));
|
||||||
|
@ -139,7 +148,16 @@ ProfileManifest::ProfileManifest(EvalState & state, const Path & profile)
|
||||||
e["attrPath"],
|
e["attrPath"],
|
||||||
e["outputs"].get<ExtendedOutputsSpec>()};
|
e["outputs"].get<ExtendedOutputsSpec>()};
|
||||||
}
|
}
|
||||||
elements.emplace_back(std::move(element));
|
|
||||||
|
// TODO(Qyriad): holy crap this chain of ternaries needs cleanup.
|
||||||
|
std::string name =
|
||||||
|
elems.is_object()
|
||||||
|
? elem.key()
|
||||||
|
: element.source
|
||||||
|
? getNameFromURL(parseURL(element.source->to_string())).value_or(element.identifier())
|
||||||
|
: element.identifier();
|
||||||
|
|
||||||
|
addElement(name, std::move(element));
|
||||||
}
|
}
|
||||||
} else if (pathExists(profile + "/manifest.nix")) {
|
} else if (pathExists(profile + "/manifest.nix")) {
|
||||||
// FIXME: needed because of pure mode; ugly.
|
// FIXME: needed because of pure mode; ugly.
|
||||||
|
@ -151,15 +169,37 @@ ProfileManifest::ProfileManifest(EvalState & state, const Path & profile)
|
||||||
for (auto & drvInfo : drvInfos) {
|
for (auto & drvInfo : drvInfos) {
|
||||||
ProfileElement element;
|
ProfileElement element;
|
||||||
element.storePaths = {drvInfo.queryOutPath()};
|
element.storePaths = {drvInfo.queryOutPath()};
|
||||||
elements.emplace_back(std::move(element));
|
addElement(std::move(element));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProfileManifest::addElement(std::string_view nameCandidate, ProfileElement element)
|
||||||
|
{
|
||||||
|
std::string finalName(nameCandidate);
|
||||||
|
|
||||||
|
for (unsigned i = 1; elements.contains(finalName); ++i) {
|
||||||
|
finalName = nameCandidate + "-" + std::to_string(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
elements.insert_or_assign(finalName, std::move(element));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfileManifest::addElement(ProfileElement element)
|
||||||
|
{
|
||||||
|
auto name =
|
||||||
|
element.source
|
||||||
|
? getNameFromURL(parseURL(element.source->to_string()))
|
||||||
|
: std::nullopt;
|
||||||
|
|
||||||
|
auto finalName = name.value_or(element.identifier());
|
||||||
|
addElement(finalName, std::move(element));
|
||||||
|
}
|
||||||
|
|
||||||
nlohmann::json ProfileManifest::toJSON(Store & store) const
|
nlohmann::json ProfileManifest::toJSON(Store & store) const
|
||||||
{
|
{
|
||||||
auto array = nlohmann::json::array();
|
auto es = nlohmann::json::object();
|
||||||
for (auto & element : elements) {
|
for (auto & [name, element] : elements) {
|
||||||
auto paths = nlohmann::json::array();
|
auto paths = nlohmann::json::array();
|
||||||
for (auto & path : element.storePaths) {
|
for (auto & path : element.storePaths) {
|
||||||
paths.push_back(store.printStorePath(path));
|
paths.push_back(store.printStorePath(path));
|
||||||
|
@ -174,11 +214,11 @@ nlohmann::json ProfileManifest::toJSON(Store & store) const
|
||||||
obj["attrPath"] = element.source->attrPath;
|
obj["attrPath"] = element.source->attrPath;
|
||||||
obj["outputs"] = element.source->outputs;
|
obj["outputs"] = element.source->outputs;
|
||||||
}
|
}
|
||||||
array.push_back(obj);
|
es[name] = obj;
|
||||||
}
|
}
|
||||||
nlohmann::json json;
|
nlohmann::json json;
|
||||||
json["version"] = 2;
|
json["version"] = 3;
|
||||||
json["elements"] = array;
|
json["elements"] = es;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,7 +229,7 @@ StorePath ProfileManifest::build(ref<Store> store)
|
||||||
StorePathSet references;
|
StorePathSet references;
|
||||||
|
|
||||||
Packages pkgs;
|
Packages pkgs;
|
||||||
for (auto & element : elements) {
|
for (auto & [name, element] : elements) {
|
||||||
for (auto & path : element.storePaths) {
|
for (auto & path : element.storePaths) {
|
||||||
if (element.active) {
|
if (element.active) {
|
||||||
pkgs.emplace_back(store->printStorePath(path), true, element.priority);
|
pkgs.emplace_back(store->printStorePath(path), true, element.priority);
|
||||||
|
@ -235,35 +275,29 @@ void ProfileManifest::printDiff(
|
||||||
const ProfileManifest & prev, const ProfileManifest & cur, std::string_view indent
|
const ProfileManifest & prev, const ProfileManifest & cur, std::string_view indent
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
auto prevElems = prev.elements;
|
auto prevElemIt = prev.elements.begin();
|
||||||
std::sort(prevElems.begin(), prevElems.end());
|
auto curElemIt = cur.elements.begin();
|
||||||
|
|
||||||
auto curElems = cur.elements;
|
|
||||||
std::sort(curElems.begin(), curElems.end());
|
|
||||||
|
|
||||||
auto i = prevElems.begin();
|
|
||||||
auto j = curElems.begin();
|
|
||||||
|
|
||||||
bool changes = false;
|
bool changes = false;
|
||||||
|
|
||||||
while (i != prevElems.end() || j != curElems.end()) {
|
while (prevElemIt != prev.elements.end() || curElemIt != cur.elements.end()) {
|
||||||
if (j != curElems.end() && (i == prevElems.end() || i->identifier() > j->identifier())) {
|
if (curElemIt != cur.elements.end() && (prevElemIt == prev.elements.end() || prevElemIt->first > curElemIt->first)) {
|
||||||
logger->cout("%s%s: ∅ -> %s", indent, j->identifier(), j->versions());
|
logger->cout("%s%s: ∅ -> %s", indent, curElemIt->second.identifier(), curElemIt->second.versions());
|
||||||
changes = true;
|
changes = true;
|
||||||
++j;
|
++curElemIt;
|
||||||
} else if (i != prevElems.end() && (j == curElems.end() || i->identifier() < j->identifier())) {
|
} else if (prevElemIt != prev.elements.end() && (curElemIt == cur.elements.end() || prevElemIt->first < curElemIt->first)) {
|
||||||
logger->cout("%s%s: %s -> ∅", indent, i->identifier(), i->versions());
|
logger->cout("%s%s: %s -> ∅", indent, prevElemIt->second.identifier(), prevElemIt->second.versions());
|
||||||
changes = true;
|
changes = true;
|
||||||
++i;
|
++prevElemIt;
|
||||||
} else {
|
} else {
|
||||||
auto v1 = i->versions();
|
auto v1 = prevElemIt->second.versions();
|
||||||
auto v2 = j->versions();
|
auto v2 = curElemIt->second.versions();
|
||||||
if (v1 != v2) {
|
if (v1 != v2) {
|
||||||
logger->cout("%s%s: %s -> %s", indent, i->identifier(), v1, v2);
|
logger->cout("%s%s: %s -> %s", indent, prevElemIt->second.identifier(), v1, v2);
|
||||||
changes = true;
|
changes = true;
|
||||||
}
|
}
|
||||||
++i;
|
++prevElemIt;
|
||||||
++j;
|
++curElemIt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
#include "flake/flakeref.hh"
|
#include "flake/flakeref.hh"
|
||||||
#include "get-drvs.hh"
|
#include "get-drvs.hh"
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
|
#include "url.hh"
|
||||||
|
#include "url-name.hh"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
@ -54,7 +56,7 @@ struct ProfileElement
|
||||||
|
|
||||||
struct ProfileManifest
|
struct ProfileManifest
|
||||||
{
|
{
|
||||||
std::vector<ProfileElement> elements;
|
std::map<std::string, ProfileElement> elements;
|
||||||
|
|
||||||
ProfileManifest() { }
|
ProfileManifest() { }
|
||||||
|
|
||||||
|
@ -64,6 +66,9 @@ struct ProfileManifest
|
||||||
|
|
||||||
StorePath build(ref<Store> store);
|
StorePath build(ref<Store> store);
|
||||||
|
|
||||||
|
void addElement(std::string_view nameCandidate, ProfileElement element);
|
||||||
|
void addElement(ProfileElement element);
|
||||||
|
|
||||||
static void printDiff(const ProfileManifest & prev, const ProfileManifest & cur, std::string_view indent);
|
static void printDiff(const ProfileManifest & prev, const ProfileManifest & cur, std::string_view indent);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ libutil_sources = files(
|
||||||
'tarfile.cc',
|
'tarfile.cc',
|
||||||
'thread-pool.cc',
|
'thread-pool.cc',
|
||||||
'url.cc',
|
'url.cc',
|
||||||
|
'url-name.cc',
|
||||||
'util.cc',
|
'util.cc',
|
||||||
'xml-writer.cc',
|
'xml-writer.cc',
|
||||||
)
|
)
|
||||||
|
@ -92,6 +93,7 @@ libutil_headers = files(
|
||||||
'topo-sort.hh',
|
'topo-sort.hh',
|
||||||
'types.hh',
|
'types.hh',
|
||||||
'url-parts.hh',
|
'url-parts.hh',
|
||||||
|
'url-name.hh',
|
||||||
'url.hh',
|
'url.hh',
|
||||||
'util.hh',
|
'util.hh',
|
||||||
'variant-wrapper.hh',
|
'variant-wrapper.hh',
|
||||||
|
|
59
src/libutil/url-name.cc
Normal file
59
src/libutil/url-name.cc
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
#include <iostream>
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
|
#include "url-name.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
static std::string const attributeNamePattern("[a-zA-Z0-9_-]+");
|
||||||
|
static std::regex const lastAttributeRegex("(?:" + attributeNamePattern + "\\.)*(?!default)(" + attributeNamePattern +")(\\^.*)?");
|
||||||
|
static std::string const pathSegmentPattern("[a-zA-Z0-9_-]+");
|
||||||
|
static std::regex const lastPathSegmentRegex(".*/(" + pathSegmentPattern +")");
|
||||||
|
static std::regex const secondPathSegmentRegex("(?:" + pathSegmentPattern + ")/(" + pathSegmentPattern +")(?:/.*)?");
|
||||||
|
static std::regex const gitProviderRegex("github|gitlab|sourcehut");
|
||||||
|
static std::regex const gitSchemeRegex("git($|\\+.*)");
|
||||||
|
static std::regex const defaultOutputRegex(".*\\.default($|\\^.*)");
|
||||||
|
|
||||||
|
std::optional<std::string> getNameFromURL(ParsedURL const & url)
|
||||||
|
{
|
||||||
|
std::smatch match;
|
||||||
|
|
||||||
|
/* If there is a dir= argument, use its value */
|
||||||
|
if (url.query.count("dir") > 0) {
|
||||||
|
return url.query.at("dir");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If the fragment isn't a "default" and contains two attribute elements, use the last one */
|
||||||
|
if (std::regex_match(url.fragment, match, lastAttributeRegex)) {
|
||||||
|
return match.str(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If this is a github/gitlab/sourcehut flake, use the repo name */
|
||||||
|
if (
|
||||||
|
std::regex_match(url.scheme, gitProviderRegex)
|
||||||
|
&& std::regex_match(url.path, match, secondPathSegmentRegex)
|
||||||
|
) {
|
||||||
|
return match.str(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If it is a regular git flake, use the directory name */
|
||||||
|
if (
|
||||||
|
std::regex_match(url.scheme, gitSchemeRegex)
|
||||||
|
&& std::regex_match(url.path, match, lastPathSegmentRegex)
|
||||||
|
) {
|
||||||
|
return match.str(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If everything failed but there is a non-default fragment, use it in full */
|
||||||
|
if (!url.fragment.empty() && !std::regex_match(url.fragment, defaultOutputRegex))
|
||||||
|
return url.fragment;
|
||||||
|
|
||||||
|
/* If there is no fragment, take the last element of the path */
|
||||||
|
if (std::regex_match(url.path, match, lastPathSegmentRegex))
|
||||||
|
return match.str(1);
|
||||||
|
|
||||||
|
/* If even that didn't work, the URL does not contain enough info to determine a useful name */
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
26
src/libutil/url-name.hh
Normal file
26
src/libutil/url-name.hh
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
#pragma once
|
||||||
|
///@file url-name.hh, for some hueristic-ish URL parsing.
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "url.hh"
|
||||||
|
#include "url-parts.hh"
|
||||||
|
#include "util.hh"
|
||||||
|
#include "split.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to extract a reasonably unique and meaningful, human-readable
|
||||||
|
* name of a flake output from a parsed URL.
|
||||||
|
* When nullopt is returned, the callsite should use information available
|
||||||
|
* to it outside of the URL to determine a useful name.
|
||||||
|
* This is a heuristic approach intended for user interfaces.
|
||||||
|
* @return nullopt if the extracted name is not useful to identify a
|
||||||
|
* flake output, for example because it is empty or "default".
|
||||||
|
* Otherwise returns the extracted name.
|
||||||
|
*/
|
||||||
|
std::optional<std::string> getNameFromURL(ParsedURL const & url);
|
||||||
|
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ const static std::string userRegex = "(?:(?:" + unreservedRegex + "|" + pctEncod
|
||||||
const static std::string authorityRegex = "(?:" + userRegex + "@)?" + hostRegex + "(?::[0-9]+)?";
|
const static std::string authorityRegex = "(?:" + userRegex + "@)?" + hostRegex + "(?::[0-9]+)?";
|
||||||
const static std::string pcharRegex = "(?:" + unreservedRegex + "|" + pctEncoded + "|" + subdelimsRegex + "|[:@])";
|
const static std::string pcharRegex = "(?:" + unreservedRegex + "|" + pctEncoded + "|" + subdelimsRegex + "|[:@])";
|
||||||
const static std::string queryRegex = "(?:" + pcharRegex + "|[/? \"])*";
|
const static std::string queryRegex = "(?:" + pcharRegex + "|[/? \"])*";
|
||||||
|
const static std::string fragmentRegex = "(?:" + pcharRegex + "|[/? \"^])*";
|
||||||
const static std::string segmentRegex = "(?:" + pcharRegex + "*)";
|
const static std::string segmentRegex = "(?:" + pcharRegex + "*)";
|
||||||
const static std::string absPathRegex = "(?:(?:/" + segmentRegex + ")*/?)";
|
const static std::string absPathRegex = "(?:(?:/" + segmentRegex + ")*/?)";
|
||||||
const static std::string pathRegex = "(?:" + segmentRegex + "(?:/" + segmentRegex + ")*/?)";
|
const static std::string pathRegex = "(?:" + segmentRegex + "(?:/" + segmentRegex + ")*/?)";
|
||||||
|
|
|
@ -16,7 +16,7 @@ ParsedURL parseURL(const std::string & url)
|
||||||
"((" + schemeRegex + "):"
|
"((" + schemeRegex + "):"
|
||||||
+ "(?:(?://(" + authorityRegex + ")(" + absPathRegex + "))|(/?" + pathRegex + ")))"
|
+ "(?:(?://(" + authorityRegex + ")(" + absPathRegex + "))|(/?" + pathRegex + ")))"
|
||||||
+ "(?:\\?(" + queryRegex + "))?"
|
+ "(?:\\?(" + queryRegex + "))?"
|
||||||
+ "(?:#(" + queryRegex + "))?",
|
+ "(?:#(" + fragmentRegex + "))?",
|
||||||
std::regex::ECMAScript);
|
std::regex::ECMAScript);
|
||||||
|
|
||||||
std::smatch match;
|
std::smatch match;
|
||||||
|
|
|
@ -6,13 +6,13 @@ R""(
|
||||||
|
|
||||||
```console
|
```console
|
||||||
# nix profile list
|
# nix profile list
|
||||||
Index: 0
|
Name: gdb
|
||||||
Flake attribute: legacyPackages.x86_64-linux.gdb
|
Flake attribute: legacyPackages.x86_64-linux.gdb
|
||||||
Original flake URL: flake:nixpkgs
|
Original flake URL: flake:nixpkgs
|
||||||
Locked flake URL: github:NixOS/nixpkgs/7b38b03d76ab71bdc8dc325e3f6338d984cc35ca
|
Locked flake URL: github:NixOS/nixpkgs/7b38b03d76ab71bdc8dc325e3f6338d984cc35ca
|
||||||
Store paths: /nix/store/indzcw5wvlhx6vwk7k4iq29q15chvr3d-gdb-11.1
|
Store paths: /nix/store/indzcw5wvlhx6vwk7k4iq29q15chvr3d-gdb-11.1
|
||||||
|
|
||||||
Index: 1
|
Name: blender-bin
|
||||||
Flake attribute: packages.x86_64-linux.default
|
Flake attribute: packages.x86_64-linux.default
|
||||||
Original flake URL: flake:blender-bin
|
Original flake URL: flake:blender-bin
|
||||||
Locked flake URL: github:edolstra/nix-warez/91f2ffee657bf834e4475865ae336e2379282d34?dir=blender
|
Locked flake URL: github:edolstra/nix-warez/91f2ffee657bf834e4475865ae336e2379282d34?dir=blender
|
||||||
|
@ -26,7 +26,7 @@ R""(
|
||||||
# nix build github:edolstra/nix-warez/91f2ffee657bf834e4475865ae336e2379282d34?dir=blender#packages.x86_64-linux.default
|
# nix build github:edolstra/nix-warez/91f2ffee657bf834e4475865ae336e2379282d34?dir=blender#packages.x86_64-linux.default
|
||||||
```
|
```
|
||||||
|
|
||||||
will build the package with index 1 shown above.
|
will build the package with name blender-bin shown above.
|
||||||
|
|
||||||
# Description
|
# Description
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ This command shows what packages are currently installed in a
|
||||||
profile. For each installed package, it shows the following
|
profile. For each installed package, it shows the following
|
||||||
information:
|
information:
|
||||||
|
|
||||||
* `Index`: An integer that can be used to unambiguously identify the
|
* `Name`: A unique name used to unambiguously identify the
|
||||||
package in invocations of `nix profile remove` and `nix profile
|
package in invocations of `nix profile remove` and `nix profile
|
||||||
upgrade`.
|
upgrade`.
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@ R""(
|
||||||
|
|
||||||
# Examples
|
# Examples
|
||||||
|
|
||||||
* Remove a package by position:
|
* Remove a package by name:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
# nix profile remove 3
|
# nix profile remove hello
|
||||||
```
|
```
|
||||||
|
|
||||||
* Remove a package by attribute path:
|
* Remove a package by attribute path:
|
||||||
|
|
|
@ -9,21 +9,16 @@ R""(
|
||||||
# nix profile upgrade '.*'
|
# nix profile upgrade '.*'
|
||||||
```
|
```
|
||||||
|
|
||||||
* Upgrade a specific package:
|
* Upgrade a specific package by name:
|
||||||
|
|
||||||
|
```console
|
||||||
|
# nix profile upgrade hello
|
||||||
|
```
|
||||||
|
|
||||||
```console
|
```console
|
||||||
# nix profile upgrade packages.x86_64-linux.hello
|
# nix profile upgrade packages.x86_64-linux.hello
|
||||||
```
|
```
|
||||||
|
|
||||||
* Upgrade a specific profile element by number:
|
|
||||||
|
|
||||||
```console
|
|
||||||
# nix profile list
|
|
||||||
0 flake:nixpkgs#legacyPackages.x86_64-linux.spotify …
|
|
||||||
|
|
||||||
# nix profile upgrade 0
|
|
||||||
```
|
|
||||||
|
|
||||||
# Description
|
# Description
|
||||||
|
|
||||||
This command upgrades a previously installed package in a Nix profile,
|
This command upgrades a previously installed package in a Nix profile,
|
||||||
|
|
|
@ -105,7 +105,7 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
|
||||||
|
|
||||||
element.updateStorePaths(getEvalStore(), store, res);
|
element.updateStorePaths(getEvalStore(), store, res);
|
||||||
|
|
||||||
manifest.elements.push_back(std::move(element));
|
manifest.addElement(std::move(element));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -115,7 +115,7 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
|
||||||
// See https://github.com/NixOS/nix/compare/3efa476c5439f8f6c1968a6ba20a31d1239c2f04..1fe5d172ece51a619e879c4b86f603d9495cc102
|
// See https://github.com/NixOS/nix/compare/3efa476c5439f8f6c1968a6ba20a31d1239c2f04..1fe5d172ece51a619e879c4b86f603d9495cc102
|
||||||
auto findRefByFilePath = [&]<typename Iterator>(Iterator begin, Iterator end) {
|
auto findRefByFilePath = [&]<typename Iterator>(Iterator begin, Iterator end) {
|
||||||
for (auto it = begin; it != end; it++) {
|
for (auto it = begin; it != end; it++) {
|
||||||
auto profileElement = *it;
|
auto & profileElement = it->second;
|
||||||
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.toInstallables(*store));
|
return std::pair(conflictError.fileA, profileElement.toInstallables(*store));
|
||||||
|
@ -183,37 +183,42 @@ public:
|
||||||
std::string pattern;
|
std::string pattern;
|
||||||
std::regex reg;
|
std::regex reg;
|
||||||
};
|
};
|
||||||
typedef std::variant<size_t, Path, RegexPattern> Matcher;
|
using Matcher = std::variant<Path, RegexPattern>;
|
||||||
|
|
||||||
std::vector<Matcher> getMatchers(ref<Store> store)
|
std::vector<Matcher> getMatchers(ref<Store> store)
|
||||||
{
|
{
|
||||||
std::vector<Matcher> res;
|
std::vector<Matcher> res;
|
||||||
|
|
||||||
for (auto & s : _matchers) {
|
for (auto & s : _matchers) {
|
||||||
if (auto n = string2Int<size_t>(s))
|
if (auto n = string2Int<size_t>(s)) {
|
||||||
res.push_back(*n);
|
throw Error("'nix profile' no longer supports indices ('%d')", *n);
|
||||||
else if (store->isStorePath(s))
|
} else if (store->isStorePath(s)) {
|
||||||
res.push_back(s);
|
res.push_back(s);
|
||||||
else
|
} else {
|
||||||
res.push_back(RegexPattern{s,std::regex(s, std::regex::extended | std::regex::icase)});
|
res.push_back(RegexPattern{s,std::regex(s, std::regex::extended | std::regex::icase)});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool matches(const Store & store, const ProfileElement & element, size_t pos, const std::vector<Matcher> & matchers)
|
bool matches(
|
||||||
|
Store const & store,
|
||||||
|
// regex_match doesn't take a string_view lol
|
||||||
|
std::string const & name,
|
||||||
|
ProfileElement const & element,
|
||||||
|
std::vector<Matcher> const & matchers
|
||||||
|
)
|
||||||
{
|
{
|
||||||
for (auto & matcher : matchers) {
|
for (auto & matcher : matchers) {
|
||||||
if (auto n = std::get_if<size_t>(&matcher)) {
|
if (auto path = std::get_if<Path>(&matcher)) {
|
||||||
if (*n == pos) return true;
|
|
||||||
} else if (auto path = std::get_if<Path>(&matcher)) {
|
|
||||||
if (element.storePaths.count(store.parseStorePath(*path))) return true;
|
if (element.storePaths.count(store.parseStorePath(*path))) return true;
|
||||||
} else if (auto regex = std::get_if<RegexPattern>(&matcher)) {
|
} else if (auto regex = std::get_if<RegexPattern>(&matcher)) {
|
||||||
if (element.source
|
if (std::regex_match(name, regex->reg)) {
|
||||||
&& std::regex_match(element.source->attrPath, regex->reg))
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -241,10 +246,9 @@ struct CmdProfileRemove : virtual EvalCommand, MixDefaultProfile, MixProfileElem
|
||||||
|
|
||||||
ProfileManifest newManifest;
|
ProfileManifest newManifest;
|
||||||
|
|
||||||
for (size_t i = 0; i < oldManifest.elements.size(); ++i) {
|
for (auto & [name, element] : oldManifest.elements) {
|
||||||
auto & element(oldManifest.elements[i]);
|
if (!matches(*store, name, element, matchers)) {
|
||||||
if (!matches(*store, element, i, matchers)) {
|
newManifest.elements.insert_or_assign(name, std::move(element));
|
||||||
newManifest.elements.push_back(std::move(element));
|
|
||||||
} else {
|
} else {
|
||||||
notice("removing '%s'", element.identifier());
|
notice("removing '%s'", element.identifier());
|
||||||
}
|
}
|
||||||
|
@ -257,9 +261,7 @@ struct CmdProfileRemove : virtual EvalCommand, MixDefaultProfile, MixProfileElem
|
||||||
|
|
||||||
if (removedCount == 0) {
|
if (removedCount == 0) {
|
||||||
for (auto matcher: matchers) {
|
for (auto matcher: matchers) {
|
||||||
if (const size_t * index = std::get_if<size_t>(&matcher)){
|
if (const Path * path = std::get_if<Path>(&matcher)) {
|
||||||
warn("'%d' is not a valid index", *index);
|
|
||||||
} else if (const Path * path = std::get_if<Path>(&matcher)){
|
|
||||||
warn("'%s' does not match any paths", *path);
|
warn("'%s' does not match any paths", *path);
|
||||||
} else if (const RegexPattern * regex = std::get_if<RegexPattern>(&matcher)){
|
} else if (const RegexPattern * regex = std::get_if<RegexPattern>(&matcher)){
|
||||||
warn("'%s' does not match any packages", regex->pattern);
|
warn("'%s' does not match any packages", regex->pattern);
|
||||||
|
@ -292,20 +294,43 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
|
||||||
auto matchers = getMatchers(store);
|
auto matchers = getMatchers(store);
|
||||||
|
|
||||||
Installables installables;
|
Installables installables;
|
||||||
std::vector<size_t> indices;
|
std::vector<ProfileElement *> elems;
|
||||||
|
|
||||||
|
auto matchedCount = 0;
|
||||||
auto upgradedCount = 0;
|
auto upgradedCount = 0;
|
||||||
|
|
||||||
for (size_t i = 0; i < manifest.elements.size(); ++i) {
|
for (auto & [name, element] : manifest.elements) {
|
||||||
auto & element(manifest.elements[i]);
|
if (!matches(*store, name, element, matchers)) {
|
||||||
if (element.source
|
continue;
|
||||||
&& !element.source->originalRef.input.isLocked()
|
}
|
||||||
&& matches(*store, element, i, matchers))
|
|
||||||
{
|
matchedCount += 1;
|
||||||
|
|
||||||
|
if (!element.source) {
|
||||||
|
warn(
|
||||||
|
"Found package '%s', but it was not installed from a flake, so it can't be checked for upgrades",
|
||||||
|
element.identifier()
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.source->originalRef.input.isLocked()) {
|
||||||
|
warn(
|
||||||
|
"Found package '%s', but it was installed from a locked flake reference so it can't be upgraded",
|
||||||
|
element.identifier()
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
upgradedCount++;
|
upgradedCount++;
|
||||||
|
|
||||||
Activity act(*logger, lvlChatty, actUnknown,
|
Activity act(
|
||||||
fmt("checking '%s' for updates", element.source->attrPath));
|
*logger,
|
||||||
|
lvlChatty,
|
||||||
|
actUnknown,
|
||||||
|
fmt("checking '%s' for updates", element.source->attrPath),
|
||||||
|
Logger::Fields{element.source->attrPath}
|
||||||
|
);
|
||||||
|
|
||||||
auto installable = make_ref<InstallableFlake>(
|
auto installable = make_ref<InstallableFlake>(
|
||||||
this,
|
this,
|
||||||
|
@ -315,19 +340,29 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
|
||||||
element.source->outputs,
|
element.source->outputs,
|
||||||
Strings{element.source->attrPath},
|
Strings{element.source->attrPath},
|
||||||
Strings{},
|
Strings{},
|
||||||
lockFlags);
|
lockFlags
|
||||||
|
);
|
||||||
|
|
||||||
auto derivedPaths = installable->toDerivedPaths();
|
auto derivedPaths = installable->toDerivedPaths();
|
||||||
if (derivedPaths.empty()) continue;
|
if (derivedPaths.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
auto * infop = dynamic_cast<ExtraPathInfoFlake *>(&*derivedPaths[0].info);
|
auto * infop = dynamic_cast<ExtraPathInfoFlake *>(&*derivedPaths[0].info);
|
||||||
// `InstallableFlake` should use `ExtraPathInfoFlake`.
|
// `InstallableFlake` should use `ExtraPathInfoFlake`.
|
||||||
assert(infop);
|
assert(infop);
|
||||||
auto & info = *infop;
|
auto & info = *infop;
|
||||||
|
|
||||||
if (element.source->lockedRef == info.flake.lockedRef) continue;
|
if (element.source->lockedRef == info.flake.lockedRef) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
printInfo("upgrading '%s' from flake '%s' to '%s'",
|
printInfo(
|
||||||
element.source->attrPath, element.source->lockedRef, info.flake.lockedRef);
|
"upgrading '%s' from flake '%s' to '%s'",
|
||||||
|
element.source->attrPath,
|
||||||
|
element.source->lockedRef,
|
||||||
|
info.flake.lockedRef
|
||||||
|
);
|
||||||
|
|
||||||
element.source = ProfileElementSource {
|
element.source = ProfileElementSource {
|
||||||
.originalRef = installable->flakeRef,
|
.originalRef = installable->flakeRef,
|
||||||
|
@ -337,20 +372,22 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
|
||||||
};
|
};
|
||||||
|
|
||||||
installables.push_back(installable);
|
installables.push_back(installable);
|
||||||
indices.push_back(i);
|
elems.push_back(&element);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (upgradedCount == 0) {
|
if (upgradedCount == 0) {
|
||||||
|
if (matchedCount == 0) {
|
||||||
for (auto & matcher : matchers) {
|
for (auto & matcher : matchers) {
|
||||||
if (const size_t * index = std::get_if<size_t>(&matcher)){
|
if (const Path * path = std::get_if<Path>(&matcher)){
|
||||||
warn("'%d' is not a valid index", *index);
|
|
||||||
} else if (const Path * path = std::get_if<Path>(&matcher)){
|
|
||||||
warn("'%s' does not match any paths", *path);
|
warn("'%s' does not match any paths", *path);
|
||||||
} else if (const RegexPattern * regex = std::get_if<RegexPattern>(&matcher)) {
|
} else if (const RegexPattern * regex = std::get_if<RegexPattern>(&matcher)) {
|
||||||
warn("'%s' does not match any packages", regex->pattern);
|
warn("'%s' does not match any packages", regex->pattern);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
warn("Found some packages but none of them could be upgraded");
|
||||||
|
}
|
||||||
warn ("Use 'nix profile list' to see the current profile.");
|
warn ("Use 'nix profile list' to see the current profile.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,7 +397,7 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
|
||||||
|
|
||||||
for (size_t i = 0; i < installables.size(); ++i) {
|
for (size_t i = 0; i < installables.size(); ++i) {
|
||||||
auto & installable = installables.at(i);
|
auto & installable = installables.at(i);
|
||||||
auto & element = manifest.elements[indices.at(i)];
|
auto & element = *elems.at(i);
|
||||||
element.updateStorePaths(
|
element.updateStorePaths(
|
||||||
getEvalStore(),
|
getEvalStore(),
|
||||||
store,
|
store,
|
||||||
|
@ -392,12 +429,16 @@ struct CmdProfileList : virtual EvalCommand, virtual StoreCommand, MixDefaultPro
|
||||||
if (json) {
|
if (json) {
|
||||||
std::cout << manifest.toJSON(*store).dump() << "\n";
|
std::cout << manifest.toJSON(*store).dump() << "\n";
|
||||||
} else {
|
} else {
|
||||||
for (size_t i = 0; i < manifest.elements.size(); ++i) {
|
for (auto const & [i, nameElemPair] : enumerate(manifest.elements)) {
|
||||||
auto & element(manifest.elements[i]);
|
auto & [name, element] = nameElemPair;
|
||||||
if (i) logger->cout("");
|
if (i) {
|
||||||
logger->cout("Index: " ANSI_BOLD "%s" ANSI_NORMAL "%s",
|
logger->cout("");
|
||||||
i,
|
}
|
||||||
element.active ? "" : " " ANSI_RED "(inactive)" ANSI_NORMAL);
|
logger->cout(
|
||||||
|
"Name: " ANSI_BOLD "%s" ANSI_NORMAL "%s",
|
||||||
|
name,
|
||||||
|
element.active ? "" : " " ANSI_RED "(inactive)" ANSI_NORMAL
|
||||||
|
);
|
||||||
if (element.source) {
|
if (element.source) {
|
||||||
logger->cout("Flake attribute: %s%s", element.source->attrPath, element.source->outputs.to_string());
|
logger->cout("Flake attribute: %s%s", element.source->attrPath, element.source->outputs.to_string());
|
||||||
logger->cout("Original flake URL: %s", element.source->originalRef.to_string());
|
logger->cout("Original flake URL: %s", element.source->originalRef.to_string());
|
||||||
|
|
|
@ -208,7 +208,8 @@ struct CmdUpgradeNix : MixDryRun, EvalCommand
|
||||||
// Find which profile element has Nix in it.
|
// Find which profile element has Nix in it.
|
||||||
// It should be impossible to *not* have Nix, since we grabbed this
|
// It should be impossible to *not* have Nix, since we grabbed this
|
||||||
// store path by looking for things with bin/nix-env in them anyway.
|
// store path by looking for things with bin/nix-env in them anyway.
|
||||||
auto findNix = [&](ProfileElement const & elem) -> bool {
|
auto findNix = [&](std::pair<std::string, ProfileElement> const & nameElemPair) -> bool {
|
||||||
|
auto const & [name, elem] = nameElemPair;
|
||||||
for (auto const & ePath : elem.storePaths) {
|
for (auto const & ePath : elem.storePaths) {
|
||||||
auto const nixEnv = store->printStorePath(ePath) + "/bin/nix-env";
|
auto const nixEnv = store->printStorePath(ePath) + "/bin/nix-env";
|
||||||
if (pathExists(nixEnv)) {
|
if (pathExists(nixEnv)) {
|
||||||
|
@ -226,14 +227,15 @@ struct CmdUpgradeNix : MixDryRun, EvalCommand
|
||||||
// *Should* be impossible...
|
// *Should* be impossible...
|
||||||
assert(elemWithNix != std::end(manifest.elements));
|
assert(elemWithNix != std::end(manifest.elements));
|
||||||
|
|
||||||
|
auto const nixElemName = elemWithNix->first;
|
||||||
|
|
||||||
// Now create a new profile element for the new Nix version...
|
// Now create a new profile element for the new Nix version...
|
||||||
ProfileElement elemForNewNix = {
|
ProfileElement elemForNewNix = {
|
||||||
.storePaths = {newNix},
|
.storePaths = {newNix},
|
||||||
};
|
};
|
||||||
|
|
||||||
// ...and splork it into the manifest where the old profile element was.
|
// ...and splork it into the manifest where the old profile element was.
|
||||||
// (Remember, elemWithNix is an iterator)
|
manifest.elements.at(nixElemName) = elemForNewNix;
|
||||||
*elemWithNix = elemForNewNix;
|
|
||||||
|
|
||||||
// Build the new profile, and switch to it.
|
// Build the new profile, and switch to it.
|
||||||
StorePath const newProfile = manifest.build(store);
|
StorePath const newProfile = manifest.build(store);
|
||||||
|
|
|
@ -47,9 +47,9 @@ cp ./config.nix $flake1Dir/
|
||||||
|
|
||||||
# Test upgrading from nix-env.
|
# Test upgrading from nix-env.
|
||||||
nix-env -f ./user-envs.nix -i foo-1.0
|
nix-env -f ./user-envs.nix -i foo-1.0
|
||||||
nix profile list | grep -A2 'Index:.*0' | grep 'Store paths:.*foo-1.0'
|
nix profile list | grep -A2 'Name:.*foo' | grep 'Store paths:.*foo-1.0'
|
||||||
nix profile install $flake1Dir -L
|
nix profile install $flake1Dir -L
|
||||||
nix profile list | grep -A4 'Index:.*1' | grep 'Locked flake URL:.*narHash'
|
nix profile list | grep -A4 'Name:.*flake1' | grep 'Locked flake URL:.*narHash'
|
||||||
[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello World" ]]
|
[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello World" ]]
|
||||||
[ -e $TEST_HOME/.nix-profile/share/man ]
|
[ -e $TEST_HOME/.nix-profile/share/man ]
|
||||||
(! [ -e $TEST_HOME/.nix-profile/include ])
|
(! [ -e $TEST_HOME/.nix-profile/include ])
|
||||||
|
@ -60,7 +60,7 @@ nix profile diff-closures | grep 'env-manifest.nix: ε → ∅'
|
||||||
# Test XDG Base Directories support
|
# Test XDG Base Directories support
|
||||||
|
|
||||||
export NIX_CONFIG="use-xdg-base-directories = true"
|
export NIX_CONFIG="use-xdg-base-directories = true"
|
||||||
nix profile remove 1
|
nix profile remove flake1 2>&1 | grep 'removed 1 packages'
|
||||||
nix profile install $flake1Dir
|
nix profile install $flake1Dir
|
||||||
[[ $($TEST_HOME/.local/state/nix/profile/bin/hello) = "Hello World" ]]
|
[[ $($TEST_HOME/.local/state/nix/profile/bin/hello) = "Hello World" ]]
|
||||||
unset NIX_CONFIG
|
unset NIX_CONFIG
|
||||||
|
@ -68,7 +68,7 @@ unset NIX_CONFIG
|
||||||
# Test upgrading a package.
|
# Test upgrading a package.
|
||||||
printf NixOS > $flake1Dir/who
|
printf NixOS > $flake1Dir/who
|
||||||
printf 2.0 > $flake1Dir/version
|
printf 2.0 > $flake1Dir/version
|
||||||
nix profile upgrade 1
|
nix profile upgrade flake1
|
||||||
[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello NixOS" ]]
|
[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello NixOS" ]]
|
||||||
nix profile history | grep "packages.$system.default: 1.0, 1.0-man -> 2.0, 2.0-man"
|
nix profile history | grep "packages.$system.default: 1.0, 1.0-man -> 2.0, 2.0-man"
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ nix profile rollback
|
||||||
|
|
||||||
# Test uninstall.
|
# Test uninstall.
|
||||||
[ -e $TEST_HOME/.nix-profile/bin/foo ]
|
[ -e $TEST_HOME/.nix-profile/bin/foo ]
|
||||||
nix profile remove 0
|
nix profile remove "foo" 2>&1 | grep 'removed 1 packages'
|
||||||
(! [ -e $TEST_HOME/.nix-profile/bin/foo ])
|
(! [ -e $TEST_HOME/.nix-profile/bin/foo ])
|
||||||
nix profile history | grep 'foo: 1.0 -> ∅'
|
nix profile history | grep 'foo: 1.0 -> ∅'
|
||||||
nix profile diff-closures | grep 'Version 3 -> 4'
|
nix profile diff-closures | grep 'Version 3 -> 4'
|
||||||
|
@ -89,10 +89,18 @@ nix profile diff-closures | grep 'Version 3 -> 4'
|
||||||
# Test installing a non-flake package.
|
# Test installing a non-flake package.
|
||||||
nix profile install --file ./simple.nix ''
|
nix profile install --file ./simple.nix ''
|
||||||
[[ $(cat $TEST_HOME/.nix-profile/hello) = "Hello World!" ]]
|
[[ $(cat $TEST_HOME/.nix-profile/hello) = "Hello World!" ]]
|
||||||
nix profile remove 1
|
nix profile remove simple 2>&1 | grep 'removed 1 packages'
|
||||||
nix profile install $(nix-build --no-out-link ./simple.nix)
|
nix profile install $(nix-build --no-out-link ./simple.nix)
|
||||||
[[ $(cat $TEST_HOME/.nix-profile/hello) = "Hello World!" ]]
|
[[ $(cat $TEST_HOME/.nix-profile/hello) = "Hello World!" ]]
|
||||||
|
|
||||||
|
# Test packages with same name from different sources
|
||||||
|
mkdir $TEST_ROOT/simple-too
|
||||||
|
cp ./simple.nix ./config.nix simple.builder.sh $TEST_ROOT/simple-too
|
||||||
|
nix profile install --file $TEST_ROOT/simple-too/simple.nix ''
|
||||||
|
nix profile list | grep -A4 'Name:.*simple' | grep 'Name:.*simple-1'
|
||||||
|
nix profile remove simple 2>&1 | grep 'removed 1 packages'
|
||||||
|
nix profile remove simple-1 2>&1 | grep 'removed 1 packages'
|
||||||
|
|
||||||
# Test wipe-history.
|
# Test wipe-history.
|
||||||
nix profile wipe-history
|
nix profile wipe-history
|
||||||
[[ $(nix profile history | grep Version | wc -l) -eq 1 ]]
|
[[ $(nix profile history | grep Version | wc -l) -eq 1 ]]
|
||||||
|
@ -100,11 +108,11 @@ nix profile wipe-history
|
||||||
# Test upgrade to CA package.
|
# Test upgrade to CA package.
|
||||||
printf true > $flake1Dir/ca.nix
|
printf true > $flake1Dir/ca.nix
|
||||||
printf 3.0 > $flake1Dir/version
|
printf 3.0 > $flake1Dir/version
|
||||||
nix profile upgrade 0
|
nix profile upgrade flake1
|
||||||
nix profile history | grep "packages.$system.default: 1.0, 1.0-man -> 3.0, 3.0-man"
|
nix profile history | grep "packages.$system.default: 1.0, 1.0-man -> 3.0, 3.0-man"
|
||||||
|
|
||||||
# Test new install of CA package.
|
# Test new install of CA package.
|
||||||
nix profile remove 0
|
nix profile remove flake1 2>&1 | grep 'removed 1 packages'
|
||||||
printf 4.0 > $flake1Dir/version
|
printf 4.0 > $flake1Dir/version
|
||||||
printf Utrecht > $flake1Dir/who
|
printf Utrecht > $flake1Dir/who
|
||||||
nix profile install $flake1Dir
|
nix profile install $flake1Dir
|
||||||
|
@ -112,26 +120,27 @@ nix profile install $flake1Dir
|
||||||
[[ $(nix path-info --json $(realpath $TEST_HOME/.nix-profile/bin/hello) | jq -r .[].ca) =~ fixed:r:sha256: ]]
|
[[ $(nix path-info --json $(realpath $TEST_HOME/.nix-profile/bin/hello) | jq -r .[].ca) =~ fixed:r:sha256: ]]
|
||||||
|
|
||||||
# Override the outputs.
|
# Override the outputs.
|
||||||
nix profile remove 0 1
|
nix profile remove simple flake1
|
||||||
nix profile install "$flake1Dir^*"
|
nix profile install "$flake1Dir^*"
|
||||||
[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello Utrecht" ]]
|
[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello Utrecht" ]]
|
||||||
[ -e $TEST_HOME/.nix-profile/share/man ]
|
[ -e $TEST_HOME/.nix-profile/share/man ]
|
||||||
[ -e $TEST_HOME/.nix-profile/include ]
|
[ -e $TEST_HOME/.nix-profile/include ]
|
||||||
|
|
||||||
printf Nix > $flake1Dir/who
|
printf Nix > $flake1Dir/who
|
||||||
nix profile upgrade 0
|
nix profile list
|
||||||
|
nix profile upgrade flake1
|
||||||
[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello Nix" ]]
|
[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello Nix" ]]
|
||||||
[ -e $TEST_HOME/.nix-profile/share/man ]
|
[ -e $TEST_HOME/.nix-profile/share/man ]
|
||||||
[ -e $TEST_HOME/.nix-profile/include ]
|
[ -e $TEST_HOME/.nix-profile/include ]
|
||||||
|
|
||||||
nix profile remove 0
|
nix profile remove flake1 2>&1 | grep 'removed 1 packages'
|
||||||
nix profile install "$flake1Dir^man"
|
nix profile install "$flake1Dir^man"
|
||||||
(! [ -e $TEST_HOME/.nix-profile/bin/hello ])
|
(! [ -e $TEST_HOME/.nix-profile/bin/hello ])
|
||||||
[ -e $TEST_HOME/.nix-profile/share/man ]
|
[ -e $TEST_HOME/.nix-profile/share/man ]
|
||||||
(! [ -e $TEST_HOME/.nix-profile/include ])
|
(! [ -e $TEST_HOME/.nix-profile/include ])
|
||||||
|
|
||||||
# test priority
|
# test priority
|
||||||
nix profile remove 0
|
nix profile remove flake1
|
||||||
|
|
||||||
# Make another flake.
|
# Make another flake.
|
||||||
flake2Dir=$TEST_ROOT/flake2
|
flake2Dir=$TEST_ROOT/flake2
|
||||||
|
@ -185,3 +194,12 @@ nix profile install $flake2Dir --priority 0
|
||||||
clearProfiles
|
clearProfiles
|
||||||
nix profile install $(nix build $flake1Dir --no-link --print-out-paths)
|
nix profile install $(nix build $flake1Dir --no-link --print-out-paths)
|
||||||
expect 1 nix profile install --impure --expr "(builtins.getFlake ''$flake2Dir'').packages.$system.default"
|
expect 1 nix profile install --impure --expr "(builtins.getFlake ''$flake2Dir'').packages.$system.default"
|
||||||
|
|
||||||
|
# Test upgrading from profile version 2.
|
||||||
|
clearProfiles
|
||||||
|
mkdir -p $TEST_ROOT/import-profile
|
||||||
|
outPath=$(nix build --no-link --print-out-paths $flake1Dir/flake.nix^out)
|
||||||
|
printf '{ "version": 2, "elements": [ { "active": true, "attrPath": "legacyPackages.x86_64-linux.hello", "originalUrl": "flake:nixpkgs", "outputs": null, "priority": 5, "storePaths": [ "%s" ], "url": "github:NixOS/nixpkgs/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" } ] }' "$outPath" > $TEST_ROOT/import-profile/manifest.json
|
||||||
|
nix build --profile $TEST_HOME/.nix-profile $(nix store add-path $TEST_ROOT/import-profile)
|
||||||
|
nix profile list | grep -A4 'Name:.*hello' | grep "Store paths:.*$outPath"
|
||||||
|
nix profile remove hello 2>&1 | grep 'removed 1 packages, kept 0 packages'
|
||||||
|
|
69
tests/unit/libutil/url-name.cc
Normal file
69
tests/unit/libutil/url-name.cc
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
#include "url-name.hh"
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
/* ----------- tests for url-name.hh --------------------------------------------------*/
|
||||||
|
|
||||||
|
TEST(getNameFromURL, getNameFromURL) {
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("path:/home/user/project")), "project");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("path:~/repos/nixpkgs#packages.x86_64-linux.hello")), "hello");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("path:~/repos/nixpkgs#legacyPackages.x86_64-linux.hello")), "hello");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("path:~/repos/nixpkgs#packages.x86_64-linux.Hello")), "Hello");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("path:.#nonStandardAttr.mylaptop")), "mylaptop");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("path:./repos/myflake#nonStandardAttr.mylaptop")), "mylaptop");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("path:./nixpkgs#packages.x86_64-linux.complex^bin,man")), "complex");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("path:./myproj#packages.x86_64-linux.default^*")), "myproj");
|
||||||
|
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("github:NixOS/nixpkgs#packages.x86_64-linux.hello")), "hello");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("github:NixOS/nixpkgs#hello")), "hello");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("github:NixOS/nix#packages.x86_64-linux.default")), "nix");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("github:NixOS/nix#")), "nix");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("github:NixOS/nix")), "nix");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("github:cachix/devenv/main#packages.x86_64-linux.default")), "devenv");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("github:edolstra/nix-warez?rev=1234&dir=blender&ref=master")), "blender");
|
||||||
|
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("gitlab:NixOS/nixpkgs#packages.x86_64-linux.hello")), "hello");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("gitlab:NixOS/nixpkgs#hello")), "hello");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("gitlab:NixOS/nix#packages.x86_64-linux.default")), "nix");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("gitlab:NixOS/nix#")), "nix");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("gitlab:NixOS/nix")), "nix");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("gitlab:cachix/devenv/main#packages.x86_64-linux.default")), "devenv");
|
||||||
|
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("sourcehut:NixOS/nixpkgs#packages.x86_64-linux.hello")), "hello");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("sourcehut:NixOS/nixpkgs#hello")), "hello");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("sourcehut:NixOS/nix#packages.x86_64-linux.default")), "nix");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("sourcehut:NixOS/nix#")), "nix");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("sourcehut:NixOS/nix")), "nix");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("sourcehut:cachix/devenv/main#packages.x86_64-linux.default")), "devenv");
|
||||||
|
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("git://github.com/edolstra/dwarffs")), "dwarffs");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("git://github.com/edolstra/nix-warez?dir=blender")), "blender");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("git+file:///home/user/project")), "project");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("git+file:///home/user/project?ref=fa1e2d23a22")), "project");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("git+ssh://git@github.com/someuser/my-repo#")), "my-repo");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("git+git://github.com/someuser/my-repo?rev=v1.2.3")), "my-repo");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("git+ssh:///home/user/project?dir=subproject&rev=v2.4")), "subproject");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("git+http://not-even-real#packages.x86_64-linux.hello")), "hello");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("git+https://not-even-real#packages.aarch64-darwin.hello")), "hello");
|
||||||
|
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("tarball+http://github.com/NixOS/nix/archive/refs/tags/2.18.1#packages.x86_64-linux.jq")), "jq");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("tarball+https://github.com/NixOS/nix/archive/refs/tags/2.18.1#packages.x86_64-linux.hg")), "hg");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("tarball+file:///home/user/Downloads/nixpkgs-2.18.1#packages.aarch64-darwin.ripgrep")), "ripgrep");
|
||||||
|
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("https://github.com/NixOS/nix/archive/refs/tags/2.18.1.tar.gz#packages.x86_64-linux.pv")), "pv");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("http://github.com/NixOS/nix/archive/refs/tags/2.18.1.tar.gz#packages.x86_64-linux.pv")), "pv");
|
||||||
|
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("file:///home/user/project?ref=fa1e2d23a22")), "project");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("file+file:///home/user/project?ref=fa1e2d23a22")), "project");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("file+http://not-even-real#packages.x86_64-linux.hello")), "hello");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("file+http://gitfantasy.com/org/user/notaflake")), "notaflake");
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("file+https://not-even-real#packages.aarch64-darwin.hello")), "hello");
|
||||||
|
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("https://www.github.com/")), std::nullopt);
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("path:.")), std::nullopt);
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("file:.#")), std::nullopt);
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("path:.#packages.x86_64-linux.default")), std::nullopt);
|
||||||
|
ASSERT_EQ(getNameFromURL(parseURL("path:.#packages.x86_64-linux.default^*")), std::nullopt);
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,6 +52,7 @@ libutil_tests_sources = files(
|
||||||
'libutil/suggestions.cc',
|
'libutil/suggestions.cc',
|
||||||
'libutil/tests.cc',
|
'libutil/tests.cc',
|
||||||
'libutil/url.cc',
|
'libutil/url.cc',
|
||||||
|
'libutil/url-name.cc',
|
||||||
'libutil/xml-writer.cc',
|
'libutil/xml-writer.cc',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue