Merge remote-tracking branch 'upstream/master' into path-info

This commit is contained in:
John Ericson 2022-03-25 19:25:08 +00:00
commit 0dc2974930
104 changed files with 1735 additions and 767 deletions

View file

@ -110,7 +110,7 @@ default, set it to `-`.
7. A comma-separated list of *mandatory features*. A machine will only 7. A comma-separated list of *mandatory features*. A machine will only
be used to build a derivation if all of the machines mandatory be used to build a derivation if all of the machines mandatory
features appear in the derivations `requiredSystemFeatures` features appear in the derivations `requiredSystemFeatures`
attribute.. attribute.
8. The (base64-encoded) public host key of the remote machine. If omitted, SSH 8. The (base64-encoded) public host key of the remote machine. If omitted, SSH
will use its regular known-hosts file. Specifically, the field is calculated will use its regular known-hosts file. Specifically, the field is calculated

View file

@ -84,7 +84,9 @@ The installer will modify `/etc/bashrc`, and `/etc/zshrc` if they exist.
The installer will first back up these files with a `.backup-before-nix` The installer will first back up these files with a `.backup-before-nix`
extension. The installer will also create `/etc/profile.d/nix.sh`. extension. The installer will also create `/etc/profile.d/nix.sh`.
You can uninstall Nix with the following commands: ## Uninstalling
### Linux
```console ```console
sudo rm -rf /etc/profile/nix.sh /etc/nix /nix ~root/.nix-profile ~root/.nix-defexpr ~root/.nix-channels ~/.nix-profile ~/.nix-defexpr ~/.nix-channels sudo rm -rf /etc/profile/nix.sh /etc/nix /nix ~root/.nix-profile ~root/.nix-defexpr ~root/.nix-channels ~/.nix-profile ~/.nix-defexpr ~/.nix-channels
@ -95,15 +97,95 @@ sudo systemctl stop nix-daemon.service
sudo systemctl disable nix-daemon.socket sudo systemctl disable nix-daemon.socket
sudo systemctl disable nix-daemon.service sudo systemctl disable nix-daemon.service
sudo systemctl daemon-reload sudo systemctl daemon-reload
# If you are on macOS, you will need to run:
sudo launchctl unload /Library/LaunchDaemons/org.nixos.nix-daemon.plist
sudo rm /Library/LaunchDaemons/org.nixos.nix-daemon.plist
``` ```
There may also be references to Nix in `/etc/profile`, `/etc/bashrc`, There may also be references to Nix in `/etc/profile`, `/etc/bashrc`,
and `/etc/zshrc` which you may remove. and `/etc/zshrc` which you may remove.
### macOS
1. Edit `/etc/zshrc` and `/etc/bashrc` to remove the lines sourcing
`nix-daemon.sh`, which should look like this:
```bash
# Nix
if [ -e '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' ]; then
. '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh'
fi
# End Nix
```
If these files haven't been altered since installing Nix you can simply put
the backups back in place:
```console
sudo mv /etc/zshrc.backup-before-nix /etc/zshrc
sudo mv /etc/bashrc.backup-before-nix /etc/bashrc
```
This will stop shells from sourcing the file and bringing everything you
installed using Nix in scope.
2. Stop and remove the Nix daemon services:
```console
sudo launchctl unload /Library/LaunchDaemons/org.nixos.nix-daemon.plist
sudo rm /Library/LaunchDaemons/org.nixos.nix-daemon.plist
sudo launchctl unload /Library/LaunchDaemons/org.nixos.darwin-store.plist
sudo rm /Library/LaunchDaemons/org.nixos.darwin-store.plist
```
This stops the Nix daemon and prevents it from being started next time you
boot the system.
3. Remove the `nixbld` group and the `_nixbuildN` users:
```console
sudo dscl . -delete /Groups/nixbld
for u in $(sudo dscl . -list /Users | grep _nixbld); do sudo dscl . -delete /Users/$u; done
```
This will remove all the build users that no longer serve a purpose.
4. Edit fstab using `sudo vifs` to remove the line mounting the Nix Store
volume on `/nix`, which looks like this,
`LABEL=Nix\040Store /nix apfs rw,nobrowse`. This will prevent automatic
mounting of the Nix Store volume.
5. Edit `/etc/synthetic.conf` to remove the `nix` line. If this is the only
line in the file you can remove it entirely, `sudo rm /etc/synthetic.conf`.
This will prevent the creation of the empty `/nix` directory to provide a
mountpoint for the Nix Store volume.
6. Remove the files Nix added to your system:
```console
sudo rm -rf /etc/nix /var/root/.nix-profile /var/root/.nix-defexpr /var/root/.nix-channels ~/.nix-profile ~/.nix-defexpr ~/.nix-channels
```
This gets rid of any data Nix may have created except for the store which is
removed next.
7. Remove the Nix Store volume:
```console
sudo diskutil apfs deleteVolume /nix
```
This will remove the Nix Store volume and everything that was added to the
store.
> **Note**
>
> After you complete the steps here, you will still have an empty `/nix`
> directory. This is an expected sign of a successful uninstall. The empty
> `/nix` directory will disappear the next time you reboot.
>
> You do not have to reboot to finish uninstalling Nix. The uninstall is
> complete. macOS (Catalina+) directly controls root directories and its
> read-only root will prevent you from manually deleting the empty `/nix`
> mountpoint.
# macOS Installation <a name="sect-macos-installation-change-store-prefix"></a><a name="sect-macos-installation-encrypted-volume"></a><a name="sect-macos-installation-symlink"></a><a name="sect-macos-installation-recommended-notes"></a> # macOS Installation <a name="sect-macos-installation-change-store-prefix"></a><a name="sect-macos-installation-encrypted-volume"></a><a name="sect-macos-installation-symlink"></a><a name="sect-macos-installation-recommended-notes"></a>
<!-- Note: anchors above to catch permalinks to old explanations --> <!-- Note: anchors above to catch permalinks to old explanations -->

View file

@ -1 +1,16 @@
# Release X.Y (202?-??-??) # Release X.Y (202?-??-??)
* Various nix commands can now read expressions from stdin with `--file -`.
* `nix store make-content-addressable` has been renamed to `nix store
make-content-addressed`.
* New experimental builtin function `builtins.fetchClosure` that
copies a closure from a binary cache at evaluation time and rewrites
it to content-addressed form (if it isn't already). Like
`builtins.storePath`, this allows importing pre-built store paths;
the difference is that it doesn't require the user to configure
binary caches and trusted public keys.
This function is only available if you enable the experimental
feature `fetch-closure`.

View file

@ -1,7 +1,8 @@
ifdef HOST_LINUX ifdef HOST_LINUX
$(foreach n, nix-daemon.socket nix-daemon.service, $(eval $(call install-file-in, $(d)/$(n), $(prefix)/lib/systemd/system, 0644))) $(foreach n, nix-daemon.socket nix-daemon.service, $(eval $(call install-file-in, $(d)/$(n), $(prefix)/lib/systemd/system, 0644)))
$(foreach n, nix-daemon.conf, $(eval $(call install-file-in, $(d)/$(n), $(prefix)/lib/tmpfiles.d, 0644)))
clean-files += $(d)/nix-daemon.socket $(d)/nix-daemon.service clean-files += $(d)/nix-daemon.socket $(d)/nix-daemon.service $(d)/nix-daemon.conf
endif endif

View file

@ -0,0 +1 @@
d @localstatedir@/nix/daemon-socket 0755 root root - -

View file

@ -1,5 +1,6 @@
[Unit] [Unit]
Description=Nix Daemon Description=Nix Daemon
Documentation=man:nix-daemon https://nixos.org/manual
RequiresMountsFor=@storedir@ RequiresMountsFor=@storedir@
RequiresMountsFor=@localstatedir@ RequiresMountsFor=@localstatedir@
ConditionPathIsReadWrite=@localstatedir@/nix/daemon-socket ConditionPathIsReadWrite=@localstatedir@/nix/daemon-socket

View file

@ -739,7 +739,7 @@ install_from_extracted_nix() {
cd "$EXTRACTED_NIX_PATH" cd "$EXTRACTED_NIX_PATH"
_sudo "to copy the basic Nix files to the new store at $NIX_ROOT/store" \ _sudo "to copy the basic Nix files to the new store at $NIX_ROOT/store" \
cp -RLp ./store/* "$NIX_ROOT/store/" cp -RPp ./store/* "$NIX_ROOT/store/"
_sudo "to make the new store non-writable at $NIX_ROOT/store" \ _sudo "to make the new store non-writable at $NIX_ROOT/store" \
chmod -R ugo-w "$NIX_ROOT/store/" chmod -R ugo-w "$NIX_ROOT/store/"

View file

@ -9,6 +9,8 @@ readonly SERVICE_DEST=/etc/systemd/system/nix-daemon.service
readonly SOCKET_SRC=/lib/systemd/system/nix-daemon.socket readonly SOCKET_SRC=/lib/systemd/system/nix-daemon.socket
readonly SOCKET_DEST=/etc/systemd/system/nix-daemon.socket readonly SOCKET_DEST=/etc/systemd/system/nix-daemon.socket
readonly TMPFILES_SRC=/lib/tmpfiles.d/nix-daemon.conf
readonly TMPFILES_DEST=/etc/tmpfiles.d/nix-daemon.conf
# Path for the systemd override unit file to contain the proxy settings # Path for the systemd override unit file to contain the proxy settings
readonly SERVICE_OVERRIDE=${SERVICE_DEST}.d/override.conf readonly SERVICE_OVERRIDE=${SERVICE_DEST}.d/override.conf
@ -83,6 +85,13 @@ EOF
poly_configure_nix_daemon_service() { poly_configure_nix_daemon_service() {
if [ -e /run/systemd/system ]; then if [ -e /run/systemd/system ]; then
task "Setting up the nix-daemon systemd service" task "Setting up the nix-daemon systemd service"
_sudo "to create the nix-daemon tmpfiles config" \
ln -sfn /nix/var/nix/profiles/default/$TMPFILES_SRC $TMPFILES_DEST
_sudo "to run systemd-tmpfiles once to pick that path up" \
sytemd-tmpfiles create --prefix=/nix/var/nix
_sudo "to set up the nix-daemon service" \ _sudo "to set up the nix-daemon service" \
systemctl link "/nix/var/nix/profiles/default$SERVICE_SRC" systemctl link "/nix/var/nix/profiles/default$SERVICE_SRC"

View file

@ -300,7 +300,7 @@ connected:
std::set<Realisation> missingRealisations; std::set<Realisation> missingRealisations;
StorePathSet missingPaths; StorePathSet missingPaths;
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) && !derivationHasKnownOutputPaths(drv.type())) { if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) && !drv.type().hasKnownOutputPaths()) {
for (auto & outputName : wantedOutputs) { for (auto & outputName : wantedOutputs) {
auto thisOutputHash = outputHashes.at(outputName); auto thisOutputHash = outputHashes.at(outputName);
auto thisOutputId = DrvOutput{ thisOutputHash, outputName }; auto thisOutputId = DrvOutput{ thisOutputHash, outputName };

View file

@ -204,7 +204,8 @@ Strings editorFor(const Pos & pos)
if (pos.line > 0 && ( if (pos.line > 0 && (
editor.find("emacs") != std::string::npos || editor.find("emacs") != std::string::npos ||
editor.find("nano") != std::string::npos || editor.find("nano") != std::string::npos ||
editor.find("vim") != std::string::npos)) editor.find("vim") != std::string::npos ||
editor.find("kak") != std::string::npos))
args.push_back(fmt("+%d", pos.line)); args.push_back(fmt("+%d", pos.line));
args.push_back(pos.file); args.push_back(pos.file);
return args; return args;

View file

@ -134,7 +134,9 @@ SourceExprCommand::SourceExprCommand()
addFlag({ addFlag({
.longName = "file", .longName = "file",
.shortName = 'f', .shortName = 'f',
.description = "Interpret installables as attribute paths relative to the Nix expression stored in *file*.", .description =
"Interpret installables as attribute paths relative to the Nix expression stored in *file*. "
"If *file* is the character -, then a Nix expression will be read from standard input.",
.category = installablesCategory, .category = installablesCategory,
.labels = {"file"}, .labels = {"file"},
.handler = {&file}, .handler = {&file},
@ -695,7 +697,10 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
auto state = getEvalState(); auto state = getEvalState();
auto vFile = state->allocValue(); auto vFile = state->allocValue();
if (file) if (file == "-") {
auto e = state->parseStdin();
state->eval(e, *vFile);
} 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

@ -21,6 +21,8 @@ struct AttrDb
{ {
std::atomic_bool failed{false}; std::atomic_bool failed{false};
const Store & cfg;
struct State struct State
{ {
SQLite db; SQLite db;
@ -33,8 +35,9 @@ struct AttrDb
std::unique_ptr<Sync<State>> _state; std::unique_ptr<Sync<State>> _state;
AttrDb(const Hash & fingerprint) AttrDb(const Store & cfg, const Hash & fingerprint)
: _state(std::make_unique<Sync<State>>()) : cfg(cfg)
, _state(std::make_unique<Sync<State>>())
{ {
auto state(_state->lock()); auto state(_state->lock());
@ -254,10 +257,10 @@ struct AttrDb
return {{rowId, attrs}}; return {{rowId, attrs}};
} }
case AttrType::String: { case AttrType::String: {
std::vector<std::pair<Path, std::string>> context; NixStringContext context;
if (!queryAttribute.isNull(3)) if (!queryAttribute.isNull(3))
for (auto & s : tokenizeString<std::vector<std::string>>(queryAttribute.getStr(3), ";")) for (auto & s : tokenizeString<std::vector<std::string>>(queryAttribute.getStr(3), ";"))
context.push_back(decodeContext(s)); context.push_back(decodeContext(cfg, s));
return {{rowId, string_t{queryAttribute.getStr(2), context}}}; return {{rowId, string_t{queryAttribute.getStr(2), context}}};
} }
case AttrType::Bool: case AttrType::Bool:
@ -274,10 +277,10 @@ struct AttrDb
} }
}; };
static std::shared_ptr<AttrDb> makeAttrDb(const Hash & fingerprint) static std::shared_ptr<AttrDb> makeAttrDb(const Store & cfg, const Hash & fingerprint)
{ {
try { try {
return std::make_shared<AttrDb>(fingerprint); return std::make_shared<AttrDb>(cfg, fingerprint);
} catch (SQLiteError &) { } catch (SQLiteError &) {
ignoreException(); ignoreException();
return nullptr; return nullptr;
@ -288,7 +291,7 @@ EvalCache::EvalCache(
std::optional<std::reference_wrapper<const Hash>> useCache, std::optional<std::reference_wrapper<const Hash>> useCache,
EvalState & state, EvalState & state,
RootLoader rootLoader) RootLoader rootLoader)
: db(useCache ? makeAttrDb(*useCache) : nullptr) : db(useCache ? makeAttrDb(*state.store, *useCache) : nullptr)
, state(state) , state(state)
, rootLoader(rootLoader) , rootLoader(rootLoader)
{ {
@ -546,7 +549,7 @@ string_t AttrCursor::getStringWithContext()
if (auto s = std::get_if<string_t>(&cachedValue->second)) { if (auto s = std::get_if<string_t>(&cachedValue->second)) {
bool valid = true; bool valid = true;
for (auto & c : s->second) { for (auto & c : s->second) {
if (!root->state.store->isValidPath(root->state.store->parseStorePath(c.first))) { if (!root->state.store->isValidPath(c.first)) {
valid = false; valid = false;
break; break;
} }
@ -563,7 +566,7 @@ string_t AttrCursor::getStringWithContext()
auto & v = forceValue(); auto & v = forceValue();
if (v.type() == nString) if (v.type() == nString)
return {v.string.s, v.getContext()}; return {v.string.s, v.getContext(*root->state.store)};
else if (v.type() == nPath) else if (v.type() == nPath)
return {v.path, {}}; return {v.path, {}};
else else

View file

@ -52,7 +52,7 @@ struct misc_t {};
struct failed_t {}; struct failed_t {};
typedef uint64_t AttrId; typedef uint64_t AttrId;
typedef std::pair<AttrId, Symbol> AttrKey; typedef std::pair<AttrId, Symbol> AttrKey;
typedef std::pair<std::string, std::vector<std::pair<Path, std::string>>> string_t; typedef std::pair<std::string, NixStringContext> string_t;
typedef std::variant< typedef std::variant<
std::vector<Symbol>, std::vector<Symbol>,

View file

@ -24,6 +24,81 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const
} }
/* Note: Various places expect the allocated memory to be zeroed. */
[[gnu::always_inline]]
inline void * allocBytes(size_t n)
{
void * p;
#if HAVE_BOEHMGC
p = GC_MALLOC(n);
#else
p = calloc(n, 1);
#endif
if (!p) throw std::bad_alloc();
return p;
}
[[gnu::always_inline]]
Value * EvalState::allocValue()
{
#if HAVE_BOEHMGC
/* We use the boehm batch allocator to speed up allocations of Values (of which there are many).
GC_malloc_many returns a linked list of objects of the given size, where the first word
of each object is also the pointer to the next object in the list. This also means that we
have to explicitly clear the first word of every object we take. */
if (!*valueAllocCache) {
*valueAllocCache = GC_malloc_many(sizeof(Value));
if (!*valueAllocCache) throw std::bad_alloc();
}
/* GC_NEXT is a convenience macro for accessing the first word of an object.
Take the first list item, advance the list to the next item, and clear the next pointer. */
void * p = *valueAllocCache;
*valueAllocCache = GC_NEXT(p);
GC_NEXT(p) = nullptr;
#else
void * p = allocBytes(sizeof(Value));
#endif
nrValues++;
return (Value *) p;
}
[[gnu::always_inline]]
Env & EvalState::allocEnv(size_t size)
{
nrEnvs++;
nrValuesInEnvs += size;
Env * env;
#if HAVE_BOEHMGC
if (size == 1) {
/* see allocValue for explanations. */
if (!*env1AllocCache) {
*env1AllocCache = GC_malloc_many(sizeof(Env) + sizeof(Value *));
if (!*env1AllocCache) throw std::bad_alloc();
}
void * p = *env1AllocCache;
*env1AllocCache = GC_NEXT(p);
GC_NEXT(p) = nullptr;
env = (Env *) p;
} else
#endif
env = (Env *) allocBytes(sizeof(Env) + size * sizeof(Value *));
env->type = Env::Plain;
/* We assume that env->values has been cleared by the allocator; maybeThunk() and lookupVar fromWith expect this. */
return *env;
}
[[gnu::always_inline]]
void EvalState::forceValue(Value & v, const Pos & pos) void EvalState::forceValue(Value & v, const Pos & pos)
{ {
forceValue(v, [&]() { return pos; }); forceValue(v, [&]() { return pos; });
@ -52,6 +127,7 @@ void EvalState::forceValue(Value & v, Callable getPos)
} }
[[gnu::always_inline]]
inline void EvalState::forceAttrs(Value & v, const Pos & pos) inline void EvalState::forceAttrs(Value & v, const Pos & pos)
{ {
forceAttrs(v, [&]() { return pos; }); forceAttrs(v, [&]() { return pos; });
@ -59,6 +135,7 @@ inline void EvalState::forceAttrs(Value & v, const Pos & pos)
template <typename Callable> template <typename Callable>
[[gnu::always_inline]]
inline void EvalState::forceAttrs(Value & v, Callable getPos) inline void EvalState::forceAttrs(Value & v, Callable getPos)
{ {
forceValue(v, getPos); forceValue(v, getPos);
@ -67,6 +144,7 @@ inline void EvalState::forceAttrs(Value & v, Callable getPos)
} }
[[gnu::always_inline]]
inline void EvalState::forceList(Value & v, const Pos & pos) inline void EvalState::forceList(Value & v, const Pos & pos)
{ {
forceValue(v, pos); forceValue(v, pos);
@ -74,18 +152,5 @@ inline void EvalState::forceList(Value & v, const Pos & pos)
throwTypeError(pos, "value is %1% while a list was expected", v); throwTypeError(pos, "value is %1% while a list was expected", v);
} }
/* Note: Various places expect the allocated memory to be zeroed. */
inline void * allocBytes(size_t n)
{
void * p;
#if HAVE_BOEHMGC
p = GC_MALLOC(n);
#else
p = calloc(n, 1);
#endif
if (!p) throw std::bad_alloc();
return p;
}
} }

View file

@ -96,20 +96,20 @@ RootValue allocRootValue(Value * v)
} }
void printValue(std::ostream & str, std::set<const void *> & seen, const Value & v) void Value::print(std::ostream & str, std::set<const void *> * seen) const
{ {
checkInterrupt(); checkInterrupt();
switch (v.internalType) { switch (internalType) {
case tInt: case tInt:
str << v.integer; str << integer;
break; break;
case tBool: case tBool:
str << (v.boolean ? "true" : "false"); str << (boolean ? "true" : "false");
break; break;
case tString: case tString:
str << "\""; str << "\"";
for (const char * i = v.string.s; *i; i++) for (const char * i = string.s; *i; i++)
if (*i == '\"' || *i == '\\') str << "\\" << *i; if (*i == '\"' || *i == '\\') str << "\\" << *i;
else if (*i == '\n') str << "\\n"; else if (*i == '\n') str << "\\n";
else if (*i == '\r') str << "\\r"; else if (*i == '\r') str << "\\r";
@ -119,19 +119,19 @@ void printValue(std::ostream & str, std::set<const void *> & seen, const Value &
str << "\""; str << "\"";
break; break;
case tPath: case tPath:
str << v.path; // !!! escaping? str << path; // !!! escaping?
break; break;
case tNull: case tNull:
str << "null"; str << "null";
break; break;
case tAttrs: { case tAttrs: {
if (!v.attrs->empty() && !seen.insert(v.attrs).second) if (seen && !attrs->empty() && !seen->insert(attrs).second)
str << "<REPEAT>"; str << "«repeated»";
else { else {
str << "{ "; str << "{ ";
for (auto & i : v.attrs->lexicographicOrder()) { for (auto & i : attrs->lexicographicOrder()) {
str << i->name << " = "; str << i->name << " = ";
printValue(str, seen, *i->value); i->value->print(str, seen);
str << "; "; str << "; ";
} }
str << "}"; str << "}";
@ -141,12 +141,12 @@ void printValue(std::ostream & str, std::set<const void *> & seen, const Value &
case tList1: case tList1:
case tList2: case tList2:
case tListN: case tListN:
if (v.listSize() && !seen.insert(v.listElems()).second) if (seen && listSize() && !seen->insert(listElems()).second)
str << "<REPEAT>"; str << "«repeated»";
else { else {
str << "[ "; str << "[ ";
for (auto v2 : v.listItems()) { for (auto v2 : listItems()) {
printValue(str, seen, *v2); v2->print(str, seen);
str << " "; str << " ";
} }
str << "]"; str << "]";
@ -166,10 +166,10 @@ void printValue(std::ostream & str, std::set<const void *> & seen, const Value &
str << "<PRIMOP-APP>"; str << "<PRIMOP-APP>";
break; break;
case tExternal: case tExternal:
str << *v.external; str << *external;
break; break;
case tFloat: case tFloat:
str << v.fpoint; str << fpoint;
break; break;
default: default:
abort(); abort();
@ -177,10 +177,16 @@ void printValue(std::ostream & str, std::set<const void *> & seen, const Value &
} }
std::ostream & operator << (std::ostream & str, const Value & v) void Value::print(std::ostream & str, bool showRepeated) const
{ {
std::set<const void *> seen; std::set<const void *> seen;
printValue(str, seen, v); print(str, showRepeated ? nullptr : &seen);
}
std::ostream & operator << (std::ostream & str, const Value & v)
{
v.print(str, false);
return str; return str;
} }
@ -449,8 +455,10 @@ EvalState::EvalState(
, regexCache(makeRegexCache()) , regexCache(makeRegexCache())
#if HAVE_BOEHMGC #if HAVE_BOEHMGC
, valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr)) , valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
, env1AllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
#else #else
, valueAllocCache(std::make_shared<void *>(nullptr)) , valueAllocCache(std::make_shared<void *>(nullptr))
, env1AllocCache(std::make_shared<void *>(nullptr))
#endif #endif
, baseEnv(allocEnv(128)) , baseEnv(allocEnv(128))
, staticBaseEnv(false, 0) , staticBaseEnv(false, 0)
@ -499,23 +507,6 @@ EvalState::~EvalState()
} }
void EvalState::requireExperimentalFeatureOnEvaluation(
const ExperimentalFeature & feature,
const std::string_view fName,
const Pos & pos)
{
if (!settings.isExperimentalFeatureEnabled(feature)) {
throw EvalError({
.msg = hintfmt(
"Cannot call '%2%' because experimental Nix feature '%1%' is disabled. You can enable it via '--extra-experimental-features %1%'.",
feature,
fName
),
.errPos = pos
});
}
}
void EvalState::allowPath(const Path & path) void EvalState::allowPath(const Path & path)
{ {
if (allowedPaths) if (allowedPaths)
@ -727,9 +718,18 @@ LocalNoInlineNoReturn(void throwEvalError(const char * s, const std::string & s2
throw EvalError(s, s2); throw EvalError(s, s2);
} }
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const Suggestions & suggestions, const char * s, const std::string & s2))
{
throw EvalError(ErrorInfo {
.msg = hintfmt(s, s2),
.errPos = pos,
.suggestions = suggestions,
});
}
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const std::string & s2)) LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const std::string & s2))
{ {
throw EvalError({ throw EvalError(ErrorInfo {
.msg = hintfmt(s, s2), .msg = hintfmt(s, s2),
.errPos = pos .errPos = pos
}); });
@ -773,6 +773,16 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const
}); });
} }
LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const Suggestions & suggestions, const char * s, const ExprLambda & fun, const Symbol & s2))
{
throw TypeError(ErrorInfo {
.msg = hintfmt(s, fun.showNamePos(), s2),
.errPos = pos,
.suggestions = suggestions,
});
}
LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v)) LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v))
{ {
throw TypeError(s, showType(v)); throw TypeError(s, showType(v));
@ -876,42 +886,6 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
} }
Value * EvalState::allocValue()
{
/* We use the boehm batch allocator to speed up allocations of Values (of which there are many).
GC_malloc_many returns a linked list of objects of the given size, where the first word
of each object is also the pointer to the next object in the list. This also means that we
have to explicitly clear the first word of every object we take. */
if (!*valueAllocCache) {
*valueAllocCache = GC_malloc_many(sizeof(Value));
if (!*valueAllocCache) throw std::bad_alloc();
}
/* GC_NEXT is a convenience macro for accessing the first word of an object.
Take the first list item, advance the list to the next item, and clear the next pointer. */
void * p = *valueAllocCache;
GC_PTR_STORE_AND_DIRTY(&*valueAllocCache, GC_NEXT(p));
GC_NEXT(p) = nullptr;
nrValues++;
auto v = (Value *) p;
return v;
}
Env & EvalState::allocEnv(size_t size)
{
nrEnvs++;
nrValuesInEnvs += size;
Env * env = (Env *) allocBytes(sizeof(Env) + size * sizeof(Value *));
env->type = Env::Plain;
/* We assume that env->values has been cleared by the allocator; maybeThunk() and lookupVar fromWith expect this. */
return *env;
}
void EvalState::mkList(Value & v, size_t size) void EvalState::mkList(Value & v, size_t size)
{ {
v.mkList(size); v.mkList(size);
@ -1281,8 +1255,15 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
} }
} else { } else {
state.forceAttrs(*vAttrs, pos); state.forceAttrs(*vAttrs, pos);
if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) {
throwEvalError(pos, "attribute '%1%' missing", name); std::set<std::string> allAttrNames;
for (auto & attr : *vAttrs->attrs)
allAttrNames.insert(attr.name);
throwEvalError(
pos,
Suggestions::bestMatches(allAttrNames, name),
"attribute '%1%' missing", name);
}
} }
vAttrs = j->value; vAttrs = j->value;
pos2 = j->pos; pos2 = j->pos;
@ -1398,8 +1379,17 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
/* Nope, so show the first unexpected argument to the /* Nope, so show the first unexpected argument to the
user. */ user. */
for (auto & i : *args[0]->attrs) for (auto & i : *args[0]->attrs)
if (!lambda.formals->has(i.name)) if (!lambda.formals->has(i.name)) {
throwTypeError(pos, "%1% called with unexpected argument '%2%'", lambda, i.name); std::set<std::string> formalNames;
for (auto & formal : lambda.formals->formals)
formalNames.insert(formal.name);
throwTypeError(
pos,
Suggestions::bestMatches(formalNames, i.name),
"%1% called with unexpected argument '%2%'",
lambda,
i.name);
}
abort(); // can't happen abort(); // can't happen
} }
} }
@ -1902,13 +1892,22 @@ std::string_view EvalState::forceString(Value & v, const Pos & pos)
/* Decode a context string !<name>!<path> into a pair <path, /* Decode a context string !<name>!<path> into a pair <path,
name>. */ name>. */
std::pair<std::string, std::string> decodeContext(std::string_view s) NixStringContextElem decodeContext(const Store & store, std::string_view s)
{ {
if (s.at(0) == '!') { if (s.at(0) == '!') {
size_t index = s.find("!", 1); size_t index = s.find("!", 1);
return {std::string(s.substr(index + 1)), std::string(s.substr(1, index - 1))}; return {
store.parseStorePath(s.substr(index + 1)),
std::string(s.substr(1, index - 1)),
};
} else } else
return {s.at(0) == '/' ? std::string(s) : std::string(s.substr(1)), ""}; return {
store.parseStorePath(
s.at(0) == '/'
? s
: s.substr(1)),
"",
};
} }
@ -1920,13 +1919,13 @@ void copyContext(const Value & v, PathSet & context)
} }
std::vector<std::pair<Path, std::string>> Value::getContext() NixStringContext Value::getContext(const Store & store)
{ {
std::vector<std::pair<Path, std::string>> res; NixStringContext res;
assert(internalType == tString); assert(internalType == tString);
if (string.context) if (string.context)
for (const char * * p = string.context; *p; ++p) for (const char * * p = string.context; *p; ++p)
res.push_back(decodeContext(*p)); res.push_back(decodeContext(store, *p));
return res; return res;
} }

View file

@ -133,9 +133,14 @@ private:
/* Cache used by prim_match(). */ /* Cache used by prim_match(). */
std::shared_ptr<RegexCache> regexCache; std::shared_ptr<RegexCache> regexCache;
#if HAVE_BOEHMGC
/* Allocation cache for GC'd Value objects. */ /* Allocation cache for GC'd Value objects. */
std::shared_ptr<void *> valueAllocCache; std::shared_ptr<void *> valueAllocCache;
/* Allocation cache for size-1 Env objects. */
std::shared_ptr<void *> env1AllocCache;
#endif
public: public:
EvalState( EvalState(
@ -144,12 +149,6 @@ public:
std::shared_ptr<Store> buildStore = nullptr); std::shared_ptr<Store> buildStore = nullptr);
~EvalState(); ~EvalState();
void requireExperimentalFeatureOnEvaluation(
const ExperimentalFeature &,
const std::string_view fName,
const Pos & pos
);
void addToSearchPath(const std::string & s); void addToSearchPath(const std::string & s);
SearchPath getSearchPath() { return searchPath; } SearchPath getSearchPath() { return searchPath; }
@ -347,8 +346,8 @@ public:
void autoCallFunction(Bindings & args, Value & fun, Value & res); void autoCallFunction(Bindings & args, Value & fun, Value & res);
/* Allocation primitives. */ /* Allocation primitives. */
Value * allocValue(); inline Value * allocValue();
Env & allocEnv(size_t size); inline Env & allocEnv(size_t size);
Value * allocAttr(Value & vAttrs, const Symbol & name); Value * allocAttr(Value & vAttrs, const Symbol & name);
Value * allocAttr(Value & vAttrs, std::string_view name); Value * allocAttr(Value & vAttrs, std::string_view name);
@ -425,7 +424,7 @@ std::string showType(const Value & v);
/* Decode a context string !<name>!<path> into a pair <path, /* Decode a context string !<name>!<path> into a pair <path,
name>. */ name>. */
std::pair<std::string, std::string> decodeContext(std::string_view s); NixStringContextElem decodeContext(const Store & store, std::string_view s);
/* If `path' refers to a directory, then append "/default.nix". */ /* If `path' refers to a directory, then append "/default.nix". */
Path resolveExprPath(Path path); Path resolveExprPath(Path path);
@ -509,3 +508,5 @@ extern EvalSettings evalSettings;
static const std::string corepkgsPrefix{"/__corepkgs__/"}; static const std::string corepkgsPrefix{"/__corepkgs__/"};
} }
#include "eval-inline.hh"

View file

@ -706,8 +706,6 @@ void callFlake(EvalState & state,
static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
state.requireExperimentalFeatureOnEvaluation(Xp::Flakes, "builtins.getFlake", pos);
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos)); std::string flakeRefS(state.forceStringNoCtx(*args[0], pos));
auto flakeRef = parseFlakeRef(flakeRefS, {}, true); auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
if (evalSettings.pureEval && !flakeRef.input.isLocked()) if (evalSettings.pureEval && !flakeRef.input.isLocked())
@ -723,7 +721,30 @@ static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Va
v); v);
} }
static RegisterPrimOp r2("__getFlake", 1, prim_getFlake); static RegisterPrimOp r2({
.name = "__getFlake",
.args = {"args"},
.doc = R"(
Fetch a flake from a flake reference, and return its output attributes and some metadata. For example:
```nix
(builtins.getFlake "nix/55bc52401966fbffa525c574c14f67b00bc4fb3a").packages.x86_64-linux.nix
```
Unless impure evaluation is allowed (`--impure`), the flake reference
must be "locked", e.g. contain a Git revision or content hash. An
example of an unlocked usage is:
```nix
(builtins.getFlake "github:edolstra/dwarffs").rev
```
This function is only available if you enable the experimental feature
`flakes`.
)",
.fun = prim_getFlake,
.experimentalFeature = Xp::Flakes,
});
} }

View file

@ -1,6 +1,7 @@
#include "get-drvs.hh" #include "get-drvs.hh"
#include "util.hh" #include "util.hh"
#include "eval-inline.hh" #include "eval-inline.hh"
#include "derivations.hh"
#include "store-api.hh" #include "store-api.hh"
#include "path-with-outputs.hh" #include "path-with-outputs.hh"
@ -102,7 +103,7 @@ StorePath DrvInfo::queryOutPath() const
} }
DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall) DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall)
{ {
if (outputs.empty()) { if (outputs.empty()) {
/* Get the outputs list. */ /* Get the outputs list. */
@ -112,9 +113,11 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall)
/* For each output... */ /* For each output... */
for (auto elem : i->value->listItems()) { for (auto elem : i->value->listItems()) {
std::string output(state->forceStringNoCtx(*elem, *i->pos));
if (withPaths) {
/* Evaluate the corresponding set. */ /* Evaluate the corresponding set. */
std::string name(state->forceStringNoCtx(*elem, *i->pos)); Bindings::iterator out = attrs->find(state->symbols.create(output));
Bindings::iterator out = attrs->find(state->symbols.create(name));
if (out == attrs->end()) continue; // FIXME: throw error? if (out == attrs->end()) continue; // FIXME: throw error?
state->forceAttrs(*out->value, *i->pos); state->forceAttrs(*out->value, *i->pos);
@ -122,10 +125,12 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall)
Bindings::iterator outPath = out->value->attrs->find(state->sOutPath); Bindings::iterator outPath = out->value->attrs->find(state->sOutPath);
if (outPath == out->value->attrs->end()) continue; // FIXME: throw error? if (outPath == out->value->attrs->end()) continue; // FIXME: throw error?
PathSet context; PathSet context;
outputs.emplace(name, state->coerceToStorePath(*outPath->pos, *outPath->value, context)); outputs.emplace(output, state->coerceToStorePath(*outPath->pos, *outPath->value, context));
} else
outputs.emplace(output, std::nullopt);
} }
} else } else
outputs.emplace("out", queryOutPath()); outputs.emplace("out", withPaths ? std::optional{queryOutPath()} : std::nullopt);
} }
if (!onlyOutputsToInstall || !attrs) if (!onlyOutputsToInstall || !attrs)
return outputs; return outputs;

View file

@ -13,7 +13,7 @@ namespace nix {
struct DrvInfo struct DrvInfo
{ {
public: public:
typedef std::map<std::string, StorePath> Outputs; typedef std::map<std::string, std::optional<StorePath>> Outputs;
private: private:
EvalState * state; EvalState * state;
@ -46,8 +46,9 @@ public:
StorePath requireDrvPath() const; StorePath requireDrvPath() const;
StorePath queryOutPath() const; StorePath queryOutPath() const;
std::string queryOutputName() const; std::string queryOutputName() const;
/** Return the list of outputs. The "outputs to install" are determined by `meta.outputsToInstall`. */ /** Return the unordered map of output names to (optional) output paths.
Outputs queryOutputs(bool onlyOutputsToInstall = false); * The "outputs to install" are determined by `meta.outputsToInstall`. */
Outputs queryOutputs(bool withPaths = true, bool onlyOutputsToInstall = false);
StringSet queryMetaNames(); StringSet queryMetaNames();
Value * queryMeta(const std::string & name); Value * queryMeta(const std::string & name);

View file

@ -28,6 +28,13 @@ using namespace nix;
namespace nix { namespace nix {
static inline Pos makeCurPos(const YYLTYPE & loc, ParseData * data)
{
return Pos(data->origin, data->file, loc.first_line, loc.first_column);
}
#define CUR_POS makeCurPos(*yylloc, data)
// backup to recover from yyless(0) // backup to recover from yyless(0)
YYLTYPE prev_yylloc; YYLTYPE prev_yylloc;
@ -37,7 +44,6 @@ static void initLoc(YYLTYPE * loc)
loc->first_column = loc->last_column = 1; loc->first_column = loc->last_column = 1;
} }
static void adjustLoc(YYLTYPE * loc, const char * s, size_t len) static void adjustLoc(YYLTYPE * loc, const char * s, size_t len)
{ {
prev_yylloc = *loc; prev_yylloc = *loc;
@ -147,14 +153,20 @@ or { return OR_KW; }
try { try {
yylval->n = boost::lexical_cast<int64_t>(yytext); yylval->n = boost::lexical_cast<int64_t>(yytext);
} catch (const boost::bad_lexical_cast &) { } catch (const boost::bad_lexical_cast &) {
throw ParseError("invalid integer '%1%'", yytext); throw ParseError({
.msg = hintfmt("invalid integer '%1%'", yytext),
.errPos = CUR_POS,
});
} }
return INT; return INT;
} }
{FLOAT} { errno = 0; {FLOAT} { errno = 0;
yylval->nf = strtod(yytext, 0); yylval->nf = strtod(yytext, 0);
if (errno != 0) if (errno != 0)
throw ParseError("invalid float '%1%'", yytext); throw ParseError({
.msg = hintfmt("invalid float '%1%'", yytext),
.errPos = CUR_POS,
});
return FLOAT; return FLOAT;
} }
@ -280,7 +292,10 @@ or { return OR_KW; }
<INPATH_SLASH>{ANY} | <INPATH_SLASH>{ANY} |
<INPATH_SLASH><<EOF>> { <INPATH_SLASH><<EOF>> {
throw ParseError("path has a trailing slash"); throw ParseError({
.msg = hintfmt("path has a trailing slash"),
.errPos = CUR_POS,
});
} }
{SPATH} { yylval->path = {yytext, (size_t) yyleng}; return SPATH; } {SPATH} { yylval->path = {yytext, (size_t) yyleng}; return SPATH; }

View file

@ -23,14 +23,13 @@ MakeError(RestrictedPathError, Error);
struct Pos struct Pos
{ {
FileOrigin origin;
Symbol file; Symbol file;
unsigned int line, column; uint32_t line;
FileOrigin origin:2;
Pos() : origin(foString), line(0), column(0) { } uint32_t column:30;
Pos(FileOrigin origin, const Symbol & file, unsigned int line, unsigned int column) Pos() : line(0), origin(foString), column(0) { };
: origin(origin), file(file), line(line), column(column) { } Pos(FileOrigin origin, const Symbol & file, uint32_t line, uint32_t column)
: file(file), line(line), origin(origin), column(column) { };
operator bool() const operator bool() const
{ {
return line != 0; return line != 0;

View file

@ -43,8 +43,8 @@ StringMap EvalState::realiseContext(const PathSet & context)
StringMap res; StringMap res;
for (auto & i : context) { for (auto & i : context) {
auto [ctxS, outputName] = decodeContext(i); auto [ctx, outputName] = decodeContext(*store, i);
auto ctx = store->parseStorePath(ctxS); auto ctxS = store->printStorePath(ctx);
if (!store->isValidPath(ctx)) if (!store->isValidPath(ctx))
throw InvalidPathError(store->printStorePath(ctx)); throw InvalidPathError(store->printStorePath(ctx));
if (!outputName.empty() && ctx.isDerivation()) { if (!outputName.empty() && ctx.isDerivation()) {
@ -694,7 +694,32 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar
static RegisterPrimOp primop_genericClosure(RegisterPrimOp::Info { static RegisterPrimOp primop_genericClosure(RegisterPrimOp::Info {
.name = "__genericClosure", .name = "__genericClosure",
.args = {"attrset"},
.arity = 1, .arity = 1,
.doc = R"(
Take an *attrset* with values named `startSet` and `operator` in order to
return a *list of attrsets* by starting with the `startSet`, recursively
applying the `operator` function to each element. The *attrsets* in the
`startSet` and produced by the `operator` must each contain value named
`key` which are comparable to each other. The result is produced by
repeatedly calling the operator for each element encountered with a
unique key, terminating when no new elements are produced. For example,
```
builtins.genericClosure {
startSet = [ {key = 5;} ];
operator = item: [{
key = if (item.key / 2 ) * 2 == item.key
then item.key / 2
else 3 * item.key + 1;
}];
}
```
evaluates to
```
[ { key = 5; } { key = 16; } { key = 8; } { key = 4; } { key = 2; } { key = 1; } ]
```
)",
.fun = prim_genericClosure, .fun = prim_genericClosure,
}); });
@ -1118,8 +1143,8 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
/* Handle derivation outputs of the form !<name>!<path>. */ /* Handle derivation outputs of the form !<name>!<path>. */
else if (path.at(0) == '!') { else if (path.at(0) == '!') {
auto ctx = decodeContext(path); auto ctx = decodeContext(*state.store, path);
drv.inputDrvs[state.store->parseStorePath(ctx.first)].insert(ctx.second); drv.inputDrvs[ctx.first].insert(ctx.second);
} }
/* Otherwise it's a source file. */ /* Otherwise it's a source file. */
@ -1169,13 +1194,12 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
{}, {},
}); });
drv.env["out"] = state.store->printStorePath(outPath); drv.env["out"] = state.store->printStorePath(outPath);
drv.outputs.insert_or_assign("out", DerivationOutput { drv.outputs.insert_or_assign("out",
.output = DerivationOutputCAFixed { DerivationOutput::CAFixed {
.hash = FixedOutputHash { .hash = FixedOutputHash {
.method = ingestionMethod, .method = ingestionMethod,
.hash = std::move(h), .hash = std::move(h),
}, },
},
}); });
} }
@ -1183,11 +1207,10 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
HashType ht = parseHashType(outputHashAlgo); HashType ht = parseHashType(outputHashAlgo);
for (auto & i : outputs) { for (auto & i : outputs) {
drv.env[i] = hashPlaceholder(i); drv.env[i] = hashPlaceholder(i);
drv.outputs.insert_or_assign(i, DerivationOutput { drv.outputs.insert_or_assign(i,
.output = DerivationOutputCAFloating { DerivationOutput::CAFloating {
.method = ingestionMethod, .method = ingestionMethod,
.hashType = ht, .hashType = ht,
},
}); });
} }
} }
@ -1202,43 +1225,36 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
for (auto & i : outputs) { for (auto & i : outputs) {
drv.env[i] = ""; drv.env[i] = "";
drv.outputs.insert_or_assign(i, drv.outputs.insert_or_assign(i,
DerivationOutput { DerivationOutput::Deferred { });
.output = DerivationOutputInputAddressed {
.path = StorePath::dummy,
},
});
} }
// Regular, non-CA derivation should always return a single hash and not // Regular, non-CA derivation should always return a single hash and not
// hash per output. // hash per output.
auto hashModulo = hashDerivationModulo(*state.store, Derivation(drv), true); auto hashModulo = hashDerivationModulo(*state.store, drv, true);
std::visit(overloaded { std::visit(overloaded {
[&](Hash & h) { [&](const DrvHash & drvHash) {
for (auto & i : outputs) { auto & h = drvHash.hash;
auto outPath = state.store->makeOutputPath(i, h, drvName); switch (drvHash.kind) {
drv.env[i] = state.store->printStorePath(outPath); case DrvHash::Kind::Deferred:
drv.outputs.insert_or_assign(i, /* Outputs already deferred, nothing to do */
DerivationOutput { break;
.output = DerivationOutputInputAddressed { case DrvHash::Kind::Regular:
for (auto & [outputName, output] : drv.outputs) {
auto outPath = state.store->makeOutputPath(outputName, h, drvName);
drv.env[outputName] = state.store->printStorePath(outPath);
output = DerivationOutput::InputAddressed {
.path = std::move(outPath), .path = std::move(outPath),
}, };
}); }
break;
} }
}, },
[&](CaOutputHashes &) { [&](const CaOutputHashes &) {
// Shouldn't happen as the toplevel derivation is not CA. // Shouldn't happen as the toplevel derivation is not CA.
assert(false); assert(false);
}, },
[&](DeferredHash &) {
for (auto & i : outputs) {
drv.outputs.insert_or_assign(i,
DerivationOutput {
.output = DerivationOutputDeferred{},
});
}
}, },
}, hashModulo.raw());
hashModulo);
} }
@ -1250,12 +1266,9 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
/* Optimisation, but required in read-only mode! because in that /* Optimisation, but required in read-only mode! because in that
case we don't actually write store derivations, so we can't case we don't actually write store derivations, so we can't
read them later. read them later. */
{
However, we don't bother doing this for floating CA derivations because auto h = hashDerivationModulo(*state.store, drv, false);
their "hash modulo" is indeterminate until built. */
if (drv.type() != DerivationType::CAFloating) {
auto h = hashDerivationModulo(*state.store, Derivation(drv), false);
drvHashes.lock()->insert_or_assign(drvPath, h); drvHashes.lock()->insert_or_assign(drvPath, h);
} }
@ -3813,7 +3826,7 @@ RegisterPrimOp::RegisterPrimOp(std::string name, size_t arity, PrimOpFun fun)
.name = name, .name = name,
.args = {}, .args = {},
.arity = arity, .arity = arity,
.fun = fun .fun = fun,
}); });
} }
@ -3885,6 +3898,9 @@ void EvalState::createBaseEnv()
if (RegisterPrimOp::primOps) if (RegisterPrimOp::primOps)
for (auto & primOp : *RegisterPrimOp::primOps) for (auto & primOp : *RegisterPrimOp::primOps)
if (!primOp.experimentalFeature
|| settings.isExperimentalFeatureEnabled(*primOp.experimentalFeature))
{
addPrimOp({ addPrimOp({
.fun = primOp.fun, .fun = primOp.fun,
.arity = std::max(primOp.args.size(), primOp.arity), .arity = std::max(primOp.args.size(), primOp.arity),
@ -3892,6 +3908,7 @@ void EvalState::createBaseEnv()
.args = primOp.args, .args = primOp.args,
.doc = primOp.doc, .doc = primOp.doc,
}); });
}
/* Add a wrapper around the derivation primop that computes the /* Add a wrapper around the derivation primop that computes the
`drvPath' and `outPath' attributes lazily. */ `drvPath' and `outPath' attributes lazily. */

View file

@ -16,6 +16,7 @@ struct RegisterPrimOp
size_t arity = 0; size_t arity = 0;
const char * doc; const char * doc;
PrimOpFun fun; PrimOpFun fun;
std::optional<ExperimentalFeature> experimentalFeature;
}; };
typedef std::vector<Info> PrimOps; typedef std::vector<Info> PrimOps;
@ -35,6 +36,7 @@ struct RegisterPrimOp
/* These primops are disabled without enableNativeCode, but plugins /* These primops are disabled without enableNativeCode, but plugins
may wish to use them in limited contexts without globally enabling may wish to use them in limited contexts without globally enabling
them. */ them. */
/* Load a ValueInitializer from a DSO and return whatever it initializes */ /* Load a ValueInitializer from a DSO and return whatever it initializes */
void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v); void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v);

View file

@ -1,5 +1,6 @@
#include "primops.hh" #include "primops.hh"
#include "eval-inline.hh" #include "eval-inline.hh"
#include "derivations.hh"
#include "store-api.hh" #include "store-api.hh"
namespace nix { namespace nix {
@ -82,8 +83,8 @@ static void prim_getContext(EvalState & state, const Pos & pos, Value * * args,
drv = std::string(p, 1); drv = std::string(p, 1);
path = &drv; path = &drv;
} else if (p.at(0) == '!') { } else if (p.at(0) == '!') {
std::pair<std::string, std::string> ctx = decodeContext(p); NixStringContextElem ctx = decodeContext(*state.store, p);
drv = ctx.first; drv = state.store->printStorePath(ctx.first);
output = ctx.second; output = ctx.second;
path = &drv; path = &drv;
} }

View file

@ -0,0 +1,154 @@
#include "primops.hh"
#include "store-api.hh"
#include "make-content-addressed.hh"
#include "url.hh"
namespace nix {
static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
state.forceAttrs(*args[0], pos);
std::optional<std::string> fromStoreUrl;
std::optional<StorePath> fromPath;
bool toCA = false;
std::optional<StorePath> toPath;
for (auto & attr : *args[0]->attrs) {
if (attr.name == "fromPath") {
PathSet context;
fromPath = state.coerceToStorePath(*attr.pos, *attr.value, context);
}
else if (attr.name == "toPath") {
state.forceValue(*attr.value, *attr.pos);
toCA = true;
if (attr.value->type() != nString || attr.value->string.s != std::string("")) {
PathSet context;
toPath = state.coerceToStorePath(*attr.pos, *attr.value, context);
}
}
else if (attr.name == "fromStore")
fromStoreUrl = state.forceStringNoCtx(*attr.value, *attr.pos);
else
throw Error({
.msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attr.name),
.errPos = pos
});
}
if (!fromPath)
throw Error({
.msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"),
.errPos = pos
});
if (!fromStoreUrl)
throw Error({
.msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"),
.errPos = pos
});
auto parsedURL = parseURL(*fromStoreUrl);
if (parsedURL.scheme != "http" &&
parsedURL.scheme != "https" &&
!(getEnv("_NIX_IN_TEST").has_value() && parsedURL.scheme == "file"))
throw Error({
.msg = hintfmt("'fetchClosure' only supports http:// and https:// stores"),
.errPos = pos
});
auto fromStore = openStore(parsedURL.to_string());
if (toCA) {
if (!toPath || !state.store->isValidPath(*toPath)) {
auto remappings = makeContentAddressed(*fromStore, *state.store, { *fromPath });
auto i = remappings.find(*fromPath);
assert(i != remappings.end());
if (toPath && *toPath != i->second)
throw Error({
.msg = hintfmt("rewriting '%s' to content-addressed form yielded '%s', while '%s' was expected",
state.store->printStorePath(*fromPath),
state.store->printStorePath(i->second),
state.store->printStorePath(*toPath)),
.errPos = pos
});
if (!toPath)
throw Error({
.msg = hintfmt(
"rewriting '%s' to content-addressed form yielded '%s'; "
"please set this in the 'toPath' attribute passed to 'fetchClosure'",
state.store->printStorePath(*fromPath),
state.store->printStorePath(i->second)),
.errPos = pos
});
}
} else {
copyClosure(*fromStore, *state.store, RealisedPath::Set { *fromPath });
toPath = fromPath;
}
/* In pure mode, require a CA path. */
if (evalSettings.pureEval) {
auto info = state.store->queryPathInfo(*toPath);
if (!info->isContentAddressed(*state.store))
throw Error({
.msg = hintfmt("in pure mode, 'fetchClosure' requires a content-addressed path, which '%s' isn't",
state.store->printStorePath(*toPath)),
.errPos = pos
});
}
auto toPathS = state.store->printStorePath(*toPath);
v.mkString(toPathS, {toPathS});
}
static RegisterPrimOp primop_fetchClosure({
.name = "__fetchClosure",
.args = {"args"},
.doc = R"(
Fetch a Nix store closure from a binary cache, rewriting it into
content-addressed form. For example,
```nix
builtins.fetchClosure {
fromStore = "https://cache.nixos.org";
fromPath = /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1;
toPath = /nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1;
}
```
fetches `/nix/store/r2jd...` from the specified binary cache,
and rewrites it into the content-addressed store path
`/nix/store/ldbh...`.
If `fromPath` is already content-addressed, or if you are
allowing impure evaluation (`--impure`), then `toPath` may be
omitted.
To find out the correct value for `toPath` given a `fromPath`,
you can use `nix store make-content-addressed`:
```console
# nix store make-content-addressed --from https://cache.nixos.org /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1
rewrote '/nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1' to '/nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1'
```
This function is similar to `builtins.storePath` in that it
allows you to use a previously built store path in a Nix
expression. However, it is more reproducible because it requires
specifying a binary cache from which the path can be fetched.
Also, requiring a content-addressed final store path avoids the
need for users to configure binary cache public keys.
This function is only available if you enable the experimental
feature `fetch-closure`.
)",
.fun = prim_fetchClosure,
.experimentalFeature = Xp::FetchClosure,
});
}

View file

@ -145,7 +145,7 @@ static void fetchTree(
if (!params.allowNameArgument) if (!params.allowNameArgument)
if (auto nameIter = attrs.find("name"); nameIter != attrs.end()) if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
throw Error({ throw Error({
.msg = hintfmt("attribute 'name' isnt supported in call to 'fetchTree'"), .msg = hintfmt("attribute 'name' isn't supported in call to 'fetchTree'"),
.errPos = pos .errPos = pos
}); });

View file

@ -57,6 +57,8 @@ struct ExprLambda;
struct PrimOp; struct PrimOp;
class Symbol; class Symbol;
struct Pos; struct Pos;
class StorePath;
class Store;
class EvalState; class EvalState;
class XMLWriter; class XMLWriter;
class JSONPlaceholder; class JSONPlaceholder;
@ -64,6 +66,8 @@ class JSONPlaceholder;
typedef int64_t NixInt; typedef int64_t NixInt;
typedef double NixFloat; typedef double NixFloat;
typedef std::pair<StorePath, std::string> NixStringContextElem;
typedef std::vector<NixStringContextElem> NixStringContext;
/* External values must descend from ExternalValueBase, so that /* External values must descend from ExternalValueBase, so that
* type-agnostic nix functions (e.g. showType) can be implemented * type-agnostic nix functions (e.g. showType) can be implemented
@ -115,10 +119,13 @@ private:
InternalType internalType; InternalType internalType;
friend std::string showType(const Value & v); friend std::string showType(const Value & v);
friend void printValue(std::ostream & str, std::set<const void *> & seen, const Value & v);
void print(std::ostream & str, std::set<const void *> * seen) const;
public: public:
void print(std::ostream & str, bool showRepeated = false) const;
// Functions needed to distinguish the type // Functions needed to distinguish the type
// These should be removed eventually, by putting the functionality that's // These should be removed eventually, by putting the functionality that's
// needed by callers into methods of this type // needed by callers into methods of this type
@ -368,7 +375,7 @@ public:
non-trivial. */ non-trivial. */
bool isTrivial() const; bool isTrivial() const;
std::vector<std::pair<Path, std::string>> getContext(); NixStringContext getContext(const Store &);
auto listItems() auto listItems()
{ {

View file

@ -222,22 +222,46 @@ struct GitInputScheme : InputScheme
if (!input.getRef() && !input.getRev() && isLocal) { if (!input.getRef() && !input.getRev() && isLocal) {
bool clean = false; bool clean = false;
/* Check whether this repo has any commits. There are auto env = getEnv();
probably better ways to do this. */ // Set LC_ALL to C: because we rely on the error messages from git rev-parse to determine what went wrong
auto gitDir = actualUrl + "/.git"; // that way unknown errors can lead to a failure instead of continuing through the wrong code path
auto commonGitDir = chomp(runProgram( env["LC_ALL"] = "C";
"git",
true,
{ "-C", actualUrl, "rev-parse", "--git-common-dir" }
));
if (commonGitDir != ".git")
gitDir = commonGitDir;
bool haveCommits = !readDirectory(gitDir + "/refs/heads").empty(); /* Check whether HEAD points to something that looks like a commit,
since that is the refrence we want to use later on. */
auto result = runProgram(RunOptions {
.program = "git",
.args = { "-C", actualUrl, "--git-dir=.git", "rev-parse", "--verify", "--no-revs", "HEAD^{commit}" },
.environment = env,
.mergeStderrToStdout = true
});
auto exitCode = WEXITSTATUS(result.first);
auto errorMessage = result.second;
if (errorMessage.find("fatal: not a git repository") != std::string::npos) {
throw Error("'%s' is not a Git repository", actualUrl);
} else if (errorMessage.find("fatal: Needed a single revision") != std::string::npos) {
// indicates that the repo does not have any commits
// we want to proceed and will consider it dirty later
} else if (exitCode != 0) {
// any other errors should lead to a failure
throw Error("getting the HEAD of the Git tree '%s' failed with exit code %d:\n%s", actualUrl, exitCode, errorMessage);
}
bool hasHead = exitCode == 0;
try { try {
if (haveCommits) { if (hasHead) {
runProgram("git", true, { "-C", actualUrl, "diff-index", "--quiet", "HEAD", "--" }); // Using git diff is preferrable over lower-level operations here,
// because its conceptually simpler and we only need the exit code anyways.
auto gitDiffOpts = Strings({ "-C", actualUrl, "diff", "HEAD", "--quiet"});
if (!submodules) {
// Changes in submodules should only make the tree dirty
// when those submodules will be copied as well.
gitDiffOpts.emplace_back("--ignore-submodules");
}
gitDiffOpts.emplace_back("--");
runProgram("git", true, gitDiffOpts);
clean = true; clean = true;
} }
} catch (ExecError & e) { } catch (ExecError & e) {
@ -282,7 +306,7 @@ struct GitInputScheme : InputScheme
// modified dirty file? // modified dirty file?
input.attrs.insert_or_assign( input.attrs.insert_or_assign(
"lastModified", "lastModified",
haveCommits ? std::stoull(runProgram("git", true, { "-C", actualUrl, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0); hasHead ? std::stoull(runProgram("git", true, { "-C", actualUrl, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0);
return {std::move(storePath), input}; return {std::move(storePath), input};
} }

View file

@ -390,7 +390,7 @@ struct SourceHutInputScheme : GitArchiveInputScheme
ref_uri = line.substr(ref_index+5, line.length()-1); ref_uri = line.substr(ref_index+5, line.length()-1);
} else } else
ref_uri = fmt("refs/heads/%s", ref); ref_uri = fmt("refs/(heads|tags)/%s", ref);
auto file = store->toRealPath( auto file = store->toRealPath(
downloadFile(store, fmt("%s/info/refs", base_url), "source", false, headers).storePath); downloadFile(store, fmt("%s/info/refs", base_url), "source", false, headers).storePath);
@ -399,9 +399,11 @@ struct SourceHutInputScheme : GitArchiveInputScheme
std::string line; std::string line;
std::string id; std::string id;
while(getline(is, line)) { while(getline(is, line)) {
auto index = line.find(ref_uri); // Append $ to avoid partial name matches
if (index != std::string::npos) { std::regex pattern(fmt("%s$", ref_uri));
id = line.substr(0, index-1);
if (std::regex_search(line, pattern)) {
id = line.substr(0, line.find('\t'));
break; break;
} }
} }

View file

@ -1,5 +1,6 @@
#include "fetchers.hh" #include "fetchers.hh"
#include "store-api.hh" #include "store-api.hh"
#include "archive.hh"
namespace nix::fetchers { namespace nix::fetchers {
@ -80,8 +81,9 @@ struct PathInputScheme : InputScheme
// nothing to do // nothing to do
} }
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) override std::pair<StorePath, Input> fetch(ref<Store> store, const Input & _input) override
{ {
Input input(_input);
std::string absPath; std::string absPath;
auto path = getStrAttr(input.attrs, "path"); auto path = getStrAttr(input.attrs, "path");
@ -111,9 +113,15 @@ struct PathInputScheme : InputScheme
if (storePath) if (storePath)
store->addTempRoot(*storePath); store->addTempRoot(*storePath);
if (!storePath || storePath->name() != "source" || !store->isValidPath(*storePath)) time_t mtime = 0;
if (!storePath || storePath->name() != "source" || !store->isValidPath(*storePath)) {
// FIXME: try to substitute storePath. // FIXME: try to substitute storePath.
storePath = store->addToStore("source", absPath); auto src = sinkToSource([&](Sink & sink) {
mtime = dumpPathAndGetMtime(absPath, sink, defaultPathFilter);
});
storePath = store->addToStoreFromDump(*src, "source");
}
input.attrs.insert_or_assign("lastModified", uint64_t(mtime));
return {std::move(*storePath), input}; return {std::move(*storePath), input};
} }

View file

@ -2,6 +2,7 @@
#include "crypto.hh" #include "crypto.hh"
#include "store-api.hh" #include "store-api.hh"
#include "log-store.hh"
#include "pool.hh" #include "pool.hh"
@ -28,7 +29,9 @@ struct BinaryCacheStoreConfig : virtual StoreConfig
"other than -1 which we reserve to indicate Nix defaults should be used"}; "other than -1 which we reserve to indicate Nix defaults should be used"};
}; };
class BinaryCacheStore : public virtual BinaryCacheStoreConfig, public virtual Store class BinaryCacheStore : public virtual BinaryCacheStoreConfig,
public virtual Store,
public virtual LogStore
{ {
private: private:

View file

@ -204,11 +204,9 @@ void DerivationGoal::haveDerivation()
{ {
trace("have derivation"); trace("have derivation");
if (drv->type() == DerivationType::CAFloating) if (!drv->type().hasKnownOutputPaths())
settings.requireExperimentalFeature(Xp::CaDerivations); settings.requireExperimentalFeature(Xp::CaDerivations);
retrySubstitution = false;
for (auto & i : drv->outputsAndOptPaths(worker.store)) for (auto & i : drv->outputsAndOptPaths(worker.store))
if (i.second.second) if (i.second.second)
worker.store.addTempRoot(*i.second.second); worker.store.addTempRoot(*i.second.second);
@ -311,14 +309,11 @@ void DerivationGoal::outputsSubstitutionTried()
gaveUpOnSubstitution(); gaveUpOnSubstitution();
} }
/* At least one of the output paths could not be /* At least one of the output paths could not be
produced using a substitute. So we have to build instead. */ produced using a substitute. So we have to build instead. */
void DerivationGoal::gaveUpOnSubstitution() void DerivationGoal::gaveUpOnSubstitution()
{ {
/* Make sure checkPathValidity() from now on checks all
outputs. */
wantedOutputs.clear();
/* The inputs must be built before we can build this goal. */ /* The inputs must be built before we can build this goal. */
if (useDerivation) if (useDerivation)
for (auto & i : dynamic_cast<Derivation *>(drv.get())->inputDrvs) for (auto & i : dynamic_cast<Derivation *>(drv.get())->inputDrvs)
@ -426,7 +421,8 @@ void DerivationGoal::inputsRealised()
return; return;
} }
if (retrySubstitution) { if (retrySubstitution && !retriedSubstitution) {
retriedSubstitution = true;
haveDerivation(); haveDerivation();
return; return;
} }
@ -440,9 +436,28 @@ void DerivationGoal::inputsRealised()
if (useDerivation) { if (useDerivation) {
auto & fullDrv = *dynamic_cast<Derivation *>(drv.get()); auto & fullDrv = *dynamic_cast<Derivation *>(drv.get());
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) && auto drvType = fullDrv.type();
((!fullDrv.inputDrvs.empty() && derivationIsCA(fullDrv.type())) bool resolveDrv = std::visit(overloaded {
|| fullDrv.type() == DerivationType::DeferredInputAddressed)) { [&](const DerivationType::InputAddressed & ia) {
/* must resolve if deferred. */
return ia.deferred;
},
[&](const DerivationType::ContentAddressed & ca) {
return !fullDrv.inputDrvs.empty() && (
ca.fixed
/* Can optionally resolve if fixed, which is good
for avoiding unnecessary rebuilds. */
? settings.isExperimentalFeatureEnabled(Xp::CaDerivations)
/* Must resolve if floating and there are any inputs
drvs. */
: true);
},
}, drvType.raw());
if (resolveDrv)
{
settings.requireExperimentalFeature(Xp::CaDerivations);
/* We are be able to resolve this derivation based on the /* We are be able to resolve this derivation based on the
now-known results of dependencies. If so, we become a stub goal now-known results of dependencies. If so, we become a stub goal
aliasing that resolved derivation goal */ aliasing that resolved derivation goal */
@ -501,7 +516,7 @@ void DerivationGoal::inputsRealised()
/* Don't repeat fixed-output derivations since they're already /* Don't repeat fixed-output derivations since they're already
verified by their output hash.*/ verified by their output hash.*/
nrRounds = derivationIsFixed(derivationType) ? 1 : settings.buildRepeat + 1; nrRounds = derivationType.isFixed() ? 1 : settings.buildRepeat + 1;
/* Okay, try to build. Note that here we don't wait for a build /* Okay, try to build. Note that here we don't wait for a build
slot to become available, since we don't need one if there is a slot to become available, since we don't need one if there is a
@ -908,7 +923,7 @@ void DerivationGoal::buildDone()
st = st =
dynamic_cast<NotDeterministic*>(&e) ? BuildResult::NotDeterministic : dynamic_cast<NotDeterministic*>(&e) ? BuildResult::NotDeterministic :
statusOk(status) ? BuildResult::OutputRejected : statusOk(status) ? BuildResult::OutputRejected :
derivationIsImpure(derivationType) || diskFull ? BuildResult::TransientFailure : derivationType.isImpure() || diskFull ? BuildResult::TransientFailure :
BuildResult::PermanentFailure; BuildResult::PermanentFailure;
} }
@ -1221,7 +1236,7 @@ void DerivationGoal::flushLine()
std::map<std::string, std::optional<StorePath>> DerivationGoal::queryPartialDerivationOutputMap() std::map<std::string, std::optional<StorePath>> DerivationGoal::queryPartialDerivationOutputMap()
{ {
if (!useDerivation || drv->type() != DerivationType::CAFloating) { if (!useDerivation || drv->type().hasKnownOutputPaths()) {
std::map<std::string, std::optional<StorePath>> res; std::map<std::string, std::optional<StorePath>> res;
for (auto & [name, output] : drv->outputs) for (auto & [name, output] : drv->outputs)
res.insert_or_assign(name, output.path(worker.store, drv->name, name)); res.insert_or_assign(name, output.path(worker.store, drv->name, name));
@ -1233,7 +1248,7 @@ std::map<std::string, std::optional<StorePath>> DerivationGoal::queryPartialDeri
OutputPathMap DerivationGoal::queryDerivationOutputMap() OutputPathMap DerivationGoal::queryDerivationOutputMap()
{ {
if (!useDerivation || drv->type() != DerivationType::CAFloating) { if (!useDerivation || drv->type().hasKnownOutputPaths()) {
OutputPathMap res; OutputPathMap res;
for (auto & [name, output] : drv->outputsAndOptPaths(worker.store)) for (auto & [name, output] : drv->outputsAndOptPaths(worker.store))
res.insert_or_assign(name, *output.second); res.insert_or_assign(name, *output.second);

View file

@ -61,8 +61,12 @@ struct DerivationGoal : public Goal
bool needRestart = false; bool needRestart = false;
/* Whether to retry substituting the outputs after building the /* Whether to retry substituting the outputs after building the
inputs. */ inputs. This is done in case of an incomplete closure. */
bool retrySubstitution; bool retrySubstitution = false;
/* Whether we've retried substitution, in which case we won't try
again. */
bool retriedSubstitution = false;
/* The derivation stored at drvPath. */ /* The derivation stored at drvPath. */
std::unique_ptr<Derivation> drv; std::unique_ptr<Derivation> drv;

View file

@ -28,7 +28,7 @@ void Goal::addWaitee(GoalPtr waitee)
void Goal::waiteeDone(GoalPtr waitee, ExitCode result) void Goal::waiteeDone(GoalPtr waitee, ExitCode result)
{ {
assert(waitees.find(waitee) != waitees.end()); assert(waitees.count(waitee));
waitees.erase(waitee); waitees.erase(waitee);
trace(fmt("waitee '%s' done; %d left", waitee->name, waitees.size())); trace(fmt("waitee '%s' done; %d left", waitee->name, waitees.size()));

View file

@ -40,21 +40,21 @@ struct Goal : public std::enable_shared_from_this<Goal>
WeakGoals waiters; WeakGoals waiters;
/* Number of goals we are/were waiting for that have failed. */ /* Number of goals we are/were waiting for that have failed. */
unsigned int nrFailed; size_t nrFailed = 0;
/* Number of substitution goals we are/were waiting for that /* Number of substitution goals we are/were waiting for that
failed because there are no substituters. */ failed because there are no substituters. */
unsigned int nrNoSubstituters; size_t nrNoSubstituters = 0;
/* Number of substitution goals we are/were waiting for that /* Number of substitution goals we are/were waiting for that
failed because they had unsubstitutable references. */ failed because they had unsubstitutable references. */
unsigned int nrIncompleteClosure; size_t nrIncompleteClosure = 0;
/* Name of this goal for debugging purposes. */ /* Name of this goal for debugging purposes. */
std::string name; std::string name;
/* Whether the goal is finished. */ /* Whether the goal is finished. */
ExitCode exitCode; ExitCode exitCode = ecBusy;
/* Build result. */ /* Build result. */
BuildResult buildResult; BuildResult buildResult;
@ -65,10 +65,7 @@ struct Goal : public std::enable_shared_from_this<Goal>
Goal(Worker & worker, DerivedPath path) Goal(Worker & worker, DerivedPath path)
: worker(worker) : worker(worker)
, buildResult { .path = std::move(path) } , buildResult { .path = std::move(path) }
{ { }
nrFailed = nrNoSubstituters = nrIncompleteClosure = 0;
exitCode = ecBusy;
}
virtual ~Goal() virtual ~Goal()
{ {

View file

@ -395,7 +395,7 @@ void LocalDerivationGoal::startBuilder()
else if (settings.sandboxMode == smDisabled) else if (settings.sandboxMode == smDisabled)
useChroot = false; useChroot = false;
else if (settings.sandboxMode == smRelaxed) else if (settings.sandboxMode == smRelaxed)
useChroot = !(derivationIsImpure(derivationType)) && !noChroot; useChroot = !(derivationType.isImpure()) && !noChroot;
} }
auto & localStore = getLocalStore(); auto & localStore = getLocalStore();
@ -608,7 +608,7 @@ void LocalDerivationGoal::startBuilder()
"nogroup:x:65534:\n", sandboxGid())); "nogroup:x:65534:\n", sandboxGid()));
/* Create /etc/hosts with localhost entry. */ /* Create /etc/hosts with localhost entry. */
if (!(derivationIsImpure(derivationType))) if (!(derivationType.isImpure()))
writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n::1 localhost\n"); writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n::1 localhost\n");
/* Make the closure of the inputs available in the chroot, /* Make the closure of the inputs available in the chroot,
@ -796,7 +796,7 @@ void LocalDerivationGoal::startBuilder()
us. us.
*/ */
if (!(derivationIsImpure(derivationType))) if (!(derivationType.isImpure()))
privateNetwork = true; privateNetwork = true;
userNamespaceSync.create(); userNamespaceSync.create();
@ -1049,7 +1049,7 @@ void LocalDerivationGoal::initEnv()
derivation, tell the builder, so that for instance `fetchurl' derivation, tell the builder, so that for instance `fetchurl'
can skip checking the output. On older Nixes, this environment can skip checking the output. On older Nixes, this environment
variable won't be set, so `fetchurl' will do the check. */ variable won't be set, so `fetchurl' will do the check. */
if (derivationIsFixed(derivationType)) env["NIX_OUTPUT_CHECKED"] = "1"; if (derivationType.isFixed()) env["NIX_OUTPUT_CHECKED"] = "1";
/* *Only* if this is a fixed-output derivation, propagate the /* *Only* if this is a fixed-output derivation, propagate the
values of the environment variables specified in the values of the environment variables specified in the
@ -1060,7 +1060,7 @@ void LocalDerivationGoal::initEnv()
to the builder is generally impure, but the output of to the builder is generally impure, but the output of
fixed-output derivations is by definition pure (since we fixed-output derivations is by definition pure (since we
already know the cryptographic hash of the output). */ already know the cryptographic hash of the output). */
if (derivationIsImpure(derivationType)) { if (derivationType.isImpure()) {
for (auto & i : parsedDrv->getStringsAttr("impureEnvVars").value_or(Strings())) for (auto & i : parsedDrv->getStringsAttr("impureEnvVars").value_or(Strings()))
env[i] = getEnv(i).value_or(""); env[i] = getEnv(i).value_or("");
} }
@ -1340,6 +1340,12 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
next->queryMissing(allowed, willBuild, willSubstitute, next->queryMissing(allowed, willBuild, willSubstitute,
unknown, downloadSize, narSize); unknown, downloadSize, narSize);
} }
virtual std::optional<std::string> getBuildLog(const StorePath & path) override
{ return std::nullopt; }
virtual void addBuildLog(const StorePath & path, std::string_view log) override
{ unsupported("addBuildLog"); }
}; };
@ -1668,7 +1674,7 @@ void LocalDerivationGoal::runChild()
/* Fixed-output derivations typically need to access the /* Fixed-output derivations typically need to access the
network, so give them access to /etc/resolv.conf and so network, so give them access to /etc/resolv.conf and so
on. */ on. */
if (derivationIsImpure(derivationType)) { if (derivationType.isImpure()) {
// Only use nss functions to resolve hosts and // Only use nss functions to resolve hosts and
// services. Dont use it for anything else that may // services. Dont use it for anything else that may
// be configured for this system. This limits the // be configured for this system. This limits the
@ -1912,7 +1918,7 @@ void LocalDerivationGoal::runChild()
sandboxProfile += "(import \"sandbox-defaults.sb\")\n"; sandboxProfile += "(import \"sandbox-defaults.sb\")\n";
if (derivationIsImpure(derivationType)) if (derivationType.isImpure())
sandboxProfile += "(import \"sandbox-network.sb\")\n"; sandboxProfile += "(import \"sandbox-network.sb\")\n";
/* Add the output paths we'll use at build-time to the chroot */ /* Add the output paths we'll use at build-time to the chroot */
@ -2272,7 +2278,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
return res; return res;
}; };
auto newInfoFromCA = [&](const DerivationOutputCAFloating outputHash) -> ValidPathInfo { auto newInfoFromCA = [&](const DerivationOutput::CAFloating outputHash) -> ValidPathInfo {
auto & st = outputStats.at(outputName); auto & st = outputStats.at(outputName);
if (outputHash.method == FileIngestionMethod::Flat) { if (outputHash.method == FileIngestionMethod::Flat) {
/* The output path should be a regular file without execute permission. */ /* The output path should be a regular file without execute permission. */
@ -2334,7 +2340,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
ValidPathInfo newInfo = std::visit(overloaded { ValidPathInfo newInfo = std::visit(overloaded {
[&](const DerivationOutputInputAddressed & output) { [&](const DerivationOutput::InputAddressed & output) {
/* input-addressed case */ /* input-addressed case */
auto requiredFinalPath = output.path; auto requiredFinalPath = output.path;
/* Preemptively add rewrite rule for final hash, as that is /* Preemptively add rewrite rule for final hash, as that is
@ -2351,8 +2357,8 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
return newInfo0; return newInfo0;
}, },
[&](const DerivationOutputCAFixed & dof) { [&](const DerivationOutput::CAFixed & dof) {
auto newInfo0 = newInfoFromCA(DerivationOutputCAFloating { auto newInfo0 = newInfoFromCA(DerivationOutput::CAFloating {
.method = dof.hash.method, .method = dof.hash.method,
.hashType = dof.hash.hash.type, .hashType = dof.hash.hash.type,
}); });
@ -2374,17 +2380,17 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
return newInfo0; return newInfo0;
}, },
[&](DerivationOutputCAFloating & dof) { [&](const DerivationOutput::CAFloating & dof) {
return newInfoFromCA(dof); return newInfoFromCA(dof);
}, },
[&](DerivationOutputDeferred) -> ValidPathInfo { [&](const DerivationOutput::Deferred &) -> ValidPathInfo {
// No derivation should reach that point without having been // No derivation should reach that point without having been
// rewritten first // rewritten first
assert(false); assert(false);
}, },
}, output.output); }, output.raw());
/* FIXME: set proper permissions in restorePath() so /* FIXME: set proper permissions in restorePath() so
we don't have to do another traversal. */ we don't have to do another traversal. */
@ -2598,6 +2604,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
signRealisation(thisRealisation); signRealisation(thisRealisation);
worker.store.registerDrvOutput(thisRealisation); worker.store.registerDrvOutput(thisRealisation);
} }
if (wantOutput(outputName, wantedOutputs))
builtOutputs.emplace(thisRealisation.id, thisRealisation); builtOutputs.emplace(thisRealisation.id, thisRealisation);
} }

View file

@ -47,9 +47,9 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
throw; throw;
} }
/* The files below are special-cased to that they don't show up /* The files below are special-cased to that they don't show
* in user profiles, either because they are useless, or * up in user profiles, either because they are useless, or
* because they would cauase pointless collisions (e.g., each * because they would cause pointless collisions (e.g., each
* Python package brings its own * Python package brings its own
* `$out/lib/pythonX.Y/site-packages/easy-install.pth'.) * `$out/lib/pythonX.Y/site-packages/easy-install.pth'.)
*/ */
@ -57,7 +57,9 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
hasSuffix(srcFile, "/nix-support") || hasSuffix(srcFile, "/nix-support") ||
hasSuffix(srcFile, "/perllocal.pod") || hasSuffix(srcFile, "/perllocal.pod") ||
hasSuffix(srcFile, "/info/dir") || hasSuffix(srcFile, "/info/dir") ||
hasSuffix(srcFile, "/log")) hasSuffix(srcFile, "/log") ||
hasSuffix(srcFile, "/manifest.nix") ||
hasSuffix(srcFile, "/manifest.json"))
continue; continue;
else if (S_ISDIR(srcSt.st_mode)) { else if (S_ISDIR(srcSt.st_mode)) {

View file

@ -3,7 +3,9 @@
#include "worker-protocol.hh" #include "worker-protocol.hh"
#include "build-result.hh" #include "build-result.hh"
#include "store-api.hh" #include "store-api.hh"
#include "store-cast.hh"
#include "gc-store.hh" #include "gc-store.hh"
#include "log-store.hh"
#include "path-with-outputs.hh" #include "path-with-outputs.hh"
#include "finally.hh" #include "finally.hh"
#include "archive.hh" #include "archive.hh"
@ -558,6 +560,8 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
BuildMode buildMode = (BuildMode) readInt(from); BuildMode buildMode = (BuildMode) readInt(from);
logger->startWork(); logger->startWork();
auto drvType = drv.type();
/* Content-addressed derivations are trustless because their output paths /* Content-addressed derivations are trustless because their output paths
are verified by their content alone, so any derivation is free to are verified by their content alone, so any derivation is free to
try to produce such a path. try to produce such a path.
@ -590,12 +594,12 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
derivations, we throw out the precomputed output paths and just derivations, we throw out the precomputed output paths and just
store the hashes, so there aren't two competing sources of truth an store the hashes, so there aren't two competing sources of truth an
attacker could exploit. */ attacker could exploit. */
if (drv.type() == DerivationType::InputAddressed && !trusted) if (!(drvType.isCA() || trusted))
throw Error("you are not privileged to build input-addressed derivations"); throw Error("you are not privileged to build input-addressed derivations");
/* Make sure that the non-input-addressed derivations that got this far /* Make sure that the non-input-addressed derivations that got this far
are in fact content-addressed if we don't trust them. */ are in fact content-addressed if we don't trust them. */
assert(derivationIsCA(drv.type()) || trusted); assert(drvType.isCA() || trusted);
/* Recompute the derivation path when we cannot trust the original. */ /* Recompute the derivation path when we cannot trust the original. */
if (!trusted) { if (!trusted) {
@ -604,7 +608,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
original not-necessarily-resolved derivation to verify the drv original not-necessarily-resolved derivation to verify the drv
derivation as adequate claim to the input-addressed output derivation as adequate claim to the input-addressed output
paths. */ paths. */
assert(derivationIsCA(drv.type())); assert(drvType.isCA());
Derivation drv2; Derivation drv2;
static_cast<BasicDerivation &>(drv2) = drv; static_cast<BasicDerivation &>(drv2) = drv;
@ -645,7 +649,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
Path path = absPath(readString(from)); Path path = absPath(readString(from));
logger->startWork(); logger->startWork();
auto & gcStore = requireGcStore(*store); auto & gcStore = require<GcStore>(*store);
gcStore.addIndirectRoot(path); gcStore.addIndirectRoot(path);
logger->stopWork(); logger->stopWork();
@ -663,7 +667,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
case wopFindRoots: { case wopFindRoots: {
logger->startWork(); logger->startWork();
auto & gcStore = requireGcStore(*store); auto & gcStore = require<GcStore>(*store);
Roots roots = gcStore.findRoots(!trusted); Roots roots = gcStore.findRoots(!trusted);
logger->stopWork(); logger->stopWork();
@ -695,7 +699,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
logger->startWork(); logger->startWork();
if (options.ignoreLiveness) if (options.ignoreLiveness)
throw Error("you are not allowed to ignore liveness"); throw Error("you are not allowed to ignore liveness");
auto & gcStore = requireGcStore(*store); auto & gcStore = require<GcStore>(*store);
gcStore.collectGarbage(options, results); gcStore.collectGarbage(options, results);
logger->stopWork(); logger->stopWork();
@ -953,11 +957,12 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
logger->startWork(); logger->startWork();
if (!trusted) if (!trusted)
throw Error("you are not privileged to add logs"); throw Error("you are not privileged to add logs");
auto & logStore = require<LogStore>(*store);
{ {
FramedSource source(from); FramedSource source(from);
StringSink sink; StringSink sink;
source.drainInto(sink); source.drainInto(sink);
store->addBuildLog(path, sink.s); logStore.addBuildLog(path, sink.s);
} }
logger->stopWork(); logger->stopWork();
to << 1; to << 1;

View file

@ -11,72 +11,71 @@ namespace nix {
std::optional<StorePath> DerivationOutput::path(const Store & store, std::string_view drvName, std::string_view outputName) const std::optional<StorePath> DerivationOutput::path(const Store & store, std::string_view drvName, std::string_view outputName) const
{ {
return std::visit(overloaded { return std::visit(overloaded {
[](const DerivationOutputInputAddressed & doi) -> std::optional<StorePath> { [](const DerivationOutput::InputAddressed & doi) -> std::optional<StorePath> {
return { doi.path }; return { doi.path };
}, },
[&](const DerivationOutputCAFixed & dof) -> std::optional<StorePath> { [&](const DerivationOutput::CAFixed & dof) -> std::optional<StorePath> {
return { return {
dof.path(store, drvName, outputName) dof.path(store, drvName, outputName)
}; };
}, },
[](const DerivationOutputCAFloating & dof) -> std::optional<StorePath> { [](const DerivationOutput::CAFloating & dof) -> std::optional<StorePath> {
return std::nullopt; return std::nullopt;
}, },
[](const DerivationOutputDeferred &) -> std::optional<StorePath> { [](const DerivationOutput::Deferred &) -> std::optional<StorePath> {
return std::nullopt; return std::nullopt;
}, },
}, output); }, raw());
} }
StorePath DerivationOutputCAFixed::path(const Store & store, std::string_view drvName, std::string_view outputName) const { StorePath DerivationOutput::CAFixed::path(const Store & store, std::string_view drvName, std::string_view outputName) const {
return store.makeFixedOutputPath( return store.makeFixedOutputPath(
outputPathName(drvName, outputName), outputPathName(drvName, outputName),
{ hash, {} }); { hash, {} });
} }
bool derivationIsCA(DerivationType dt) { bool DerivationType::isCA() const {
switch (dt) { /* Normally we do the full `std::visit` to make sure we have
case DerivationType::InputAddressed: return false; exhaustively handled all variants, but so long as there is a
case DerivationType::CAFixed: return true; variant called `ContentAddressed`, it must be the only one for
case DerivationType::CAFloating: return true; which `isCA` is true for this to make sense!. */
case DerivationType::DeferredInputAddressed: return false; return std::holds_alternative<ContentAddressed>(raw());
};
// Since enums can have non-variant values, but making a `default:` would
// disable exhaustiveness warnings.
assert(false);
} }
bool derivationIsFixed(DerivationType dt) { bool DerivationType::isFixed() const {
switch (dt) { return std::visit(overloaded {
case DerivationType::InputAddressed: return false; [](const InputAddressed & ia) {
case DerivationType::CAFixed: return true; return false;
case DerivationType::CAFloating: return false; },
case DerivationType::DeferredInputAddressed: return false; [](const ContentAddressed & ca) {
}; return ca.fixed;
assert(false); },
}, raw());
} }
bool derivationHasKnownOutputPaths(DerivationType dt) { bool DerivationType::hasKnownOutputPaths() const {
switch (dt) { return std::visit(overloaded {
case DerivationType::InputAddressed: return true; [](const InputAddressed & ia) {
case DerivationType::CAFixed: return true; return !ia.deferred;
case DerivationType::CAFloating: return false; },
case DerivationType::DeferredInputAddressed: return false; [](const ContentAddressed & ca) {
}; return ca.fixed;
assert(false); },
}, raw());
} }
bool derivationIsImpure(DerivationType dt) { bool DerivationType::isImpure() const {
switch (dt) { return std::visit(overloaded {
case DerivationType::InputAddressed: return false; [](const InputAddressed & ia) {
case DerivationType::CAFixed: return true; return false;
case DerivationType::CAFloating: return false; },
case DerivationType::DeferredInputAddressed: return false; [](const ContentAddressed & ca) {
}; return !ca.pure;
assert(false); },
}, raw());
} }
@ -179,35 +178,27 @@ static DerivationOutput parseDerivationOutput(const Store & store,
const auto hashType = parseHashType(hashAlgo); const auto hashType = parseHashType(hashAlgo);
if (hash != "") { if (hash != "") {
validatePath(pathS); validatePath(pathS);
return DerivationOutput { return DerivationOutput::CAFixed {
.output = DerivationOutputCAFixed {
.hash = FixedOutputHash { .hash = FixedOutputHash {
.method = std::move(method), .method = std::move(method),
.hash = Hash::parseNonSRIUnprefixed(hash, hashType), .hash = Hash::parseNonSRIUnprefixed(hash, hashType),
}, },
},
}; };
} else { } else {
settings.requireExperimentalFeature(Xp::CaDerivations); settings.requireExperimentalFeature(Xp::CaDerivations);
assert(pathS == ""); assert(pathS == "");
return DerivationOutput { return DerivationOutput::CAFloating {
.output = DerivationOutputCAFloating {
.method = std::move(method), .method = std::move(method),
.hashType = std::move(hashType), .hashType = std::move(hashType),
},
}; };
} }
} else { } else {
if (pathS == "") { if (pathS == "") {
return DerivationOutput { return DerivationOutput::Deferred { };
.output = DerivationOutputDeferred { }
};
} }
validatePath(pathS); validatePath(pathS);
return DerivationOutput { return DerivationOutput::InputAddressed {
.output = DerivationOutputInputAddressed {
.path = store.parseStorePath(pathS), .path = store.parseStorePath(pathS),
}
}; };
} }
} }
@ -335,27 +326,27 @@ std::string Derivation::unparse(const Store & store, bool maskOutputs,
if (first) first = false; else s += ','; if (first) first = false; else s += ',';
s += '('; printUnquotedString(s, i.first); s += '('; printUnquotedString(s, i.first);
std::visit(overloaded { std::visit(overloaded {
[&](const DerivationOutputInputAddressed & doi) { [&](const DerivationOutput::InputAddressed & doi) {
s += ','; printUnquotedString(s, maskOutputs ? "" : store.printStorePath(doi.path)); s += ','; printUnquotedString(s, maskOutputs ? "" : store.printStorePath(doi.path));
s += ','; printUnquotedString(s, ""); s += ','; printUnquotedString(s, "");
s += ','; printUnquotedString(s, ""); s += ','; printUnquotedString(s, "");
}, },
[&](const DerivationOutputCAFixed & dof) { [&](const DerivationOutput::CAFixed & dof) {
s += ','; printUnquotedString(s, maskOutputs ? "" : store.printStorePath(dof.path(store, name, i.first))); s += ','; printUnquotedString(s, maskOutputs ? "" : store.printStorePath(dof.path(store, name, i.first)));
s += ','; printUnquotedString(s, dof.hash.printMethodAlgo()); s += ','; printUnquotedString(s, dof.hash.printMethodAlgo());
s += ','; printUnquotedString(s, dof.hash.hash.to_string(Base16, false)); s += ','; printUnquotedString(s, dof.hash.hash.to_string(Base16, false));
}, },
[&](const DerivationOutputCAFloating & dof) { [&](const DerivationOutput::CAFloating & dof) {
s += ','; printUnquotedString(s, ""); s += ','; printUnquotedString(s, "");
s += ','; printUnquotedString(s, makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType)); s += ','; printUnquotedString(s, makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType));
s += ','; printUnquotedString(s, ""); s += ','; printUnquotedString(s, "");
}, },
[&](const DerivationOutputDeferred &) { [&](const DerivationOutput::Deferred &) {
s += ','; printUnquotedString(s, ""); s += ','; printUnquotedString(s, "");
s += ','; printUnquotedString(s, ""); s += ','; printUnquotedString(s, "");
s += ','; printUnquotedString(s, ""); s += ','; printUnquotedString(s, "");
} }
}, i.second.output); }, i.second.raw());
s += ')'; s += ')';
} }
@ -423,13 +414,13 @@ DerivationType BasicDerivation::type() const
std::optional<HashType> floatingHashType; std::optional<HashType> floatingHashType;
for (auto & i : outputs) { for (auto & i : outputs) {
std::visit(overloaded { std::visit(overloaded {
[&](const DerivationOutputInputAddressed &) { [&](const DerivationOutput::InputAddressed &) {
inputAddressedOutputs.insert(i.first); inputAddressedOutputs.insert(i.first);
}, },
[&](const DerivationOutputCAFixed &) { [&](const DerivationOutput::CAFixed &) {
fixedCAOutputs.insert(i.first); fixedCAOutputs.insert(i.first);
}, },
[&](const DerivationOutputCAFloating & dof) { [&](const DerivationOutput::CAFloating & dof) {
floatingCAOutputs.insert(i.first); floatingCAOutputs.insert(i.first);
if (!floatingHashType) { if (!floatingHashType) {
floatingHashType = dof.hashType; floatingHashType = dof.hashType;
@ -438,27 +429,37 @@ DerivationType BasicDerivation::type() const
throw Error("All floating outputs must use the same hash type"); throw Error("All floating outputs must use the same hash type");
} }
}, },
[&](const DerivationOutputDeferred &) { [&](const DerivationOutput::Deferred &) {
deferredIAOutputs.insert(i.first); deferredIAOutputs.insert(i.first);
}, },
}, i.second.output); }, i.second.raw());
} }
if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) { if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
throw Error("Must have at least one output"); throw Error("Must have at least one output");
} else if (! inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) { } else if (! inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
return DerivationType::InputAddressed; return DerivationType::InputAddressed {
.deferred = false,
};
} else if (inputAddressedOutputs.empty() && ! fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) { } else if (inputAddressedOutputs.empty() && ! fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
if (fixedCAOutputs.size() > 1) if (fixedCAOutputs.size() > 1)
// FIXME: Experimental feature? // FIXME: Experimental feature?
throw Error("Only one fixed output is allowed for now"); throw Error("Only one fixed output is allowed for now");
if (*fixedCAOutputs.begin() != "out") if (*fixedCAOutputs.begin() != "out")
throw Error("Single fixed output must be named \"out\""); throw Error("Single fixed output must be named \"out\"");
return DerivationType::CAFixed; return DerivationType::ContentAddressed {
.pure = false,
.fixed = true,
};
} else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && ! floatingCAOutputs.empty() && deferredIAOutputs.empty()) { } else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && ! floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
return DerivationType::CAFloating; return DerivationType::ContentAddressed {
.pure = true,
.fixed = false,
};
} else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && !deferredIAOutputs.empty()) { } else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && !deferredIAOutputs.empty()) {
return DerivationType::DeferredInputAddressed; return DerivationType::InputAddressed {
.deferred = true,
};
} else { } else {
throw Error("Can't mix derivation output types"); throw Error("Can't mix derivation output types");
} }
@ -510,13 +511,13 @@ static const DrvHashModulo pathDerivationModulo(Store & store, const StorePath &
*/ */
DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutputs) DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutputs)
{ {
bool isDeferred = false; auto type = drv.type();
/* Return a fixed hash for fixed-output derivations. */ /* Return a fixed hash for fixed-output derivations. */
switch (drv.type()) { if (type.isFixed()) {
case DerivationType::CAFixed: {
std::map<std::string, Hash> outputHashes; std::map<std::string, Hash> outputHashes;
for (const auto & i : drv.outputs) { for (const auto & i : drv.outputs) {
auto & dof = std::get<DerivationOutputCAFixed>(i.second.output); auto & dof = std::get<DerivationOutput::CAFixed>(i.second.raw());
auto hash = hashString(htSHA256, "fixed:out:" auto hash = hashString(htSHA256, "fixed:out:"
+ dof.hash.printMethodAlgo() + ":" + dof.hash.printMethodAlgo() + ":"
+ dof.hash.hash.to_string(Base16, false) + ":" + dof.hash.hash.to_string(Base16, false) + ":"
@ -525,33 +526,37 @@ DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool m
} }
return outputHashes; return outputHashes;
} }
case DerivationType::CAFloating:
isDeferred = true; auto kind = std::visit(overloaded {
break; [](const DerivationType::InputAddressed & ia) {
case DerivationType::InputAddressed: /* This might be a "pesimistically" deferred output, so we don't
break; "taint" the kind yet. */
case DerivationType::DeferredInputAddressed: return DrvHash::Kind::Regular;
break; },
} [](const DerivationType::ContentAddressed & ca) {
return ca.fixed
? DrvHash::Kind::Regular
: DrvHash::Kind::Deferred;
},
}, drv.type().raw());
/* For other derivations, replace the inputs paths with recursive /* For other derivations, replace the inputs paths with recursive
calls to this function. */ calls to this function. */
std::map<std::string, StringSet> inputs2; std::map<std::string, StringSet> inputs2;
for (auto & i : drv.inputDrvs) { for (auto & [drvPath, inputOutputs0] : drv.inputDrvs) {
const auto & res = pathDerivationModulo(store, i.first); // Avoid lambda capture restriction with standard / Clang
auto & inputOutputs = inputOutputs0;
const auto & res = pathDerivationModulo(store, drvPath);
std::visit(overloaded { std::visit(overloaded {
// Regular non-CA derivation, replace derivation // Regular non-CA derivation, replace derivation
[&](const Hash & drvHash) { [&](const DrvHash & drvHash) {
inputs2.insert_or_assign(drvHash.to_string(Base16, false), i.second); kind |= drvHash.kind;
}, inputs2.insert_or_assign(drvHash.hash.to_string(Base16, false), inputOutputs);
[&](const DeferredHash & deferredHash) {
isDeferred = true;
inputs2.insert_or_assign(deferredHash.hash.to_string(Base16, false), i.second);
}, },
// CA derivation's output hashes // CA derivation's output hashes
[&](const CaOutputHashes & outputHashes) { [&](const CaOutputHashes & outputHashes) {
std::set<std::string> justOut = { "out" }; std::set<std::string> justOut = { "out" };
for (auto & output : i.second) { for (auto & output : inputOutputs) {
/* Put each one in with a single "out" output.. */ /* Put each one in with a single "out" output.. */
const auto h = outputHashes.at(output); const auto h = outputHashes.at(output);
inputs2.insert_or_assign( inputs2.insert_or_assign(
@ -559,15 +564,24 @@ DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool m
justOut); justOut);
} }
}, },
}, res); }, res.raw());
} }
auto hash = hashString(htSHA256, drv.unparse(store, maskOutputs, &inputs2)); auto hash = hashString(htSHA256, drv.unparse(store, maskOutputs, &inputs2));
if (isDeferred) return DrvHash { .hash = hash, .kind = kind };
return DeferredHash { hash }; }
else
return hash;
void operator |= (DrvHash::Kind & self, const DrvHash::Kind & other) noexcept
{
switch (other) {
case DrvHash::Kind::Regular:
break;
case DrvHash::Kind::Deferred:
self = other;
break;
}
} }
@ -575,20 +589,15 @@ std::map<std::string, Hash> staticOutputHashes(Store & store, const Derivation &
{ {
std::map<std::string, Hash> res; std::map<std::string, Hash> res;
std::visit(overloaded { std::visit(overloaded {
[&](const Hash & drvHash) { [&](const DrvHash & drvHash) {
for (auto & outputName : drv.outputNames()) { for (auto & outputName : drv.outputNames()) {
res.insert({outputName, drvHash}); res.insert({outputName, drvHash.hash});
}
},
[&](const DeferredHash & deferredHash) {
for (auto & outputName : drv.outputNames()) {
res.insert({outputName, deferredHash.hash});
} }
}, },
[&](const CaOutputHashes & outputHashes) { [&](const CaOutputHashes & outputHashes) {
res = outputHashes; res = outputHashes;
}, },
}, hashDerivationModulo(store, drv, true)); }, hashDerivationModulo(store, drv, true).raw());
return res; return res;
} }
@ -669,27 +678,27 @@ void writeDerivation(Sink & out, const Store & store, const BasicDerivation & dr
for (auto & i : drv.outputs) { for (auto & i : drv.outputs) {
out << i.first; out << i.first;
std::visit(overloaded { std::visit(overloaded {
[&](const DerivationOutputInputAddressed & doi) { [&](const DerivationOutput::InputAddressed & doi) {
out << store.printStorePath(doi.path) out << store.printStorePath(doi.path)
<< "" << ""
<< ""; << "";
}, },
[&](const DerivationOutputCAFixed & dof) { [&](const DerivationOutput::CAFixed & dof) {
out << store.printStorePath(dof.path(store, drv.name, i.first)) out << store.printStorePath(dof.path(store, drv.name, i.first))
<< dof.hash.printMethodAlgo() << dof.hash.printMethodAlgo()
<< dof.hash.hash.to_string(Base16, false); << dof.hash.hash.to_string(Base16, false);
}, },
[&](const DerivationOutputCAFloating & dof) { [&](const DerivationOutput::CAFloating & dof) {
out << "" out << ""
<< (makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType)) << (makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType))
<< ""; << "";
}, },
[&](const DerivationOutputDeferred &) { [&](const DerivationOutput::Deferred &) {
out << "" out << ""
<< "" << ""
<< ""; << "";
}, },
}, i.second.output); }, i.second.raw());
} }
worker_proto::write(store, out, drv.inputSrcs); worker_proto::write(store, out, drv.inputSrcs);
out << drv.platform << drv.builder << drv.args; out << drv.platform << drv.builder << drv.args;
@ -737,45 +746,63 @@ static void rewriteDerivation(Store & store, BasicDerivation & drv, const String
auto hashModulo = hashDerivationModulo(store, Derivation(drv), true); auto hashModulo = hashDerivationModulo(store, Derivation(drv), true);
for (auto & [outputName, output] : drv.outputs) { for (auto & [outputName, output] : drv.outputs) {
if (std::holds_alternative<DerivationOutputDeferred>(output.output)) { if (std::holds_alternative<DerivationOutput::Deferred>(output.raw())) {
Hash h = std::get<Hash>(hashModulo); auto & h = hashModulo.requireNoFixedNonDeferred();
auto outPath = store.makeOutputPath(outputName, h, drv.name); auto outPath = store.makeOutputPath(outputName, h, drv.name);
drv.env[outputName] = store.printStorePath(outPath); drv.env[outputName] = store.printStorePath(outPath);
output = DerivationOutput { output = DerivationOutput::InputAddressed {
.output = DerivationOutputInputAddressed {
.path = std::move(outPath), .path = std::move(outPath),
},
}; };
} }
} }
} }
const Hash & DrvHashModulo::requireNoFixedNonDeferred() const {
auto * drvHashOpt = std::get_if<DrvHash>(&raw());
assert(drvHashOpt);
assert(drvHashOpt->kind == DrvHash::Kind::Regular);
return drvHashOpt->hash;
}
static bool tryResolveInput(
Store & store, StorePathSet & inputSrcs, StringMap & inputRewrites,
const StorePath & inputDrv, const StringSet & inputOutputs)
{
auto inputDrvOutputs = store.queryPartialDerivationOutputMap(inputDrv);
auto getOutput = [&](const std::string & outputName) {
auto & actualPathOpt = inputDrvOutputs.at(outputName);
if (!actualPathOpt)
warn("output %s of input %s missing, aborting the resolving",
outputName,
store.printStorePath(inputDrv)
);
return actualPathOpt;
};
for (auto & outputName : inputOutputs) {
auto actualPathOpt = getOutput(outputName);
if (!actualPathOpt) return false;
auto actualPath = *actualPathOpt;
inputRewrites.emplace(
downstreamPlaceholder(store, inputDrv, outputName),
store.printStorePath(actualPath));
inputSrcs.insert(std::move(actualPath));
}
return true;
}
std::optional<BasicDerivation> Derivation::tryResolve(Store & store) { std::optional<BasicDerivation> Derivation::tryResolve(Store & store) {
BasicDerivation resolved { *this }; BasicDerivation resolved { *this };
// Input paths that we'll want to rewrite in the derivation // Input paths that we'll want to rewrite in the derivation
StringMap inputRewrites; StringMap inputRewrites;
for (auto & input : inputDrvs) { for (auto & [inputDrv, inputOutputs] : inputDrvs)
auto inputDrvOutputs = store.queryPartialDerivationOutputMap(input.first); if (!tryResolveInput(store, resolved.inputSrcs, inputRewrites, inputDrv, inputOutputs))
StringSet newOutputNames;
for (auto & outputName : input.second) {
auto actualPathOpt = inputDrvOutputs.at(outputName);
if (!actualPathOpt) {
warn("output %s of input %s missing, aborting the resolving",
outputName,
store.printStorePath(input.first)
);
return std::nullopt; return std::nullopt;
}
auto actualPath = *actualPathOpt;
inputRewrites.emplace(
downstreamPlaceholder(store, input.first, outputName),
store.printStorePath(actualPath));
resolved.inputSrcs.insert(std::move(actualPath));
}
}
rewriteDerivation(store, resolved, inputRewrites); rewriteDerivation(store, resolved, inputRewrites);

View file

@ -4,6 +4,7 @@
#include "types.hh" #include "types.hh"
#include "hash.hh" #include "hash.hh"
#include "content-address.hh" #include "content-address.hh"
#include "repair-flag.hh"
#include "sync.hh" #include "sync.hh"
#include <map> #include <map>
@ -44,19 +45,31 @@ struct DerivationOutputCAFloating
*/ */
struct DerivationOutputDeferred {}; struct DerivationOutputDeferred {};
struct DerivationOutput typedef std::variant<
{
std::variant<
DerivationOutputInputAddressed, DerivationOutputInputAddressed,
DerivationOutputCAFixed, DerivationOutputCAFixed,
DerivationOutputCAFloating, DerivationOutputCAFloating,
DerivationOutputDeferred DerivationOutputDeferred
> output; > _DerivationOutputRaw;
struct DerivationOutput : _DerivationOutputRaw
{
using Raw = _DerivationOutputRaw;
using Raw::Raw;
using InputAddressed = DerivationOutputInputAddressed;
using CAFixed = DerivationOutputCAFixed;
using CAFloating = DerivationOutputCAFloating;
using Deferred = DerivationOutputDeferred;
/* Note, when you use this function you should make sure that you're passing /* Note, when you use this function you should make sure that you're passing
the right derivation name. When in doubt, you should use the safer the right derivation name. When in doubt, you should use the safer
interface provided by BasicDerivation::outputsAndOptPaths */ interface provided by BasicDerivation::outputsAndOptPaths */
std::optional<StorePath> path(const Store & store, std::string_view drvName, std::string_view outputName) const; std::optional<StorePath> path(const Store & store, std::string_view drvName, std::string_view outputName) const;
inline const Raw & raw() const {
return static_cast<const Raw &>(*this);
}
}; };
typedef std::map<std::string, DerivationOutput> DerivationOutputs; typedef std::map<std::string, DerivationOutput> DerivationOutputs;
@ -72,30 +85,50 @@ typedef std::map<std::string, std::pair<DerivationOutput, std::optional<StorePat
output IDs we are interested in. */ output IDs we are interested in. */
typedef std::map<StorePath, StringSet> DerivationInputs; typedef std::map<StorePath, StringSet> DerivationInputs;
enum struct DerivationType : uint8_t { struct DerivationType_InputAddressed {
InputAddressed, bool deferred;
DeferredInputAddressed,
CAFixed,
CAFloating,
}; };
struct DerivationType_ContentAddressed {
bool pure;
bool fixed;
};
typedef std::variant<
DerivationType_InputAddressed,
DerivationType_ContentAddressed
> _DerivationTypeRaw;
struct DerivationType : _DerivationTypeRaw {
using Raw = _DerivationTypeRaw;
using Raw::Raw;
using InputAddressed = DerivationType_InputAddressed;
using ContentAddressed = DerivationType_ContentAddressed;
/* Do the outputs of the derivation have paths calculated from their content, /* Do the outputs of the derivation have paths calculated from their content,
or from the derivation itself? */ or from the derivation itself? */
bool derivationIsCA(DerivationType); bool isCA() const;
/* Is the content of the outputs fixed a-priori via a hash? Never true for /* Is the content of the outputs fixed a-priori via a hash? Never true for
non-CA derivations. */ non-CA derivations. */
bool derivationIsFixed(DerivationType); bool isFixed() const;
/* Is the derivation impure and needs to access non-deterministic resources, or /* Is the derivation impure and needs to access non-deterministic resources, or
pure and can be sandboxed? Note that whether or not we actually sandbox the pure and can be sandboxed? Note that whether or not we actually sandbox the
derivation is controlled separately. Never true for non-CA derivations. */ derivation is controlled separately. Never true for non-CA derivations. */
bool derivationIsImpure(DerivationType); bool isImpure() const;
/* Does the derivation knows its own output paths? /* Does the derivation knows its own output paths?
* Only true when there's no floating-ca derivation involved in the closure. Only true when there's no floating-ca derivation involved in the
closure, or if fixed output.
*/ */
bool derivationHasKnownOutputPaths(DerivationType); bool hasKnownOutputPaths() const;
inline const Raw & raw() const {
return static_cast<const Raw &>(*this);
}
};
struct BasicDerivation struct BasicDerivation
{ {
@ -150,8 +183,6 @@ struct Derivation : BasicDerivation
class Store; class Store;
enum RepairFlag : bool { NoRepair = false, Repair = true };
/* Write a derivation to the Nix store, and return its path. */ /* Write a derivation to the Nix store, and return its path. */
StorePath writeDerivation(Store & store, StorePath writeDerivation(Store & store,
const Derivation & drv, const Derivation & drv,
@ -175,13 +206,43 @@ std::string outputPathName(std::string_view drvName, std::string_view outputName
// whose output hashes are always known since they are fixed up-front. // whose output hashes are always known since they are fixed up-front.
typedef std::map<std::string, Hash> CaOutputHashes; typedef std::map<std::string, Hash> CaOutputHashes;
struct DeferredHash { Hash hash; }; struct DrvHash {
Hash hash;
enum struct Kind: bool {
// Statically determined derivations.
// This hash will be directly used to compute the output paths
Regular,
// Floating-output derivations (and their reverse dependencies).
Deferred,
};
Kind kind;
};
void operator |= (DrvHash::Kind & self, const DrvHash::Kind & other) noexcept;
typedef std::variant< typedef std::variant<
Hash, // regular DRV normalized hash // Regular normalized derivation hash, and whether it was deferred (because
CaOutputHashes, // Fixed-output derivation hashes // an ancestor derivation is a floating content addressed derivation).
DeferredHash // Deferred hashes for floating outputs drvs and their dependencies DrvHash,
> DrvHashModulo; // Fixed-output derivation hashes
CaOutputHashes
> _DrvHashModuloRaw;
struct DrvHashModulo : _DrvHashModuloRaw {
using Raw = _DrvHashModuloRaw;
using Raw::Raw;
/* Get hash, throwing if it is per-output CA hashes or a
deferred Drv hash.
*/
const Hash & requireNoFixedNonDeferred() const;
inline const Raw & raw() const {
return static_cast<const Raw &>(*this);
}
};
/* Returns hashes with the details of fixed-output subderivations /* Returns hashes with the details of fixed-output subderivations
expunged. expunged.

View file

@ -1,4 +1,5 @@
#include "derived-path.hh" #include "derived-path.hh"
#include "derivations.hh"
#include "store-api.hh" #include "store-api.hh"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@ -11,6 +12,21 @@ nlohmann::json DerivedPath::Opaque::toJSON(ref<Store> store) const {
return res; return res;
} }
nlohmann::json DerivedPath::Built::toJSON(ref<Store> store) const {
nlohmann::json res;
res["drvPath"] = store->printStorePath(drvPath);
// Fallback for the input-addressed derivation case: We expect to always be
// able to print the output paths, so lets do it
auto knownOutputs = store->queryPartialDerivationOutputMap(drvPath);
for (const auto& output : outputs) {
if (knownOutputs.at(output))
res["outputs"][output] = store->printStorePath(knownOutputs.at(output).value());
else
res["outputs"][output] = nullptr;
}
return res;
}
nlohmann::json BuiltPath::Built::toJSON(ref<Store> store) const { nlohmann::json BuiltPath::Built::toJSON(ref<Store> store) const {
nlohmann::json res; nlohmann::json res;
res["drvPath"] = store->printStorePath(drvPath); res["drvPath"] = store->printStorePath(drvPath);
@ -35,16 +51,22 @@ StorePathSet BuiltPath::outPaths() const
); );
} }
nlohmann::json derivedPathsWithHintsToJSON(const BuiltPaths & buildables, ref<Store> store) { template<typename T>
nlohmann::json stuffToJSON(const std::vector<T> & ts, ref<Store> store) {
auto res = nlohmann::json::array(); auto res = nlohmann::json::array();
for (const BuiltPath & buildable : buildables) { for (const T & t : ts) {
std::visit([&res, store](const auto & buildable) { std::visit([&res, store](const auto & t) {
res.push_back(buildable.toJSON(store)); res.push_back(t.toJSON(store));
}, buildable.raw()); }, t.raw());
} }
return res; return res;
} }
nlohmann::json derivedPathsWithHintsToJSON(const BuiltPaths & buildables, ref<Store> store)
{ return stuffToJSON<BuiltPath>(buildables, store); }
nlohmann::json derivedPathsToJSON(const DerivedPaths & paths, ref<Store> store)
{ return stuffToJSON<DerivedPath>(paths, store); }
std::string DerivedPath::Opaque::to_string(const Store & store) const { std::string DerivedPath::Opaque::to_string(const Store & store) const {
return store.printStorePath(path); return store.printStorePath(path);

View file

@ -46,6 +46,7 @@ struct DerivedPathBuilt {
std::string to_string(const Store & store) const; std::string to_string(const Store & store) const;
static DerivedPathBuilt parse(const Store & store, std::string_view); static DerivedPathBuilt parse(const Store & store, std::string_view);
nlohmann::json toJSON(ref<Store> store) const;
}; };
using _DerivedPathRaw = std::variant< using _DerivedPathRaw = std::variant<
@ -120,5 +121,6 @@ typedef std::vector<DerivedPath> DerivedPaths;
typedef std::vector<BuiltPath> BuiltPaths; typedef std::vector<BuiltPath> BuiltPaths;
nlohmann::json derivedPathsWithHintsToJSON(const BuiltPaths & buildables, ref<Store> store); nlohmann::json derivedPathsWithHintsToJSON(const BuiltPaths & buildables, ref<Store> store);
nlohmann::json derivedPathsToJSON(const DerivedPaths & , ref<Store> store);
} }

View file

@ -1,13 +0,0 @@
#include "gc-store.hh"
namespace nix {
GcStore & requireGcStore(Store & store)
{
auto * gcStore = dynamic_cast<GcStore *>(&store);
if (!gcStore)
throw UsageError("Garbage collection not supported by this store");
return *gcStore;
}
}

View file

@ -61,6 +61,8 @@ struct GCResults
struct GcStore : public virtual Store struct GcStore : public virtual Store
{ {
inline static std::string operationName = "Garbage collection";
/* Add an indirect root, which is merely a symlink to `path' from /* Add an indirect root, which is merely a symlink to `path' from
/nix/var/nix/gcroots/auto/<hash of `path'>. `path' is supposed /nix/var/nix/gcroots/auto/<hash of `path'>. `path' is supposed
to be a symlink to a store path. The garbage collector will to be a symlink to a store path. The garbage collector will
@ -79,6 +81,4 @@ struct GcStore : public virtual Store
virtual void collectGarbage(const GCOptions & options, GCResults & results) = 0; virtual void collectGarbage(const GCOptions & options, GCResults & results) = 0;
}; };
GcStore & requireGcStore(Store & store);
} }

View file

@ -678,7 +678,8 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
alive.insert(start); alive.insert(start);
try { try {
StorePathSet closure; StorePathSet closure;
computeFSClosure(*path, closure); computeFSClosure(*path, closure,
/* flipDirection */ false, gcKeepOutputs, gcKeepDerivations);
for (auto & p : closure) for (auto & p : closure)
alive.insert(p); alive.insert(p);
} catch (InvalidPath &) { } } catch (InvalidPath &) { }
@ -841,7 +842,8 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
if (unlink(path.c_str()) == -1) if (unlink(path.c_str()) == -1)
throw SysError("deleting '%1%'", path); throw SysError("deleting '%1%'", path);
results.bytesFreed += st.st_size; /* Do not accound for deleted file here. Rely on deletePath()
accounting. */
} }
struct stat st; struct stat st;

View file

@ -2,6 +2,7 @@
#include "store-api.hh" #include "store-api.hh"
#include "gc-store.hh" #include "gc-store.hh"
#include "log-store.hh"
namespace nix { namespace nix {
@ -24,7 +25,10 @@ struct LocalFSStoreConfig : virtual StoreConfig
"physical path to the Nix store"}; "physical path to the Nix store"};
}; };
class LocalFSStore : public virtual LocalFSStoreConfig, public virtual Store, virtual GcStore class LocalFSStore : public virtual LocalFSStoreConfig,
public virtual Store,
public virtual GcStore,
public virtual LogStore
{ {
public: public:

View file

@ -698,11 +698,11 @@ void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivat
std::optional<Hash> h; std::optional<Hash> h;
for (auto & i : drv.outputs) { for (auto & i : drv.outputs) {
std::visit(overloaded { std::visit(overloaded {
[&](const DerivationOutputInputAddressed & doia) { [&](const DerivationOutput::InputAddressed & doia) {
if (!h) { if (!h) {
// somewhat expensive so we do lazily // somewhat expensive so we do lazily
auto temp = hashDerivationModulo(*this, drv, true); auto h0 = hashDerivationModulo(*this, drv, true);
h = std::get<Hash>(temp); h = h0.requireNoFixedNonDeferred();
} }
StorePath recomputed = makeOutputPath(i.first, *h, drvName); StorePath recomputed = makeOutputPath(i.first, *h, drvName);
if (doia.path != recomputed) if (doia.path != recomputed)
@ -710,16 +710,17 @@ void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivat
printStorePath(drvPath), printStorePath(doia.path), printStorePath(recomputed)); printStorePath(drvPath), printStorePath(doia.path), printStorePath(recomputed));
envHasRightPath(doia.path, i.first); envHasRightPath(doia.path, i.first);
}, },
[&](const DerivationOutputCAFixed & dof) { [&](const DerivationOutput::CAFixed & dof) {
StorePath path = makeFixedOutputPath(drvName, { dof.hash, {} }); StorePath path = makeFixedOutputPath(drvName, { dof.hash, {} });
envHasRightPath(path, i.first); envHasRightPath(path, i.first);
}, },
[&](const DerivationOutputCAFloating &) { [&](const DerivationOutput::CAFloating &) {
/* Nothing to check */ /* Nothing to check */
}, },
[&](const DerivationOutputDeferred &) { [&](const DerivationOutput::Deferred &) {
/* Nothing to check */
}, },
}, i.second.output); }, i.second.raw());
} }
} }

21
src/libstore/log-store.hh Normal file
View file

@ -0,0 +1,21 @@
#pragma once
#include "store-api.hh"
namespace nix {
struct LogStore : public virtual Store
{
inline static std::string operationName = "Build log storage and retrieval";
/* Return the build log of the specified store path, if available,
or null otherwise. */
virtual std::optional<std::string> getBuildLog(const StorePath & path) = 0;
virtual void addBuildLog(const StorePath & path, std::string_view log) = 0;
static LogStore & require(Store & store);
};
}

View file

@ -0,0 +1,83 @@
#include "make-content-addressed.hh"
#include "references.hh"
namespace nix {
std::map<StorePath, StorePath> makeContentAddressed(
Store & srcStore,
Store & dstStore,
const StorePathSet & storePaths)
{
StorePathSet closure;
srcStore.computeFSClosure(storePaths, closure);
auto paths = srcStore.topoSortPaths(closure);
std::reverse(paths.begin(), paths.end());
std::map<StorePath, StorePath> remappings;
for (auto & path : paths) {
auto pathS = srcStore.printStorePath(path);
auto oldInfo = srcStore.queryPathInfo(path);
std::string oldHashPart(path.hashPart());
StringSink sink;
srcStore.narFromPath(path, sink);
StringMap rewrites;
PathReferences<StorePath> refs;
refs.hasSelfReference = oldInfo->hasSelfReference;
for (auto & ref : oldInfo->references) {
auto i = remappings.find(ref);
auto replacement = i != remappings.end() ? i->second : ref;
// FIXME: warn about unremapped paths?
if (replacement != ref) {
rewrites.insert_or_assign(srcStore.printStorePath(ref), srcStore.printStorePath(replacement));
refs.references.insert(std::move(replacement));
}
}
sink.s = rewriteStrings(sink.s, rewrites);
HashModuloSink hashModuloSink(htSHA256, oldHashPart);
hashModuloSink(sink.s);
auto narModuloHash = hashModuloSink.finish().first;
ValidPathInfo info {
dstStore,
StorePathDescriptor {
.name = std::string { path.name() },
.info = FixedOutputInfo {
{
.method = FileIngestionMethod::Recursive,
.hash = narModuloHash,
},
std::move(refs),
},
},
Hash::dummy,
};
printInfo("rewriting '%s' to '%s'", pathS, dstStore.printStorePath(info.path));
StringSink sink2;
RewritingSink rsink2(oldHashPart, std::string(info.path.hashPart()), sink2);
rsink2(sink.s);
rsink2.flush();
info.narHash = hashString(htSHA256, sink2.s);
info.narSize = sink.s.size();
StringSource source(sink2.s);
dstStore.addToStore(info, source);
remappings.insert_or_assign(std::move(path), std::move(info.path));
}
return remappings;
}
}

View file

@ -0,0 +1,12 @@
#pragma once
#include "store-api.hh"
namespace nix {
std::map<StorePath, StorePath> makeContentAddressed(
Store & srcStore,
Store & dstStore,
const StorePathSet & storePaths);
}

View file

@ -86,7 +86,7 @@ std::optional<ContentAddress> getDerivationCA(const BasicDerivation & drv)
{ {
auto out = drv.outputs.find("out"); auto out = drv.outputs.find("out");
if (out != drv.outputs.end()) { if (out != drv.outputs.end()) {
if (auto v = std::get_if<DerivationOutputCAFixed>(&out->second.output)) if (const auto * v = std::get_if<DerivationOutput::CAFixed>(&out->second.raw()))
return v->hash; return v->hash;
} }
return std::nullopt; return std::nullopt;

View file

@ -93,7 +93,7 @@ StringSet ParsedDerivation::getRequiredSystemFeatures() const
StringSet res; StringSet res;
for (auto & i : getStringsAttr("requiredSystemFeatures").value_or(Strings())) for (auto & i : getStringsAttr("requiredSystemFeatures").value_or(Strings()))
res.insert(i); res.insert(i);
if (!derivationHasKnownOutputPaths(drv.type())) if (!drv.type().hasKnownOutputPaths())
res.insert("ca-derivations"); res.insert("ca-derivations");
return res; return res;
} }

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "derivations.hh"
#include "store-api.hh" #include "store-api.hh"
#include <nlohmann/json_fwd.hpp> #include <nlohmann/json_fwd.hpp>

View file

@ -5,6 +5,7 @@
#include "store-api.hh" #include "store-api.hh"
#include "gc-store.hh" #include "gc-store.hh"
#include "log-store.hh"
namespace nix { namespace nix {
@ -30,7 +31,10 @@ struct RemoteStoreConfig : virtual StoreConfig
/* FIXME: RemoteStore is a misnomer - should be something like /* FIXME: RemoteStore is a misnomer - should be something like
DaemonStore. */ DaemonStore. */
class RemoteStore : public virtual RemoteStoreConfig, public virtual Store, public virtual GcStore class RemoteStore : public virtual RemoteStoreConfig,
public virtual Store,
public virtual GcStore,
public virtual LogStore
{ {
public: public:

View file

@ -0,0 +1,7 @@
#pragma once
namespace nix {
enum RepairFlag : bool { NoRepair = false, Repair = true };
}

View file

@ -52,6 +52,10 @@ public:
bool sameMachine() override bool sameMachine() override
{ return false; } { return false; }
// FIXME extend daemon protocol, move implementation to RemoteStore
std::optional<std::string> getBuildLog(const StorePath & path) override
{ unsupported("getBuildLog"); }
private: private:
struct Connection : RemoteStore::Connection struct Connection : RemoteStore::Connection

View file

@ -1,6 +1,7 @@
#include "crypto.hh" #include "crypto.hh"
#include "fs-accessor.hh" #include "fs-accessor.hh"
#include "globals.hh" #include "globals.hh"
#include "derivations.hh"
#include "store-api.hh" #include "store-api.hh"
#include "util.hh" #include "util.hh"
#include "nar-info-disk-cache.hh" #include "nar-info-disk-cache.hh"

View file

@ -10,8 +10,8 @@
#include "sync.hh" #include "sync.hh"
#include "globals.hh" #include "globals.hh"
#include "config.hh" #include "config.hh"
#include "derivations.hh"
#include "path-info.hh" #include "path-info.hh"
#include "repair-flag.hh"
#include <atomic> #include <atomic>
#include <limits> #include <limits>
@ -62,6 +62,8 @@ MakeError(BadStorePath, Error);
MakeError(InvalidStoreURI, Error); MakeError(InvalidStoreURI, Error);
struct BasicDerivation;
struct Derivation;
class FSAccessor; class FSAccessor;
class NarInfoDiskCache; class NarInfoDiskCache;
class Store; class Store;
@ -601,14 +603,6 @@ public:
*/ */
StorePathSet exportReferences(const StorePathSet & storePaths, const StorePathSet & inputPaths); StorePathSet exportReferences(const StorePathSet & storePaths, const StorePathSet & inputPaths);
/* Return the build log of the specified store path, if available,
or null otherwise. */
virtual std::optional<std::string> getBuildLog(const StorePath & path)
{ return std::nullopt; }
virtual void addBuildLog(const StorePath & path, std::string_view log)
{ unsupported("addBuildLog"); }
/* Hack to allow long-running processes like hydra-queue-runner to /* Hack to allow long-running processes like hydra-queue-runner to
occasionally flush their path info cache. */ occasionally flush their path info cache. */
void clearPathInfoCache() void clearPathInfoCache()

View file

@ -0,0 +1,16 @@
#pragma once
#include "store-api.hh"
namespace nix {
template<typename T>
T & require(Store & store)
{
auto * castedStore = dynamic_cast<T *>(&store);
if (!castedStore)
throw UsageError("%s not supported by store '%s'", T::operationName, store.getUri());
return *castedStore;
}
}

View file

@ -64,11 +64,12 @@ static void dumpContents(const Path & path, off_t size,
} }
static void dump(const Path & path, Sink & sink, PathFilter & filter) static time_t dump(const Path & path, Sink & sink, PathFilter & filter)
{ {
checkInterrupt(); checkInterrupt();
auto st = lstat(path); auto st = lstat(path);
time_t result = st.st_mtime;
sink << "("; sink << "(";
@ -103,7 +104,10 @@ static void dump(const Path & path, Sink & sink, PathFilter & filter)
for (auto & i : unhacked) for (auto & i : unhacked)
if (filter(path + "/" + i.first)) { if (filter(path + "/" + i.first)) {
sink << "entry" << "(" << "name" << i.first << "node"; sink << "entry" << "(" << "name" << i.first << "node";
dump(path + "/" + i.second, sink, filter); auto tmp_mtime = dump(path + "/" + i.second, sink, filter);
if (tmp_mtime > result) {
result = tmp_mtime;
}
sink << ")"; sink << ")";
} }
} }
@ -114,13 +118,20 @@ static void dump(const Path & path, Sink & sink, PathFilter & filter)
else throw Error("file '%1%' has an unsupported type", path); else throw Error("file '%1%' has an unsupported type", path);
sink << ")"; sink << ")";
return result;
} }
void dumpPath(const Path & path, Sink & sink, PathFilter & filter) time_t dumpPathAndGetMtime(const Path & path, Sink & sink, PathFilter & filter)
{ {
sink << narVersionMagic1; sink << narVersionMagic1;
dump(path, sink, filter); return dump(path, sink, filter);
}
void dumpPath(const Path & path, Sink & sink, PathFilter & filter)
{
dumpPathAndGetMtime(path, sink, filter);
} }

View file

@ -48,6 +48,10 @@ namespace nix {
void dumpPath(const Path & path, Sink & sink, void dumpPath(const Path & path, Sink & sink,
PathFilter & filter = defaultPathFilter); PathFilter & filter = defaultPathFilter);
/* Same as `void dumpPath()`, but returns the last modified date of the path */
time_t dumpPathAndGetMtime(const Path & path, Sink & sink,
PathFilter & filter = defaultPathFilter);
void dumpString(std::string_view s, Sink & sink); void dumpString(std::string_view s, Sink & sink);
/* FIXME: fix this API, it sucks. */ /* FIXME: fix this API, it sucks. */

View file

@ -54,6 +54,7 @@ typedef enum {
lvlVomit lvlVomit
} Verbosity; } Verbosity;
/* adjust Pos::origin bit width when adding stuff here */
typedef enum { typedef enum {
foFile, foFile,
foStdin, foStdin,

View file

@ -11,6 +11,7 @@ std::map<ExperimentalFeature, std::string> stringifiedXpFeatures = {
{ Xp::NixCommand, "nix-command" }, { Xp::NixCommand, "nix-command" },
{ Xp::RecursiveNix, "recursive-nix" }, { Xp::RecursiveNix, "recursive-nix" },
{ Xp::NoUrlLiterals, "no-url-literals" }, { Xp::NoUrlLiterals, "no-url-literals" },
{ Xp::FetchClosure, "fetch-closure" },
}; };
const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::string_view & name) const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::string_view & name)

View file

@ -19,7 +19,8 @@ enum struct ExperimentalFeature
Flakes, Flakes,
NixCommand, NixCommand,
RecursiveNix, RecursiveNix,
NoUrlLiterals NoUrlLiterals,
FetchClosure,
}; };
/** /**

View file

@ -1,14 +1,13 @@
#pragma once #pragma once
#include <functional>
/* A trivial class to run a function at the end of a scope. */ /* A trivial class to run a function at the end of a scope. */
template<typename Fn>
class Finally class Finally
{ {
private: private:
std::function<void()> fun; Fn fun;
public: public:
Finally(std::function<void()> fun) : fun(fun) { } Finally(Fn fun) : fun(std::move(fun)) { }
~Finally() { fun(); } ~Finally() { fun(); }
}; };

View file

@ -39,30 +39,32 @@ void TarArchive::check(int err, const std::string & reason)
throw Error(reason, archive_error_string(this->archive)); throw Error(reason, archive_error_string(this->archive));
} }
TarArchive::TarArchive(Source & source, bool raw) TarArchive::TarArchive(Source & source, bool raw) : buffer(4096)
: source(&source), buffer(4096)
{ {
init(); this->archive = archive_read_new();
if (!raw) this->source = &source;
if (!raw) {
archive_read_support_filter_all(archive);
archive_read_support_format_all(archive); archive_read_support_format_all(archive);
else } else {
archive_read_support_filter_all(archive);
archive_read_support_format_raw(archive); archive_read_support_format_raw(archive);
archive_read_support_format_empty(archive);
}
check(archive_read_open(archive, (void *)this, callback_open, callback_read, callback_close), "Failed to open archive (%s)"); check(archive_read_open(archive, (void *)this, callback_open, callback_read, callback_close), "Failed to open archive (%s)");
} }
TarArchive::TarArchive(const Path & path) TarArchive::TarArchive(const Path & path)
{ {
init(); this->archive = archive_read_new();
archive_read_support_filter_all(archive);
archive_read_support_format_all(archive); archive_read_support_format_all(archive);
check(archive_read_open_filename(archive, path.c_str(), 16384), "failed to open archive: %s"); check(archive_read_open_filename(archive, path.c_str(), 16384), "failed to open archive: %s");
} }
void TarArchive::init()
{
archive = archive_read_new();
archive_read_support_filter_all(archive);
}
void TarArchive::close() void TarArchive::close()
{ {
check(archive_read_close(this->archive), "Failed to close archive (%s)"); check(archive_read_close(this->archive), "Failed to close archive (%s)");

View file

@ -17,13 +17,10 @@ struct TarArchive {
// disable copy constructor // disable copy constructor
TarArchive(const TarArchive &) = delete; TarArchive(const TarArchive &) = delete;
void init();
void close(); void close();
~TarArchive(); ~TarArchive();
}; };
void unpackTarfile(Source & source, const Path & destDir); void unpackTarfile(Source & source, const Path & destDir);
void unpackTarfile(const Path & tarFile, const Path & destDir); void unpackTarfile(const Path & tarFile, const Path & destDir);

View file

@ -406,8 +406,29 @@ static void _deletePath(int parentfd, const Path & path, uint64_t & bytesFreed)
throw SysError("getting status of '%1%'", path); throw SysError("getting status of '%1%'", path);
} }
if (!S_ISDIR(st.st_mode) && st.st_nlink == 1) if (!S_ISDIR(st.st_mode)) {
/* We are about to delete a file. Will it likely free space? */
switch (st.st_nlink) {
/* Yes: last link. */
case 1:
bytesFreed += st.st_size; bytesFreed += st.st_size;
break;
/* Maybe: yes, if 'auto-optimise-store' or manual optimisation
was performed. Instead of checking for real let's assume
it's an optimised file and space will be freed.
In worst case we will double count on freed space for files
with exactly two hardlinks for unoptimised packages.
*/
case 2:
bytesFreed += st.st_size;
break;
/* No: 3+ links. */
default:
break;
}
}
if (S_ISDIR(st.st_mode)) { if (S_ISDIR(st.st_mode)) {
/* Make the directory accessible. */ /* Make the directory accessible. */
@ -682,7 +703,14 @@ std::string drainFD(int fd, bool block, const size_t reserveSize)
void drainFD(int fd, Sink & sink, bool block) void drainFD(int fd, Sink & sink, bool block)
{ {
int saved; // silence GCC maybe-uninitialized warning in finally
int saved = 0;
if (!block) {
saved = fcntl(fd, F_GETFL);
if (fcntl(fd, F_SETFL, saved | O_NONBLOCK) == -1)
throw SysError("making file descriptor non-blocking");
}
Finally finally([&]() { Finally finally([&]() {
if (!block) { if (!block) {
@ -691,12 +719,6 @@ void drainFD(int fd, Sink & sink, bool block)
} }
}); });
if (!block) {
saved = fcntl(fd, F_GETFL);
if (fcntl(fd, F_SETFL, saved | O_NONBLOCK) == -1)
throw SysError("making file descriptor non-blocking");
}
std::vector<unsigned char> buf(64 * 1024); std::vector<unsigned char> buf(64 * 1024);
while (1) { while (1) {
checkInterrupt(); checkInterrupt();

View file

@ -325,8 +325,7 @@ static void main_nix_build(int argc, char * * argv)
state->printStats(); state->printStats();
auto buildPaths = [&](const std::vector<StorePathWithOutputs> & paths0) { auto buildPaths = [&](const std::vector<DerivedPath> & paths) {
auto paths = toDerivedPaths(paths0);
/* Note: we do this even when !printMissing to efficiently /* Note: we do this even when !printMissing to efficiently
fetch binary cache data. */ fetch binary cache data. */
uint64_t downloadSize, narSize; uint64_t downloadSize, narSize;
@ -348,7 +347,7 @@ static void main_nix_build(int argc, char * * argv)
auto & drvInfo = drvs.front(); auto & drvInfo = drvs.front();
auto drv = evalStore->derivationFromPath(drvInfo.requireDrvPath()); auto drv = evalStore->derivationFromPath(drvInfo.requireDrvPath());
std::vector<StorePathWithOutputs> pathsToBuild; std::vector<DerivedPath> pathsToBuild;
RealisedPath::Set pathsToCopy; RealisedPath::Set pathsToCopy;
/* Figure out what bash shell to use. If $NIX_BUILD_SHELL /* Figure out what bash shell to use. If $NIX_BUILD_SHELL
@ -370,7 +369,10 @@ static void main_nix_build(int argc, char * * argv)
throw Error("the 'bashInteractive' attribute in <nixpkgs> did not evaluate to a derivation"); throw Error("the 'bashInteractive' attribute in <nixpkgs> did not evaluate to a derivation");
auto bashDrv = drv->requireDrvPath(); auto bashDrv = drv->requireDrvPath();
pathsToBuild.push_back({bashDrv}); pathsToBuild.push_back(DerivedPath::Built {
.drvPath = bashDrv,
.outputs = {},
});
pathsToCopy.insert(bashDrv); pathsToCopy.insert(bashDrv);
shellDrv = bashDrv; shellDrv = bashDrv;
@ -382,17 +384,24 @@ static void main_nix_build(int argc, char * * argv)
} }
// Build or fetch all dependencies of the derivation. // Build or fetch all dependencies of the derivation.
for (const auto & input : drv.inputDrvs) for (const auto & [inputDrv0, inputOutputs] : drv.inputDrvs) {
// To get around lambda capturing restrictions in the
// standard.
const auto & inputDrv = inputDrv0;
if (std::all_of(envExclude.cbegin(), envExclude.cend(), if (std::all_of(envExclude.cbegin(), envExclude.cend(),
[&](const std::string & exclude) { [&](const std::string & exclude) {
return !std::regex_search(store->printStorePath(input.first), std::regex(exclude)); return !std::regex_search(store->printStorePath(inputDrv), std::regex(exclude));
})) }))
{ {
pathsToBuild.push_back({input.first, input.second}); pathsToBuild.push_back(DerivedPath::Built {
pathsToCopy.insert(input.first); .drvPath = inputDrv,
.outputs = inputOutputs
});
pathsToCopy.insert(inputDrv);
}
} }
for (const auto & src : drv.inputSrcs) { for (const auto & src : drv.inputSrcs) {
pathsToBuild.push_back({src}); pathsToBuild.push_back(DerivedPath::Opaque{src});
pathsToCopy.insert(src); pathsToCopy.insert(src);
} }
@ -543,7 +552,7 @@ static void main_nix_build(int argc, char * * argv)
else { else {
std::vector<StorePathWithOutputs> pathsToBuild; std::vector<DerivedPath> pathsToBuild;
std::vector<std::pair<StorePath, std::string>> pathsToBuildOrdered; std::vector<std::pair<StorePath, std::string>> pathsToBuildOrdered;
RealisedPath::Set drvsToCopy; RealisedPath::Set drvsToCopy;
@ -556,7 +565,7 @@ static void main_nix_build(int argc, char * * argv)
if (outputName == "") if (outputName == "")
throw Error("derivation '%s' lacks an 'outputName' attribute", store->printStorePath(drvPath)); throw Error("derivation '%s' lacks an 'outputName' attribute", store->printStorePath(drvPath));
pathsToBuild.push_back({drvPath, {outputName}}); pathsToBuild.push_back(DerivedPath::Built{drvPath, {outputName}});
pathsToBuildOrdered.push_back({drvPath, {outputName}}); pathsToBuildOrdered.push_back({drvPath, {outputName}});
drvsToCopy.insert(drvPath); drvsToCopy.insert(drvPath);

View file

@ -1,4 +1,5 @@
#include "store-api.hh" #include "store-api.hh"
#include "store-cast.hh"
#include "gc-store.hh" #include "gc-store.hh"
#include "profiles.hh" #include "profiles.hh"
#include "shared.hh" #include "shared.hh"
@ -81,7 +82,7 @@ static int main_nix_collect_garbage(int argc, char * * argv)
// Run the actual garbage collector. // Run the actual garbage collector.
if (!dryRun) { if (!dryRun) {
auto store = openStore(); auto store = openStore();
auto & gcStore = requireGcStore(*store); auto & gcStore = require<GcStore>(*store);
options.action = GCOptions::gcDeleteDead; options.action = GCOptions::gcDeleteDead;
GCResults results; GCResults results;
PrintFreed freed(true, results); PrintFreed freed(true, results);

View file

@ -128,7 +128,12 @@ static void getAllExprs(EvalState & state,
if (hasSuffix(attrName, ".nix")) if (hasSuffix(attrName, ".nix"))
attrName = std::string(attrName, 0, attrName.size() - 4); attrName = std::string(attrName, 0, attrName.size() - 4);
if (!seen.insert(attrName).second) { if (!seen.insert(attrName).second) {
printError("warning: name collision in input Nix expressions, skipping '%1%'", path2); std::string suggestionMessage = "";
if (path2.find("channels") != std::string::npos && path.find("channels") != std::string::npos) {
suggestionMessage = fmt("\nsuggestion: remove '%s' from either the root channels or the user channels", attrName);
}
printError("warning: name collision in input Nix expressions, skipping '%1%'"
"%2%", path2, suggestionMessage);
continue; continue;
} }
/* Load the expression on demand. */ /* Load the expression on demand. */
@ -918,12 +923,17 @@ static void queryJSON(Globals & globals, std::vector<DrvInfo> & elems, bool prin
pkgObj.attr("pname", drvName.name); pkgObj.attr("pname", drvName.name);
pkgObj.attr("version", drvName.version); pkgObj.attr("version", drvName.version);
pkgObj.attr("system", i.querySystem()); pkgObj.attr("system", i.querySystem());
pkgObj.attr("outputName", i.queryOutputName());
if (printOutPath) { {
DrvInfo::Outputs outputs = i.queryOutputs(); DrvInfo::Outputs outputs = i.queryOutputs(printOutPath);
JSONObject outputObj = pkgObj.object("outputs"); JSONObject outputObj = pkgObj.object("outputs");
for (auto & j : outputs) for (auto & j : outputs) {
outputObj.attr(j.first, globals.state->store->printStorePath(j.second)); if (j.second)
outputObj.attr(j.first, globals.state->store->printStorePath(*j.second));
else
outputObj.attr(j.first, nullptr);
}
} }
if (printMeta) { if (printMeta) {
@ -1052,6 +1062,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
/* Print the desired columns, or XML output. */ /* Print the desired columns, or XML output. */
if (jsonOutput) { if (jsonOutput) {
queryJSON(globals, elems, printOutPath, printMeta); queryJSON(globals, elems, printOutPath, printMeta);
cout << '\n';
return; return;
} }
@ -1154,13 +1165,16 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
columns.push_back(drvPath ? store.printStorePath(*drvPath) : "-"); columns.push_back(drvPath ? store.printStorePath(*drvPath) : "-");
} }
if (xmlOutput)
attrs["outputName"] = i.queryOutputName();
if (printOutPath && !xmlOutput) { if (printOutPath && !xmlOutput) {
DrvInfo::Outputs outputs = i.queryOutputs(); DrvInfo::Outputs outputs = i.queryOutputs();
std::string s; std::string s;
for (auto & j : outputs) { for (auto & j : outputs) {
if (!s.empty()) s += ';'; if (!s.empty()) s += ';';
if (j.first != "out") { s += j.first; s += "="; } if (j.first != "out") { s += j.first; s += "="; }
s += store.printStorePath(j.second); s += store.printStorePath(*j.second);
} }
columns.push_back(s); columns.push_back(s);
} }
@ -1174,17 +1188,15 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
} }
if (xmlOutput) { if (xmlOutput) {
if (printOutPath || printMeta) {
XMLOpenElement item(xml, "item", attrs); XMLOpenElement item(xml, "item", attrs);
if (printOutPath) { DrvInfo::Outputs outputs = i.queryOutputs(printOutPath);
DrvInfo::Outputs outputs = i.queryOutputs();
for (auto & j : outputs) { for (auto & j : outputs) {
XMLAttrs attrs2; XMLAttrs attrs2;
attrs2["name"] = j.first; attrs2["name"] = j.first;
attrs2["path"] = store.printStorePath(j.second); if (j.second)
attrs2["path"] = store.printStorePath(*j.second);
xml.writeEmptyElement("output", attrs2); xml.writeEmptyElement("output", attrs2);
} }
}
if (printMeta) { if (printMeta) {
StringSet metaNames = i.queryMetaNames(); StringSet metaNames = i.queryMetaNames();
for (auto & j : metaNames) { for (auto & j : metaNames) {
@ -1237,8 +1249,6 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
} }
} }
} }
} else
xml.writeEmptyElement("item", attrs);
} else } else
table.push_back(columns); table.push_back(columns);

View file

@ -56,7 +56,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
output paths, and optionally the derivation path, as well output paths, and optionally the derivation path, as well
as the meta attributes. */ as the meta attributes. */
std::optional<StorePath> drvPath = keepDerivations ? i.queryDrvPath() : std::nullopt; std::optional<StorePath> drvPath = keepDerivations ? i.queryDrvPath() : std::nullopt;
DrvInfo::Outputs outputs = i.queryOutputs(true); DrvInfo::Outputs outputs = i.queryOutputs(true, true);
StringSet metaNames = i.queryMetaNames(); StringSet metaNames = i.queryMetaNames();
auto attrs = state.buildBindings(7 + outputs.size()); auto attrs = state.buildBindings(7 + outputs.size());
@ -76,15 +76,15 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
for (const auto & [m, j] : enumerate(outputs)) { for (const auto & [m, j] : enumerate(outputs)) {
(vOutputs.listElems()[m] = state.allocValue())->mkString(j.first); (vOutputs.listElems()[m] = state.allocValue())->mkString(j.first);
auto outputAttrs = state.buildBindings(2); auto outputAttrs = state.buildBindings(2);
outputAttrs.alloc(state.sOutPath).mkString(state.store->printStorePath(j.second)); outputAttrs.alloc(state.sOutPath).mkString(state.store->printStorePath(*j.second));
attrs.alloc(j.first).mkAttrs(outputAttrs); attrs.alloc(j.first).mkAttrs(outputAttrs);
/* This is only necessary when installing store paths, e.g., /* This is only necessary when installing store paths, e.g.,
`nix-env -i /nix/store/abcd...-foo'. */ `nix-env -i /nix/store/abcd...-foo'. */
state.store->addTempRoot(j.second); state.store->addTempRoot(*j.second);
state.store->ensurePath(j.second); state.store->ensurePath(*j.second);
references.insert(j.second); references.insert(*j.second);
} }
// Copy the meta attributes. // Copy the meta attributes.
@ -105,8 +105,10 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
/* Also write a copy of the list of user environment elements to /* Also write a copy of the list of user environment elements to
the store; we need it for future modifications of the the store; we need it for future modifications of the
environment. */ environment. */
std::ostringstream str;
manifest.print(str, true);
auto manifestFile = state.store->addTextToStore("env-manifest.nix", auto manifestFile = state.store->addTextToStore("env-manifest.nix",
fmt("%s", manifest), references); str.str(), references);
/* Get the environment builder expression. */ /* Get the environment builder expression. */
Value envBuilder; Value envBuilder;

View file

@ -3,7 +3,9 @@
#include "dotgraph.hh" #include "dotgraph.hh"
#include "globals.hh" #include "globals.hh"
#include "build-result.hh" #include "build-result.hh"
#include "store-cast.hh"
#include "gc-store.hh" #include "gc-store.hh"
#include "log-store.hh"
#include "local-store.hh" #include "local-store.hh"
#include "monitor-fd.hh" #include "monitor-fd.hh"
#include "serve-protocol.hh" #include "serve-protocol.hh"
@ -435,7 +437,7 @@ static void opQuery(Strings opFlags, Strings opArgs)
store->computeFSClosure( store->computeFSClosure(
args, referrers, true, settings.gcKeepOutputs, settings.gcKeepDerivations); args, referrers, true, settings.gcKeepOutputs, settings.gcKeepDerivations);
auto & gcStore = requireGcStore(*store); auto & gcStore = require<GcStore>(*store);
Roots roots = gcStore.findRoots(false); Roots roots = gcStore.findRoots(false);
for (auto & [target, links] : roots) for (auto & [target, links] : roots)
if (referrers.find(target) != referrers.end()) if (referrers.find(target) != referrers.end())
@ -480,13 +482,15 @@ static void opReadLog(Strings opFlags, Strings opArgs)
{ {
if (!opFlags.empty()) throw UsageError("unknown flag"); if (!opFlags.empty()) throw UsageError("unknown flag");
auto & logStore = require<LogStore>(*store);
RunPager pager; RunPager pager;
for (auto & i : opArgs) { for (auto & i : opArgs) {
auto path = store->followLinksToStorePath(i); auto path = logStore.followLinksToStorePath(i);
auto log = store->getBuildLog(path); auto log = logStore.getBuildLog(path);
if (!log) if (!log)
throw Error("build log of derivation '%s' is not available", store->printStorePath(path)); throw Error("build log of derivation '%s' is not available", logStore.printStorePath(path));
std::cout << *log; std::cout << *log;
} }
} }
@ -596,7 +600,7 @@ static void opGC(Strings opFlags, Strings opArgs)
if (!opArgs.empty()) throw UsageError("no arguments expected"); if (!opArgs.empty()) throw UsageError("no arguments expected");
auto & gcStore = requireGcStore(*store); auto & gcStore = require<GcStore>(*store);
if (printRoots) { if (printRoots) {
Roots roots = gcStore.findRoots(false); Roots roots = gcStore.findRoots(false);
@ -635,7 +639,7 @@ static void opDelete(Strings opFlags, Strings opArgs)
for (auto & i : opArgs) for (auto & i : opArgs)
options.pathsToDelete.insert(store->followLinksToStorePath(i)); options.pathsToDelete.insert(store->followLinksToStorePath(i));
auto & gcStore = requireGcStore(*store); auto & gcStore = require<GcStore>(*store);
GCResults results; GCResults results;
PrintFreed freed(true, results); PrintFreed freed(true, results);

View file

@ -4,6 +4,7 @@
#include "eval-cache.hh" #include "eval-cache.hh"
#include "names.hh" #include "names.hh"
#include "command.hh" #include "command.hh"
#include "derivations.hh"
namespace nix { namespace nix {
@ -70,7 +71,7 @@ UnresolvedApp Installable::toApp(EvalState & state)
std::vector<StorePathWithOutputs> context2; std::vector<StorePathWithOutputs> context2;
for (auto & [path, name] : context) for (auto & [path, name] : context)
context2.push_back({state.store->parseStorePath(path), {name}}); context2.push_back({path, {name}});
return UnresolvedApp{App { return UnresolvedApp{App {
.context = std::move(context2), .context = std::move(context2),

View file

@ -52,15 +52,26 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile
void run(ref<Store> store) override void run(ref<Store> store) override
{ {
if (dryRun) {
std::vector<DerivedPath> pathsToBuild;
for (auto & i : installables) {
auto b = i->toDerivedPaths();
pathsToBuild.insert(pathsToBuild.end(), b.begin(), b.end());
}
printMissing(store, pathsToBuild, lvlError);
if (json)
logger->cout("%s", derivedPathsToJSON(pathsToBuild, store).dump());
return;
}
auto buildables = Installable::build( auto buildables = Installable::build(
getEvalStore(), store, getEvalStore(), store,
dryRun ? Realise::Derivation : Realise::Outputs, Realise::Outputs,
installables, buildMode); installables, buildMode);
if (json) logger->cout("%s", derivedPathsWithHintsToJSON(buildables, store).dump()); if (json) logger->cout("%s", derivedPathsWithHintsToJSON(buildables, store).dump());
if (dryRun) return;
if (outLink != "") if (outLink != "")
if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>()) if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>())
for (const auto & [_i, buildable] : enumerate(buildables)) { for (const auto & [_i, buildable] : enumerate(buildables)) {

View file

@ -196,21 +196,22 @@ static StorePath getDerivationEnvironment(ref<Store> store, ref<Store> evalStore
drv.inputSrcs.insert(std::move(getEnvShPath)); drv.inputSrcs.insert(std::move(getEnvShPath));
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) { if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
for (auto & output : drv.outputs) { for (auto & output : drv.outputs) {
output.second = { output.second = DerivationOutput::Deferred {},
.output = DerivationOutputDeferred{},
};
drv.env[output.first] = hashPlaceholder(output.first); drv.env[output.first] = hashPlaceholder(output.first);
} }
} else { } else {
for (auto & output : drv.outputs) { for (auto & output : drv.outputs) {
output.second = { .output = DerivationOutputInputAddressed { .path = StorePath::dummy } }; output.second = DerivationOutput::Deferred { };
drv.env[output.first] = ""; drv.env[output.first] = "";
} }
Hash h = std::get<0>(hashDerivationModulo(*evalStore, drv, true)); auto h0 = hashDerivationModulo(*evalStore, drv, true);
const Hash & h = h0.requireNoFixedNonDeferred();
for (auto & output : drv.outputs) { for (auto & output : drv.outputs) {
auto outPath = store->makeOutputPath(output.first, h, drv.name); auto outPath = store->makeOutputPath(output.first, h, drv.name);
output.second = { .output = DerivationOutputInputAddressed { .path = outPath } }; output.second = DerivationOutput::InputAddressed {
.path = outPath,
};
drv.env[output.first] = store->printStorePath(outPath); drv.env[output.first] = store->printStorePath(outPath);
} }
} }

View file

@ -24,8 +24,8 @@ this attribute to the location of the definition of the
`meta.description`, `version` or `name` derivation attributes. `meta.description`, `version` or `name` derivation attributes.
The editor to invoke is specified by the `EDITOR` environment The editor to invoke is specified by the `EDITOR` environment
variable. It defaults to `cat`. If the editor is `emacs`, `nano` or variable. It defaults to `cat`. If the editor is `emacs`, `nano`,
`vim`, it is passed the line number of the derivation using the `vim` or `kak`, it is passed the line number of the derivation using
argument `+<lineno>`. the argument `+<lineno>`.
)"" )""

View file

@ -2,6 +2,7 @@
#include "common-args.hh" #include "common-args.hh"
#include "shared.hh" #include "shared.hh"
#include "store-api.hh" #include "store-api.hh"
#include "log-store.hh"
#include "progress-bar.hh" #include "progress-bar.hh"
using namespace nix; using namespace nix;
@ -34,17 +35,24 @@ struct CmdLog : InstallableCommand
RunPager pager; RunPager pager;
for (auto & sub : subs) { for (auto & sub : subs) {
auto * logSubP = dynamic_cast<LogStore *>(&*sub);
if (!logSubP) {
printInfo("Skipped '%s' which does not support retrieving build logs", sub->getUri());
continue;
}
auto & logSub = *logSubP;
auto log = std::visit(overloaded { auto log = std::visit(overloaded {
[&](const DerivedPath::Opaque & bo) { [&](const DerivedPath::Opaque & bo) {
return sub->getBuildLog(bo.path); return logSub.getBuildLog(bo.path);
}, },
[&](const DerivedPath::Built & bfd) { [&](const DerivedPath::Built & bfd) {
return sub->getBuildLog(bfd.drvPath); return logSub.getBuildLog(bfd.drvPath);
}, },
}, b.raw()); }, b.raw());
if (!log) continue; if (!log) continue;
stopProgressBar(); stopProgressBar();
printInfo("got build log for '%s' from '%s'", installable->what(), sub->getUri()); printInfo("got build log for '%s' from '%s'", installable->what(), logSub.getUri());
std::cout << *log; std::cout << *log;
return; return;
} }

View file

@ -117,7 +117,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
{"hash-path", {"hash", "path"}}, {"hash-path", {"hash", "path"}},
{"ls-nar", {"nar", "ls"}}, {"ls-nar", {"nar", "ls"}},
{"ls-store", {"store", "ls"}}, {"ls-store", {"store", "ls"}},
{"make-content-addressable", {"store", "make-content-addressable"}}, {"make-content-addressable", {"store", "make-content-addressed"}},
{"optimise-store", {"store", "optimise"}}, {"optimise-store", {"store", "optimise"}},
{"ping-store", {"store", "ping"}}, {"ping-store", {"store", "ping"}},
{"sign-paths", {"store", "sign"}}, {"sign-paths", {"store", "sign"}},
@ -289,6 +289,7 @@ void mainWrapped(int argc, char * * argv)
} }
if (argc == 2 && std::string(argv[1]) == "__dump-builtins") { if (argc == 2 && std::string(argv[1]) == "__dump-builtins") {
settings.experimentalFeatures = {Xp::Flakes, Xp::FetchClosure};
evalSettings.pureEval = false; evalSettings.pureEval = false;
EvalState state({}, openStore("dummy://")); EvalState state({}, openStore("dummy://"));
auto res = nlohmann::json::object(); auto res = nlohmann::json::object();

View file

@ -1,102 +0,0 @@
#include "command.hh"
#include "store-api.hh"
#include "references.hh"
#include "common-args.hh"
#include "json.hh"
using namespace nix;
struct CmdMakeContentAddressable : StorePathsCommand, MixJSON
{
CmdMakeContentAddressable()
{
realiseMode = Realise::Outputs;
}
std::string description() override
{
return "rewrite a path or closure to content-addressed form";
}
std::string doc() override
{
return
#include "make-content-addressable.md"
;
}
void run(ref<Store> store, StorePaths && storePaths) override
{
auto paths = store->topoSortPaths(StorePathSet(storePaths.begin(), storePaths.end()));
std::reverse(paths.begin(), paths.end());
std::map<StorePath, StorePath> remappings;
auto jsonRoot = json ? std::make_unique<JSONObject>(std::cout) : nullptr;
auto jsonRewrites = json ? std::make_unique<JSONObject>(jsonRoot->object("rewrites")) : nullptr;
for (auto & path : paths) {
auto pathS = store->printStorePath(path);
auto oldInfo = store->queryPathInfo(path);
std::string oldHashPart(path.hashPart());
StringSink sink;
store->narFromPath(path, sink);
StringMap rewrites;
PathReferences<StorePath> refs;
refs.hasSelfReference = oldInfo->hasSelfReference;
for (auto & ref : oldInfo->references) {
auto i = remappings.find(ref);
auto replacement = i != remappings.end() ? i->second : ref;
// FIXME: warn about unremapped paths?
if (replacement != ref)
rewrites.insert_or_assign(store->printStorePath(ref), store->printStorePath(replacement));
refs.references.insert(std::move(replacement));
}
sink.s = rewriteStrings(sink.s, rewrites);
HashModuloSink hashModuloSink(htSHA256, oldHashPart);
hashModuloSink(sink.s);
auto narHash = hashModuloSink.finish().first;
ValidPathInfo info {
*store,
StorePathDescriptor {
.name = std::string { path.name() },
.info = FixedOutputInfo {
{
.method = FileIngestionMethod::Recursive,
.hash = narHash,
},
std::move(refs),
},
},
narHash,
};
info.narSize = sink.s.size();
if (!json)
notice("rewrote '%s' to '%s'", pathS, store->printStorePath(info.path));
auto source = sinkToSource([&](Sink & nextSink) {
RewritingSink rsink2(oldHashPart, std::string(info.path.hashPart()), nextSink);
rsink2(sink.s);
rsink2.flush();
});
store->addToStore(info, *source);
if (json)
jsonRewrites->attr(store->printStorePath(path), store->printStorePath(info.path));
remappings.insert_or_assign(std::move(path), std::move(info.path));
}
}
};
static auto rCmdMakeContentAddressable = registerCommand2<CmdMakeContentAddressable>({"store", "make-content-addressable"});

View file

@ -0,0 +1,55 @@
#include "command.hh"
#include "store-api.hh"
#include "make-content-addressed.hh"
#include "common-args.hh"
#include "json.hh"
using namespace nix;
struct CmdMakeContentAddressed : virtual CopyCommand, virtual StorePathsCommand, MixJSON
{
CmdMakeContentAddressed()
{
realiseMode = Realise::Outputs;
}
std::string description() override
{
return "rewrite a path or closure to content-addressed form";
}
std::string doc() override
{
return
#include "make-content-addressed.md"
;
}
void run(ref<Store> srcStore, StorePaths && storePaths) override
{
auto dstStore = dstUri.empty() ? openStore() : openStore(dstUri);
auto remappings = makeContentAddressed(*srcStore, *dstStore,
StorePathSet(storePaths.begin(), storePaths.end()));
if (json) {
JSONObject jsonRoot(std::cout);
JSONObject jsonRewrites(jsonRoot.object("rewrites"));
for (auto & path : storePaths) {
auto i = remappings.find(path);
assert(i != remappings.end());
jsonRewrites.attr(srcStore->printStorePath(path), srcStore->printStorePath(i->second));
}
} else {
for (auto & path : storePaths) {
auto i = remappings.find(path);
assert(i != remappings.end());
notice("rewrote '%s' to '%s'",
srcStore->printStorePath(path),
srcStore->printStorePath(i->second));
}
}
}
};
static auto rCmdMakeContentAddressed = registerCommand2<CmdMakeContentAddressed>({"store", "make-content-addressed"});

View file

@ -5,7 +5,7 @@ R""(
* Create a content-addressed representation of the closure of GNU Hello: * Create a content-addressed representation of the closure of GNU Hello:
```console ```console
# nix store make-content-addressable -r nixpkgs#hello # nix store make-content-addressed nixpkgs#hello
rewrote '/nix/store/v5sv61sszx301i0x6xysaqzla09nksnd-hello-2.10' to '/nix/store/5skmmcb9svys5lj3kbsrjg7vf2irid63-hello-2.10' rewrote '/nix/store/v5sv61sszx301i0x6xysaqzla09nksnd-hello-2.10' to '/nix/store/5skmmcb9svys5lj3kbsrjg7vf2irid63-hello-2.10'
``` ```
@ -29,7 +29,7 @@ R""(
system closure: system closure:
```console ```console
# nix store make-content-addressable -r /run/current-system # nix store make-content-addressed /run/current-system
``` ```
# Description # Description

View file

@ -107,8 +107,9 @@ struct ProfileManifest
element.storePaths.insert(state.store->parseStorePath((std::string) p)); element.storePaths.insert(state.store->parseStorePath((std::string) p));
element.active = e["active"]; element.active = e["active"];
if (e.value("uri", "") != "") { if (e.value("uri", "") != "") {
auto originalUrl = e.value("originalUrl", e["originalUri"]);
element.source = ProfileElementSource{ element.source = ProfileElementSource{
parseFlakeRef(e["originalUri"]), parseFlakeRef(originalUrl),
parseFlakeRef(e["uri"]), parseFlakeRef(e["uri"]),
e["attrPath"] e["attrPath"]
}; };
@ -143,7 +144,7 @@ struct ProfileManifest
obj["storePaths"] = paths; obj["storePaths"] = paths;
obj["active"] = element.active; obj["active"] = element.active;
if (element.source) { if (element.source) {
obj["originalUri"] = element.source->originalRef.to_string(); obj["originalUrl"] = element.source->originalRef.to_string();
obj["uri"] = element.source->resolvedRef.to_string(); obj["uri"] = element.source->resolvedRef.to_string();
obj["attrPath"] = element.source->attrPath; obj["attrPath"] = element.source->attrPath;
} }

View file

@ -70,7 +70,7 @@ are installed in this version of the profile. It looks like this:
{ {
"active": true, "active": true,
"attrPath": "legacyPackages.x86_64-linux.zoom-us", "attrPath": "legacyPackages.x86_64-linux.zoom-us",
"originalUri": "flake:nixpkgs", "originalUrl": "flake:nixpkgs",
"storePaths": [ "storePaths": [
"/nix/store/wbhg2ga8f3h87s9h5k0slxk0m81m4cxl-zoom-us-5.3.469451.0927" "/nix/store/wbhg2ga8f3h87s9h5k0slxk0m81m4cxl-zoom-us-5.3.469451.0927"
], ],
@ -84,11 +84,11 @@ are installed in this version of the profile. It looks like this:
Each object in the array `elements` denotes an installed package and Each object in the array `elements` denotes an installed package and
has the following fields: has the following fields:
* `originalUri`: The [flake reference](./nix3-flake.md) specified by * `originalUrl`: The [flake reference](./nix3-flake.md) specified by
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 `originalUri` * `uri`: The immutable flake reference to which `originalUrl`
resolved. resolved.
* `attrPath`: The flake output attribute that provided this * `attrPath`: The flake output attribute that provided this

View file

@ -25,6 +25,7 @@ extern "C" {
#include "eval-inline.hh" #include "eval-inline.hh"
#include "attr-path.hh" #include "attr-path.hh"
#include "store-api.hh" #include "store-api.hh"
#include "log-store.hh"
#include "common-eval-args.hh" #include "common-eval-args.hh"
#include "get-drvs.hh" #include "get-drvs.hh"
#include "derivations.hh" #include "derivations.hh"
@ -395,6 +396,7 @@ StorePath NixRepl::getDerivationPath(Value & v) {
bool NixRepl::processLine(std::string line) bool NixRepl::processLine(std::string line)
{ {
line = trim(line);
if (line == "") return true; if (line == "") return true;
_isInterrupted = false; _isInterrupted = false;
@ -526,9 +528,16 @@ bool NixRepl::processLine(std::string line)
bool foundLog = false; bool foundLog = false;
RunPager pager; RunPager pager;
for (auto & sub : subs) { for (auto & sub : subs) {
auto log = sub->getBuildLog(drvPath); auto * logSubP = dynamic_cast<LogStore *>(&*sub);
if (!logSubP) {
printInfo("Skipped '%s' which does not support retrieving build logs", sub->getUri());
continue;
}
auto & logSub = *logSubP;
auto log = logSub.getBuildLog(drvPath);
if (log) { if (log) {
printInfo("got build log for '%s' from '%s'", drvPathRaw, sub->getUri()); printInfo("got build log for '%s' from '%s'", drvPathRaw, logSub.getUri());
logger->writeToStdout(*log); logger->writeToStdout(*log);
foundLog = true; foundLog = true;
break; break;

View file

@ -65,19 +65,19 @@ struct CmdShowDerivation : InstallablesCommand
auto & outputName = _outputName; // work around clang bug auto & outputName = _outputName; // work around clang bug
auto outputObj { outputsObj.object(outputName) }; auto outputObj { outputsObj.object(outputName) };
std::visit(overloaded { std::visit(overloaded {
[&](const DerivationOutputInputAddressed & doi) { [&](const DerivationOutput::InputAddressed & doi) {
outputObj.attr("path", store->printStorePath(doi.path)); outputObj.attr("path", store->printStorePath(doi.path));
}, },
[&](const DerivationOutputCAFixed & dof) { [&](const DerivationOutput::CAFixed & dof) {
outputObj.attr("path", store->printStorePath(dof.path(*store, drv.name, outputName))); outputObj.attr("path", store->printStorePath(dof.path(*store, drv.name, outputName)));
outputObj.attr("hashAlgo", dof.hash.printMethodAlgo()); outputObj.attr("hashAlgo", dof.hash.printMethodAlgo());
outputObj.attr("hash", dof.hash.hash.to_string(Base16, false)); outputObj.attr("hash", dof.hash.hash.to_string(Base16, false));
}, },
[&](const DerivationOutputCAFloating & dof) { [&](const DerivationOutput::CAFloating & dof) {
outputObj.attr("hashAlgo", makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType)); outputObj.attr("hashAlgo", makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType));
}, },
[&](const DerivationOutputDeferred &) {}, [&](const DerivationOutput::Deferred &) {},
}, output.output); }, output.raw());
} }
} }

View file

@ -1,6 +1,8 @@
#include "command.hh" #include "command.hh"
#include "shared.hh" #include "shared.hh"
#include "store-api.hh" #include "store-api.hh"
#include "store-cast.hh"
#include "log-store.hh"
#include "sync.hh" #include "sync.hh"
#include "thread-pool.hh" #include "thread-pool.hh"
@ -26,7 +28,10 @@ struct CmdCopyLog : virtual CopyCommand, virtual InstallablesCommand
void run(ref<Store> srcStore) override void run(ref<Store> srcStore) override
{ {
auto & srcLogStore = require<LogStore>(*srcStore);
auto dstStore = getDstStore(); auto dstStore = getDstStore();
auto & dstLogStore = require<LogStore>(*dstStore);
StorePathSet drvPaths; StorePathSet drvPaths;
@ -35,8 +40,8 @@ struct CmdCopyLog : virtual CopyCommand, virtual InstallablesCommand
drvPaths.insert(drvPath); drvPaths.insert(drvPath);
for (auto & drvPath : drvPaths) { for (auto & drvPath : drvPaths) {
if (auto log = srcStore->getBuildLog(drvPath)) if (auto log = srcLogStore.getBuildLog(drvPath))
dstStore->addBuildLog(drvPath, *log); dstLogStore.addBuildLog(drvPath, *log);
else else
throw Error("build log for '%s' is not available", srcStore->printStorePath(drvPath)); throw Error("build log for '%s' is not available", srcStore->printStorePath(drvPath));
} }

View file

@ -2,6 +2,7 @@
#include "common-args.hh" #include "common-args.hh"
#include "shared.hh" #include "shared.hh"
#include "store-api.hh" #include "store-api.hh"
#include "store-cast.hh"
#include "gc-store.hh" #include "gc-store.hh"
using namespace nix; using namespace nix;
@ -33,7 +34,7 @@ struct CmdStoreDelete : StorePathsCommand
void run(ref<Store> store, std::vector<StorePath> && storePaths) override void run(ref<Store> store, std::vector<StorePath> && storePaths) override
{ {
auto & gcStore = requireGcStore(*store); auto & gcStore = require<GcStore>(*store);
for (auto & path : storePaths) for (auto & path : storePaths)
options.pathsToDelete.insert(path); options.pathsToDelete.insert(path);

View file

@ -2,6 +2,7 @@
#include "common-args.hh" #include "common-args.hh"
#include "shared.hh" #include "shared.hh"
#include "store-api.hh" #include "store-api.hh"
#include "store-cast.hh"
#include "gc-store.hh" #include "gc-store.hh"
using namespace nix; using namespace nix;
@ -34,7 +35,7 @@ struct CmdStoreGC : StoreCommand, MixDryRun
void run(ref<Store> store) override void run(ref<Store> store) override
{ {
auto & gcStore = requireGcStore(*store); auto & gcStore = require<GcStore>(*store);
options.action = dryRun ? GCOptions::gcReturnDead : GCOptions::gcDeleteDead; options.action = dryRun ? GCOptions::gcReturnDead : GCOptions::gcDeleteDead;
GCResults results; GCResults results;

View file

@ -1,6 +1,6 @@
source common.sh source common.sh
needLocalStore "“--no-require-sigs” cant be used with the daemon" needLocalStore "'--no-require-sigs' cant be used with the daemon"
# We can produce drvs directly into the binary cache # We can produce drvs directly into the binary cache
clearStore clearStore

View file

@ -50,3 +50,22 @@ nix build -f dependencies.nix -o $RESULT --dry-run
nix build -f dependencies.nix -o $RESULT nix build -f dependencies.nix -o $RESULT
[[ -h $RESULT ]] [[ -h $RESULT ]]
###################################################
# Check the JSON output
clearStore
clearCache
RES=$(nix build -f dependencies.nix --dry-run --json)
if [[ -z "$NIX_TESTS_CA_BY_DEFAULT" ]]; then
echo "$RES" | jq '.[0] | [
(.drvPath | test("'$NIX_STORE_DIR'.*\\.drv")),
(.outputs.out | test("'$NIX_STORE_DIR'"))
] | all'
else
echo "$RES" | jq '.[0] | [
(.drvPath | test("'$NIX_STORE_DIR'.*\\.drv")),
.outputs.out == null
] | all'
fi

View file

@ -1,15 +1,27 @@
source common.sh source common.sh
expectedJSONRegex='\[\{"drvPath":".*multiple-outputs-a.drv","outputs":\{"first":".*multiple-outputs-a-first","second":".*multiple-outputs-a-second"}},\{"drvPath":".*multiple-outputs-b.drv","outputs":\{"out":".*multiple-outputs-b"}}]' clearStore
# Make sure that 'nix build' only returns the outputs we asked for.
nix build -f multiple-outputs.nix --json a --no-link | jq --exit-status '
(.[0] |
(.drvPath | match(".*multiple-outputs-a.drv")) and
(.outputs | keys | length == 1) and
(.outputs.first | match(".*multiple-outputs-a-first")))
'
nix build -f multiple-outputs.nix --json a.all b.all --no-link | jq --exit-status ' nix build -f multiple-outputs.nix --json a.all b.all --no-link | jq --exit-status '
(.[0] | (.[0] |
(.drvPath | match(".*multiple-outputs-a.drv")) and (.drvPath | match(".*multiple-outputs-a.drv")) and
(.outputs | keys | length == 2) and
(.outputs.first | match(".*multiple-outputs-a-first")) and (.outputs.first | match(".*multiple-outputs-a-first")) and
(.outputs.second | match(".*multiple-outputs-a-second"))) (.outputs.second | match(".*multiple-outputs-a-second")))
and (.[1] | and (.[1] |
(.drvPath | match(".*multiple-outputs-b.drv")) and (.drvPath | match(".*multiple-outputs-b.drv")) and
(.outputs | keys | length == 1) and
(.outputs.out | match(".*multiple-outputs-b"))) (.outputs.out | match(".*multiple-outputs-b")))
' '
testNormalization () { testNormalization () {
clearStore clearStore
outPath=$(nix-build ./simple.nix --no-out-link) outPath=$(nix-build ./simple.nix --no-out-link)

6
tests/ca/build-dry.sh Normal file
View file

@ -0,0 +1,6 @@
source common.sh
export NIX_TESTS_CA_BY_DEFAULT=1
cd .. && source build-dry.sh

5
tests/eval.nix Normal file
View file

@ -0,0 +1,5 @@
{
int = 123;
str = "foo";
attr.foo = "bar";
}

29
tests/eval.sh Normal file
View file

@ -0,0 +1,29 @@
source common.sh
clearStore
testStdinHeredoc=$(nix eval -f - <<EOF
{
bar = 3 + 1;
foo = 2 + 2;
}
EOF
)
[[ $testStdinHeredoc == '{ bar = 4; foo = 4; }' ]]
nix eval --expr 'assert 1 + 2 == 3; true'
[[ $(nix eval int -f "./eval.nix") == 123 ]]
[[ $(nix eval str -f "./eval.nix") == '"foo"' ]]
[[ $(nix eval str --raw -f "./eval.nix") == 'foo' ]]
[[ $(nix eval attr -f "./eval.nix") == '{ foo = "bar"; }' ]]
[[ $(nix eval attr --json -f "./eval.nix") == '{"foo":"bar"}' ]]
[[ $(nix eval int -f - < "./eval.nix") == 123 ]]
nix-instantiate --eval -E 'assert 1 + 2 == 3; true'
[[ $(nix-instantiate -A int --eval "./eval.nix") == 123 ]]
[[ $(nix-instantiate -A str --eval "./eval.nix") == '"foo"' ]]
[[ $(nix-instantiate -A attr --eval "./eval.nix") == '{ foo = "bar"; }' ]]
[[ $(nix-instantiate -A attr --eval --json "./eval.nix") == '{"foo":"bar"}' ]]
[[ $(nix-instantiate -A int --eval - < "./eval.nix") == 123 ]]

58
tests/fetchClosure.sh Normal file
View file

@ -0,0 +1,58 @@
source common.sh
enableFeatures "fetch-closure"
needLocalStore "'--no-require-sigs' cant be used with the daemon"
clearStore
clearCacheCache
# Initialize binary cache.
nonCaPath=$(nix build --json --file ./dependencies.nix | jq -r .[].outputs.out)
caPath=$(nix store make-content-addressed --json $nonCaPath | jq -r '.rewrites | map(.) | .[]')
nix copy --to file://$cacheDir $nonCaPath
# Test basic fetchClosure rewriting from non-CA to CA.
clearStore
[ ! -e $nonCaPath ]
[ ! -e $caPath ]
[[ $(nix eval -v --raw --expr "
builtins.fetchClosure {
fromStore = \"file://$cacheDir\";
fromPath = $nonCaPath;
toPath = $caPath;
}
") = $caPath ]]
[ ! -e $nonCaPath ]
[ -e $caPath ]
# In impure mode, we can use non-CA paths.
[[ $(nix eval --raw --no-require-sigs --impure --expr "
builtins.fetchClosure {
fromStore = \"file://$cacheDir\";
fromPath = $nonCaPath;
}
") = $nonCaPath ]]
[ -e $nonCaPath ]
# 'toPath' set to empty string should fail but print the expected path.
nix eval -v --json --expr "
builtins.fetchClosure {
fromStore = \"file://$cacheDir\";
fromPath = $nonCaPath;
toPath = \"\";
}
" 2>&1 | grep "error: rewriting.*$nonCaPath.*yielded.*$caPath"
# If fromPath is CA, then toPath isn't needed.
nix copy --to file://$cacheDir $caPath
[[ $(nix eval -v --raw --expr "
builtins.fetchClosure {
fromStore = \"file://$cacheDir\";
fromPath = $caPath;
}
") = $caPath ]]

View file

@ -11,7 +11,7 @@ repo=$TEST_ROOT/git
export _NIX_FORCE_HTTP=1 export _NIX_FORCE_HTTP=1
rm -rf $repo ${repo}-tmp $TEST_HOME/.cache/nix $TEST_ROOT/worktree $TEST_ROOT/shallow rm -rf $repo ${repo}-tmp $TEST_HOME/.cache/nix $TEST_ROOT/worktree $TEST_ROOT/shallow $TEST_ROOT/minimal
git init $repo git init $repo
git -C $repo config user.email "foobar@example.com" git -C $repo config user.email "foobar@example.com"
@ -147,8 +147,13 @@ path3=$(nix eval --impure --raw --expr "(builtins.fetchGit $repo).outPath")
# (check dirty-tree handling was used) # (check dirty-tree handling was used)
[[ $(nix eval --impure --raw --expr "(builtins.fetchGit $repo).rev") = 0000000000000000000000000000000000000000 ]] [[ $(nix eval --impure --raw --expr "(builtins.fetchGit $repo).rev") = 0000000000000000000000000000000000000000 ]]
[[ $(nix eval --impure --raw --expr "(builtins.fetchGit $repo).shortRev") = 0000000 ]] [[ $(nix eval --impure --raw --expr "(builtins.fetchGit $repo).shortRev") = 0000000 ]]
# Making a dirty tree clean again and fetching it should
# record correct revision information. See: #4140
echo world > $repo/hello
[[ $(nix eval --impure --raw --expr "(builtins.fetchGit $repo).rev") = $rev2 ]]
# Committing shouldn't change store path, or switch to using 'master' # Committing shouldn't change store path, or switch to using 'master'
echo dev > $repo/hello
git -C $repo commit -m 'Bla5' -a git -C $repo commit -m 'Bla5' -a
path4=$(nix eval --impure --raw --expr "(builtins.fetchGit $repo).outPath") path4=$(nix eval --impure --raw --expr "(builtins.fetchGit $repo).outPath")
[[ $(cat $path4/hello) = dev ]] [[ $(cat $path4/hello) = dev ]]
@ -170,6 +175,14 @@ NIX=$(command -v nix)
path5=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = $repo; ref = \"dev\"; }).outPath") path5=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = $repo; ref = \"dev\"; }).outPath")
[[ $path3 = $path5 ]] [[ $path3 = $path5 ]]
# Fetching from a repo with only a specific revision and no branches should
# not fall back to copying files and record correct revision information. See: #5302
mkdir $TEST_ROOT/minimal
git -C $TEST_ROOT/minimal init
git -C $TEST_ROOT/minimal fetch $repo $rev2
git -C $TEST_ROOT/minimal checkout $rev2
[[ $(nix eval --impure --raw --expr "(builtins.fetchGit { url = $TEST_ROOT/minimal; }).rev") = $rev2 ]]
# Fetching a shallow repo shouldn't work by default, because we can't # Fetching a shallow repo shouldn't work by default, because we can't
# return a revCount. # return a revCount.
git clone --depth 1 file://$repo $TEST_ROOT/shallow git clone --depth 1 file://$repo $TEST_ROOT/shallow
@ -193,3 +206,11 @@ rev4_nix=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$
# The name argument should be handled # The name argument should be handled
path9=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repo\"; ref = \"HEAD\"; name = \"foo\"; }).outPath") path9=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repo\"; ref = \"HEAD\"; name = \"foo\"; }).outPath")
[[ $path9 =~ -foo$ ]] [[ $path9 =~ -foo$ ]]
# should fail if there is no repo
rm -rf $repo/.git
(! nix eval --impure --raw --expr "(builtins.fetchGit \"file://$repo\").outPath")
# should succeed for a repo without commits
git init $repo
path10=$(nix eval --impure --raw --expr "(builtins.fetchGit \"file://$repo\").outPath")

6
tests/fetchPath.sh Normal file
View file

@ -0,0 +1,6 @@
source common.sh
touch foo -t 202211111111
# We only check whether 2022-11-1* **:**:** is the last modified date since
# `lastModified` is transformed into UTC in `builtins.fetchTarball`.
[[ "$(nix eval --impure --raw --expr "(builtins.fetchTree \"path://$PWD/foo\").lastModifiedDate")" =~ 2022111.* ]]

View file

@ -21,6 +21,7 @@ nix_tests = \
tarball.sh \ tarball.sh \
fetchGit.sh \ fetchGit.sh \
fetchurl.sh \ fetchurl.sh \
fetchPath.sh \
simple.sh \ simple.sh \
referrers.sh \ referrers.sh \
optimise-store.sh \ optimise-store.sh \
@ -52,6 +53,7 @@ nix_tests = \
build-remote-content-addressed-floating.sh \ build-remote-content-addressed-floating.sh \
nar-access.sh \ nar-access.sh \
pure-eval.sh \ pure-eval.sh \
eval.sh \
ca/post-hook.sh \ ca/post-hook.sh \
repl.sh \ repl.sh \
ca/repl.sh \ ca/repl.sh \
@ -94,7 +96,8 @@ nix_tests = \
describe-stores.sh \ describe-stores.sh \
nix-profile.sh \ nix-profile.sh \
suggestions.sh \ suggestions.sh \
store-ping.sh store-ping.sh \
fetchClosure.sh
ifeq ($(HAVE_LIBCPUID), 1) ifeq ($(HAVE_LIBCPUID), 1)
nix_tests += compute-levels.sh nix_tests += compute-levels.sh

View file

@ -13,3 +13,14 @@ rm -rf $NIX_LOG_DIR
(! nix-store -l $path) (! nix-store -l $path)
nix-build dependencies.nix --no-out-link --compress-build-log nix-build dependencies.nix --no-out-link --compress-build-log
[ "$(nix-store -l $path)" = FOO ] [ "$(nix-store -l $path)" = FOO ]
# test whether empty logs work fine with `nix log`.
builder="$(mktemp)"
echo -e "#!/bin/sh\nmkdir \$out" > "$builder"
outp="$(nix-build -E \
'with import ./config.nix; mkDerivation { name = "fnord"; builder = '"$builder"'; }' \
--out-link "$(mktemp -d)/result")"
test -d "$outp"
nix log "$outp"

Some files were not shown because too many files have changed in this diff Show more