aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/manual/change-authors.yml5
-rw-r--r--doc/manual/rl-next/pipe-operator.md10
-rw-r--r--doc/manual/src/language/operators.md32
-rw-r--r--src/libexpr/parser/grammar.hh7
-rw-r--r--src/libexpr/parser/parser.cc25
-rw-r--r--src/libstore/build/derivation-goal.cc54
-rw-r--r--src/libstore/build/derivation-goal.hh3
-rw-r--r--src/libstore/build/drv-output-substitution-goal.cc12
-rw-r--r--src/libstore/build/goal.cc7
-rw-r--r--src/libstore/build/goal.hh20
-rw-r--r--src/libstore/build/local-derivation-goal.cc15
-rw-r--r--src/libstore/build/substitution-goal.cc16
-rw-r--r--src/libstore/build/worker.cc20
-rw-r--r--src/libstore/build/worker.hh74
-rw-r--r--src/libutil/experimental-features.cc10
-rw-r--r--src/libutil/experimental-features.hh1
-rw-r--r--tests/unit/libexpr/trivial.cc36
17 files changed, 244 insertions, 103 deletions
diff --git a/doc/manual/change-authors.yml b/doc/manual/change-authors.yml
index 630af29ff..e18abada1 100644
--- a/doc/manual/change-authors.yml
+++ b/doc/manual/change-authors.yml
@@ -103,6 +103,11 @@ midnightveil:
ncfavier:
github: ncfavier
+piegames:
+ display_name: piegames
+ forgejo: piegames
+ github: piegamesde
+
puck:
display_name: puck
forgejo: puck
diff --git a/doc/manual/rl-next/pipe-operator.md b/doc/manual/rl-next/pipe-operator.md
new file mode 100644
index 000000000..49dc01308
--- /dev/null
+++ b/doc/manual/rl-next/pipe-operator.md
@@ -0,0 +1,10 @@
+---
+synopsis: Pipe operator `|>` (experimental)
+issues: [fj#438]
+cls: [1654]
+category: Features
+credits: [piegames, horrors]
+---
+
+Implementation of the pipe operator (`|>`) in the language as described in [RFC 148](https://github.com/NixOS/rfcs/pull/148).
+The feature is still marked experimental, enable `--extra-experimental-features pipe-operator` to use it.
diff --git a/doc/manual/src/language/operators.md b/doc/manual/src/language/operators.md
index 6dcdc6eb0..2d4707814 100644
--- a/doc/manual/src/language/operators.md
+++ b/doc/manual/src/language/operators.md
@@ -26,6 +26,8 @@
| Logical conjunction (`AND`) | *bool* `&&` *bool* | left | 12 |
| Logical disjunction (`OR`) | *bool* <code>\|\|</code> *bool* | left | 13 |
| [Logical implication] | *bool* `->` *bool* | none | 14 |
+| \[Experimental\] [Function piping] | *expr* |> *func* | left | 15 |
+| \[Experimental\] [Function piping] | *expr* <| *func* | right | 16 |
[string]: ./values.md#type-string
[path]: ./values.md#type-path
@@ -215,3 +217,33 @@ nix-repl> let f = x: 1; s = { func = f; }; in [ (f == f) (s == s) ]
Equivalent to `!`*b1* `||` *b2*.
[Logical implication]: #logical-implication
+
+## \[Experimental\] Function piping
+
+*This language feature is still experimental and may change at any time. Enable `--extra-experimental-features pipe-operator` to use it.*
+
+Pipes are a dedicated operator for function application, but with reverse order and a lower binding strength.
+This allows you to chain function calls together in way that is more natural to read and requires less parentheses.
+
+`a |> f b |> g` is equivalent to `g (f b a)`.
+`g <| f b <| a` is equivalent to `g (f b a)`.
+
+Example code snippet:
+
+```nix
+defaultPrefsFile = defaultPrefs
+ |> lib.mapAttrsToList (
+ key: value: ''
+ // ${value.reason}
+ pref("${key}", ${builtins.toJSON value.value});
+ ''
+ )
+ |> lib.concatStringsSep "\n"
+ |> pkgs.writeText "nixos-default-prefs.js";
+```
+
+Note how `mapAttrsToList` is called with two arguments (the lambda and `defaultPrefs`),
+but moving the last argument in front of the rest improves the reading flow.
+This is common for functions with long first argument, including all `map`-like functions.
+
+[Function piping]: #experimental-function-piping
diff --git a/src/libexpr/parser/grammar.hh b/src/libexpr/parser/grammar.hh
index 82df63bc5..2c5a3d1be 100644
--- a/src/libexpr/parser/grammar.hh
+++ b/src/libexpr/parser/grammar.hh
@@ -434,6 +434,8 @@ struct op {
struct and_ : _op<TAO_PEGTL_STRING("&&"), 12> {};
struct or_ : _op<TAO_PEGTL_STRING("||"), 13> {};
struct implies : _op<TAO_PEGTL_STRING("->"), 14, kind::rightAssoc> {};
+ struct pipe_right : _op<TAO_PEGTL_STRING("|>"), 15> {};
+ struct pipe_left : _op<TAO_PEGTL_STRING("<|"), 16, kind::rightAssoc> {};
};
struct _expr {
@@ -521,6 +523,7 @@ struct _expr {
app
> {};
+ /* Order matters here. The order is the parsing order, not the precedence order: '<=' must be parsed before '<'. */
struct _binary_operator : sor<
operator_<op::implies>,
operator_<op::update>,
@@ -529,6 +532,8 @@ struct _expr {
operator_<op::minus>,
operator_<op::mul>,
operator_<op::div>,
+ operator_<op::pipe_right>,
+ operator_<op::pipe_left>,
operator_<op::less_eq>,
operator_<op::greater_eq>,
operator_<op::less>,
@@ -649,6 +654,8 @@ struct operator_semantics {
grammar::op::minus,
grammar::op::mul,
grammar::op::div,
+ grammar::op::pipe_right,
+ grammar::op::pipe_left,
has_attr
> op;
};
diff --git a/src/libexpr/parser/parser.cc b/src/libexpr/parser/parser.cc
index 68aa3ddc5..6d496d141 100644
--- a/src/libexpr/parser/parser.cc
+++ b/src/libexpr/parser/parser.cc
@@ -113,6 +113,29 @@ struct ExprState
return std::make_unique<ExprCall>(pos, std::make_unique<ExprVar>(fn), std::move(args));
}
+ std::unique_ptr<Expr> pipe(PosIdx pos, State & state, bool flip = false)
+ {
+ if (!state.xpSettings.isEnabled(Xp::PipeOperator))
+ throw ParseError({
+ .msg = HintFmt("Pipe operator is disabled"),
+ .pos = state.positions[pos]
+ });
+
+ // Reverse the order compared to normal function application: arg |> fn
+ std::unique_ptr<Expr> fn, arg;
+ if (flip) {
+ fn = popExprOnly();
+ arg = popExprOnly();
+ } else {
+ arg = popExprOnly();
+ fn = popExprOnly();
+ }
+ std::vector<std::unique_ptr<Expr>> args{1};
+ args[0] = std::move(arg);
+
+ return std::make_unique<ExprCall>(pos, std::move(fn), std::move(args));
+ }
+
std::unique_ptr<Expr> order(PosIdx pos, bool less, State & state)
{
return call(pos, state.s.lessThan, !less);
@@ -162,6 +185,8 @@ struct ExprState
[&] (Op::concat) { return applyBinary<ExprOpConcatLists>(pos); },
[&] (has_attr & a) { return applyUnary<ExprOpHasAttr>(std::move(a.path)); },
[&] (Op::unary_minus) { return negate(pos, state); },
+ [&] (Op::pipe_right) { return pipe(pos, state, true); },
+ [&] (Op::pipe_left) { return pipe(pos, state); },
})(op)
};
}
diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc
index 17a2b04f1..aa89f9e7d 100644
--- a/src/libstore/build/derivation-goal.cc
+++ b/src/libstore/build/derivation-goal.cc
@@ -174,10 +174,9 @@ Goal::WorkResult DerivationGoal::getDerivation()
return loadDerivation();
}
- addWaitee(worker.makePathSubstitutionGoal(drvPath));
state = &DerivationGoal::loadDerivation;
- return StillAlive{};
+ return WaitForGoals{{worker.makePathSubstitutionGoal(drvPath)}};
}
@@ -268,11 +267,12 @@ Goal::WorkResult DerivationGoal::haveDerivation()
/* We are first going to try to create the invalid output paths
through substitutes. If that doesn't work, we'll build
them. */
+ WaitForGoals result;
if (settings.useSubstitutes && parsedDrv->substitutesAllowed())
for (auto & [outputName, status] : initialOutputs) {
if (!status.wanted) continue;
if (!status.known)
- addWaitee(
+ result.goals.insert(
worker.makeDrvOutputSubstitutionGoal(
DrvOutput{status.outputHash, outputName},
buildMode == bmRepair ? Repair : NoRepair
@@ -280,18 +280,18 @@ Goal::WorkResult DerivationGoal::haveDerivation()
);
else {
auto * cap = getDerivationCA(*drv);
- addWaitee(worker.makePathSubstitutionGoal(
+ result.goals.insert(worker.makePathSubstitutionGoal(
status.known->path,
buildMode == bmRepair ? Repair : NoRepair,
cap ? std::optional { *cap } : std::nullopt));
}
}
- if (waitees.empty()) { /* to prevent hang (no wake-up event) */
+ if (result.goals.empty()) { /* to prevent hang (no wake-up event) */
return outputsSubstitutionTried();
} else {
state = &DerivationGoal::outputsSubstitutionTried;
- return StillAlive{};
+ return result;
}
}
@@ -362,6 +362,8 @@ Goal::WorkResult DerivationGoal::outputsSubstitutionTried()
produced using a substitute. So we have to build instead. */
Goal::WorkResult DerivationGoal::gaveUpOnSubstitution()
{
+ WaitForGoals result;
+
/* At this point we are building all outputs, so if more are wanted there
is no need to restart. */
needRestart = NeedRestartForMoreOutputs::BuildInProgressWillNotNeed;
@@ -373,7 +375,7 @@ Goal::WorkResult DerivationGoal::gaveUpOnSubstitution()
addWaiteeDerivedPath = [&](ref<SingleDerivedPath> inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
if (!inputNode.value.empty())
- addWaitee(worker.makeGoal(
+ result.goals.insert(worker.makeGoal(
DerivedPath::Built {
.drvPath = inputDrv,
.outputs = inputNode.value,
@@ -418,14 +420,14 @@ Goal::WorkResult DerivationGoal::gaveUpOnSubstitution()
if (!settings.useSubstitutes)
throw Error("dependency '%s' of '%s' does not exist, and substitution is disabled",
worker.store.printStorePath(i), worker.store.printStorePath(drvPath));
- addWaitee(worker.makePathSubstitutionGoal(i));
+ result.goals.insert(worker.makePathSubstitutionGoal(i));
}
- if (waitees.empty()) {/* to prevent hang (no wake-up event) */
+ if (result.goals.empty()) {/* to prevent hang (no wake-up event) */
return inputsRealised();
} else {
state = &DerivationGoal::inputsRealised;
- return StillAlive{};
+ return result;
}
}
@@ -466,6 +468,7 @@ Goal::WorkResult DerivationGoal::repairClosure()
}
/* Check each path (slow!). */
+ WaitForGoals result;
for (auto & i : outputClosure) {
if (worker.pathContentsGood(i)) continue;
printError(
@@ -473,9 +476,9 @@ Goal::WorkResult DerivationGoal::repairClosure()
worker.store.printStorePath(i), worker.store.printStorePath(drvPath));
auto drvPath2 = outputsToDrv.find(i);
if (drvPath2 == outputsToDrv.end())
- addWaitee(worker.makePathSubstitutionGoal(i, Repair));
+ result.goals.insert(worker.makePathSubstitutionGoal(i, Repair));
else
- addWaitee(worker.makeGoal(
+ result.goals.insert(worker.makeGoal(
DerivedPath::Built {
.drvPath = makeConstantStorePathRef(drvPath2->second),
.outputs = OutputsSpec::All { },
@@ -483,12 +486,12 @@ Goal::WorkResult DerivationGoal::repairClosure()
bmRepair));
}
- if (waitees.empty()) {
+ if (result.goals.empty()) {
return done(BuildResult::AlreadyValid, assertPathValidity());
}
state = &DerivationGoal::closureRepaired;
- return StillAlive{};
+ return result;
}
@@ -580,10 +583,9 @@ Goal::WorkResult DerivationGoal::inputsRealised()
resolvedDrvGoal = worker.makeDerivationGoal(
pathResolved, wantedOutputs, buildMode);
- addWaitee(resolvedDrvGoal);
state = &DerivationGoal::resolvedFinished;
- return StillAlive{};
+ return WaitForGoals{{resolvedDrvGoal}};
}
std::function<void(const StorePath &, const DerivedPathMap<StringSet>::ChildNode &)> accumInputPaths;
@@ -648,8 +650,7 @@ Goal::WorkResult DerivationGoal::inputsRealised()
slot to become available, since we don't need one if there is a
build hook. */
state = &DerivationGoal::tryToBuild;
- worker.wakeUp(shared_from_this());
- return StillAlive{};
+ return ContinueImmediately{};
}
Goal::WorkResult DerivationGoal::started()
@@ -701,8 +702,7 @@ Goal::WorkResult DerivationGoal::tryToBuild()
if (!actLock)
actLock = std::make_unique<Activity>(*logger, lvlWarn, actBuildWaiting,
fmt("waiting for lock on %s", Magenta(showPaths(lockFiles))));
- worker.waitForAWhile(shared_from_this());
- return StillAlive{};
+ return WaitForAWhile{};
}
actLock.reset();
@@ -753,9 +753,8 @@ Goal::WorkResult DerivationGoal::tryToBuild()
if (!actLock)
actLock = std::make_unique<Activity>(*logger, lvlTalkative, actBuildWaiting,
fmt("waiting for a machine to build '%s'", Magenta(worker.store.printStorePath(drvPath))));
- worker.waitForAWhile(shared_from_this());
outputLocks.unlock();
- return StillAlive{};
+ return WaitForAWhile{};
case rpDecline:
/* We should do it ourselves. */
break;
@@ -765,8 +764,7 @@ Goal::WorkResult DerivationGoal::tryToBuild()
actLock.reset();
state = &DerivationGoal::tryLocalBuild;
- worker.wakeUp(shared_from_this());
- return StillAlive{};
+ return ContinueImmediately{};
}
Goal::WorkResult DerivationGoal::tryLocalBuild() {
@@ -1508,10 +1506,6 @@ Goal::Finished DerivationGoal::done(
buildResult.status = status;
if (ex)
buildResult.errorMsg = fmt("%s", Uncolored(ex->info().msg));
- if (buildResult.status == BuildResult::TimedOut)
- worker.timedOut = true;
- if (buildResult.status == BuildResult::PermanentFailure)
- worker.permanentFailure = true;
mcExpectedBuilds.reset();
mcRunningBuilds.reset();
@@ -1537,6 +1531,10 @@ Goal::Finished DerivationGoal::done(
return Finished{
.result = buildResult.success() ? ecSuccess : ecFailed,
.ex = ex ? std::make_unique<Error>(std::move(*ex)) : nullptr,
+ .permanentFailure = buildResult.status == BuildResult::PermanentFailure,
+ .timedOut = buildResult.status == BuildResult::TimedOut,
+ .hashMismatch = anyHashMismatchSeen,
+ .checkMismatch = anyCheckMismatchSeen,
};
}
diff --git a/src/libstore/build/derivation-goal.hh b/src/libstore/build/derivation-goal.hh
index c43e2aed5..257282308 100644
--- a/src/libstore/build/derivation-goal.hh
+++ b/src/libstore/build/derivation-goal.hh
@@ -107,6 +107,9 @@ struct DerivationGoal : public Goal
*/
NeedRestartForMoreOutputs needRestart = NeedRestartForMoreOutputs::OutputsUnmodifedDontNeed;
+ bool anyHashMismatchSeen = false;
+ bool anyCheckMismatchSeen = false;
+
/**
* See `retrySubstitution`; just for that field.
*/
diff --git a/src/libstore/build/drv-output-substitution-goal.cc b/src/libstore/build/drv-output-substitution-goal.cc
index 62e86e1cc..b41dae5d6 100644
--- a/src/libstore/build/drv-output-substitution-goal.cc
+++ b/src/libstore/build/drv-output-substitution-goal.cc
@@ -41,8 +41,7 @@ Goal::WorkResult DrvOutputSubstitutionGoal::tryNext()
if maxSubstitutionJobs == 0, we still allow a substituter to run. This
prevents infinite waiting. */
if (worker.runningSubstitutions >= std::max(1U, settings.maxSubstitutionJobs.get())) {
- worker.waitForBuildSlot(shared_from_this());
- return StillAlive{};
+ return WaitForSlot{};
}
maintainRunningSubstitutions =
@@ -101,6 +100,7 @@ Goal::WorkResult DrvOutputSubstitutionGoal::realisationFetched()
return tryNext();
}
+ WaitForGoals result;
for (const auto & [depId, depPath] : outputInfo->dependentRealisations) {
if (depId != id) {
if (auto localOutputInfo = worker.store.queryRealisation(depId);
@@ -116,17 +116,17 @@ Goal::WorkResult DrvOutputSubstitutionGoal::realisationFetched()
);
return tryNext();
}
- addWaitee(worker.makeDrvOutputSubstitutionGoal(depId));
+ result.goals.insert(worker.makeDrvOutputSubstitutionGoal(depId));
}
}
- addWaitee(worker.makePathSubstitutionGoal(outputInfo->outPath));
+ result.goals.insert(worker.makePathSubstitutionGoal(outputInfo->outPath));
- if (waitees.empty()) {
+ if (result.goals.empty()) {
return outPathValid();
} else {
state = &DrvOutputSubstitutionGoal::outPathValid;
- return StillAlive{};
+ return result;
}
}
diff --git a/src/libstore/build/goal.cc b/src/libstore/build/goal.cc
index f26c2c671..a4e66d989 100644
--- a/src/libstore/build/goal.cc
+++ b/src/libstore/build/goal.cc
@@ -11,13 +11,6 @@ bool CompareGoalPtrs::operator() (const GoalPtr & a, const GoalPtr & b) const {
}
-void Goal::addWaitee(GoalPtr waitee)
-{
- waitees.insert(waitee);
- waitee->waiters.insert(shared_from_this());
-}
-
-
void Goal::trace(std::string_view s)
{
debug("%1%: %2%", name, s);
diff --git a/src/libstore/build/goal.hh b/src/libstore/build/goal.hh
index dd29b9fc4..5d7bb72b8 100644
--- a/src/libstore/build/goal.hh
+++ b/src/libstore/build/goal.hh
@@ -106,12 +106,28 @@ struct Goal : public std::enable_shared_from_this<Goal>
public:
struct [[nodiscard]] StillAlive {};
+ struct [[nodiscard]] WaitForSlot {};
+ struct [[nodiscard]] WaitForAWhile {};
+ struct [[nodiscard]] ContinueImmediately {};
+ struct [[nodiscard]] WaitForGoals {
+ Goals goals;
+ };
struct [[nodiscard]] Finished {
ExitCode result;
std::unique_ptr<Error> ex;
+ bool permanentFailure = false;
+ bool timedOut = false;
+ bool hashMismatch = false;
+ bool checkMismatch = false;
};
- struct [[nodiscard]] WorkResult : std::variant<StillAlive, Finished>
+ struct [[nodiscard]] WorkResult : std::variant<
+ StillAlive,
+ WaitForSlot,
+ WaitForAWhile,
+ ContinueImmediately,
+ WaitForGoals,
+ Finished>
{
WorkResult() = delete;
using variant::variant;
@@ -133,8 +149,6 @@ public:
virtual WorkResult work() = 0;
- void addWaitee(GoalPtr waitee);
-
virtual void waiteeDone(GoalPtr waitee) { }
virtual WorkResult handleChildOutput(int fd, std::string_view data)
diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc
index c435a3c00..8b640e4ad 100644
--- a/src/libstore/build/local-derivation-goal.cc
+++ b/src/libstore/build/local-derivation-goal.cc
@@ -158,9 +158,8 @@ Goal::WorkResult LocalDerivationGoal::tryLocalBuild()
unsigned int curBuilds = worker.getNrLocalBuilds();
if (curBuilds >= settings.maxBuildJobs) {
state = &DerivationGoal::tryToBuild;
- worker.waitForBuildSlot(shared_from_this());
outputLocks.unlock();
- return StillAlive{};
+ return WaitForSlot{};
}
assert(derivationType);
@@ -202,8 +201,7 @@ Goal::WorkResult LocalDerivationGoal::tryLocalBuild()
if (!actLock)
actLock = std::make_unique<Activity>(*logger, lvlWarn, actBuildWaiting,
fmt("waiting for a free build user ID for '%s'", Magenta(worker.store.printStorePath(drvPath))));
- worker.waitForAWhile(shared_from_this());
- return StillAlive{};
+ return WaitForAWhile{};
}
}
@@ -237,8 +235,9 @@ Goal::WorkResult LocalDerivationGoal::tryLocalBuild()
} catch (BuildError & e) {
outputLocks.unlock();
buildUser.reset();
- worker.permanentFailure = true;
- return done(BuildResult::InputRejected, {}, std::move(e));
+ auto report = done(BuildResult::InputRejected, {}, std::move(e));
+ report.permanentFailure = true;
+ return report;
}
/* This state will be reached when we get EOF on the child's
@@ -2197,7 +2196,7 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
if (wanted != got) {
/* Throw an error after registering the path as
valid. */
- worker.hashMismatch = true;
+ anyHashMismatchSeen = true;
// XXX: shameless layering violation hack that makes the hash mismatch error at least not utterly worthless
auto guessedUrl = getOr(drv->env, "urls", getOr(drv->env, "url", "(unknown)"));
delayedException = std::make_exception_ptr(
@@ -2284,7 +2283,7 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
if (!worker.store.isValidPath(newInfo.path)) continue;
ValidPathInfo oldInfo(*worker.store.queryPathInfo(newInfo.path));
if (newInfo.narHash != oldInfo.narHash) {
- worker.checkMismatch = true;
+ anyCheckMismatchSeen = true;
if (settings.runDiffHook || settings.keepFailed) {
auto dst = worker.store.toRealPath(finalDestPath + checkSuffix);
deletePath(dst);
diff --git a/src/libstore/build/substitution-goal.cc b/src/libstore/build/substitution-goal.cc
index fb2949fd0..67a5f20bb 100644
--- a/src/libstore/build/substitution-goal.cc
+++ b/src/libstore/build/substitution-goal.cc
@@ -152,15 +152,16 @@ Goal::WorkResult PathSubstitutionGoal::tryNext()
/* To maintain the closure invariant, we first have to realise the
paths referenced by this one. */
+ WaitForGoals result;
for (auto & i : info->references)
if (i != storePath) /* ignore self-references */
- addWaitee(worker.makePathSubstitutionGoal(i));
+ result.goals.insert(worker.makePathSubstitutionGoal(i));
- if (waitees.empty()) {/* to prevent hang (no wake-up event) */
+ if (result.goals.empty()) {/* to prevent hang (no wake-up event) */
return referencesValid();
} else {
state = &PathSubstitutionGoal::referencesValid;
- return StillAlive{};
+ return result;
}
}
@@ -181,8 +182,7 @@ Goal::WorkResult PathSubstitutionGoal::referencesValid()
assert(worker.store.isValidPath(i));
state = &PathSubstitutionGoal::tryToRun;
- worker.wakeUp(shared_from_this());
- return StillAlive{};
+ return ContinueImmediately{};
}
@@ -194,8 +194,7 @@ Goal::WorkResult PathSubstitutionGoal::tryToRun()
if maxSubstitutionJobs == 0, we still allow a substituter to run. This
prevents infinite waiting. */
if (worker.getNrSubstitutions() >= std::max(1U, (unsigned int) settings.maxSubstitutionJobs)) {
- worker.waitForBuildSlot(shared_from_this());
- return StillAlive{};
+ return WaitForSlot{};
}
maintainRunningSubstitutions = std::make_unique<MaintainCount<uint64_t>>(worker.runningSubstitutions);
@@ -256,8 +255,7 @@ Goal::WorkResult PathSubstitutionGoal::finished()
/* Try the next substitute. */
state = &PathSubstitutionGoal::tryNext;
- worker.wakeUp(shared_from_this());
- return StillAlive{};
+ return ContinueImmediately{};
}
worker.markContentsGood(storePath);
diff --git a/src/libstore/build/worker.cc b/src/libstore/build/worker.cc
index 135cfecf5..aaa4e8907 100644
--- a/src/libstore/build/worker.cc
+++ b/src/libstore/build/worker.cc
@@ -21,10 +21,6 @@ Worker::Worker(Store & store, Store & evalStore)
nrLocalBuilds = 0;
nrSubstitutions = 0;
lastWokenUp = steady_time_point::min();
- permanentFailure = false;
- timedOut = false;
- hashMismatch = false;
- checkMismatch = false;
}
@@ -145,6 +141,11 @@ void Worker::goalFinished(GoalPtr goal, Goal::Finished & f)
assert(!goal->exitCode.has_value());
goal->exitCode = f.result;
+ permanentFailure |= f.permanentFailure;
+ timedOut |= f.timedOut;
+ hashMismatch |= f.hashMismatch;
+ checkMismatch |= f.checkMismatch;
+
if (f.ex) {
if (!goal->waiters.empty())
logError(f.ex->info());
@@ -187,6 +188,15 @@ void Worker::handleWorkResult(GoalPtr goal, Goal::WorkResult how)
std::visit(
overloaded{
[&](Goal::StillAlive) {},
+ [&](Goal::WaitForSlot) { waitForBuildSlot(goal); },
+ [&](Goal::WaitForAWhile) { waitForAWhile(goal); },
+ [&](Goal::ContinueImmediately) { wakeUp(goal); },
+ [&](Goal::WaitForGoals & w) {
+ for (auto & dep : w.goals) {
+ goal->waitees.insert(dep);
+ dep->waiters.insert(goal);
+ }
+ },
[&](Goal::Finished & f) { goalFinished(goal, f); },
},
how
@@ -521,7 +531,7 @@ void Worker::waitForInput()
if (rd == 0 || (rd == -1 && errno == EIO)) {
debug("%1%: got EOF", goal->getName());
goal->handleEOF(k);
- wakeUp(goal);
+ handleWorkResult(goal, Goal::ContinueImmediately{});
j->fds.erase(k);
} else if (rd == -1) {
if (errno != EINTR)
diff --git a/src/libstore/build/worker.hh b/src/libstore/build/worker.hh
index 5af93b49e..a741b2672 100644
--- a/src/libstore/build/worker.hh
+++ b/src/libstore/build/worker.hh
@@ -105,35 +105,59 @@ private:
*/
std::map<StorePath, bool> pathContentsGoodCache;
- void goalFinished(GoalPtr goal, Goal::Finished & f);
- void handleWorkResult(GoalPtr goal, Goal::WorkResult how);
-
-public:
-
- const Activity act;
- const Activity actDerivations;
- const Activity actSubstitutions;
-
/**
* Set if at least one derivation had a BuildError (i.e. permanent
* failure).
*/
- bool permanentFailure;
+ bool permanentFailure = false;
/**
* Set if at least one derivation had a timeout.
*/
- bool timedOut;
+ bool timedOut = false;
/**
* Set if at least one derivation fails with a hash mismatch.
*/
- bool hashMismatch;
+ bool hashMismatch = false;
/**
* Set if at least one derivation is not deterministic in check mode.
*/
- bool checkMismatch;
+ bool checkMismatch = false;
+
+ void goalFinished(GoalPtr goal, Goal::Finished & f);
+ void handleWorkResult(GoalPtr goal, Goal::WorkResult how);
+
+ /**
+ * Put `goal` to sleep until a build slot becomes available (which
+ * might be right away).
+ */
+ void waitForBuildSlot(GoalPtr goal);
+
+ /**
+ * Wait for a few seconds and then retry this goal. Used when
+ * waiting for a lock held by another process. This kind of
+ * polling is inefficient, but POSIX doesn't really provide a way
+ * to wait for multiple locks in the main select() loop.
+ */
+ void waitForAWhile(GoalPtr goal);
+
+ /**
+ * Wake up a goal (i.e., there is something for it to do).
+ */
+ void wakeUp(GoalPtr goal);
+
+ /**
+ * Wait for input to become available.
+ */
+ void waitForInput();
+
+public:
+
+ const Activity act;
+ const Activity actDerivations;
+ const Activity actSubstitutions;
Store & store;
Store & evalStore;
@@ -206,11 +230,6 @@ public:
void removeGoal(GoalPtr goal);
/**
- * Wake up a goal (i.e., there is something for it to do).
- */
- void wakeUp(GoalPtr goal);
-
- /**
* Return the number of local build processes currently running (but not
* remote builds via the build hook).
*/
@@ -234,29 +253,10 @@ public:
void childTerminated(Goal * goal);
/**
- * Put `goal` to sleep until a build slot becomes available (which
- * might be right away).
- */
- void waitForBuildSlot(GoalPtr goal);
-
- /**
- * Wait for a few seconds and then retry this goal. Used when
- * waiting for a lock held by another process. This kind of
- * polling is inefficient, but POSIX doesn't really provide a way
- * to wait for multiple locks in the main select() loop.
- */
- void waitForAWhile(GoalPtr goal);
-
- /**
* Loop until the specified top-level goals have finished.
*/
void run(const Goals & topGoals);
- /**
- * Wait for input to become available.
- */
- void waitForInput();
-
/***
* The exit status in case of failure.
*
diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc
index 3a834293a..15a18c770 100644
--- a/src/libutil/experimental-features.cc
+++ b/src/libutil/experimental-features.cc
@@ -167,6 +167,16 @@ constexpr std::array<ExperimentalFeatureDetails, numXpFeatures> xpFeatureDetails
)",
},
{
+ .tag = Xp::PipeOperator,
+ .name = "pipe-operator",
+ .description = R"(
+ Enable new operators for function application to "pipe" arguments through a chain of functions similar to `lib.pipe`.
+ This implementation is based on Nix [RFC 148](https://github.com/NixOS/rfcs/pull/148).
+
+ Tracking issue: https://git.lix.systems/lix-project/lix/issues/438
+ )",
+ },
+ {
.tag = Xp::FetchClosure,
.name = "fetch-closure",
.description = R"(
diff --git a/src/libutil/experimental-features.hh b/src/libutil/experimental-features.hh
index 38889e7bc..121318d23 100644
--- a/src/libutil/experimental-features.hh
+++ b/src/libutil/experimental-features.hh
@@ -21,6 +21,7 @@ enum struct ExperimentalFeature
NixCommand,
RecursiveNix,
NoUrlLiterals,
+ PipeOperator,
FetchClosure,
ReplFlake,
AutoAllocateUids,
diff --git a/tests/unit/libexpr/trivial.cc b/tests/unit/libexpr/trivial.cc
index 19b62aff8..c984657fd 100644
--- a/tests/unit/libexpr/trivial.cc
+++ b/tests/unit/libexpr/trivial.cc
@@ -210,4 +210,40 @@ namespace nix {
TEST_F(TrivialExpressionTest, orCantBeUsed) {
ASSERT_THROW(eval("let or = 1; in or"), Error);
}
+
+ // pipes are gated behind an experimental feature flag
+ TEST_F(TrivialExpressionTest, pipeDisabled) {
+ ASSERT_THROW(eval("let add = l: r: l + r; in ''a'' |> add ''b''"), Error);
+ ASSERT_THROW(eval("let add = l: r: l + r; in ''a'' <| add ''b''"), Error);
+ }
+
+ TEST_F(TrivialExpressionTest, pipeRight) {
+ ExperimentalFeatureSettings mockXpSettings;
+ mockXpSettings.set("experimental-features", "pipe-operator");
+
+ auto v = eval("let add = l: r: l + r; in ''a'' |> add ''b''", true, mockXpSettings);
+ ASSERT_THAT(v, IsStringEq("ba"));
+ v = eval("let add = l: r: l + r; in ''a'' |> add ''b'' |> add ''c''", true, mockXpSettings);
+ ASSERT_THAT(v, IsStringEq("cba"));
+ }
+
+ TEST_F(TrivialExpressionTest, pipeLeft) {
+ ExperimentalFeatureSettings mockXpSettings;
+ mockXpSettings.set("experimental-features", "pipe-operator");
+
+ auto v = eval("let add = l: r: l + r; in add ''a'' <| ''b''", true, mockXpSettings);
+ ASSERT_THAT(v, IsStringEq("ab"));
+ v = eval("let add = l: r: l + r; in add ''a'' <| add ''b'' <| ''c''", true, mockXpSettings);
+ ASSERT_THAT(v, IsStringEq("abc"));
+ }
+
+ TEST_F(TrivialExpressionTest, pipeMixed) {
+ ExperimentalFeatureSettings mockXpSettings;
+ mockXpSettings.set("experimental-features", "pipe-operator");
+
+ auto v = eval("let add = l: r: l + r; in add ''a'' <| ''b'' |> add ''c''", true, mockXpSettings);
+ ASSERT_THAT(v, IsStringEq("acb"));
+ v = eval("let add = l: r: l + r; in ''a'' |> add <| ''c''", true, mockXpSettings);
+ ASSERT_THAT(v, IsStringEq("ac"));
+ }
} /* namespace nix */