Trivial changes from the lazy-trees branch

This commit is contained in:
Eelco Dolstra 2022-12-07 12:58:58 +01:00
parent c4a6113800
commit 703d863a48
62 changed files with 394 additions and 248 deletions

View file

@ -177,7 +177,7 @@ fi
PKG_CHECK_MODULES([OPENSSL], [libcrypto], [CXXFLAGS="$OPENSSL_CFLAGS $CXXFLAGS"]) PKG_CHECK_MODULES([OPENSSL], [libcrypto], [CXXFLAGS="$OPENSSL_CFLAGS $CXXFLAGS"])
# Checks for libarchive # Look for libarchive.
PKG_CHECK_MODULES([LIBARCHIVE], [libarchive >= 3.1.2], [CXXFLAGS="$LIBARCHIVE_CFLAGS $CXXFLAGS"]) PKG_CHECK_MODULES([LIBARCHIVE], [libarchive >= 3.1.2], [CXXFLAGS="$LIBARCHIVE_CFLAGS $CXXFLAGS"])
# Workaround until https://github.com/libarchive/libarchive/issues/1446 is fixed # Workaround until https://github.com/libarchive/libarchive/issues/1446 is fixed
if test "$shared" != yes; then if test "$shared" != yes; then

View file

@ -8,41 +8,10 @@ Most Nix commands interpret the following environment variables:
- [`NIX_PATH`]{#env-NIX_PATH}\ - [`NIX_PATH`]{#env-NIX_PATH}\
A colon-separated list of directories used to look up Nix A colon-separated list of directories used to look up Nix
expressions enclosed in angle brackets (i.e., `<path>`). For expressions enclosed in angle brackets (i.e., `<path>`),
instance, the value e.g. `/home/eelco/Dev:/etc/nixos`. It can be extended using the
`-I` option. For more information about the semantics of the Nix
/home/eelco/Dev:/etc/nixos search path, see the documentation for `-I`.
will cause Nix to look for paths relative to `/home/eelco/Dev` and
`/etc/nixos`, in this order. It is also possible to match paths
against a prefix. For example, the value
nixpkgs=/home/eelco/Dev/nixpkgs-branch:/etc/nixos
will cause Nix to search for `<nixpkgs/path>` in
`/home/eelco/Dev/nixpkgs-branch/path` and `/etc/nixos/nixpkgs/path`.
If a path in the Nix search path starts with `http://` or
`https://`, it is interpreted as the URL of a tarball that will be
downloaded and unpacked to a temporary location. The tarball must
consist of a single top-level directory. For example, setting
`NIX_PATH` to
nixpkgs=https://github.com/NixOS/nixpkgs/archive/master.tar.gz
tells Nix to download and use the current contents of the
`master` branch in the `nixpkgs` repository.
The URLs of the tarballs from the official nixos.org channels (see
[the manual for `nix-channel`](nix-channel.md)) can be abbreviated
as `channel:<channel-name>`. For instance, the following two
values of `NIX_PATH` are equivalent:
nixpkgs=channel:nixos-21.05
nixpkgs=https://nixos.org/channels/nixos-21.05/nixexprs.tar.xz
The Nix search path can also be extended using the `-I` option to
many Nix commands, which takes precedence over `NIX_PATH`.
- [`NIX_IGNORE_SYMLINK_STORE`]{#env-NIX_IGNORE_SYMLINK_STORE}\ - [`NIX_IGNORE_SYMLINK_STORE`]{#env-NIX_IGNORE_SYMLINK_STORE}\
Normally, the Nix store directory (typically `/nix/store`) is not Normally, the Nix store directory (typically `/nix/store`) is not

View file

@ -32,7 +32,52 @@ MixEvalArgs::MixEvalArgs()
addFlag({ addFlag({
.longName = "include", .longName = "include",
.shortName = 'I', .shortName = 'I',
.description = "Add *path* to the list of locations used to look up `<...>` file names.", .description = R"(
Add *path* to the Nix search path. The Nix search path is
initialized from the colon-separated `NIX_PATH` environment
variable, and is used to look up Nix expressions enclosed in angle
brackets (i.e., `<nixpkgs>`). For instance, if the Nix search path
consists of the entries
```
/home/eelco/Dev
/etc/nixos
```
Nix will look for paths relative to `/home/eelco/Dev` and
`/etc/nixos`, in this order. It is also possible to match paths
against a prefix. For example, the search path
```
nixpkgs=/home/eelco/Dev/nixpkgs-branch
/etc/nixos
```
will cause Nix to search for `<nixpkgs/path>` in
`/home/eelco/Dev/nixpkgs-branch/path` and `/etc/nixos/nixpkgs/path`.
If a path in the Nix search path starts with `http://` or `https://`,
it is interpreted as the URL of a tarball that will be downloaded and
unpacked to a temporary location. The tarball must consist of a single
top-level directory. For example, setting `NIX_PATH` to
```
nixpkgs=https://github.com/NixOS/nixpkgs/archive/master.tar.gz
```
tells Nix to download and use the current contents of the `master`
branch in the `nixpkgs` repository.
The URLs of the tarballs from the official `nixos.org` channels
(see [the manual page for `nix-channel`](nix-channel.md)) can be
abbreviated as `channel:<channel-name>`. For instance, the
following two values of `NIX_PATH` are equivalent:
```
nixpkgs=channel:nixos-21.05
nixpkgs=https://nixos.org/channels/nixos-21.05/nixexprs.tar.xz
```
)",
.category = category, .category = category,
.labels = {"path"}, .labels = {"path"},
.handler = {[&](std::string s) { searchPath.push_back(s); }} .handler = {[&](std::string s) { searchPath.push_back(s); }}

View file

@ -781,7 +781,8 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
if (file == "-") { if (file == "-") {
auto e = state->parseStdin(); auto e = state->parseStdin();
state->eval(e, *vFile); state->eval(e, *vFile);
} else if (file) }
else if (file)
state->evalFile(lookupFileArg(*state, *file), *vFile); state->evalFile(lookupFileArg(*state, *file), *vFile);
else { else {
auto e = state->parseExprFromString(*expr, absPath(".")); auto e = state->parseExprFromString(*expr, absPath("."));

View file

@ -787,7 +787,7 @@ void NixRepl::loadFlake(const std::string & flakeRefS)
flake::LockFlags { flake::LockFlags {
.updateLockFile = false, .updateLockFile = false,
.useRegistries = !evalSettings.pureEval, .useRegistries = !evalSettings.pureEval,
.allowMutable = !evalSettings.pureEval, .allowUnlocked = !evalSettings.pureEval,
}), }),
v); v);
addAttrsToScope(v); addAttrsToScope(v);

View file

@ -645,17 +645,17 @@ NixInt AttrCursor::getInt()
cachedValue = root->db->getAttr(getKey()); cachedValue = root->db->getAttr(getKey());
if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) { if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
if (auto i = std::get_if<int_t>(&cachedValue->second)) { if (auto i = std::get_if<int_t>(&cachedValue->second)) {
debug("using cached Integer attribute '%s'", getAttrPathStr()); debug("using cached integer attribute '%s'", getAttrPathStr());
return i->x; return i->x;
} else } else
throw TypeError("'%s' is not an Integer", getAttrPathStr()); throw TypeError("'%s' is not an integer", getAttrPathStr());
} }
} }
auto & v = forceValue(); auto & v = forceValue();
if (v.type() != nInt) if (v.type() != nInt)
throw TypeError("'%s' is not an Integer", getAttrPathStr()); throw TypeError("'%s' is not an integer", getAttrPathStr());
return v.integer; return v.integer;
} }

View file

@ -353,7 +353,7 @@ LockedFlake lockFlake(
std::function<void( std::function<void(
const FlakeInputs & flakeInputs, const FlakeInputs & flakeInputs,
std::shared_ptr<Node> node, ref<Node> node,
const InputPath & inputPathPrefix, const InputPath & inputPathPrefix,
std::shared_ptr<const Node> oldNode, std::shared_ptr<const Node> oldNode,
const InputPath & lockRootPath, const InputPath & lockRootPath,
@ -362,9 +362,15 @@ LockedFlake lockFlake(
computeLocks; computeLocks;
computeLocks = [&]( computeLocks = [&](
/* The inputs of this node, either from flake.nix or
flake.lock. */
const FlakeInputs & flakeInputs, const FlakeInputs & flakeInputs,
std::shared_ptr<Node> node, /* The node whose locks are to be updated.*/
ref<Node> node,
/* The path to this node in the lock file graph. */
const InputPath & inputPathPrefix, const InputPath & inputPathPrefix,
/* The old node, if any, from which locks can be
copied. */
std::shared_ptr<const Node> oldNode, std::shared_ptr<const Node> oldNode,
const InputPath & lockRootPath, const InputPath & lockRootPath,
const Path & parentPath, const Path & parentPath,
@ -452,7 +458,7 @@ LockedFlake lockFlake(
/* Copy the input from the old lock since its flakeref /* Copy the input from the old lock since its flakeref
didn't change and there is no override from a didn't change and there is no override from a
higher level flake. */ higher level flake. */
auto childNode = std::make_shared<LockedNode>( auto childNode = make_ref<LockedNode>(
oldLock->lockedRef, oldLock->originalRef, oldLock->isFlake); oldLock->lockedRef, oldLock->originalRef, oldLock->isFlake);
node->inputs.insert_or_assign(id, childNode); node->inputs.insert_or_assign(id, childNode);
@ -481,7 +487,7 @@ LockedFlake lockFlake(
.isFlake = (*lockedNode)->isFlake, .isFlake = (*lockedNode)->isFlake,
}); });
} else if (auto follows = std::get_if<1>(&i.second)) { } else if (auto follows = std::get_if<1>(&i.second)) {
if (! trustLock) { if (!trustLock) {
// It is possible that the flake has changed, // It is possible that the flake has changed,
// so we must confirm all the follows that are in the lock file are also in the flake. // so we must confirm all the follows that are in the lock file are also in the flake.
auto overridePath(inputPath); auto overridePath(inputPath);
@ -521,8 +527,8 @@ LockedFlake lockFlake(
this input. */ this input. */
debug("creating new input '%s'", inputPathS); debug("creating new input '%s'", inputPathS);
if (!lockFlags.allowMutable && !input.ref->input.isLocked()) if (!lockFlags.allowUnlocked && !input.ref->input.isLocked())
throw Error("cannot update flake input '%s' in pure mode", inputPathS); throw Error("cannot update unlocked flake input '%s' in pure mode", inputPathS);
/* Note: in case of an --override-input, we use /* Note: in case of an --override-input, we use
the *original* ref (input2.ref) for the the *original* ref (input2.ref) for the
@ -544,7 +550,7 @@ LockedFlake lockFlake(
auto inputFlake = getFlake(state, localRef, useRegistries, flakeCache, inputPath); auto inputFlake = getFlake(state, localRef, useRegistries, flakeCache, inputPath);
auto childNode = std::make_shared<LockedNode>(inputFlake.lockedRef, ref); auto childNode = make_ref<LockedNode>(inputFlake.lockedRef, ref);
node->inputs.insert_or_assign(id, childNode); node->inputs.insert_or_assign(id, childNode);
@ -563,16 +569,20 @@ LockedFlake lockFlake(
inputFlake.inputs, childNode, inputPath, inputFlake.inputs, childNode, inputPath,
oldLock oldLock
? std::dynamic_pointer_cast<const Node>(oldLock) ? std::dynamic_pointer_cast<const Node>(oldLock)
: LockFile::read( : (std::shared_ptr<Node>) LockFile::read(
inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root, inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root,
oldLock ? lockRootPath : inputPath, localPath, false); oldLock ? lockRootPath : inputPath,
localPath,
false);
} }
else { else {
auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree( auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
state, *input.ref, useRegistries, flakeCache); state, *input.ref, useRegistries, flakeCache);
node->inputs.insert_or_assign(id,
std::make_shared<LockedNode>(lockedRef, ref, false)); auto childNode = make_ref<LockedNode>(lockedRef, ref, false);
node->inputs.insert_or_assign(id, childNode);
} }
} }
@ -587,8 +597,13 @@ LockedFlake lockFlake(
auto parentPath = canonPath(flake.sourceInfo->actualPath + "/" + flake.lockedRef.subdir, true); auto parentPath = canonPath(flake.sourceInfo->actualPath + "/" + flake.lockedRef.subdir, true);
computeLocks( computeLocks(
flake.inputs, newLockFile.root, {}, flake.inputs,
lockFlags.recreateLockFile ? nullptr : oldLockFile.root, {}, parentPath, false); newLockFile.root,
{},
lockFlags.recreateLockFile ? nullptr : (std::shared_ptr<Node>) oldLockFile.root,
{},
parentPath,
false);
for (auto & i : lockFlags.inputOverrides) for (auto & i : lockFlags.inputOverrides)
if (!overridesUsed.count(i.first)) if (!overridesUsed.count(i.first))
@ -611,9 +626,9 @@ LockedFlake lockFlake(
if (lockFlags.writeLockFile) { if (lockFlags.writeLockFile) {
if (auto sourcePath = topRef.input.getSourcePath()) { if (auto sourcePath = topRef.input.getSourcePath()) {
if (!newLockFile.isImmutable()) { if (auto unlockedInput = newLockFile.isUnlocked()) {
if (fetchSettings.warnDirty) if (fetchSettings.warnDirty)
warn("will not write lock file of flake '%s' because it has a mutable input", topRef); warn("will not write lock file of flake '%s' because it has an unlocked input ('%s')", topRef, *unlockedInput);
} else { } else {
if (!lockFlags.updateLockFile) if (!lockFlags.updateLockFile)
throw Error("flake '%s' requires lock file changes but they're not allowed due to '--no-update-lock-file'", topRef); throw Error("flake '%s' requires lock file changes but they're not allowed due to '--no-update-lock-file'", topRef);
@ -737,7 +752,7 @@ static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, V
.updateLockFile = false, .updateLockFile = false,
.writeLockFile = false, .writeLockFile = false,
.useRegistries = !evalSettings.pureEval && fetchSettings.useRegistries, .useRegistries = !evalSettings.pureEval && fetchSettings.useRegistries,
.allowMutable = !evalSettings.pureEval, .allowUnlocked = !evalSettings.pureEval,
}), }),
v); v);
} }

View file

@ -108,11 +108,11 @@ struct LockFlags
bool applyNixConfig = false; bool applyNixConfig = false;
/* Whether mutable flake references (i.e. those without a Git /* Whether unlocked flake references (i.e. those without a Git
revision or similar) without a corresponding lock are revision or similar) without a corresponding lock are
allowed. Mutable flake references with a lock are always allowed. Unlocked flake references with a lock are always
allowed. */ allowed. */
bool allowMutable = true; bool allowUnlocked = true;
/* Whether to commit changes to flake.lock. */ /* Whether to commit changes to flake.lock. */
bool commitLockFile = false; bool commitLockFile = false;

View file

@ -35,7 +35,7 @@ typedef std::string FlakeId;
struct FlakeRef struct FlakeRef
{ {
/* fetcher-specific representation of the input, sufficient to /* Fetcher-specific representation of the input, sufficient to
perform the fetch operation. */ perform the fetch operation. */
fetchers::Input input; fetchers::Input input;

View file

@ -31,7 +31,7 @@ FlakeRef getFlakeRef(
} }
LockedNode::LockedNode(const nlohmann::json & json) LockedNode::LockedNode(const nlohmann::json & json)
: lockedRef(getFlakeRef(json, "locked", "info")) : lockedRef(getFlakeRef(json, "locked", "info")) // FIXME: remove "info"
, originalRef(getFlakeRef(json, "original", nullptr)) , originalRef(getFlakeRef(json, "original", nullptr))
, isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true) , isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true)
{ {
@ -49,15 +49,15 @@ std::shared_ptr<Node> LockFile::findInput(const InputPath & path)
{ {
auto pos = root; auto pos = root;
if (!pos) return {};
for (auto & elem : path) { for (auto & elem : path) {
if (auto i = get(pos->inputs, elem)) { if (auto i = get(pos->inputs, elem)) {
if (auto node = std::get_if<0>(&*i)) if (auto node = std::get_if<0>(&*i))
pos = *node; pos = *node;
else if (auto follows = std::get_if<1>(&*i)) { else if (auto follows = std::get_if<1>(&*i)) {
pos = findInput(*follows); if (auto p = findInput(*follows))
if (!pos) return {}; pos = ref(p);
else
return {};
} }
} else } else
return {}; return {};
@ -72,7 +72,7 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path)
if (version < 5 || version > 7) if (version < 5 || version > 7)
throw Error("lock file '%s' has unsupported version %d", path, version); throw Error("lock file '%s' has unsupported version %d", path, version);
std::unordered_map<std::string, std::shared_ptr<Node>> nodeMap; std::map<std::string, ref<Node>> nodeMap;
std::function<void(Node & node, const nlohmann::json & jsonNode)> getInputs; std::function<void(Node & node, const nlohmann::json & jsonNode)> getInputs;
@ -93,12 +93,12 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path)
auto jsonNode2 = nodes.find(inputKey); auto jsonNode2 = nodes.find(inputKey);
if (jsonNode2 == nodes.end()) if (jsonNode2 == nodes.end())
throw Error("lock file references missing node '%s'", inputKey); throw Error("lock file references missing node '%s'", inputKey);
auto input = std::make_shared<LockedNode>(*jsonNode2); auto input = make_ref<LockedNode>(*jsonNode2);
k = nodeMap.insert_or_assign(inputKey, input).first; k = nodeMap.insert_or_assign(inputKey, input).first;
getInputs(*input, *jsonNode2); getInputs(*input, *jsonNode2);
} }
if (auto child = std::dynamic_pointer_cast<LockedNode>(k->second)) if (auto child = k->second.dynamic_pointer_cast<LockedNode>())
node.inputs.insert_or_assign(i.key(), child); node.inputs.insert_or_assign(i.key(), ref(child));
else else
// FIXME: replace by follows node // FIXME: replace by follows node
throw Error("lock file contains cycle to root node"); throw Error("lock file contains cycle to root node");
@ -122,9 +122,9 @@ nlohmann::json LockFile::toJSON() const
std::unordered_map<std::shared_ptr<const Node>, std::string> nodeKeys; std::unordered_map<std::shared_ptr<const Node>, std::string> nodeKeys;
std::unordered_set<std::string> keys; std::unordered_set<std::string> keys;
std::function<std::string(const std::string & key, std::shared_ptr<const Node> node)> dumpNode; std::function<std::string(const std::string & key, ref<const Node> node)> dumpNode;
dumpNode = [&](std::string key, std::shared_ptr<const Node> node) -> std::string dumpNode = [&](std::string key, ref<const Node> node) -> std::string
{ {
auto k = nodeKeys.find(node); auto k = nodeKeys.find(node);
if (k != nodeKeys.end()) if (k != nodeKeys.end())
@ -159,10 +159,11 @@ nlohmann::json LockFile::toJSON() const
n["inputs"] = std::move(inputs); n["inputs"] = std::move(inputs);
} }
if (auto lockedNode = std::dynamic_pointer_cast<const LockedNode>(node)) { if (auto lockedNode = node.dynamic_pointer_cast<const LockedNode>()) {
n["original"] = fetchers::attrsToJSON(lockedNode->originalRef.toAttrs()); n["original"] = fetchers::attrsToJSON(lockedNode->originalRef.toAttrs());
n["locked"] = fetchers::attrsToJSON(lockedNode->lockedRef.toAttrs()); n["locked"] = fetchers::attrsToJSON(lockedNode->lockedRef.toAttrs());
if (!lockedNode->isFlake) n["flake"] = false; if (!lockedNode->isFlake)
n["flake"] = false;
} }
nodes[key] = std::move(n); nodes[key] = std::move(n);
@ -201,13 +202,13 @@ void LockFile::write(const Path & path) const
writeFile(path, fmt("%s\n", *this)); writeFile(path, fmt("%s\n", *this));
} }
bool LockFile::isImmutable() const std::optional<FlakeRef> LockFile::isUnlocked() const
{ {
std::unordered_set<std::shared_ptr<const Node>> nodes; std::set<ref<const Node>> nodes;
std::function<void(std::shared_ptr<const Node> node)> visit; std::function<void(ref<const Node> node)> visit;
visit = [&](std::shared_ptr<const Node> node) visit = [&](ref<const Node> node)
{ {
if (!nodes.insert(node).second) return; if (!nodes.insert(node).second) return;
for (auto & i : node->inputs) for (auto & i : node->inputs)
@ -219,11 +220,12 @@ bool LockFile::isImmutable() const
for (auto & i : nodes) { for (auto & i : nodes) {
if (i == root) continue; if (i == root) continue;
auto lockedNode = std::dynamic_pointer_cast<const LockedNode>(i); auto node = i.dynamic_pointer_cast<const LockedNode>();
if (lockedNode && !lockedNode->lockedRef.input.isLocked()) return false; if (node && !node->lockedRef.input.isLocked())
return node->lockedRef;
} }
return true; return {};
} }
bool LockFile::operator ==(const LockFile & other) const bool LockFile::operator ==(const LockFile & other) const
@ -247,12 +249,12 @@ InputPath parseInputPath(std::string_view s)
std::map<InputPath, Node::Edge> LockFile::getAllInputs() const std::map<InputPath, Node::Edge> LockFile::getAllInputs() const
{ {
std::unordered_set<std::shared_ptr<Node>> done; std::set<ref<Node>> done;
std::map<InputPath, Node::Edge> res; std::map<InputPath, Node::Edge> res;
std::function<void(const InputPath & prefix, std::shared_ptr<Node> node)> recurse; std::function<void(const InputPath & prefix, ref<Node> node)> recurse;
recurse = [&](const InputPath & prefix, std::shared_ptr<Node> node) recurse = [&](const InputPath & prefix, ref<Node> node)
{ {
if (!done.insert(node).second) return; if (!done.insert(node).second) return;

View file

@ -20,7 +20,7 @@ struct LockedNode;
type LockedNode. */ type LockedNode. */
struct Node : std::enable_shared_from_this<Node> struct Node : std::enable_shared_from_this<Node>
{ {
typedef std::variant<std::shared_ptr<LockedNode>, InputPath> Edge; typedef std::variant<ref<LockedNode>, InputPath> Edge;
std::map<FlakeId, Edge> inputs; std::map<FlakeId, Edge> inputs;
@ -47,11 +47,13 @@ struct LockedNode : Node
struct LockFile struct LockFile
{ {
std::shared_ptr<Node> root = std::make_shared<Node>(); ref<Node> root = make_ref<Node>();
LockFile() {}; LockFile() {};
LockFile(const nlohmann::json & json, const Path & path); LockFile(const nlohmann::json & json, const Path & path);
typedef std::map<ref<const Node>, std::string> KeyMap;
nlohmann::json toJSON() const; nlohmann::json toJSON() const;
std::string to_string() const; std::string to_string() const;
@ -60,7 +62,8 @@ struct LockFile
void write(const Path & path) const; void write(const Path & path) const;
bool isImmutable() const; /* Check whether this lock file has any unlocked inputs. */
std::optional<FlakeRef> isUnlocked() const;
bool operator ==(const LockFile & other) const; bool operator ==(const LockFile & other) const;

View file

@ -150,7 +150,7 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall
/* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */ /* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */
const Value * outTI = queryMeta("outputsToInstall"); const Value * outTI = queryMeta("outputsToInstall");
if (!outTI) return outputs; if (!outTI) return outputs;
const auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'"); auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'");
/* ^ this shows during `nix-env -i` right under the bad derivation */ /* ^ this shows during `nix-env -i` right under the bad derivation */
if (!outTI->isList()) throw errMsg; if (!outTI->isList()) throw errMsg;
Outputs result; Outputs result;

View file

@ -289,7 +289,6 @@ std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath)
} }
/* Computing levels/displacements for variables. */ /* Computing levels/displacements for variables. */
void Expr::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) void Expr::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)

View file

@ -266,7 +266,7 @@ std::optional<time_t> Input::getLastModified() const
return {}; return {};
} }
ParsedURL InputScheme::toURL(const Input & input) ParsedURL InputScheme::toURL(const Input & input) const
{ {
throw Error("don't know how to convert input '%s' to a URL", attrsToJSON(input.attrs)); throw Error("don't know how to convert input '%s' to a URL", attrsToJSON(input.attrs));
} }
@ -274,7 +274,7 @@ ParsedURL InputScheme::toURL(const Input & input)
Input InputScheme::applyOverrides( Input InputScheme::applyOverrides(
const Input & input, const Input & input,
std::optional<std::string> ref, std::optional<std::string> ref,
std::optional<Hash> rev) std::optional<Hash> rev) const
{ {
if (ref) if (ref)
throw Error("don't know how to set branch/tag name of input '%s' to '%s'", input.to_string(), *ref); throw Error("don't know how to set branch/tag name of input '%s' to '%s'", input.to_string(), *ref);
@ -293,7 +293,7 @@ void InputScheme::markChangedFile(const Input & input, std::string_view file, st
assert(false); assert(false);
} }
void InputScheme::clone(const Input & input, const Path & destDir) void InputScheme::clone(const Input & input, const Path & destDir) const
{ {
throw Error("do not know how to clone input '%s'", input.to_string()); throw Error("do not know how to clone input '%s'", input.to_string());
} }

View file

@ -107,26 +107,25 @@ public:
* recognized. The Input object contains the information the fetcher * recognized. The Input object contains the information the fetcher
* needs to actually perform the "fetch()" when called. * needs to actually perform the "fetch()" when called.
*/ */
struct InputScheme struct InputScheme
{ {
virtual ~InputScheme() virtual ~InputScheme()
{ } { }
virtual std::optional<Input> inputFromURL(const ParsedURL & url) = 0; virtual std::optional<Input> inputFromURL(const ParsedURL & url) const = 0;
virtual std::optional<Input> inputFromAttrs(const Attrs & attrs) = 0; virtual std::optional<Input> inputFromAttrs(const Attrs & attrs) const = 0;
virtual ParsedURL toURL(const Input & input); virtual ParsedURL toURL(const Input & input) const;
virtual bool hasAllInfo(const Input & input) = 0; virtual bool hasAllInfo(const Input & input) const = 0;
virtual Input applyOverrides( virtual Input applyOverrides(
const Input & input, const Input & input,
std::optional<std::string> ref, std::optional<std::string> ref,
std::optional<Hash> rev); std::optional<Hash> rev) const;
virtual void clone(const Input & input, const Path & destDir); virtual void clone(const Input & input, const Path & destDir) const;
virtual std::optional<Path> getSourcePath(const Input & input); virtual std::optional<Path> getSourcePath(const Input & input);

View file

@ -18,6 +18,7 @@
using namespace std::string_literals; using namespace std::string_literals;
namespace nix::fetchers { namespace nix::fetchers {
namespace { namespace {
// Explicit initial branch of our bare repo to suppress warnings from new version of git. // Explicit initial branch of our bare repo to suppress warnings from new version of git.
@ -26,12 +27,12 @@ namespace {
// old version of git, which will ignore unrecognized `-c` options. // old version of git, which will ignore unrecognized `-c` options.
const std::string gitInitialBranch = "__nix_dummy_branch"; const std::string gitInitialBranch = "__nix_dummy_branch";
bool isCacheFileWithinTtl(const time_t now, const struct stat & st) bool isCacheFileWithinTtl(time_t now, const struct stat & st)
{ {
return st.st_mtime + settings.tarballTtl > now; return st.st_mtime + settings.tarballTtl > now;
} }
bool touchCacheFile(const Path& path, const time_t& touch_time) bool touchCacheFile(const Path & path, time_t touch_time)
{ {
struct timeval times[2]; struct timeval times[2];
times[0].tv_sec = touch_time; times[0].tv_sec = touch_time;
@ -42,7 +43,7 @@ bool touchCacheFile(const Path& path, const time_t& touch_time)
return lutimes(path.c_str(), times) == 0; return lutimes(path.c_str(), times) == 0;
} }
Path getCachePath(std::string key) Path getCachePath(std::string_view key)
{ {
return getCacheDir() + "/nix/gitv3/" + return getCacheDir() + "/nix/gitv3/" +
hashString(htSHA256, key).to_string(Base32, false); hashString(htSHA256, key).to_string(Base32, false);
@ -57,13 +58,12 @@ Path getCachePath(std::string key)
// ... // ...
std::optional<std::string> readHead(const Path & path) std::optional<std::string> readHead(const Path & path)
{ {
auto [exit_code, output] = runProgram(RunOptions { auto [status, output] = runProgram(RunOptions {
.program = "git", .program = "git",
// FIXME: use 'HEAD' to avoid returning all refs
.args = {"ls-remote", "--symref", path}, .args = {"ls-remote", "--symref", path},
}); });
if (exit_code != 0) { if (status != 0) return std::nullopt;
return std::nullopt;
}
std::string_view line = output; std::string_view line = output;
line = line.substr(0, line.find("\n")); line = line.substr(0, line.find("\n"));
@ -82,12 +82,11 @@ std::optional<std::string> readHead(const Path & path)
} }
// Persist the HEAD ref from the remote repo in the local cached repo. // Persist the HEAD ref from the remote repo in the local cached repo.
bool storeCachedHead(const std::string& actualUrl, const std::string& headRef) bool storeCachedHead(const std::string & actualUrl, const std::string & headRef)
{ {
Path cacheDir = getCachePath(actualUrl); Path cacheDir = getCachePath(actualUrl);
auto gitDir = ".";
try { try {
runProgram("git", true, { "-C", cacheDir, "--git-dir", gitDir, "symbolic-ref", "--", "HEAD", headRef }); runProgram("git", true, { "-C", cacheDir, "--git-dir", ".", "symbolic-ref", "--", "HEAD", headRef });
} catch (ExecError &e) { } catch (ExecError &e) {
if (!WIFEXITED(e.status)) throw; if (!WIFEXITED(e.status)) throw;
return false; return false;
@ -96,7 +95,7 @@ bool storeCachedHead(const std::string& actualUrl, const std::string& headRef)
return true; return true;
} }
std::optional<std::string> readHeadCached(const std::string& actualUrl) std::optional<std::string> readHeadCached(const std::string & actualUrl)
{ {
// Create a cache path to store the branch of the HEAD ref. Append something // Create a cache path to store the branch of the HEAD ref. Append something
// in front of the URL to prevent collision with the repository itself. // in front of the URL to prevent collision with the repository itself.
@ -110,16 +109,15 @@ std::optional<std::string> readHeadCached(const std::string& actualUrl)
cachedRef = readHead(cacheDir); cachedRef = readHead(cacheDir);
if (cachedRef != std::nullopt && if (cachedRef != std::nullopt &&
*cachedRef != gitInitialBranch && *cachedRef != gitInitialBranch &&
isCacheFileWithinTtl(now, st)) { isCacheFileWithinTtl(now, st))
{
debug("using cached HEAD ref '%s' for repo '%s'", *cachedRef, actualUrl); debug("using cached HEAD ref '%s' for repo '%s'", *cachedRef, actualUrl);
return cachedRef; return cachedRef;
} }
} }
auto ref = readHead(actualUrl); auto ref = readHead(actualUrl);
if (ref) { if (ref) return ref;
return ref;
}
if (cachedRef) { if (cachedRef) {
// If the cached git ref is expired in fetch() below, and the 'git fetch' // If the cached git ref is expired in fetch() below, and the 'git fetch'
@ -250,7 +248,7 @@ std::pair<StorePath, Input> fetchFromWorkdir(ref<Store> store, Input & input, co
struct GitInputScheme : InputScheme struct GitInputScheme : InputScheme
{ {
std::optional<Input> inputFromURL(const ParsedURL & url) override std::optional<Input> inputFromURL(const ParsedURL & url) const override
{ {
if (url.scheme != "git" && if (url.scheme != "git" &&
url.scheme != "git+http" && url.scheme != "git+http" &&
@ -265,7 +263,7 @@ struct GitInputScheme : InputScheme
Attrs attrs; Attrs attrs;
attrs.emplace("type", "git"); attrs.emplace("type", "git");
for (auto &[name, value] : url.query) { for (auto & [name, value] : url.query) {
if (name == "rev" || name == "ref") if (name == "rev" || name == "ref")
attrs.emplace(name, value); attrs.emplace(name, value);
else if (name == "shallow" || name == "submodules") else if (name == "shallow" || name == "submodules")
@ -279,7 +277,7 @@ struct GitInputScheme : InputScheme
return inputFromAttrs(attrs); return inputFromAttrs(attrs);
} }
std::optional<Input> inputFromAttrs(const Attrs & attrs) override std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
{ {
if (maybeGetStrAttr(attrs, "type") != "git") return {}; if (maybeGetStrAttr(attrs, "type") != "git") return {};
@ -302,7 +300,7 @@ struct GitInputScheme : InputScheme
return input; return input;
} }
ParsedURL toURL(const Input & input) override ParsedURL toURL(const Input & input) const override
{ {
auto url = parseURL(getStrAttr(input.attrs, "url")); auto url = parseURL(getStrAttr(input.attrs, "url"));
if (url.scheme != "git") url.scheme = "git+" + url.scheme; if (url.scheme != "git") url.scheme = "git+" + url.scheme;
@ -313,7 +311,7 @@ struct GitInputScheme : InputScheme
return url; return url;
} }
bool hasAllInfo(const Input & input) override bool hasAllInfo(const Input & input) const override
{ {
bool maybeDirty = !input.getRef(); bool maybeDirty = !input.getRef();
bool shallow = maybeGetBoolAttr(input.attrs, "shallow").value_or(false); bool shallow = maybeGetBoolAttr(input.attrs, "shallow").value_or(false);
@ -325,7 +323,7 @@ struct GitInputScheme : InputScheme
Input applyOverrides( Input applyOverrides(
const Input & input, const Input & input,
std::optional<std::string> ref, std::optional<std::string> ref,
std::optional<Hash> rev) override std::optional<Hash> rev) const override
{ {
auto res(input); auto res(input);
if (rev) res.attrs.insert_or_assign("rev", rev->gitRev()); if (rev) res.attrs.insert_or_assign("rev", rev->gitRev());
@ -335,7 +333,7 @@ struct GitInputScheme : InputScheme
return res; return res;
} }
void clone(const Input & input, const Path & destDir) override void clone(const Input & input, const Path & destDir) const override
{ {
auto [isLocal, actualUrl] = getActualUrl(input); auto [isLocal, actualUrl] = getActualUrl(input);

View file

@ -26,11 +26,11 @@ std::regex hostRegex(hostRegexS, std::regex::ECMAScript);
struct GitArchiveInputScheme : InputScheme struct GitArchiveInputScheme : InputScheme
{ {
virtual std::string type() = 0; virtual std::string type() const = 0;
virtual std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const = 0; virtual std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const = 0;
std::optional<Input> inputFromURL(const ParsedURL & url) override std::optional<Input> inputFromURL(const ParsedURL & url) const override
{ {
if (url.scheme != type()) return {}; if (url.scheme != type()) return {};
@ -100,7 +100,7 @@ struct GitArchiveInputScheme : InputScheme
return input; return input;
} }
std::optional<Input> inputFromAttrs(const Attrs & attrs) override std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
{ {
if (maybeGetStrAttr(attrs, "type") != type()) return {}; if (maybeGetStrAttr(attrs, "type") != type()) return {};
@ -116,7 +116,7 @@ struct GitArchiveInputScheme : InputScheme
return input; return input;
} }
ParsedURL toURL(const Input & input) override ParsedURL toURL(const Input & input) const override
{ {
auto owner = getStrAttr(input.attrs, "owner"); auto owner = getStrAttr(input.attrs, "owner");
auto repo = getStrAttr(input.attrs, "repo"); auto repo = getStrAttr(input.attrs, "repo");
@ -132,7 +132,7 @@ struct GitArchiveInputScheme : InputScheme
}; };
} }
bool hasAllInfo(const Input & input) override bool hasAllInfo(const Input & input) const override
{ {
return input.getRev() && maybeGetIntAttr(input.attrs, "lastModified"); return input.getRev() && maybeGetIntAttr(input.attrs, "lastModified");
} }
@ -140,7 +140,7 @@ struct GitArchiveInputScheme : InputScheme
Input applyOverrides( Input applyOverrides(
const Input & _input, const Input & _input,
std::optional<std::string> ref, std::optional<std::string> ref,
std::optional<Hash> rev) override std::optional<Hash> rev) const override
{ {
auto input(_input); auto input(_input);
if (rev && ref) if (rev && ref)
@ -227,7 +227,7 @@ struct GitArchiveInputScheme : InputScheme
struct GitHubInputScheme : GitArchiveInputScheme struct GitHubInputScheme : GitArchiveInputScheme
{ {
std::string type() override { return "github"; } std::string type() const override { return "github"; }
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
{ {
@ -240,14 +240,29 @@ struct GitHubInputScheme : GitArchiveInputScheme
return std::pair<std::string, std::string>("Authorization", fmt("token %s", token)); return std::pair<std::string, std::string>("Authorization", fmt("token %s", token));
} }
std::string getHost(const Input & input) const
{
return maybeGetStrAttr(input.attrs, "host").value_or("github.com");
}
std::string getOwner(const Input & input) const
{
return getStrAttr(input.attrs, "owner");
}
std::string getRepo(const Input & input) const
{
return getStrAttr(input.attrs, "repo");
}
Hash getRevFromRef(nix::ref<Store> store, const Input & input) const override Hash getRevFromRef(nix::ref<Store> store, const Input & input) const override
{ {
auto host = maybeGetStrAttr(input.attrs, "host").value_or("github.com"); auto host = getHost(input);
auto url = fmt( auto url = fmt(
host == "github.com" host == "github.com"
? "https://api.%s/repos/%s/%s/commits/%s" ? "https://api.%s/repos/%s/%s/commits/%s"
: "https://%s/api/v3/repos/%s/%s/commits/%s", : "https://%s/api/v3/repos/%s/%s/commits/%s",
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"), *input.getRef()); host, getOwner(input), getRepo(input), *input.getRef());
Headers headers = makeHeadersWithAuthTokens(host); Headers headers = makeHeadersWithAuthTokens(host);
@ -262,8 +277,10 @@ struct GitHubInputScheme : GitArchiveInputScheme
DownloadUrl getDownloadUrl(const Input & input) const override DownloadUrl getDownloadUrl(const Input & input) const override
{ {
auto host = maybeGetStrAttr(input.attrs, "host").value_or("github.com"); auto host = getHost(input);
Headers headers = makeHeadersWithAuthTokens(host); Headers headers = makeHeadersWithAuthTokens(host);
// If we have no auth headers then we default to the public archive // If we have no auth headers then we default to the public archive
// urls so we do not run into rate limits. // urls so we do not run into rate limits.
const auto urlFmt = const auto urlFmt =
@ -273,17 +290,17 @@ struct GitHubInputScheme : GitArchiveInputScheme
? "https://%s/%s/%s/archive/%s.tar.gz" ? "https://%s/%s/%s/archive/%s.tar.gz"
: "https://api.%s/repos/%s/%s/tarball/%s"; : "https://api.%s/repos/%s/%s/tarball/%s";
const auto url = fmt(urlFmt, host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"), const auto url = fmt(urlFmt, host, getOwner(input), getRepo(input),
input.getRev()->to_string(Base16, false)); input.getRev()->to_string(Base16, false));
return DownloadUrl { url, headers }; return DownloadUrl { url, headers };
} }
void clone(const Input & input, const Path & destDir) override void clone(const Input & input, const Path & destDir) const override
{ {
auto host = maybeGetStrAttr(input.attrs, "host").value_or("github.com"); auto host = getHost(input);
Input::fromURL(fmt("git+https://%s/%s/%s.git", Input::fromURL(fmt("git+https://%s/%s/%s.git",
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"))) host, getOwner(input), getRepo(input)))
.applyOverrides(input.getRef(), input.getRev()) .applyOverrides(input.getRef(), input.getRev())
.clone(destDir); .clone(destDir);
} }
@ -291,7 +308,7 @@ struct GitHubInputScheme : GitArchiveInputScheme
struct GitLabInputScheme : GitArchiveInputScheme struct GitLabInputScheme : GitArchiveInputScheme
{ {
std::string type() override { return "gitlab"; } std::string type() const override { return "gitlab"; }
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
{ {
@ -346,7 +363,7 @@ struct GitLabInputScheme : GitArchiveInputScheme
return DownloadUrl { url, headers }; return DownloadUrl { url, headers };
} }
void clone(const Input & input, const Path & destDir) override void clone(const Input & input, const Path & destDir) const override
{ {
auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com"); auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com");
// FIXME: get username somewhere // FIXME: get username somewhere
@ -359,7 +376,7 @@ struct GitLabInputScheme : GitArchiveInputScheme
struct SourceHutInputScheme : GitArchiveInputScheme struct SourceHutInputScheme : GitArchiveInputScheme
{ {
std::string type() override { return "sourcehut"; } std::string type() const override { return "sourcehut"; }
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
{ {
@ -433,7 +450,7 @@ struct SourceHutInputScheme : GitArchiveInputScheme
return DownloadUrl { url, headers }; return DownloadUrl { url, headers };
} }
void clone(const Input & input, const Path & destDir) override void clone(const Input & input, const Path & destDir) const override
{ {
auto host = maybeGetStrAttr(input.attrs, "host").value_or("git.sr.ht"); auto host = maybeGetStrAttr(input.attrs, "host").value_or("git.sr.ht");
Input::fromURL(fmt("git+https://%s/%s/%s", Input::fromURL(fmt("git+https://%s/%s/%s",

View file

@ -7,7 +7,7 @@ std::regex flakeRegex("[a-zA-Z][a-zA-Z0-9_-]*", std::regex::ECMAScript);
struct IndirectInputScheme : InputScheme struct IndirectInputScheme : InputScheme
{ {
std::optional<Input> inputFromURL(const ParsedURL & url) override std::optional<Input> inputFromURL(const ParsedURL & url) const override
{ {
if (url.scheme != "flake") return {}; if (url.scheme != "flake") return {};
@ -50,7 +50,7 @@ struct IndirectInputScheme : InputScheme
return input; return input;
} }
std::optional<Input> inputFromAttrs(const Attrs & attrs) override std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
{ {
if (maybeGetStrAttr(attrs, "type") != "indirect") return {}; if (maybeGetStrAttr(attrs, "type") != "indirect") return {};
@ -68,7 +68,7 @@ struct IndirectInputScheme : InputScheme
return input; return input;
} }
ParsedURL toURL(const Input & input) override ParsedURL toURL(const Input & input) const override
{ {
ParsedURL url; ParsedURL url;
url.scheme = "flake"; url.scheme = "flake";
@ -78,7 +78,7 @@ struct IndirectInputScheme : InputScheme
return url; return url;
} }
bool hasAllInfo(const Input & input) override bool hasAllInfo(const Input & input) const override
{ {
return false; return false;
} }
@ -86,7 +86,7 @@ struct IndirectInputScheme : InputScheme
Input applyOverrides( Input applyOverrides(
const Input & _input, const Input & _input,
std::optional<std::string> ref, std::optional<std::string> ref,
std::optional<Hash> rev) override std::optional<Hash> rev) const override
{ {
auto input(_input); auto input(_input);
if (rev) input.attrs.insert_or_assign("rev", rev->gitRev()); if (rev) input.attrs.insert_or_assign("rev", rev->gitRev());

View file

@ -43,7 +43,7 @@ static std::string runHg(const Strings & args, const std::optional<std::string>
struct MercurialInputScheme : InputScheme struct MercurialInputScheme : InputScheme
{ {
std::optional<Input> inputFromURL(const ParsedURL & url) override std::optional<Input> inputFromURL(const ParsedURL & url) const override
{ {
if (url.scheme != "hg+http" && if (url.scheme != "hg+http" &&
url.scheme != "hg+https" && url.scheme != "hg+https" &&
@ -69,7 +69,7 @@ struct MercurialInputScheme : InputScheme
return inputFromAttrs(attrs); return inputFromAttrs(attrs);
} }
std::optional<Input> inputFromAttrs(const Attrs & attrs) override std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
{ {
if (maybeGetStrAttr(attrs, "type") != "hg") return {}; if (maybeGetStrAttr(attrs, "type") != "hg") return {};
@ -89,7 +89,7 @@ struct MercurialInputScheme : InputScheme
return input; return input;
} }
ParsedURL toURL(const Input & input) override ParsedURL toURL(const Input & input) const override
{ {
auto url = parseURL(getStrAttr(input.attrs, "url")); auto url = parseURL(getStrAttr(input.attrs, "url"));
url.scheme = "hg+" + url.scheme; url.scheme = "hg+" + url.scheme;
@ -98,7 +98,7 @@ struct MercurialInputScheme : InputScheme
return url; return url;
} }
bool hasAllInfo(const Input & input) override bool hasAllInfo(const Input & input) const override
{ {
// FIXME: ugly, need to distinguish between dirty and clean // FIXME: ugly, need to distinguish between dirty and clean
// default trees. // default trees.
@ -108,7 +108,7 @@ struct MercurialInputScheme : InputScheme
Input applyOverrides( Input applyOverrides(
const Input & input, const Input & input,
std::optional<std::string> ref, std::optional<std::string> ref,
std::optional<Hash> rev) override std::optional<Hash> rev) const override
{ {
auto res(input); auto res(input);
if (rev) res.attrs.insert_or_assign("rev", rev->gitRev()); if (rev) res.attrs.insert_or_assign("rev", rev->gitRev());

View file

@ -6,7 +6,7 @@ namespace nix::fetchers {
struct PathInputScheme : InputScheme struct PathInputScheme : InputScheme
{ {
std::optional<Input> inputFromURL(const ParsedURL & url) override std::optional<Input> inputFromURL(const ParsedURL & url) const override
{ {
if (url.scheme != "path") return {}; if (url.scheme != "path") return {};
@ -32,7 +32,7 @@ struct PathInputScheme : InputScheme
return input; return input;
} }
std::optional<Input> inputFromAttrs(const Attrs & attrs) override std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
{ {
if (maybeGetStrAttr(attrs, "type") != "path") return {}; if (maybeGetStrAttr(attrs, "type") != "path") return {};
@ -54,7 +54,7 @@ struct PathInputScheme : InputScheme
return input; return input;
} }
ParsedURL toURL(const Input & input) override ParsedURL toURL(const Input & input) const override
{ {
auto query = attrsToQuery(input.attrs); auto query = attrsToQuery(input.attrs);
query.erase("path"); query.erase("path");
@ -66,7 +66,7 @@ struct PathInputScheme : InputScheme
}; };
} }
bool hasAllInfo(const Input & input) override bool hasAllInfo(const Input & input) const override
{ {
return true; return true;
} }

View file

@ -185,7 +185,7 @@ struct CurlInputScheme : InputScheme
virtual bool isValidURL(const ParsedURL & url) const = 0; virtual bool isValidURL(const ParsedURL & url) const = 0;
std::optional<Input> inputFromURL(const ParsedURL & url) override std::optional<Input> inputFromURL(const ParsedURL & url) const override
{ {
if (!isValidURL(url)) if (!isValidURL(url))
return std::nullopt; return std::nullopt;
@ -203,7 +203,7 @@ struct CurlInputScheme : InputScheme
return input; return input;
} }
std::optional<Input> inputFromAttrs(const Attrs & attrs) override std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
{ {
auto type = maybeGetStrAttr(attrs, "type"); auto type = maybeGetStrAttr(attrs, "type");
if (type != inputType()) return {}; if (type != inputType()) return {};
@ -220,16 +220,17 @@ struct CurlInputScheme : InputScheme
return input; return input;
} }
ParsedURL toURL(const Input & input) override ParsedURL toURL(const Input & input) const override
{ {
auto url = parseURL(getStrAttr(input.attrs, "url")); auto url = parseURL(getStrAttr(input.attrs, "url"));
// NAR hashes are preferred over file hashes since tar/zip files // don't have a canonical representation. // NAR hashes are preferred over file hashes since tar/zip
// files don't have a canonical representation.
if (auto narHash = input.getNarHash()) if (auto narHash = input.getNarHash())
url.query.insert_or_assign("narHash", narHash->to_string(SRI, true)); url.query.insert_or_assign("narHash", narHash->to_string(SRI, true));
return url; return url;
} }
bool hasAllInfo(const Input & input) override bool hasAllInfo(const Input & input) const override
{ {
return true; return true;
} }

View file

@ -132,7 +132,7 @@ public:
log(*state, lvl, fs.s); log(*state, lvl, fs.s);
} }
void logEI(const ErrorInfo &ei) override void logEI(const ErrorInfo & ei) override
{ {
auto state(state_.lock()); auto state(state_.lock());

View file

@ -346,7 +346,7 @@ void BinaryCacheStore::narFromPath(const StorePath & storePath, Sink & sink)
try { try {
getFile(info->url, *decompressor); getFile(info->url, *decompressor);
} catch (NoSuchBinaryCacheFile & e) { } catch (NoSuchBinaryCacheFile & e) {
throw SubstituteGone(e.info()); throw SubstituteGone(std::move(e.info()));
} }
decompressor->finish(); decompressor->finish();

View file

@ -134,7 +134,7 @@ void DerivationGoal::killChild()
void DerivationGoal::timedOut(Error && ex) void DerivationGoal::timedOut(Error && ex)
{ {
killChild(); killChild();
done(BuildResult::TimedOut, {}, ex); done(BuildResult::TimedOut, {}, std::move(ex));
} }
@ -984,7 +984,7 @@ void DerivationGoal::buildDone()
BuildResult::PermanentFailure; BuildResult::PermanentFailure;
} }
done(st, {}, e); done(st, {}, std::move(e));
return; return;
} }
} }
@ -1435,7 +1435,7 @@ void DerivationGoal::done(
fs << worker.store.printStorePath(drvPath) << "\t" << buildResult.toString() << std::endl; fs << worker.store.printStorePath(drvPath) << "\t" << buildResult.toString() << std::endl;
} }
amDone(buildResult.success() ? ecSuccess : ecFailed, ex); amDone(buildResult.success() ? ecSuccess : ecFailed, std::move(ex));
} }

View file

@ -30,7 +30,7 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
if (ex) if (ex)
logError(i->ex->info()); logError(i->ex->info());
else else
ex = i->ex; ex = std::move(i->ex);
} }
if (i->exitCode != Goal::ecSuccess) { if (i->exitCode != Goal::ecSuccess) {
if (auto i2 = dynamic_cast<DerivationGoal *>(i.get())) failed.insert(i2->drvPath); if (auto i2 = dynamic_cast<DerivationGoal *>(i.get())) failed.insert(i2->drvPath);
@ -40,7 +40,7 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
if (failed.size() == 1 && ex) { if (failed.size() == 1 && ex) {
ex->status = worker.exitStatus(); ex->status = worker.exitStatus();
throw *ex; throw std::move(*ex);
} else if (!failed.empty()) { } else if (!failed.empty()) {
if (ex) logError(ex->info()); if (ex) logError(ex->info());
throw Error(worker.exitStatus(), "build of %s failed", showPaths(failed)); throw Error(worker.exitStatus(), "build of %s failed", showPaths(failed));
@ -109,7 +109,7 @@ void Store::ensurePath(const StorePath & path)
if (goal->exitCode != Goal::ecSuccess) { if (goal->exitCode != Goal::ecSuccess) {
if (goal->ex) { if (goal->ex) {
goal->ex->status = worker.exitStatus(); goal->ex->status = worker.exitStatus();
throw *goal->ex; throw std::move(*goal->ex);
} else } else
throw Error(worker.exitStatus(), "path '%s' does not exist and cannot be created", printStorePath(path)); throw Error(worker.exitStatus(), "path '%s' does not exist and cannot be created", printStorePath(path));
} }

View file

@ -230,7 +230,7 @@ void LocalDerivationGoal::tryLocalBuild() {
outputLocks.unlock(); outputLocks.unlock();
buildUser.reset(); buildUser.reset();
worker.permanentFailure = true; worker.permanentFailure = true;
done(BuildResult::InputRejected, {}, e); done(BuildResult::InputRejected, {}, std::move(e));
return; return;
} }

View file

@ -448,7 +448,7 @@ std::string Derivation::unparse(const Store & store, bool maskOutputs,
// FIXME: remove // FIXME: remove
bool isDerivation(const std::string & fileName) bool isDerivation(std::string_view fileName)
{ {
return hasSuffix(fileName, drvExtension); return hasSuffix(fileName, drvExtension);
} }

View file

@ -224,7 +224,7 @@ StorePath writeDerivation(Store & store,
Derivation parseDerivation(const Store & store, std::string && s, std::string_view name); Derivation parseDerivation(const Store & store, std::string && s, std::string_view name);
// FIXME: remove // FIXME: remove
bool isDerivation(const std::string & fileName); bool isDerivation(std::string_view fileName);
/* Calculate the name that will be used for the store path for this /* Calculate the name that will be used for the store path for this
output. output.

View file

@ -142,9 +142,9 @@ struct curlFileTransfer : public FileTransfer
} }
template<class T> template<class T>
void fail(const T & e) void fail(T && e)
{ {
failEx(std::make_exception_ptr(e)); failEx(std::make_exception_ptr(std::move(e)));
} }
LambdaSink finalSink; LambdaSink finalSink;
@ -472,7 +472,7 @@ struct curlFileTransfer : public FileTransfer
fileTransfer.enqueueItem(shared_from_this()); fileTransfer.enqueueItem(shared_from_this());
} }
else else
fail(exc); fail(std::move(exc));
} }
} }
}; };

View file

@ -447,7 +447,7 @@ void RemoteStore::queryPathInfoUncached(const StorePath & path,
} catch (Error & e) { } catch (Error & e) {
// Ugly backwards compatibility hack. // Ugly backwards compatibility hack.
if (e.msg().find("is not valid") != std::string::npos) if (e.msg().find("is not valid") != std::string::npos)
throw InvalidPath(e.info()); throw InvalidPath(std::move(e.info()));
throw; throw;
} }
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 17) { if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 17) {

View file

@ -19,21 +19,21 @@ using json = nlohmann::json;
namespace nix { namespace nix {
bool Store::isInStore(const Path & path) const bool Store::isInStore(PathView path) const
{ {
return isInDir(path, storeDir); return isInDir(path, storeDir);
} }
std::pair<StorePath, Path> Store::toStorePath(const Path & path) const std::pair<StorePath, Path> Store::toStorePath(PathView path) const
{ {
if (!isInStore(path)) if (!isInStore(path))
throw Error("path '%1%' is not in the Nix store", path); throw Error("path '%1%' is not in the Nix store", path);
Path::size_type slash = path.find('/', storeDir.size() + 1); auto slash = path.find('/', storeDir.size() + 1);
if (slash == Path::npos) if (slash == Path::npos)
return {parseStorePath(path), ""}; return {parseStorePath(path), ""};
else else
return {parseStorePath(std::string_view(path).substr(0, slash)), path.substr(slash)}; return {parseStorePath(path.substr(0, slash)), (Path) path.substr(slash)};
} }

View file

@ -179,7 +179,7 @@ public:
/* Return true if path is in the Nix store (but not the Nix /* Return true if path is in the Nix store (but not the Nix
store itself). */ store itself). */
bool isInStore(const Path & path) const; bool isInStore(PathView path) const;
/* Return true if path is a store path, i.e. a direct child of /* Return true if path is a store path, i.e. a direct child of
the Nix store. */ the Nix store. */
@ -187,7 +187,7 @@ public:
/* Split a path like /nix/store/<hash>-<name>/<bla> into /* Split a path like /nix/store/<hash>-<name>/<bla> into
/nix/store/<hash>-<name> and /<bla>. */ /nix/store/<hash>-<name> and /<bla>. */
std::pair<StorePath, Path> toStorePath(const Path & path) const; std::pair<StorePath, Path> toStorePath(PathView path) const;
/* Follow symlinks until we end up with a path in the Nix store. */ /* Follow symlinks until we end up with a path in the Nix store. */
Path followLinksToStore(std::string_view path) const; Path followLinksToStore(std::string_view path) const;

View file

@ -35,10 +35,6 @@ static ArchiveSettings archiveSettings;
static GlobalConfig::Register rArchiveSettings(&archiveSettings); static GlobalConfig::Register rArchiveSettings(&archiveSettings);
const std::string narVersionMagic1 = "nix-archive-1";
static std::string caseHackSuffix = "~nix~case~hack~";
PathFilter defaultPathFilter = [](const Path &) { return true; }; PathFilter defaultPathFilter = [](const Path &) { return true; };

View file

@ -103,7 +103,9 @@ void copyNAR(Source & source, Sink & sink);
void copyPath(const Path & from, const Path & to); void copyPath(const Path & from, const Path & to);
extern const std::string narVersionMagic1; inline constexpr std::string_view narVersionMagic1 = "nix-archive-1";
inline constexpr std::string_view caseHackSuffix = "~nix~case~hack~";
} }

View file

@ -148,7 +148,7 @@ inline hintformat hintfmt(const std::string & fs, const Args & ... args)
return f; return f;
} }
inline hintformat hintfmt(std::string plain_string) inline hintformat hintfmt(const std::string & plain_string)
{ {
// we won't be receiving any args in this case, so just print the original string // we won't be receiving any args in this case, so just print the original string
return hintfmt("%s", normaltxt(plain_string)); return hintfmt("%s", normaltxt(plain_string));

View file

@ -105,14 +105,6 @@ public:
Verbosity verbosity = lvlInfo; Verbosity verbosity = lvlInfo;
void warnOnce(bool & haveWarned, const FormatOrString & fs)
{
if (!haveWarned) {
warn(fs.s);
haveWarned = true;
}
}
void writeToStderr(std::string_view s) void writeToStderr(std::string_view s)
{ {
try { try {
@ -130,11 +122,11 @@ Logger * makeSimpleLogger(bool printBuildLogs)
return new SimpleLogger(printBuildLogs); return new SimpleLogger(printBuildLogs);
} }
std::atomic<uint64_t> nextId{(uint64_t) getpid() << 32}; std::atomic<uint64_t> nextId{0};
Activity::Activity(Logger & logger, Verbosity lvl, ActivityType type, Activity::Activity(Logger & logger, Verbosity lvl, ActivityType type,
const std::string & s, const Logger::Fields & fields, ActivityId parent) const std::string & s, const Logger::Fields & fields, ActivityId parent)
: logger(logger), id(nextId++) : logger(logger), id(nextId++ + (((uint64_t) getpid()) << 32))
{ {
logger.startActivity(id, lvl, type, s, fields, parent); logger.startActivity(id, lvl, type, s, fields, parent);
} }

View file

@ -82,7 +82,7 @@ public:
log(lvlInfo, fs); log(lvlInfo, fs);
} }
virtual void logEI(const ErrorInfo &ei) = 0; virtual void logEI(const ErrorInfo & ei) = 0;
void logEI(Verbosity lvl, ErrorInfo ei) void logEI(Verbosity lvl, ErrorInfo ei)
{ {
@ -225,7 +225,11 @@ inline void warn(const std::string & fs, const Args & ... args)
logger->warn(f.str()); logger->warn(f.str());
} }
void warnOnce(bool & haveWarned, const FormatOrString & fs); #define warnOnce(haveWarned, args...) \
if (!haveWarned) { \
haveWarned = true; \
warn(args); \
}
void writeToStderr(std::string_view s); void writeToStderr(std::string_view s);

View file

@ -83,6 +83,11 @@ public:
return p != other.p; return p != other.p;
} }
bool operator < (const ref<T> & other) const
{
return p < other.p;
}
private: private:
template<typename T2, typename... Args> template<typename T2, typename... Args>

View file

@ -338,7 +338,7 @@ Sink & operator << (Sink & sink, const StringSet & s)
Sink & operator << (Sink & sink, const Error & ex) Sink & operator << (Sink & sink, const Error & ex)
{ {
auto info = ex.info(); auto & info = ex.info();
sink sink
<< "Error" << "Error"
<< info.level << info.level

View file

@ -331,17 +331,9 @@ T readNum(Source & source)
unsigned char buf[8]; unsigned char buf[8];
source((char *) buf, sizeof(buf)); source((char *) buf, sizeof(buf));
uint64_t n = auto n = readLittleEndian<uint64_t>(buf);
((uint64_t) buf[0]) |
((uint64_t) buf[1] << 8) |
((uint64_t) buf[2] << 16) |
((uint64_t) buf[3] << 24) |
((uint64_t) buf[4] << 32) |
((uint64_t) buf[5] << 40) |
((uint64_t) buf[6] << 48) |
((uint64_t) buf[7] << 56);
if (n > (uint64_t)std::numeric_limits<T>::max()) if (n > (uint64_t) std::numeric_limits<T>::max())
throw SerialisationError("serialised integer %d is too large for type '%s'", n, typeid(T).name()); throw SerialisationError("serialised integer %d is too large for type '%s'", n, typeid(T).name());
return (T) n; return (T) n;

View file

@ -1594,6 +1594,21 @@ std::string stripIndentation(std::string_view s)
} }
std::pair<std::string_view, std::string_view> getLine(std::string_view s)
{
auto newline = s.find('\n');
if (newline == s.npos) {
return {s, ""};
} else {
auto line = s.substr(0, newline);
if (!line.empty() && line[line.size() - 1] == '\r')
line = line.substr(0, line.size() - 1);
return {line, s.substr(newline + 1)};
}
}
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
static Sync<std::pair<unsigned short, unsigned short>> windowSize{{0, 0}}; static Sync<std::pair<unsigned short, unsigned short>> windowSize{{0, 0}};

View file

@ -510,6 +510,17 @@ std::optional<N> string2Float(const std::string_view s)
} }
/* Convert a little-endian integer to host order. */
template<typename T>
T readLittleEndian(unsigned char * p)
{
T x = 0;
for (size_t i = 0; i < sizeof(x); ++i)
x |= ((T) *p++) << (i * 8);
return x;
}
/* Return true iff `s' starts with `prefix'. */ /* Return true iff `s' starts with `prefix'. */
bool hasPrefix(std::string_view s, std::string_view prefix); bool hasPrefix(std::string_view s, std::string_view prefix);
@ -563,6 +574,12 @@ std::string base64Decode(std::string_view s);
std::string stripIndentation(std::string_view s); std::string stripIndentation(std::string_view s);
/* Get the prefix of 's' up to and excluding the next line break (LF
optionally preceded by CR), and the remainder following the line
break. */
std::pair<std::string_view, std::string_view> getLine(std::string_view s);
/* Get a value for the specified key from an associate container. */ /* Get a value for the specified key from an associate container. */
template <class T> template <class T>
const typename T::mapped_type * get(const T & map, const typename T::key_type & key) const typename T::mapped_type * get(const T & map, const typename T::key_type & key)
@ -737,4 +754,11 @@ inline std::string operator + (std::string && s, std::string_view s2)
return std::move(s); return std::move(s);
} }
inline std::string operator + (std::string_view s1, const char * s2)
{
std::string s(s1);
s.append(s2);
return s;
}
} }

View file

@ -926,7 +926,6 @@ static void opServe(Strings opFlags, Strings opArgs)
worker_proto::write(*store, out, status.builtOutputs); worker_proto::write(*store, out, status.builtOutputs);
} }
break; break;
} }

View file

@ -257,7 +257,7 @@ static void daemonLoop()
} catch (Interrupted & e) { } catch (Interrupted & e) {
return; return;
} catch (Error & error) { } catch (Error & error) {
ErrorInfo ei = error.info(); auto ei = error.info();
// FIXME: add to trace? // FIXME: add to trace?
ei.msg = hintfmt("error processing connection: %1%", ei.msg.str()); ei.msg = hintfmt("error processing connection: %1%", ei.msg.str());
logError(ei); logError(ei);

View file

@ -16,7 +16,7 @@ R""(
# Description # Description
This command recreates the lock file of a flake (`flake.lock`), thus This command recreates the lock file of a flake (`flake.lock`), thus
updating the lock for every mutable input (like `nixpkgs`) to its updating the lock for every unlocked input (like `nixpkgs`) to its
current version. This is equivalent to passing `--recreate-lock-file` current version. This is equivalent to passing `--recreate-lock-file`
to any command that operates on a flake. That is, to any command that operates on a flake. That is,

View file

@ -215,7 +215,7 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON
if (!lockedFlake.lockFile.root->inputs.empty()) if (!lockedFlake.lockFile.root->inputs.empty())
logger->cout(ANSI_BOLD "Inputs:" ANSI_NORMAL); logger->cout(ANSI_BOLD "Inputs:" ANSI_NORMAL);
std::unordered_set<std::shared_ptr<Node>> visited; std::set<ref<Node>> visited;
std::function<void(const Node & node, const std::string & prefix)> recurse; std::function<void(const Node & node, const std::string & prefix)> recurse;
@ -227,7 +227,7 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON
if (auto lockedNode = std::get_if<0>(&input.second)) { if (auto lockedNode = std::get_if<0>(&input.second)) {
logger->cout("%s" ANSI_BOLD "%s" ANSI_NORMAL ": %s", logger->cout("%s" ANSI_BOLD "%s" ANSI_NORMAL ": %s",
prefix + (last ? treeLast : treeConn), input.first, prefix + (last ? treeLast : treeConn), input.first,
*lockedNode ? (*lockedNode)->lockedRef : flake.lockedRef); (*lockedNode)->lockedRef);
bool firstVisit = visited.insert(*lockedNode).second; bool firstVisit = visited.insert(*lockedNode).second;

View file

@ -20,11 +20,11 @@ following fields:
* An integer that can be used to unambiguously identify the package in * An integer that can be used to unambiguously identify the package in
invocations of `nix profile remove` and `nix profile upgrade`. invocations of `nix profile remove` and `nix profile upgrade`.
* The original ("mutable") flake reference and output attribute path * The original ("unlocked") flake reference and output attribute path
used at installation time. used at installation time.
* The immutable flake reference to which the mutable flake reference * The locked flake reference to which the unlocked flake reference was
was resolved. resolved.
* The store path(s) of the package. * The store path(s) of the package.

View file

@ -2,7 +2,7 @@ R""(
# Examples # Examples
* Upgrade all packages that were installed using a mutable flake * Upgrade all packages that were installed using an unlocked flake
reference: reference:
```console ```console
@ -32,9 +32,9 @@ the package was installed.
> **Warning** > **Warning**
> >
> This only works if you used a *mutable* flake reference at > This only works if you used an *unlocked* flake reference at
> installation time, e.g. `nixpkgs#hello`. It does not work if you > installation time, e.g. `nixpkgs#hello`. It does not work if you
> used an *immutable* flake reference > used a *locked* flake reference
> (e.g. `github:NixOS/nixpkgs/13d0c311e3ae923a00f734b43fd1d35b47d8943a#hello`), > (e.g. `github:NixOS/nixpkgs/13d0c311e3ae923a00f734b43fd1d35b47d8943a#hello`),
> since in that case the "latest version" is always the same. > since in that case the "latest version" is always the same.

View file

@ -88,8 +88,7 @@ has the following fields:
the user at the time of installation (e.g. `nixpkgs`). This is also the user at the time of installation (e.g. `nixpkgs`). This is also
the flake reference that will be used by `nix profile upgrade`. the flake reference that will be used by `nix profile upgrade`.
* `uri`: The immutable flake reference to which `originalUrl` * `uri`: The locked flake reference to which `originalUrl` resolved.
resolved.
* `attrPath`: The flake output attribute that provided this * `attrPath`: The flake output attribute that provided this
package. Note that this is not necessarily the attribute that the package. Note that this is not necessarily the attribute that the

View file

@ -183,14 +183,12 @@ struct CmdRegistryPin : RegistryCommand, EvalCommand
void run(nix::ref<nix::Store> store) override void run(nix::ref<nix::Store> store) override
{ {
if (locked.empty()) { if (locked.empty()) locked = url;
locked = url;
}
auto registry = getRegistry(); auto registry = getRegistry();
auto ref = parseFlakeRef(url); auto ref = parseFlakeRef(url);
auto locked_ref = parseFlakeRef(locked); auto lockedRef = parseFlakeRef(locked);
registry->remove(ref.input); registry->remove(ref.input);
auto [tree, resolved] = locked_ref.resolve(store).input.fetch(store); auto [tree, resolved] = lockedRef.resolve(store).input.fetch(store);
fetchers::Attrs extraAttrs; fetchers::Attrs extraAttrs;
if (ref.subdir != "") extraAttrs["dir"] = ref.subdir; if (ref.subdir != "") extraAttrs["dir"] = ref.subdir;
registry->add(ref.input, resolved, extraAttrs); registry->add(ref.input, resolved, extraAttrs);

View file

@ -29,3 +29,7 @@ nix-instantiate --eval -E 'assert 1 + 2 == 3; true'
[[ $(nix-instantiate -A attr --eval "./eval.nix") == '{ foo = "bar"; }' ]] [[ $(nix-instantiate -A attr --eval "./eval.nix") == '{ foo = "bar"; }' ]]
[[ $(nix-instantiate -A attr --eval --json "./eval.nix") == '{"foo":"bar"}' ]] [[ $(nix-instantiate -A attr --eval --json "./eval.nix") == '{"foo":"bar"}' ]]
[[ $(nix-instantiate -A int --eval - < "./eval.nix") == 123 ]] [[ $(nix-instantiate -A int --eval - < "./eval.nix") == 123 ]]
# Check that symlink cycles don't cause a hang.
ln -sfn cycle.nix $TEST_ROOT/cycle.nix
(! nix eval --file $TEST_ROOT/cycle.nix)

View file

@ -122,6 +122,7 @@ git -C $repo commit -m 'Bla3' -a
path4=$(nix eval --impure --refresh --raw --expr "(builtins.fetchGit file://$repo).outPath") path4=$(nix eval --impure --refresh --raw --expr "(builtins.fetchGit file://$repo).outPath")
[[ $path2 = $path4 ]] [[ $path2 = $path4 ]]
status=0
nix eval --impure --raw --expr "(builtins.fetchGit { url = $repo; rev = \"$rev2\"; narHash = \"sha256-B5yIPHhEm0eysJKEsO7nqxprh9vcblFxpJG11gXJus1=\"; }).outPath" || status=$? nix eval --impure --raw --expr "(builtins.fetchGit { url = $repo; rev = \"$rev2\"; narHash = \"sha256-B5yIPHhEm0eysJKEsO7nqxprh9vcblFxpJG11gXJus1=\"; }).outPath" || status=$?
[[ "$status" = "102" ]] [[ "$status" = "102" ]]

View file

@ -0,0 +1,17 @@
source ./common.sh
requireGit
flake1Dir=$TEST_ROOT/flake1
flake2Dir=$TEST_ROOT/flake2
createGitRepo $flake1Dir
cat > $flake1Dir/flake.nix <<EOF
{
outputs = { self }: { x = builtins.readFile $(pwd)/absolute-paths.sh; };
}
EOF
git -C $flake1Dir add flake.nix
git -C $flake1Dir commit -m Initial
nix eval --impure --json $flake1Dir#x

View file

@ -53,7 +53,11 @@ cat > $flake3Dir/flake.nix <<EOF
} }
EOF EOF
git -C $flake3Dir add flake.nix cat > $flake3Dir/default.nix <<EOF
{ x = 123; }
EOF
git -C $flake3Dir add flake.nix default.nix
git -C $flake3Dir commit -m 'Initial' git -C $flake3Dir commit -m 'Initial'
cat > $nonFlakeDir/README.md <<EOF cat > $nonFlakeDir/README.md <<EOF
@ -109,11 +113,12 @@ nix build -o $TEST_ROOT/result git+file://$flake1Dir
nix build -o $flake1Dir/result git+file://$flake1Dir nix build -o $flake1Dir/result git+file://$flake1Dir
nix path-info $flake1Dir/result nix path-info $flake1Dir/result
# 'getFlake' on a mutable flakeref should fail in pure mode, but succeed in impure mode. # 'getFlake' on an unlocked flakeref should fail in pure mode, but
# succeed in impure mode.
(! nix build -o $TEST_ROOT/result --expr "(builtins.getFlake \"$flake1Dir\").packages.$system.default") (! nix build -o $TEST_ROOT/result --expr "(builtins.getFlake \"$flake1Dir\").packages.$system.default")
nix build -o $TEST_ROOT/result --expr "(builtins.getFlake \"$flake1Dir\").packages.$system.default" --impure nix build -o $TEST_ROOT/result --expr "(builtins.getFlake \"$flake1Dir\").packages.$system.default" --impure
# 'getFlake' on an immutable flakeref should succeed even in pure mode. # 'getFlake' on a locked flakeref should succeed even in pure mode.
nix build -o $TEST_ROOT/result --expr "(builtins.getFlake \"git+file://$flake1Dir?rev=$hash2\").packages.$system.default" nix build -o $TEST_ROOT/result --expr "(builtins.getFlake \"git+file://$flake1Dir?rev=$hash2\").packages.$system.default"
# Building a flake with an unlocked dependency should fail in pure mode. # Building a flake with an unlocked dependency should fail in pure mode.
@ -460,7 +465,7 @@ nix flake lock $flake3Dir --update-input flake2/flake1
# Test 'nix flake metadata --json'. # Test 'nix flake metadata --json'.
nix flake metadata $flake3Dir --json | jq . nix flake metadata $flake3Dir --json | jq .
# Test flake in store does not evaluate # Test flake in store does not evaluate.
rm -rf $badFlakeDir rm -rf $badFlakeDir
mkdir $badFlakeDir mkdir $badFlakeDir
echo INVALID > $badFlakeDir/flake.nix echo INVALID > $badFlakeDir/flake.nix

View file

@ -0,0 +1,30 @@
source ./common.sh
requireGit
flake1Dir=$TEST_ROOT/flake1
flake2Dir=$TEST_ROOT/flake2
createGitRepo $flake1Dir
cat > $flake1Dir/flake.nix <<EOF
{
outputs = { self }: { x = import ./x.nix; };
}
EOF
echo 123 > $flake1Dir/x.nix
git -C $flake1Dir add flake.nix x.nix
git -C $flake1Dir commit -m Initial
createGitRepo $flake2Dir
cat > $flake2Dir/flake.nix <<EOF
{
outputs = { self, flake1 }: { x = flake1.x; };
}
EOF
git -C $flake2Dir add flake.nix
[[ $(nix eval --json $flake2Dir#x --override-input flake1 $TEST_ROOT/flake1) = 123 ]]
echo 456 > $flake1Dir/x.nix
[[ $(nix eval --json $flake2Dir#x --override-input flake1 $TEST_ROOT/flake1) = 456 ]]

View file

@ -11,7 +11,7 @@ expect_trace() {
--expr "$expr" 2>&1 \ --expr "$expr" 2>&1 \
| grep "function-trace" \ | grep "function-trace" \
| sed -e 's/ [0-9]*$//' | sed -e 's/ [0-9]*$//'
); )
echo -n "Tracing expression '$expr'" echo -n "Tracing expression '$expr'"
set +e set +e

View file

@ -7,6 +7,8 @@ nix_tests = \
flakes/follow-paths.sh \ flakes/follow-paths.sh \
flakes/bundle.sh \ flakes/bundle.sh \
flakes/check.sh \ flakes/check.sh \
flakes/unlocked-override.sh \
flakes/absolute-paths.sh \
ca/gc.sh \ ca/gc.sh \
gc.sh \ gc.sh \
remote-store.sh \ remote-store.sh \
@ -110,7 +112,8 @@ nix_tests = \
fetchClosure.sh \ fetchClosure.sh \
completions.sh \ completions.sh \
impure-derivations.sh \ impure-derivations.sh \
path-from-hash-part.sh path-from-hash-part.sh \
toString-path.sh
ifeq ($(HAVE_LIBCPUID), 1) ifeq ($(HAVE_LIBCPUID), 1)
nix_tests += compute-levels.sh nix_tests += compute-levels.sh

View file

@ -9,3 +9,6 @@ nix-instantiate --eval -E '<by-relative-path/simple.nix>' --restrict-eval
# Should ideally also test this, but theres no pure way to do it, so just trust me that it works # Should ideally also test this, but theres no pure way to do it, so just trust me that it works
# nix-instantiate --eval -E '<nixpkgs>' -I nixpkgs=channel:nixos-unstable --restrict-eval # nix-instantiate --eval -E '<nixpkgs>' -I nixpkgs=channel:nixos-unstable --restrict-eval
[[ $(nix-instantiate --find-file by-absolute-path/simple.nix) = $PWD/simple.nix ]]
[[ $(nix-instantiate --find-file by-relative-path/simple.nix) = $PWD/simple.nix ]]

View file

@ -3,7 +3,7 @@ source common.sh
clearStore clearStore
nix-instantiate --restrict-eval --eval -E '1 + 2' nix-instantiate --restrict-eval --eval -E '1 + 2'
(! nix-instantiate --restrict-eval ./restricted.nix) (! nix-instantiate --eval --restrict-eval ./restricted.nix)
(! nix-instantiate --eval --restrict-eval <(echo '1 + 2')) (! nix-instantiate --eval --restrict-eval <(echo '1 + 2'))
nix-instantiate --restrict-eval ./simple.nix -I src=. nix-instantiate --restrict-eval ./simple.nix -I src=.
nix-instantiate --restrict-eval ./simple.nix -I src1=simple.nix -I src2=config.nix -I src3=./simple.builder.sh nix-instantiate --restrict-eval ./simple.nix -I src1=simple.nix -I src2=config.nix -I src3=./simple.builder.sh

8
tests/toString-path.sh Normal file
View file

@ -0,0 +1,8 @@
source common.sh
mkdir -p $TEST_ROOT/foo
echo bla > $TEST_ROOT/foo/bar
[[ $(nix eval --raw --impure --expr "builtins.readFile (builtins.toString (builtins.fetchTree { type = \"path\"; path = \"$TEST_ROOT/foo\"; } + \"/bar\"))") = bla ]]
[[ $(nix eval --json --impure --expr "builtins.readDir (builtins.toString (builtins.fetchTree { type = \"path\"; path = \"$TEST_ROOT/foo\"; }))") = '{"bar":"regular"}' ]]