Merge remote-tracking branch 'nixos/master'

This commit is contained in:
Max Headroom 2022-08-25 20:23:56 +02:00
commit 3076571fc9
51 changed files with 583 additions and 350 deletions

View file

@ -1 +1 @@
2.10.0 2.12.0

View file

@ -296,15 +296,6 @@ AC_CHECK_FUNCS([setresuid setreuid lchown])
AC_CHECK_FUNCS([strsignal posix_fallocate sysconf]) AC_CHECK_FUNCS([strsignal posix_fallocate sysconf])
# This is needed if bzip2 is a static library, and the Nix libraries
# are dynamic.
case "${host_os}" in
darwin*)
LDFLAGS="-all_load $LDFLAGS"
;;
esac
AC_ARG_WITH(sandbox-shell, AS_HELP_STRING([--with-sandbox-shell=PATH],[path of a statically-linked shell to use as /bin/sh in sandboxes]), AC_ARG_WITH(sandbox-shell, AS_HELP_STRING([--with-sandbox-shell=PATH],[path of a statically-linked shell to use as /bin/sh in sandboxes]),
sandbox_shell=$withval) sandbox_shell=$withval)
AC_SUBST(sandbox_shell) AC_SUBST(sandbox_shell)

View file

@ -73,6 +73,7 @@
- [CLI guideline](contributing/cli-guideline.md) - [CLI guideline](contributing/cli-guideline.md)
- [Release Notes](release-notes/release-notes.md) - [Release Notes](release-notes/release-notes.md)
- [Release X.Y (202?-??-??)](release-notes/rl-next.md) - [Release X.Y (202?-??-??)](release-notes/rl-next.md)
- [Release 2.11 (2022-08-25)](release-notes/rl-2.11.md)
- [Release 2.10 (2022-07-11)](release-notes/rl-2.10.md) - [Release 2.10 (2022-07-11)](release-notes/rl-2.10.md)
- [Release 2.9 (2022-05-30)](release-notes/rl-2.9.md) - [Release 2.9 (2022-05-30)](release-notes/rl-2.9.md)
- [Release 2.8 (2022-04-19)](release-notes/rl-2.8.md) - [Release 2.8 (2022-04-19)](release-notes/rl-2.8.md)

View file

@ -13,7 +13,7 @@ for your platform:
- multi-user on macOS - multi-user on macOS
> **Notes on read-only filesystem root in macOS 10.15 Catalina +** > **Notes on read-only filesystem root in macOS 10.15 Catalina +**
> >
> - It took some time to support this cleanly. You may see posts, > - It took some time to support this cleanly. You may see posts,
> examples, and tutorials using obsolete workarounds. > examples, and tutorials using obsolete workarounds.
> - Supporting it cleanly made macOS installs too complex to qualify > - Supporting it cleanly made macOS installs too complex to qualify
@ -31,8 +31,8 @@ $ sh <(curl -L https://nixos.org/nix/install) --no-daemon
``` ```
This will perform a single-user installation of Nix, meaning that `/nix` This will perform a single-user installation of Nix, meaning that `/nix`
is owned by the invoking user. You should run this under your usual user is owned by the invoking user. You can run this under your usual user
account, *not* as root. The script will invoke `sudo` to create `/nix` account or root. The script will invoke `sudo` to create `/nix`
if it doesnt already exist. If you dont have `sudo`, you should if it doesnt already exist. If you dont have `sudo`, you should
manually create `/nix` first as root, e.g.: manually create `/nix` first as root, e.g.:
@ -71,11 +71,11 @@ $ sh <(curl -L https://nixos.org/nix/install) --daemon
The multi-user installation of Nix will create build users between the The multi-user installation of Nix will create build users between the
user IDs 30001 and 30032, and a group with the group ID 30000. You user IDs 30001 and 30032, and a group with the group ID 30000. You
should run this under your usual user account, *not* as root. The script can run this under your usual user account or root. The script
will invoke `sudo` as needed. will invoke `sudo` as needed.
> **Note** > **Note**
> >
> If you need Nix to use a different group ID or user ID set, you will > If you need Nix to use a different group ID or user ID set, you will
> have to download the tarball manually and [edit the install > have to download the tarball manually and [edit the install
> script](#installing-from-a-binary-tarball). > script](#installing-from-a-binary-tarball).
@ -168,7 +168,7 @@ and `/etc/zshrc` which you may remove.
removed next. removed next.
7. Remove the Nix Store volume: 7. Remove the Nix Store volume:
```console ```console
sudo diskutil apfs deleteVolume /nix sudo diskutil apfs deleteVolume /nix
``` ```
@ -189,7 +189,7 @@ and `/etc/zshrc` which you may remove.
identifier. identifier.
> **Note** > **Note**
> >
> After you complete the steps here, you will still have an empty `/nix` > 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 > directory. This is an expected sign of a successful uninstall. The empty
> `/nix` directory will disappear the next time you reboot. > `/nix` directory will disappear the next time you reboot.

View file

@ -1,13 +1,33 @@
# Nix Language # Nix Language
The Nix language is a pure, lazy, functional language. Purity The Nix language is
means that operations in the language don't have side-effects (for
instance, there is no variable assignment). Laziness means that
arguments to functions are evaluated only when they are needed.
Functional means that functions are “normal” values that can be passed
around and manipulated in interesting ways. The language is not a
full-featured, general purpose language. Its main job is to describe
packages, compositions of packages, and the variability within packages.
This section presents the various features of the language. - *domain-specific*
It only exists for the Nix package manager:
to describe packages and configurations as well as their variants and compositions.
It is not intended for general purpose use.
- *declarative*
There is no notion of executing sequential steps.
Dependencies between operations are established only through data.
- *pure*
Values cannot change during computation.
Functions always produce the same output if their input does not change.
- *functional*
Functions are like any other value.
Functions can be assigned to names, taken as arguments, or returned by functions.
- *lazy*
Expressions are only evaluated when their value is needed.
- *dynamically typed*
Type errors are only detected when expressions are evaluated.

View file

@ -0,0 +1,5 @@
# Release 2.11 (2022-08-24)
* `nix copy` now copies the store paths in parallel as much as possible (again).
This doesn't apply for the `daemon` and `ssh-ng` stores which copy everything
in one batch to avoid latencies issues.

View file

@ -2,6 +2,7 @@
, lib ? pkgs.lib , lib ? pkgs.lib
, name ? "nix" , name ? "nix"
, tag ? "latest" , tag ? "latest"
, bundleNixpkgs ? true
, channelName ? "nixpkgs" , channelName ? "nixpkgs"
, channelURL ? "https://nixos.org/channels/nixpkgs-unstable" , channelURL ? "https://nixos.org/channels/nixpkgs-unstable"
, extraPkgs ? [] , extraPkgs ? []
@ -139,10 +140,12 @@ let
baseSystem = baseSystem =
let let
nixpkgs = pkgs.path; nixpkgs = pkgs.path;
channel = pkgs.runCommand "channel-nixos" { } '' channel = pkgs.runCommand "channel-nixos" { inherit bundleNixpkgs; } ''
mkdir $out mkdir $out
ln -s ${nixpkgs} $out/nixpkgs if [ "$bundleNixpkgs" ]; then
echo "[]" > $out/manifest.nix ln -s ${nixpkgs} $out/nixpkgs
echo "[]" > $out/manifest.nix
fi
''; '';
rootEnv = pkgs.buildPackages.buildEnv { rootEnv = pkgs.buildPackages.buildEnv {
name = "root-profile-env"; name = "root-profile-env";

View file

@ -167,7 +167,7 @@ poly_user_shell_get() {
} }
poly_user_shell_set() { poly_user_shell_set() {
_sudo "in order to give $1 a safe home directory" \ _sudo "in order to give $1 a safe shell" \
/usr/bin/dscl . -create "/Users/$1" "UserShell" "$2" /usr/bin/dscl . -create "/Users/$1" "UserShell" "$2"
} }

View file

@ -59,6 +59,30 @@ headless() {
fi fi
} }
is_root() {
if [ "$EUID" -eq 0 ]; then
return 0
else
return 1
fi
}
is_os_linux() {
if [ "$(uname -s)" = "Linux" ]; then
return 0
else
return 1
fi
}
is_os_darwin() {
if [ "$(uname -s)" = "Darwin" ]; then
return 0
else
return 1
fi
}
contact_us() { contact_us() {
echo "You can open an issue at https://github.com/nixos/nix/issues" echo "You can open an issue at https://github.com/nixos/nix/issues"
echo "" echo ""
@ -313,14 +337,23 @@ __sudo() {
_sudo() { _sudo() {
local expl="$1" local expl="$1"
shift shift
if ! headless; then if ! headless || is_root; then
__sudo "$expl" "$*" >&2 __sudo "$expl" "$*" >&2
fi fi
sudo "$@"
if is_root; then
env "$@"
else
sudo "$@"
fi
} }
# Ensure that $TMPDIR exists if defined.
if [[ -n "${TMPDIR:-}" ]] && [[ ! -d "${TMPDIR:-}" ]]; then
mkdir -m 0700 -p "${TMPDIR:-}"
fi
readonly SCRATCH=$(mktemp -d "${TMPDIR:-/tmp/}tmp.XXXXXXXXXX") readonly SCRATCH=$(mktemp -d)
finish_cleanup() { finish_cleanup() {
rm -rf "$SCRATCH" rm -rf "$SCRATCH"
} }
@ -423,7 +456,7 @@ EOF
fi fi
done done
if [ "$(uname -s)" = "Linux" ] && [ ! -e /run/systemd/system ]; then if is_os_linux && [ ! -e /run/systemd/system ]; then
warning <<EOF warning <<EOF
We did not detect systemd on your system. With a multi-user install We did not detect systemd on your system. With a multi-user install
without systemd you will have to manually configure your init system to without systemd you will have to manually configure your init system to
@ -640,7 +673,7 @@ place_channel_configuration() {
check_selinux() { check_selinux() {
if command -v getenforce > /dev/null 2>&1; then if command -v getenforce > /dev/null 2>&1; then
if ! [ "$(getenforce)" = "Disabled" ]; then if [ "$(getenforce)" = "Enforcing" ]; then
failure <<EOF failure <<EOF
Nix does not work with selinux enabled yet! Nix does not work with selinux enabled yet!
see https://github.com/NixOS/nix/issues/2374 see https://github.com/NixOS/nix/issues/2374
@ -865,24 +898,14 @@ EOF
install -m 0664 "$SCRATCH/nix.conf" /etc/nix/nix.conf install -m 0664 "$SCRATCH/nix.conf" /etc/nix/nix.conf
} }
main() {
# TODO: I've moved this out of validate_starting_assumptions so we
# can fail faster in this case. Sourcing install-darwin... now runs
# `touch /` to detect Read-only root, but it could update times on
# pre-Catalina macOS if run as root user.
if [ "$EUID" -eq 0 ]; then
failure <<EOF
Please do not run this script with root privileges. I will call sudo
when I need to.
EOF
fi
main() {
check_selinux check_selinux
if [ "$(uname -s)" = "Darwin" ]; then if is_os_darwin; then
# shellcheck source=./install-darwin-multi-user.sh # shellcheck source=./install-darwin-multi-user.sh
. "$EXTRACTED_NIX_PATH/install-darwin-multi-user.sh" . "$EXTRACTED_NIX_PATH/install-darwin-multi-user.sh"
elif [ "$(uname -s)" = "Linux" ]; then elif is_os_linux; then
# shellcheck source=./install-systemd-multi-user.sh # shellcheck source=./install-systemd-multi-user.sh
. "$EXTRACTED_NIX_PATH/install-systemd-multi-user.sh" # most of this works on non-systemd distros also . "$EXTRACTED_NIX_PATH/install-systemd-multi-user.sh" # most of this works on non-systemd distros also
else else
@ -890,7 +913,10 @@ EOF
fi fi
welcome_to_nix welcome_to_nix
chat_about_sudo
if ! is_root; then
chat_about_sudo
fi
cure_artifacts cure_artifacts
# TODO: there's a tension between cure and validate. I moved the # TODO: there's a tension between cure and validate. I moved the

View file

@ -692,6 +692,8 @@ InstallableFlake::InstallableFlake(
std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableFlake::toDerivation() std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableFlake::toDerivation()
{ {
Activity act(*logger, lvlTalkative, actUnknown, fmt("evaluating derivation '%s'", what()));
auto attr = getCursor(*state); auto attr = getCursor(*state);
auto attrPath = attr->getAttrPathStr(); auto attrPath = attr->getAttrPathStr();

View file

@ -35,6 +35,7 @@ extern "C" {
#include "finally.hh" #include "finally.hh"
#include "markdown.hh" #include "markdown.hh"
#include "local-fs-store.hh" #include "local-fs-store.hh"
#include "progress-bar.hh"
#if HAVE_BOEHMGC #if HAVE_BOEHMGC
#define GC_INCLUDE_NEW #define GC_INCLUDE_NEW
@ -252,6 +253,10 @@ void NixRepl::mainLoop()
rl_set_list_possib_func(listPossibleCallback); rl_set_list_possib_func(listPossibleCallback);
#endif #endif
/* Stop the progress bar because it interferes with the display of
the repl. */
stopProgressBar();
std::string input; std::string input;
while (true) { while (true) {
@ -1037,9 +1042,10 @@ void runRepl(
struct CmdRepl : InstallablesCommand struct CmdRepl : InstallablesCommand
{ {
CmdRepl(){ CmdRepl() {
evalSettings.pureEval = false; evalSettings.pureEval = false;
} }
void prepare() void prepare()
{ {
if (!settings.isExperimentalFeatureEnabled(Xp::ReplFlake) && !(file) && this->_installables.size() >= 1) { if (!settings.isExperimentalFeatureEnabled(Xp::ReplFlake) && !(file) && this->_installables.size() >= 1) {
@ -1053,12 +1059,15 @@ struct CmdRepl : InstallablesCommand
} }
installables = InstallablesCommand::load(); installables = InstallablesCommand::load();
} }
std::vector<std::string> files; std::vector<std::string> files;
Strings getDefaultFlakeAttrPaths() override Strings getDefaultFlakeAttrPaths() override
{ {
return {""}; return {""};
} }
virtual bool useDefaultInstallables() override
bool useDefaultInstallables() override
{ {
return file.has_value() or expr.has_value(); return file.has_value() or expr.has_value();
} }

View file

@ -507,11 +507,6 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
return nullptr; return nullptr;
//throw TypeError("'%s' is not an attribute set", getAttrPathStr()); //throw TypeError("'%s' is not an attribute set", getAttrPathStr());
for (auto & attr : *v.attrs) {
if (root->db)
root->db->setPlaceholder({cachedValue->first, attr.name});
}
auto attr = v.attrs->get(name); auto attr = v.attrs->get(name);
if (!attr) { if (!attr) {

View file

@ -10,7 +10,7 @@
namespace nix { namespace nix {
void printValueAsJSON(EvalState & state, bool strict, void printValueAsJSON(EvalState & state, bool strict,
Value & v, const PosIdx pos, JSONPlaceholder & out, PathSet & context) Value & v, const PosIdx pos, JSONPlaceholder & out, PathSet & context, bool copyToStore)
{ {
checkInterrupt(); checkInterrupt();
@ -32,7 +32,10 @@ void printValueAsJSON(EvalState & state, bool strict,
break; break;
case nPath: case nPath:
out.write(state.copyPathToStore(context, v.path)); if (copyToStore)
out.write(state.copyPathToStore(context, v.path));
else
out.write(v.path);
break; break;
case nNull: case nNull:
@ -54,10 +57,10 @@ void printValueAsJSON(EvalState & state, bool strict,
for (auto & j : names) { for (auto & j : names) {
Attr & a(*v.attrs->find(state.symbols.create(j))); Attr & a(*v.attrs->find(state.symbols.create(j)));
auto placeholder(obj.placeholder(j)); auto placeholder(obj.placeholder(j));
printValueAsJSON(state, strict, *a.value, a.pos, placeholder, context); printValueAsJSON(state, strict, *a.value, a.pos, placeholder, context, copyToStore);
} }
} else } else
printValueAsJSON(state, strict, *i->value, i->pos, out, context); printValueAsJSON(state, strict, *i->value, i->pos, out, context, copyToStore);
break; break;
} }
@ -65,13 +68,13 @@ void printValueAsJSON(EvalState & state, bool strict,
auto list(out.list()); auto list(out.list());
for (auto elem : v.listItems()) { for (auto elem : v.listItems()) {
auto placeholder(list.placeholder()); auto placeholder(list.placeholder());
printValueAsJSON(state, strict, *elem, pos, placeholder, context); printValueAsJSON(state, strict, *elem, pos, placeholder, context, copyToStore);
} }
break; break;
} }
case nExternal: case nExternal:
v.external->printValueAsJSON(state, strict, out, context); v.external->printValueAsJSON(state, strict, out, context, copyToStore);
break; break;
case nFloat: case nFloat:
@ -91,14 +94,14 @@ void printValueAsJSON(EvalState & state, bool strict,
} }
void printValueAsJSON(EvalState & state, bool strict, void printValueAsJSON(EvalState & state, bool strict,
Value & v, const PosIdx pos, std::ostream & str, PathSet & context) Value & v, const PosIdx pos, std::ostream & str, PathSet & context, bool copyToStore)
{ {
JSONPlaceholder out(str); JSONPlaceholder out(str);
printValueAsJSON(state, strict, v, pos, out, context); printValueAsJSON(state, strict, v, pos, out, context, copyToStore);
} }
void ExternalValueBase::printValueAsJSON(EvalState & state, bool strict, void ExternalValueBase::printValueAsJSON(EvalState & state, bool strict,
JSONPlaceholder & out, PathSet & context) const JSONPlaceholder & out, PathSet & context, bool copyToStore) const
{ {
state.debugThrowLastTrace(TypeError("cannot convert %1% to JSON", showType())); state.debugThrowLastTrace(TypeError("cannot convert %1% to JSON", showType()));
} }

View file

@ -11,9 +11,9 @@ namespace nix {
class JSONPlaceholder; class JSONPlaceholder;
void printValueAsJSON(EvalState & state, bool strict, void printValueAsJSON(EvalState & state, bool strict,
Value & v, const PosIdx pos, JSONPlaceholder & out, PathSet & context); Value & v, const PosIdx pos, JSONPlaceholder & out, PathSet & context, bool copyToStore = true);
void printValueAsJSON(EvalState & state, bool strict, void printValueAsJSON(EvalState & state, bool strict,
Value & v, const PosIdx pos, std::ostream & str, PathSet & context); Value & v, const PosIdx pos, std::ostream & str, PathSet & context, bool copyToStore = true);
} }

View file

@ -99,7 +99,7 @@ class ExternalValueBase
/* Print the value as JSON. Defaults to unconvertable, i.e. throws an error */ /* Print the value as JSON. Defaults to unconvertable, i.e. throws an error */
virtual void printValueAsJSON(EvalState & state, bool strict, virtual void printValueAsJSON(EvalState & state, bool strict,
JSONPlaceholder & out, PathSet & context) const; JSONPlaceholder & out, PathSet & context, bool copyToStore = true) const;
/* Print the value as XML. Defaults to unevaluated */ /* Print the value as XML. Defaults to unevaluated */
virtual void printValueAsXML(EvalState & state, bool strict, bool location, virtual void printValueAsXML(EvalState & state, bool strict, bool location,

View file

@ -371,7 +371,7 @@ struct GitInputScheme : InputScheme
auto gitDir = ".git"; auto gitDir = ".git";
runProgram("git", true, runProgram("git", true,
{ "-C", *sourcePath, "--git-dir", gitDir, "add", "--force", "--intent-to-add", "--", std::string(file) }); { "-C", *sourcePath, "--git-dir", gitDir, "add", "--intent-to-add", "--", std::string(file) });
if (commitMsg) if (commitMsg)
runProgram("git", true, runProgram("git", true,

View file

@ -30,8 +30,11 @@ Logger * makeDefaultLogger() {
return makeJSONLogger(*makeSimpleLogger(true)); return makeJSONLogger(*makeSimpleLogger(true));
case LogFormat::bar: case LogFormat::bar:
return makeProgressBar(); return makeProgressBar();
case LogFormat::barWithLogs: case LogFormat::barWithLogs: {
return makeProgressBar(true); auto logger = makeProgressBar();
logger->setPrintBuildLogs(true);
return logger;
}
default: default:
abort(); abort();
} }

View file

@ -8,6 +8,7 @@
#include <map> #include <map>
#include <thread> #include <thread>
#include <iostream> #include <iostream>
#include <chrono>
namespace nix { namespace nix {
@ -48,6 +49,7 @@ private:
bool visible = true; bool visible = true;
ActivityId parent; ActivityId parent;
std::optional<std::string> name; std::optional<std::string> name;
std::chrono::time_point<std::chrono::steady_clock> startTime;
}; };
struct ActivitiesByType struct ActivitiesByType
@ -79,22 +81,22 @@ private:
std::condition_variable quitCV, updateCV; std::condition_variable quitCV, updateCV;
bool printBuildLogs; bool printBuildLogs = false;
bool isTTY; bool isTTY;
public: public:
ProgressBar(bool printBuildLogs, bool isTTY) ProgressBar(bool isTTY)
: printBuildLogs(printBuildLogs) : isTTY(isTTY)
, isTTY(isTTY)
{ {
state_.lock()->active = isTTY; state_.lock()->active = isTTY;
updateThread = std::thread([&]() { updateThread = std::thread([&]() {
auto state(state_.lock()); auto state(state_.lock());
auto nextWakeup = std::chrono::milliseconds::max();
while (state->active) { while (state->active) {
if (!state->haveUpdate) if (!state->haveUpdate)
state.wait(updateCV); state.wait_for(updateCV, nextWakeup);
draw(*state); nextWakeup = draw(*state);
state.wait_for(quitCV, std::chrono::milliseconds(50)); state.wait_for(quitCV, std::chrono::milliseconds(50));
} }
}); });
@ -118,7 +120,8 @@ public:
updateThread.join(); updateThread.join();
} }
bool isVerbose() override { bool isVerbose() override
{
return printBuildLogs; return printBuildLogs;
} }
@ -159,11 +162,13 @@ public:
if (lvl <= verbosity && !s.empty() && type != actBuildWaiting) if (lvl <= verbosity && !s.empty() && type != actBuildWaiting)
log(*state, lvl, s + "..."); log(*state, lvl, s + "...");
state->activities.emplace_back(ActInfo()); state->activities.emplace_back(ActInfo {
.s = s,
.type = type,
.parent = parent,
.startTime = std::chrono::steady_clock::now()
});
auto i = std::prev(state->activities.end()); auto i = std::prev(state->activities.end());
i->s = s;
i->type = type;
i->parent = parent;
state->its.emplace(act, i); state->its.emplace(act, i);
state->activitiesByType[type].its.emplace(act, i); state->activitiesByType[type].its.emplace(act, i);
@ -327,10 +332,12 @@ public:
updateCV.notify_one(); updateCV.notify_one();
} }
void draw(State & state) std::chrono::milliseconds draw(State & state)
{ {
auto nextWakeup = std::chrono::milliseconds::max();
state.haveUpdate = false; state.haveUpdate = false;
if (!state.active) return; if (!state.active) return nextWakeup;
std::string line; std::string line;
@ -341,12 +348,25 @@ public:
line += "]"; line += "]";
} }
auto now = std::chrono::steady_clock::now();
if (!state.activities.empty()) { if (!state.activities.empty()) {
if (!status.empty()) line += " "; if (!status.empty()) line += " ";
auto i = state.activities.rbegin(); auto i = state.activities.rbegin();
while (i != state.activities.rend() && (!i->visible || (i->s.empty() && i->lastLine.empty()))) while (i != state.activities.rend()) {
if (i->visible && (!i->s.empty() || !i->lastLine.empty())) {
/* Don't show activities until some time has
passed, to avoid displaying very short
activities. */
auto delay = std::chrono::milliseconds(10);
if (i->startTime + delay < now)
break;
else
nextWakeup = std::min(nextWakeup, std::chrono::duration_cast<std::chrono::milliseconds>(delay - (now - i->startTime)));
}
++i; ++i;
}
if (i != state.activities.rend()) { if (i != state.activities.rend()) {
line += i->s; line += i->s;
@ -366,6 +386,8 @@ public:
if (width <= 0) width = std::numeric_limits<decltype(width)>::max(); if (width <= 0) width = std::numeric_limits<decltype(width)>::max();
writeToStderr("\r" + filterANSIEscapes(line, false, width) + ANSI_NORMAL + "\e[K"); writeToStderr("\r" + filterANSIEscapes(line, false, width) + ANSI_NORMAL + "\e[K");
return nextWakeup;
} }
std::string getStatus(State & state) std::string getStatus(State & state)
@ -480,19 +502,21 @@ public:
draw(*state); draw(*state);
return s[0]; return s[0];
} }
virtual void setPrintBuildLogs(bool printBuildLogs)
{
this->printBuildLogs = printBuildLogs;
}
}; };
Logger * makeProgressBar(bool printBuildLogs) Logger * makeProgressBar()
{ {
return new ProgressBar( return new ProgressBar(shouldANSI());
printBuildLogs,
shouldANSI()
);
} }
void startProgressBar(bool printBuildLogs) void startProgressBar()
{ {
logger = makeProgressBar(printBuildLogs); logger = makeProgressBar();
} }
void stopProgressBar() void stopProgressBar()

View file

@ -4,9 +4,9 @@
namespace nix { namespace nix {
Logger * makeProgressBar(bool printBuildLogs = false); Logger * makeProgressBar();
void startProgressBar(bool printBuildLogs = false); void startProgressBar();
void stopProgressBar(); void stopProgressBar();

View file

@ -705,8 +705,7 @@ static void movePath(const Path & src, const Path & dst)
if (changePerm) if (changePerm)
chmod_(src, st.st_mode | S_IWUSR); chmod_(src, st.st_mode | S_IWUSR);
if (rename(src.c_str(), dst.c_str())) renameFile(src, dst);
throw SysError("renaming '%1%' to '%2%'", src, dst);
if (changePerm) if (changePerm)
chmod_(dst, st.st_mode); chmod_(dst, st.st_mode);
@ -914,12 +913,6 @@ void DerivationGoal::buildDone()
outputPaths outputPaths
); );
if (buildMode == bmCheck) {
cleanupPostOutputsRegisteredModeCheck();
done(BuildResult::Built, std::move(builtOutputs));
return;
}
cleanupPostOutputsRegisteredModeNonCheck(); cleanupPostOutputsRegisteredModeNonCheck();
/* Repeat the build if necessary. */ /* Repeat the build if necessary. */

View file

@ -223,8 +223,7 @@ static void movePath(const Path & src, const Path & dst)
if (changePerm) if (changePerm)
chmod_(src, st.st_mode | S_IWUSR); chmod_(src, st.st_mode | S_IWUSR);
if (rename(src.c_str(), dst.c_str())) renameFile(src, dst);
throw SysError("renaming '%1%' to '%2%'", src, dst);
if (changePerm) if (changePerm)
chmod_(dst, st.st_mode); chmod_(dst, st.st_mode);
@ -311,7 +310,7 @@ bool LocalDerivationGoal::cleanupDecideWhetherDiskFull()
if (buildMode != bmCheck && status.known->isValid()) continue; if (buildMode != bmCheck && status.known->isValid()) continue;
auto p = worker.store.printStorePath(status.known->path); auto p = worker.store.printStorePath(status.known->path);
if (pathExists(chrootRootDir + p)) if (pathExists(chrootRootDir + p))
rename((chrootRootDir + p).c_str(), p.c_str()); renameFile((chrootRootDir + p), p);
} }
return diskFull; return diskFull;
@ -2375,10 +2374,8 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
if (*scratchPath != finalPath) { if (*scratchPath != finalPath) {
// Also rewrite the output path // Also rewrite the output path
auto source = sinkToSource([&](Sink & nextSink) { auto source = sinkToSource([&](Sink & nextSink) {
StringSink sink;
dumpPath(actualPath, sink);
RewritingSink rsink2(oldHashPart, std::string(finalPath.hashPart()), nextSink); RewritingSink rsink2(oldHashPart, std::string(finalPath.hashPart()), nextSink);
rsink2(sink.s); dumpPath(actualPath, rsink2);
rsink2.flush(); rsink2.flush();
}); });
Path tmpPath = actualPath + ".tmp"; Path tmpPath = actualPath + ".tmp";
@ -2625,8 +2622,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
Path prev = path + checkSuffix; Path prev = path + checkSuffix;
deletePath(prev); deletePath(prev);
Path dst = path + checkSuffix; Path dst = path + checkSuffix;
if (rename(path.c_str(), dst.c_str())) renameFile(path, dst);
throw SysError("renaming '%s' to '%s'", path, dst);
} }
} }

View file

@ -22,8 +22,7 @@ void builtinUnpackChannel(const BasicDerivation & drv)
auto entries = readDirectory(out); auto entries = readDirectory(out);
if (entries.size() != 1) if (entries.size() != 1)
throw Error("channel tarball '%s' contains more than one file", src); throw Error("channel tarball '%s' contains more than one file", src);
if (rename((out + "/" + entries[0].name).c_str(), (out + "/" + channelName).c_str()) == -1) renameFile((out + "/" + entries[0].name), (out + "/" + channelName));
throw SysError("renaming channel directory");
} }
} }

View file

@ -308,6 +308,9 @@ struct curlFileTransfer : public FileTransfer
curl_easy_setopt(req, CURLOPT_HTTPHEADER, requestHeaders); curl_easy_setopt(req, CURLOPT_HTTPHEADER, requestHeaders);
if (settings.downloadSpeed.get() > 0)
curl_easy_setopt(req, CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t) (settings.downloadSpeed.get() * 1024));
if (request.head) if (request.head)
curl_easy_setopt(req, CURLOPT_NOBODY, 1); curl_easy_setopt(req, CURLOPT_NOBODY, 1);

View file

@ -39,9 +39,7 @@ static void makeSymlink(const Path & link, const Path & target)
createSymlink(target, tempLink); createSymlink(target, tempLink);
/* Atomically replace the old one. */ /* Atomically replace the old one. */
if (rename(tempLink.c_str(), link.c_str()) == -1) renameFile(tempLink, link);
throw SysError("cannot rename '%1%' to '%2%'",
tempLink , link);
} }

View file

@ -746,6 +746,13 @@ public:
/nix/store/xfghy8ixrhz3kyy6p724iv3cxji088dx-bash-4.4-p23`. /nix/store/xfghy8ixrhz3kyy6p724iv3cxji088dx-bash-4.4-p23`.
)"}; )"};
Setting<unsigned int> downloadSpeed {
this, 0, "download-speed",
R"(
Specify the maximum transfer rate in kilobytes per second you want
Nix to use for downloads.
)"};
Setting<std::string> netrcFile{ Setting<std::string> netrcFile{
this, fmt("%s/%s", nixConfDir, "netrc"), "netrc-file", this, fmt("%s/%s", nixConfDir, "netrc"), "netrc-file",
R"( R"(

View file

@ -57,8 +57,7 @@ protected:
AutoDelete del(tmp, false); AutoDelete del(tmp, false);
StreamToSourceAdapter source(istream); StreamToSourceAdapter source(istream);
writeFile(tmp, source); writeFile(tmp, source);
if (rename(tmp.c_str(), path2.c_str())) renameFile(tmp, path2);
throw SysError("renaming '%1%' to '%2%'", tmp, path2);
del.cancel(); del.cancel();
} }

View file

@ -1430,8 +1430,7 @@ StorePath LocalStore::addToStoreFromDump(Source & source0, std::string_view name
writeFile(realPath, dumpSource); writeFile(realPath, dumpSource);
} else { } else {
/* Move the temporary path we restored above. */ /* Move the temporary path we restored above. */
if (rename(tempPath.c_str(), realPath.c_str())) moveFile(tempPath, realPath);
throw Error("renaming '%s' to '%s'", tempPath, realPath);
} }
/* For computing the nar hash. In recursive SHA-256 mode, this /* For computing the nar hash. In recursive SHA-256 mode, this
@ -1942,8 +1941,7 @@ void LocalStore::addBuildLog(const StorePath & drvPath, std::string_view log)
writeFile(tmpFile, compress("bzip2", log)); writeFile(tmpFile, compress("bzip2", log));
if (rename(tmpFile.c_str(), logPath.c_str()) != 0) renameFile(tmpFile, logPath);
throw SysError("renaming '%1%' to '%2%'", tmpFile, logPath);
} }
std::optional<std::string> LocalStore::getVersion() std::optional<std::string> LocalStore::getVersion()

View file

@ -229,7 +229,9 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
} }
/* Atomically replace the old file with the new hard link. */ /* Atomically replace the old file with the new hard link. */
if (rename(tempLink.c_str(), path.c_str()) == -1) { try {
renameFile(tempLink, path);
} catch (SysError & e) {
if (unlink(tempLink.c_str()) == -1) if (unlink(tempLink.c_str()) == -1)
printError("unable to unlink '%1%'", tempLink); printError("unable to unlink '%1%'", tempLink);
if (errno == EMLINK) { if (errno == EMLINK) {
@ -240,7 +242,7 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
debug("'%s' has reached maximum number of links", linkPath); debug("'%s' has reached maximum number of links", linkPath);
return; return;
} }
throw SysError("cannot rename '%1%' to '%2%'", tempLink, path); throw;
} }
stats.filesLinked++; stats.filesLinked++;

View file

@ -580,7 +580,6 @@ ref<const ValidPathInfo> RemoteStore::addCAToStore(
try { try {
conn->to.written = 0; conn->to.written = 0;
conn->to.warn = true;
connections->incCapacity(); connections->incCapacity();
{ {
Finally cleanup([&]() { connections->decCapacity(); }); Finally cleanup([&]() { connections->decCapacity(); });
@ -591,7 +590,6 @@ ref<const ValidPathInfo> RemoteStore::addCAToStore(
dumpString(contents, conn->to); dumpString(contents, conn->to);
} }
} }
conn->to.warn = false;
conn.processStderr(); conn.processStderr();
} catch (SysError & e) { } catch (SysError & e) {
/* Daemon closed while we were sending the path. Probably OOM /* Daemon closed while we were sending the path. Probably OOM
@ -673,6 +671,23 @@ void RemoteStore::addToStore(const ValidPathInfo & info, Source & source,
} }
void RemoteStore::addMultipleToStore(
PathsSource & pathsToCopy,
Activity & act,
RepairFlag repair,
CheckSigsFlag checkSigs)
{
auto source = sinkToSource([&](Sink & sink) {
sink << pathsToCopy.size();
for (auto & [pathInfo, pathSource] : pathsToCopy) {
pathInfo.write(sink, *this, 16);
pathSource->drainInto(sink);
}
});
addMultipleToStore(*source, repair, checkSigs);
}
void RemoteStore::addMultipleToStore( void RemoteStore::addMultipleToStore(
Source & source, Source & source,
RepairFlag repair, RepairFlag repair,

View file

@ -88,6 +88,12 @@ public:
RepairFlag repair, RepairFlag repair,
CheckSigsFlag checkSigs) override; CheckSigsFlag checkSigs) override;
void addMultipleToStore(
PathsSource & pathsToCopy,
Activity & act,
RepairFlag repair,
CheckSigsFlag checkSigs) override;
StorePath addTextToStore( StorePath addTextToStore(
std::string_view name, std::string_view name,
std::string_view s, std::string_view s,

View file

@ -258,6 +258,84 @@ StorePath Store::addToStore(
return addToStoreFromDump(*source, name, method, hashAlgo, repair, references); return addToStoreFromDump(*source, name, method, hashAlgo, repair, references);
} }
void Store::addMultipleToStore(
PathsSource & pathsToCopy,
Activity & act,
RepairFlag repair,
CheckSigsFlag checkSigs)
{
std::atomic<size_t> nrDone{0};
std::atomic<size_t> nrFailed{0};
std::atomic<uint64_t> bytesExpected{0};
std::atomic<uint64_t> nrRunning{0};
using PathWithInfo = std::pair<ValidPathInfo, std::unique_ptr<Source>>;
std::map<StorePath, PathWithInfo *> infosMap;
StorePathSet storePathsToAdd;
for (auto & thingToAdd : pathsToCopy) {
infosMap.insert_or_assign(thingToAdd.first.path, &thingToAdd);
storePathsToAdd.insert(thingToAdd.first.path);
}
auto showProgress = [&]() {
act.progress(nrDone, pathsToCopy.size(), nrRunning, nrFailed);
};
ThreadPool pool;
processGraph<StorePath>(pool,
storePathsToAdd,
[&](const StorePath & path) {
auto & [info, _] = *infosMap.at(path);
if (isValidPath(info.path)) {
nrDone++;
showProgress();
return StorePathSet();
}
bytesExpected += info.narSize;
act.setExpected(actCopyPath, bytesExpected);
return info.references;
},
[&](const StorePath & path) {
checkInterrupt();
auto & [info_, source_] = *infosMap.at(path);
auto info = info_;
info.ultimate = false;
/* Make sure that the Source object is destroyed when
we're done. In particular, a SinkToSource object must
be destroyed to ensure that the destructors on its
stack frame are run; this includes
LegacySSHStore::narFromPath()'s connection lock. */
auto source = std::move(source_);
if (!isValidPath(info.path)) {
MaintainCount<decltype(nrRunning)> mc(nrRunning);
showProgress();
try {
addToStore(info, *source, repair, checkSigs);
} catch (Error & e) {
nrFailed++;
if (!settings.keepGoing)
throw e;
printMsg(lvlError, "could not copy %s: %s", printStorePath(path), e.what());
showProgress();
return;
}
}
nrDone++;
showProgress();
});
}
void Store::addMultipleToStore( void Store::addMultipleToStore(
Source & source, Source & source,
@ -992,113 +1070,61 @@ std::map<StorePath, StorePath> copyPaths(
for (auto & path : storePaths) for (auto & path : storePaths)
if (!valid.count(path)) missing.insert(path); if (!valid.count(path)) missing.insert(path);
Activity act(*logger, lvlInfo, actCopyPaths, fmt("copying %d paths", missing.size()));
// In the general case, `addMultipleToStore` requires a sorted list of
// store paths to add, so sort them right now
auto sortedMissing = srcStore.topoSortPaths(missing);
std::reverse(sortedMissing.begin(), sortedMissing.end());
std::map<StorePath, StorePath> pathsMap; std::map<StorePath, StorePath> pathsMap;
for (auto & path : storePaths) for (auto & path : storePaths)
pathsMap.insert_or_assign(path, path); pathsMap.insert_or_assign(path, path);
Activity act(*logger, lvlInfo, actCopyPaths, fmt("copying %d paths", missing.size())); Store::PathsSource pathsToCopy;
auto sorted = srcStore.topoSortPaths(missing); auto computeStorePathForDst = [&](const ValidPathInfo & currentPathInfo) -> StorePath {
std::reverse(sorted.begin(), sorted.end()); auto storePathForSrc = currentPathInfo.path;
auto storePathForDst = storePathForSrc;
if (currentPathInfo.ca && currentPathInfo.references.empty()) {
storePathForDst = dstStore.makeFixedOutputPathFromCA(storePathForSrc.name(), *currentPathInfo.ca);
if (dstStore.storeDir == srcStore.storeDir)
assert(storePathForDst == storePathForSrc);
if (storePathForDst != storePathForSrc)
debug("replaced path '%s' to '%s' for substituter '%s'",
srcStore.printStorePath(storePathForSrc),
dstStore.printStorePath(storePathForDst),
dstStore.getUri());
}
return storePathForDst;
};
auto source = sinkToSource([&](Sink & sink) { for (auto & missingPath : sortedMissing) {
sink << sorted.size(); auto info = srcStore.queryPathInfo(missingPath);
for (auto & storePath : sorted) {
auto storePathForDst = computeStorePathForDst(*info);
pathsMap.insert_or_assign(missingPath, storePathForDst);
ValidPathInfo infoForDst = *info;
infoForDst.path = storePathForDst;
auto source = sinkToSource([&](Sink & sink) {
// We can reasonably assume that the copy will happen whenever we
// read the path, so log something about that at that point
auto srcUri = srcStore.getUri(); auto srcUri = srcStore.getUri();
auto dstUri = dstStore.getUri(); auto dstUri = dstStore.getUri();
auto storePathS = srcStore.printStorePath(storePath); auto storePathS = srcStore.printStorePath(missingPath);
Activity act(*logger, lvlInfo, actCopyPath, Activity act(*logger, lvlInfo, actCopyPath,
makeCopyPathMessage(srcUri, dstUri, storePathS), makeCopyPathMessage(srcUri, dstUri, storePathS),
{storePathS, srcUri, dstUri}); {storePathS, srcUri, dstUri});
PushActivity pact(act.id); PushActivity pact(act.id);
auto info = srcStore.queryPathInfo(storePath); srcStore.narFromPath(missingPath, sink);
info->write(sink, srcStore, 16);
srcStore.narFromPath(storePath, sink);
}
});
dstStore.addMultipleToStore(*source, repair, checkSigs);
#if 0
std::atomic<size_t> nrDone{0};
std::atomic<size_t> nrFailed{0};
std::atomic<uint64_t> bytesExpected{0};
std::atomic<uint64_t> nrRunning{0};
auto showProgress = [&]() {
act.progress(nrDone, missing.size(), nrRunning, nrFailed);
};
ThreadPool pool;
processGraph<StorePath>(pool,
StorePathSet(missing.begin(), missing.end()),
[&](const StorePath & storePath) {
auto info = srcStore.queryPathInfo(storePath);
auto storePathForDst = storePath;
if (info->ca && info->references.empty()) {
storePathForDst = dstStore.makeFixedOutputPathFromCA(storePath.name(), *info->ca);
if (dstStore.storeDir == srcStore.storeDir)
assert(storePathForDst == storePath);
if (storePathForDst != storePath)
debug("replaced path '%s' to '%s' for substituter '%s'",
srcStore.printStorePath(storePath),
dstStore.printStorePath(storePathForDst),
dstStore.getUri());
}
pathsMap.insert_or_assign(storePath, storePathForDst);
if (dstStore.isValidPath(storePath)) {
nrDone++;
showProgress();
return StorePathSet();
}
bytesExpected += info->narSize;
act.setExpected(actCopyPath, bytesExpected);
return info->references;
},
[&](const StorePath & storePath) {
checkInterrupt();
auto info = srcStore.queryPathInfo(storePath);
auto storePathForDst = storePath;
if (info->ca && info->references.empty()) {
storePathForDst = dstStore.makeFixedOutputPathFromCA(storePath.name(), *info->ca);
if (dstStore.storeDir == srcStore.storeDir)
assert(storePathForDst == storePath);
if (storePathForDst != storePath)
debug("replaced path '%s' to '%s' for substituter '%s'",
srcStore.printStorePath(storePath),
dstStore.printStorePath(storePathForDst),
dstStore.getUri());
}
pathsMap.insert_or_assign(storePath, storePathForDst);
if (!dstStore.isValidPath(storePathForDst)) {
MaintainCount<decltype(nrRunning)> mc(nrRunning);
showProgress();
try {
copyStorePath(srcStore, dstStore, storePath, repair, checkSigs);
} catch (Error &e) {
nrFailed++;
if (!settings.keepGoing)
throw e;
printMsg(lvlError, "could not copy %s: %s", dstStore.printStorePath(storePath), e.what());
showProgress();
return;
}
}
nrDone++;
showProgress();
}); });
#endif pathsToCopy.push_back(std::pair{infoForDst, std::move(source)});
}
dstStore.addMultipleToStore(pathsToCopy, act, repair, checkSigs);
return pathsMap; return pathsMap;
} }

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "nar-info.hh"
#include "realisation.hh" #include "realisation.hh"
#include "path.hh" #include "path.hh"
#include "derived-path.hh" #include "derived-path.hh"
@ -359,12 +360,22 @@ public:
virtual void addToStore(const ValidPathInfo & info, Source & narSource, virtual void addToStore(const ValidPathInfo & info, Source & narSource,
RepairFlag repair = NoRepair, CheckSigsFlag checkSigs = CheckSigs) = 0; RepairFlag repair = NoRepair, CheckSigsFlag checkSigs = CheckSigs) = 0;
// A list of paths infos along with a source providing the content of the
// associated store path
using PathsSource = std::vector<std::pair<ValidPathInfo, std::unique_ptr<Source>>>;
/* Import multiple paths into the store. */ /* Import multiple paths into the store. */
virtual void addMultipleToStore( virtual void addMultipleToStore(
Source & source, Source & source,
RepairFlag repair = NoRepair, RepairFlag repair = NoRepair,
CheckSigsFlag checkSigs = CheckSigs); CheckSigsFlag checkSigs = CheckSigs);
virtual void addMultipleToStore(
PathsSource & pathsToCopy,
Activity & act,
RepairFlag repair = NoRepair,
CheckSigsFlag checkSigs = CheckSigs);
/* Copy the contents of a path to the store and register the /* Copy the contents of a path to the store and register the
validity the resulting path. The resulting path is returned. validity the resulting path. The resulting path is returned.
The function object `filter' can be used to exclude files (see The function object `filter' can be used to exclude files (see

172
src/libutil/filesystem.cc Normal file
View file

@ -0,0 +1,172 @@
#include <sys/time.h>
#include <filesystem>
#include "finally.hh"
#include "util.hh"
#include "types.hh"
namespace fs = std::filesystem;
namespace nix {
static Path tempName(Path tmpRoot, const Path & prefix, bool includePid,
int & counter)
{
tmpRoot = canonPath(tmpRoot.empty() ? getEnv("TMPDIR").value_or("/tmp") : tmpRoot, true);
if (includePid)
return (format("%1%/%2%-%3%-%4%") % tmpRoot % prefix % getpid() % counter++).str();
else
return (format("%1%/%2%-%3%") % tmpRoot % prefix % counter++).str();
}
Path createTempDir(const Path & tmpRoot, const Path & prefix,
bool includePid, bool useGlobalCounter, mode_t mode)
{
static int globalCounter = 0;
int localCounter = 0;
int & counter(useGlobalCounter ? globalCounter : localCounter);
while (1) {
checkInterrupt();
Path tmpDir = tempName(tmpRoot, prefix, includePid, counter);
if (mkdir(tmpDir.c_str(), mode) == 0) {
#if __FreeBSD__
/* Explicitly set the group of the directory. This is to
work around around problems caused by BSD's group
ownership semantics (directories inherit the group of
the parent). For instance, the group of /tmp on
FreeBSD is "wheel", so all directories created in /tmp
will be owned by "wheel"; but if the user is not in
"wheel", then "tar" will fail to unpack archives that
have the setgid bit set on directories. */
if (chown(tmpDir.c_str(), (uid_t) -1, getegid()) != 0)
throw SysError("setting group of directory '%1%'", tmpDir);
#endif
return tmpDir;
}
if (errno != EEXIST)
throw SysError("creating directory '%1%'", tmpDir);
}
}
std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix)
{
Path tmpl(getEnv("TMPDIR").value_or("/tmp") + "/" + prefix + ".XXXXXX");
// Strictly speaking, this is UB, but who cares...
// FIXME: use O_TMPFILE.
AutoCloseFD fd(mkstemp((char *) tmpl.c_str()));
if (!fd)
throw SysError("creating temporary file '%s'", tmpl);
closeOnExec(fd.get());
return {std::move(fd), tmpl};
}
void createSymlink(const Path & target, const Path & link,
std::optional<time_t> mtime)
{
if (symlink(target.c_str(), link.c_str()))
throw SysError("creating symlink from '%1%' to '%2%'", link, target);
if (mtime) {
struct timeval times[2];
times[0].tv_sec = *mtime;
times[0].tv_usec = 0;
times[1].tv_sec = *mtime;
times[1].tv_usec = 0;
if (lutimes(link.c_str(), times))
throw SysError("setting time of symlink '%s'", link);
}
}
void replaceSymlink(const Path & target, const Path & link,
std::optional<time_t> mtime)
{
for (unsigned int n = 0; true; n++) {
Path tmp = canonPath(fmt("%s/.%d_%s", dirOf(link), n, baseNameOf(link)));
try {
createSymlink(target, tmp, mtime);
} catch (SysError & e) {
if (e.errNo == EEXIST) continue;
throw;
}
renameFile(tmp, link);
break;
}
}
void setWriteTime(const fs::path & p, const struct stat & st)
{
struct timeval times[2];
times[0] = {
.tv_sec = st.st_atime,
.tv_usec = 0,
};
times[1] = {
.tv_sec = st.st_mtime,
.tv_usec = 0,
};
if (lutimes(p.c_str(), times) != 0)
throw SysError("changing modification time of '%s'", p);
}
void copy(const fs::directory_entry & from, const fs::path & to, bool andDelete)
{
// TODO: Rewrite the `is_*` to use `symlink_status()`
auto statOfFrom = lstat(from.path().c_str());
auto fromStatus = from.symlink_status();
// Mark the directory as writable so that we can delete its children
if (andDelete && fs::is_directory(fromStatus)) {
fs::permissions(from.path(), fs::perms::owner_write, fs::perm_options::add | fs::perm_options::nofollow);
}
if (fs::is_symlink(fromStatus) || fs::is_regular_file(fromStatus)) {
fs::copy(from.path(), to, fs::copy_options::copy_symlinks | fs::copy_options::overwrite_existing);
} else if (fs::is_directory(fromStatus)) {
fs::create_directory(to);
for (auto & entry : fs::directory_iterator(from.path())) {
copy(entry, to / entry.path().filename(), andDelete);
}
} else {
throw Error("file '%s' has an unsupported type", from.path());
}
setWriteTime(to, statOfFrom);
if (andDelete) {
if (!fs::is_symlink(fromStatus))
fs::permissions(from.path(), fs::perms::owner_write, fs::perm_options::add | fs::perm_options::nofollow);
fs::remove(from.path());
}
}
void renameFile(const Path & oldName, const Path & newName)
{
fs::rename(oldName, newName);
}
void moveFile(const Path & oldName, const Path & newName)
{
try {
renameFile(oldName, newName);
} catch (fs::filesystem_error & e) {
auto oldPath = fs::path(oldName);
auto newPath = fs::path(newName);
// For the move to be as atomic as possible, copy to a temporary
// directory
fs::path temp = createTempDir(newPath.parent_path(), "rename-tmp");
Finally removeTemp = [&]() { fs::remove(temp); };
auto tempCopyTarget = temp / "copy-target";
if (e.code().value() == EXDEV) {
fs::remove(newPath);
warn("Cant rename %s as %s, copying instead", oldName, newName);
copy(fs::directory_entry(oldPath), tempCopyTarget, true);
renameFile(tempCopyTarget, newPath);
}
}
}
}

View file

@ -193,7 +193,11 @@ JSONObject JSONPlaceholder::object()
JSONPlaceholder::~JSONPlaceholder() JSONPlaceholder::~JSONPlaceholder()
{ {
assert(!first || std::uncaught_exceptions()); if (first) {
assert(std::uncaught_exceptions());
if (state->stack != 0)
write(nullptr);
}
} }
} }

View file

@ -111,6 +111,9 @@ public:
virtual std::optional<char> ask(std::string_view s) virtual std::optional<char> ask(std::string_view s)
{ return {}; } { return {}; }
virtual void setPrintBuildLogs(bool printBuildLogs)
{ }
}; };
ActivityId getCurActivity(); ActivityId getCurActivity();

View file

@ -48,24 +48,9 @@ FdSink::~FdSink()
} }
size_t threshold = 256 * 1024 * 1024;
static void warnLargeDump()
{
warn("dumping very large path (> 256 MiB); this may run out of memory");
}
void FdSink::write(std::string_view data) void FdSink::write(std::string_view data)
{ {
written += data.size(); written += data.size();
static bool warned = false;
if (warn && !warned) {
if (written > threshold) {
warnLargeDump();
warned = true;
}
}
try { try {
writeFull(fd, data); writeFull(fd, data);
} catch (SysError & e) { } catch (SysError & e) {
@ -448,11 +433,6 @@ Error readError(Source & source)
void StringSink::operator () (std::string_view data) void StringSink::operator () (std::string_view data)
{ {
static bool warned = false;
if (!warned && s.size() > threshold) {
warnLargeDump();
warned = true;
}
s.append(data); s.append(data);
} }

View file

@ -97,19 +97,17 @@ protected:
struct FdSink : BufferedSink struct FdSink : BufferedSink
{ {
int fd; int fd;
bool warn = false;
size_t written = 0; size_t written = 0;
FdSink() : fd(-1) { } FdSink() : fd(-1) { }
FdSink(int fd) : fd(fd) { } FdSink(int fd) : fd(fd) { }
FdSink(FdSink&&) = default; FdSink(FdSink&&) = default;
FdSink& operator=(FdSink && s) FdSink & operator=(FdSink && s)
{ {
flush(); flush();
fd = s.fd; fd = s.fd;
s.fd = -1; s.fd = -1;
warn = s.warn;
written = s.written; written = s.written;
return *this; return *this;
} }

View file

@ -508,61 +508,6 @@ void deletePath(const Path & path, uint64_t & bytesFreed)
} }
static Path tempName(Path tmpRoot, const Path & prefix, bool includePid,
int & counter)
{
tmpRoot = canonPath(tmpRoot.empty() ? getEnv("TMPDIR").value_or("/tmp") : tmpRoot, true);
if (includePid)
return (format("%1%/%2%-%3%-%4%") % tmpRoot % prefix % getpid() % counter++).str();
else
return (format("%1%/%2%-%3%") % tmpRoot % prefix % counter++).str();
}
Path createTempDir(const Path & tmpRoot, const Path & prefix,
bool includePid, bool useGlobalCounter, mode_t mode)
{
static int globalCounter = 0;
int localCounter = 0;
int & counter(useGlobalCounter ? globalCounter : localCounter);
while (1) {
checkInterrupt();
Path tmpDir = tempName(tmpRoot, prefix, includePid, counter);
if (mkdir(tmpDir.c_str(), mode) == 0) {
#if __FreeBSD__
/* Explicitly set the group of the directory. This is to
work around around problems caused by BSD's group
ownership semantics (directories inherit the group of
the parent). For instance, the group of /tmp on
FreeBSD is "wheel", so all directories created in /tmp
will be owned by "wheel"; but if the user is not in
"wheel", then "tar" will fail to unpack archives that
have the setgid bit set on directories. */
if (chown(tmpDir.c_str(), (uid_t) -1, getegid()) != 0)
throw SysError("setting group of directory '%1%'", tmpDir);
#endif
return tmpDir;
}
if (errno != EEXIST)
throw SysError("creating directory '%1%'", tmpDir);
}
}
std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix)
{
Path tmpl(getEnv("TMPDIR").value_or("/tmp") + "/" + prefix + ".XXXXXX");
// Strictly speaking, this is UB, but who cares...
// FIXME: use O_TMPFILE.
AutoCloseFD fd(mkstemp((char *) tmpl.c_str()));
if (!fd)
throw SysError("creating temporary file '%s'", tmpl);
closeOnExec(fd.get());
return {std::move(fd), tmpl};
}
std::string getUserName() std::string getUserName()
{ {
auto pw = getpwuid(geteuid()); auto pw = getpwuid(geteuid());
@ -577,6 +522,7 @@ Path getHome()
{ {
static Path homeDir = []() static Path homeDir = []()
{ {
std::optional<std::string> unownedUserHomeDir = {};
auto homeDir = getEnv("HOME"); auto homeDir = getEnv("HOME");
if (homeDir) { if (homeDir) {
// Only use $HOME if doesn't exist or is owned by the current user. // Only use $HOME if doesn't exist or is owned by the current user.
@ -588,8 +534,7 @@ Path getHome()
homeDir.reset(); homeDir.reset();
} }
} else if (st.st_uid != geteuid()) { } else if (st.st_uid != geteuid()) {
warn("$HOME ('%s') is not owned by you, falling back to the one defined in the 'passwd' file", *homeDir); unownedUserHomeDir.swap(homeDir);
homeDir.reset();
} }
} }
if (!homeDir) { if (!homeDir) {
@ -600,6 +545,9 @@ Path getHome()
|| !pw || !pw->pw_dir || !pw->pw_dir[0]) || !pw || !pw->pw_dir || !pw->pw_dir[0])
throw Error("cannot determine user's home directory"); throw Error("cannot determine user's home directory");
homeDir = pw->pw_dir; homeDir = pw->pw_dir;
if (unownedUserHomeDir.has_value() && unownedUserHomeDir != homeDir) {
warn("$HOME ('%s') is not owned by you, falling back to the one defined in the 'passwd' file ('%s')", *unownedUserHomeDir, *homeDir);
}
} }
return *homeDir; return *homeDir;
}(); }();
@ -681,44 +629,6 @@ Paths createDirs(const Path & path)
} }
void createSymlink(const Path & target, const Path & link,
std::optional<time_t> mtime)
{
if (symlink(target.c_str(), link.c_str()))
throw SysError("creating symlink from '%1%' to '%2%'", link, target);
if (mtime) {
struct timeval times[2];
times[0].tv_sec = *mtime;
times[0].tv_usec = 0;
times[1].tv_sec = *mtime;
times[1].tv_usec = 0;
if (lutimes(link.c_str(), times))
throw SysError("setting time of symlink '%s'", link);
}
}
void replaceSymlink(const Path & target, const Path & link,
std::optional<time_t> mtime)
{
for (unsigned int n = 0; true; n++) {
Path tmp = canonPath(fmt("%s/.%d_%s", dirOf(link), n, baseNameOf(link)));
try {
createSymlink(target, tmp, mtime);
} catch (SysError & e) {
if (e.errNo == EEXIST) continue;
throw;
}
if (rename(tmp.c_str(), link.c_str()) != 0)
throw SysError("renaming '%1%' to '%2%'", tmp, link);
break;
}
}
void readFull(int fd, char * buf, size_t count) void readFull(int fd, char * buf, size_t count)
{ {
while (count) { while (count) {

View file

@ -168,6 +168,17 @@ void createSymlink(const Path & target, const Path & link,
void replaceSymlink(const Path & target, const Path & link, void replaceSymlink(const Path & target, const Path & link,
std::optional<time_t> mtime = {}); std::optional<time_t> mtime = {});
void renameFile(const Path & src, const Path & dst);
/**
* Similar to 'renameFile', but fallback to a copy+remove if `src` and `dst`
* are on a different filesystem.
*
* Beware that this might not be atomic because of the copy that happens behind
* the scenes
*/
void moveFile(const Path & src, const Path & dst);
/* Wrappers arount read()/write() that read/write exactly the /* Wrappers arount read()/write() that read/write exactly the
requested number of bytes. */ requested number of bytes. */

View file

@ -401,7 +401,7 @@ static void main_nix_build(int argc, char * * argv)
auto bashDrv = drv->requireDrvPath(); auto bashDrv = drv->requireDrvPath();
pathsToBuild.push_back(DerivedPath::Built { pathsToBuild.push_back(DerivedPath::Built {
.drvPath = bashDrv, .drvPath = bashDrv,
.outputs = {}, .outputs = {"out"},
}); });
pathsToCopy.insert(bashDrv); pathsToCopy.insert(bashDrv);
shellDrv = bashDrv; shellDrv = bashDrv;

View file

@ -940,12 +940,12 @@ static void queryJSON(Globals & globals, std::vector<DrvInfo> & elems, bool prin
JSONObject metaObj = pkgObj.object("meta"); JSONObject metaObj = pkgObj.object("meta");
StringSet metaNames = i.queryMetaNames(); StringSet metaNames = i.queryMetaNames();
for (auto & j : metaNames) { for (auto & j : metaNames) {
auto placeholder = metaObj.placeholder(j);
Value * v = i.queryMeta(j); Value * v = i.queryMeta(j);
if (!v) { if (!v) {
printError("derivation '%s' has invalid meta attribute '%s'", i.queryName(), j); printError("derivation '%s' has invalid meta attribute '%s'", i.queryName(), j);
placeholder.write(nullptr); metaObj.attr(j, nullptr);
} else { } else {
auto placeholder = metaObj.placeholder(j);
PathSet context; PathSet context;
printValueAsJSON(*globals.state, true, *v, noPos, placeholder, context); printValueAsJSON(*globals.state, true, *v, noPos, placeholder, context);
} }

View file

@ -52,9 +52,10 @@ void processExpr(EvalState & state, const Strings & attrPaths,
state.autoCallFunction(autoArgs, v, vRes); state.autoCallFunction(autoArgs, v, vRes);
if (output == okXML) if (output == okXML)
printValueAsXML(state, strict, location, vRes, std::cout, context, noPos); printValueAsXML(state, strict, location, vRes, std::cout, context, noPos);
else if (output == okJSON) else if (output == okJSON) {
printValueAsJSON(state, strict, vRes, v.determinePos(noPos), std::cout, context); printValueAsJSON(state, strict, vRes, v.determinePos(noPos), std::cout, context);
else { std::cout << std::endl;
} else {
if (strict) state.forceValueDeep(vRes); if (strict) state.forceValueDeep(vRes);
vRes.print(state.symbols, std::cout); vRes.print(state.symbols, std::cout);
std::cout << std::endl; std::cout << std::endl;

View file

@ -922,7 +922,7 @@ static void opServe(Strings opFlags, Strings opArgs)
if (GET_PROTOCOL_MINOR(clientVersion) >= 3) if (GET_PROTOCOL_MINOR(clientVersion) >= 3)
out << status.timesBuilt << status.isNonDeterministic << status.startTime << status.stopTime; out << status.timesBuilt << status.isNonDeterministic << status.startTime << status.stopTime;
if (GET_PROTOCOL_MINOR(clientVersion >= 6)) { if (GET_PROTOCOL_MINOR(clientVersion) >= 6) {
worker_proto::write(*store, out, status.builtOutputs); worker_proto::write(*store, out, status.builtOutputs);
} }

View file

@ -44,7 +44,7 @@ flake output attributes:
* `bundlers.<system>.default` * `bundlers.<system>.default`
If an attribute *name* is given, `nix run` tries the following flake If an attribute *name* is given, `nix bundle` tries the following flake
output attributes: output attributes:
* `bundlers.<system>.<name>` * `bundlers.<system>.<name>`

View file

@ -116,7 +116,8 @@ struct CmdEval : MixJSON, InstallableCommand
else if (json) { else if (json) {
JSONPlaceholder jsonOut(std::cout); JSONPlaceholder jsonOut(std::cout);
printValueAsJSON(*state, true, *v, pos, jsonOut, context); printValueAsJSON(*state, true, *v, pos, jsonOut, context, false);
std::cout << std::endl;
} }
else { else {

View file

@ -212,7 +212,8 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON
ANSI_BOLD "Last modified:" ANSI_NORMAL " %s", ANSI_BOLD "Last modified:" ANSI_NORMAL " %s",
std::put_time(std::localtime(&*lastModified), "%F %T")); std::put_time(std::localtime(&*lastModified), "%F %T"));
logger->cout(ANSI_BOLD "Inputs:" ANSI_NORMAL); if (!lockedFlake.lockFile.root->inputs.empty())
logger->cout(ANSI_BOLD "Inputs:" ANSI_NORMAL);
std::unordered_set<std::shared_ptr<Node>> visited; std::unordered_set<std::shared_ptr<Node>> visited;

View file

@ -43,6 +43,7 @@ __dumpEnv() {
local __var_name="${BASH_REMATCH[2]}" local __var_name="${BASH_REMATCH[2]}"
if [[ $__var_name =~ ^BASH_ || \ if [[ $__var_name =~ ^BASH_ || \
$__var_name =~ ^COMP_ || \
$__var_name = _ || \ $__var_name = _ || \
$__var_name = DIRSTACK || \ $__var_name = DIRSTACK || \
$__var_name = EUID || \ $__var_name = EUID || \
@ -54,7 +55,9 @@ __dumpEnv() {
$__var_name = PWD || \ $__var_name = PWD || \
$__var_name = RANDOM || \ $__var_name = RANDOM || \
$__var_name = SHLVL || \ $__var_name = SHLVL || \
$__var_name = SECONDS \ $__var_name = SECONDS || \
$__var_name = EPOCHREALTIME || \
$__var_name = EPOCHSECONDS \
]]; then continue; fi ]]; then continue; fi
if [[ -z $__first ]]; then printf ',\n'; else __first=; fi if [[ -z $__first ]]; then printf ',\n'; else __first=; fi

View file

@ -82,7 +82,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
.shortName = 'L', .shortName = 'L',
.description = "Print full build logs on standard error.", .description = "Print full build logs on standard error.",
.category = loggingCategory, .category = loggingCategory,
.handler = {[&]() {setLogFormat(LogFormat::barWithLogs); }}, .handler = {[&]() { logger->setPrintBuildLogs(true); }},
}); });
addFlag({ addFlag({

View file

@ -11,7 +11,7 @@ them to be rolled back easily.
The default profile used by `nix profile` is `$HOME/.nix-profile`, The default profile used by `nix profile` is `$HOME/.nix-profile`,
which, if it does not exist, is created as a symlink to which, if it does not exist, is created as a symlink to
`/nix/var/nix/profiles/per-user/default` if Nix is invoked by the `/nix/var/nix/profiles/default` if Nix is invoked by the
`root` user, or `/nix/var/nix/profiles/per-user/`*username* otherwise. `root` user, or `/nix/var/nix/profiles/per-user/`*username* otherwise.
You can specify another profile location using `--profile` *path*. You can specify another profile location using `--profile` *path*.

View file

@ -40,6 +40,14 @@ nix-build check.nix -A deterministic --argstr checkBuildId $checkBuildId \
if grep -q 'may not be deterministic' $TEST_ROOT/log; then false; fi if grep -q 'may not be deterministic' $TEST_ROOT/log; then false; fi
checkBuildTempDirRemoved $TEST_ROOT/log checkBuildTempDirRemoved $TEST_ROOT/log
nix build -f check.nix deterministic --rebuild --repeat 1 \
--argstr checkBuildId $checkBuildId --keep-failed --no-link \
2> $TEST_ROOT/log
if grep -q 'checking is not possible' $TEST_ROOT/log; then false; fi
# Repeat is set to 1, ie. nix should build deterministic twice.
if [ "$(grep "checking outputs" $TEST_ROOT/log | wc -l)" -ne 2 ]; then false; fi
checkBuildTempDirRemoved $TEST_ROOT/log
nix-build check.nix -A nondeterministic --argstr checkBuildId $checkBuildId \ nix-build check.nix -A nondeterministic --argstr checkBuildId $checkBuildId \
--no-out-link 2> $TEST_ROOT/log --no-out-link 2> $TEST_ROOT/log
checkBuildTempDirRemoved $TEST_ROOT/log checkBuildTempDirRemoved $TEST_ROOT/log
@ -50,6 +58,12 @@ grep 'may not be deterministic' $TEST_ROOT/log
[ "$status" = "104" ] [ "$status" = "104" ]
checkBuildTempDirRemoved $TEST_ROOT/log checkBuildTempDirRemoved $TEST_ROOT/log
nix build -f check.nix nondeterministic --rebuild --repeat 1 \
--argstr checkBuildId $checkBuildId --keep-failed --no-link \
2> $TEST_ROOT/log || status=$?
grep 'may not be deterministic' $TEST_ROOT/log
checkBuildTempDirRemoved $TEST_ROOT/log
nix-build check.nix -A nondeterministic --argstr checkBuildId $checkBuildId \ nix-build check.nix -A nondeterministic --argstr checkBuildId $checkBuildId \
--no-out-link --check --keep-failed 2> $TEST_ROOT/log || status=$? --no-out-link --check --keep-failed 2> $TEST_ROOT/log || status=$?
grep 'may not be deterministic' $TEST_ROOT/log grep 'may not be deterministic' $TEST_ROOT/log

View file

@ -193,7 +193,7 @@ fi
onError() { onError() {
set +x set +x
echo "$0: test failed at:" >&2 echo "$0: test failed at:" >&2
for ((i = 1; i < 16; i++)); do for ((i = 1; i < ${#BASH_SOURCE[@]}; i++)); do
if [[ -z ${BASH_SOURCE[i]} ]]; then break; fi if [[ -z ${BASH_SOURCE[i]} ]]; then break; fi
echo " ${FUNCNAME[i]} in ${BASH_SOURCE[i]}:${BASH_LINENO[i-1]}" >&2 echo " ${FUNCNAME[i]} in ${BASH_SOURCE[i]}:${BASH_LINENO[i-1]}" >&2
done done