Compare commits

...
Author SHA1 Message Date
Jade Lovelace 9b8d10daa7 jade wip
Change-Id: I4d5a0bebeeefc35dac0bbc065c104c70c24aae17
2025-12-11 14:35:33 -08:00
Jade Lovelace 6f483e5f52 refactor: use std::unique_ptr for libarchive state, remove destructor
Change-Id: Ib16eefc17ae53874b295476fae515305525a7f2b
2025-12-10 14:29:35 -08:00
Tom Hubrecht 36a61d922c lix-clang-tidy: Avoid using llvm::Twine
> A Twine is not intended for use directly and should not be stored

Change-Id: Ie243d109bc3feb75867ed748c5e4cbcddfafd3e1
2025-12-09 17:13:36 +01:00
Tom Hubrecht 5d21b8262b clang-tidy: enable bugprone-multi-level-implicit-pointer-conversion
The default clang version in nixos 25.05 was llvm 19, we are now even
past that

Change-Id: Ieb62616fb87c4d2a8d892136a7164822aa1eceb6
2025-12-09 14:51:43 +00:00
Tom Hubrecht 50c47c340f misc: Remove unused private field, move unused variable in #if, remove virtual specifier
Those are new warnings that come with a more up-to-date llvm

Fixes #1066

Co-authored-by: eldritch horrors <pennae@lix.systems>

Change-Id: I305d0d810d12a5e8d31c7d89e0cdb3a82df71556
2025-12-09 14:29:35 +01:00
Qyriad 2c68244142 build/functional2: don't require installation for tests to run correctly
Change-Id: I9a2bd83c09c8a009e683c73bf2a669036a6a6964
2025-12-09 11:13:55 +01:00
Kate Temkin 15969c51c3 legacy/build-remote: fix a missing format string argument
Change-Id: Ic3156695a7f41367d413f2da3b5791d489576d47
2025-12-09 09:47:21 +00:00
Tom Hubrecht 3db533c637 clang-tidy: enable bugprone-implicit-widening-of-multiplication-result
Change-Id: I7c7bae6c27aa59da23097ec89305cfc60ec9e8e3
2025-12-09 08:32:37 +00:00
Qyriad b2bcd99d59 functional2: fix bug in physical_store_path_for()
I guess on aarch64-darwin dirs.store_dir can be a str?

Change-Id: If4baa1f48f641775c6e3ab9ce7a58e476a6a6964
2025-12-08 19:07:44 +01:00
Alois Wohlschlager a99842e0af nix-eval-jobs: asyncify the coordinator
Change-Id: I62007ed4d43029a4c793c0152c06490d6a6a6964
2025-12-08 18:32:12 +01:00
Raito Bezarius b5a8720a47 legacy/nix-shell: export NIX_LOG_FD
As far as I can tell, there's no harm to let know a nix-shell invocation
about NIX_LOG_FD being stderr.

Fixes #336.

Change-Id: Ifdb3591813251d4bc481158d28dc18e1489de72c
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-12-08 09:12:40 +00:00
Raito Bezarius 908acdfa2f nix3-develop: export NIX_LOG_FD
As far as I can tell, there's no harm to let stdenv know that they can
write to file descriptor 2 which is stderr inside of a nix3-develop
shell.

Contributes towards #336.

Change-Id: I51dcbcbc19a1698a0d1255f9d943d9ebd15d115d
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-12-08 09:12:40 +00:00
Maximilian Bosch be3e4bf78e tests/functional/output-cycles: move to functional2
Change-Id: I682f62a695ba7722e2b3063bf831fc50a19eb153
2025-12-07 21:47:10 +01:00
Maximilian Bosch 0a5f474a25 libstore: fix reporting output cycles on drvs with references to other drvs
Closes #1064

The culprit here is that `genGraphString` is only invoked with the
store-paths associated with the outputs of the derivation, so when
filling `dependents`, the `graph_data.find(p)` call would return the end
of the iterator when doing this for references to other store-paths.

As a result, the code wrote information behind the graph data-structure
causing a corruption. For me, this resulted in a SIGSEGV most of the
time and in a few cases in an uncaught `map::at`-exception as reported
by Niklas.

This patch changes two aspects of the original implementation:

* When filling `dependents` in the node-set, use `map.at()` instead of
  `map.find()->second`. The latter doesn't make any sense and was the
  cause of corrupting memory. The `at` would've made it far easier to
  spot this in the first place.

* Filter out store-paths that don't belong to a different output of the
  derivation when creating `outputGraph`. This variable is used on two
  places, `genGraphString` and for topological sorting.

  The latter already filters out store-paths from a different drv, so
  this is happening now when creating the variable in the first place
  such that `genGraphString` never ends up with corrupt data in the
  first place. This is the actual bugfix.

Implemented a regression-test for this case to be sure.

Change-Id: Ie02144d89c32b0a776cb1ece0601d0229315ebc3
2025-12-07 21:47:09 +01:00
eldritch horrors 6410748c9f libcmd: add raw arg access to legacy commands
we'll need this to modify argv for socket-activated daemons. this is our
replacement for the old savedArgv mechanism that was unscoped and fucky.

Change-Id: Ie048eb8ea99f1c9cd627a051292c836c83197068
2025-12-07 18:49:34 +00:00
eldritch horrors aba740a276 libcmd: remove unused savedArgv
this was only used in the pre-exec daemon days.

Change-Id: I3bbb113f9940e6980f01af60e6614a9656b0fd03
2025-12-07 18:49:34 +00:00
eldritch horrors 1e8f7c7c76 nix/daemon: remove settings copy from parent
the parent daemon does not change any settings before starting a child,
so there's nothing we may want to change that is not already set by the
config file. this also doesn't prevent changes of the config file being
applied to daemons where we do not expect it since it'll only restore a
setting to the parents' value if the child also has an override for it.

Change-Id: Ic5a9ef13458c103ec9979cb187ba8d3ce5e1e719
2025-12-07 18:49:34 +00:00
Qyriad 400b55a410 nix-eval-jobs: use C++23 to match Lix
Change-Id: Ia770f849fd43b360fc641d478abe084e6a6a6964
2025-12-07 18:34:24 +01:00
Qyriad 0714a21b03 perl-bindings: use C++23 to match Lix
Lix changed to C++23 in 4ea8c9d61¹, but the perl subproject wasn't
updated with it. This commit corrects that.

[1]: 4ea8c9d643

Change-Id: I5464138d206d87cf25762720b147487d6a6a6964
2025-12-07 18:34:24 +01:00
Commentator2.0andQyriad 0bcaffa640 functional2: add a hash_path shortcut to Nix
Co-authored-by: Qyriad <qyriad@qyriad.me>

Change-Id: If0b9213d264b60e687ceb2d4d232d23f6a6a6964
2025-12-07 15:34:03 +01:00
Qyriad f6f03d0a52 functional2: add helper to get actual store paths to Nix fixture
Co-authored-by: Commentator2.0 <lix@crystal-cavern.systems>

Change-Id: I13f8ba9d59ae863cf08109a01c638c7f6a6a6964
2025-12-07 15:34:03 +01:00
Tom Hubrecht 8ebe893854 clang-tidy: enable lix-fixincludes
The reorganization has been done

Change-Id: Ib8eec6656f375f8329fe4caefd009369aa36d8f2
2025-12-07 14:05:39 +00:00
Tom Hubrecht 1a304964aa package: Add libllvm to the inputs when linting
This should allow better stacktraces when clang-tidy fails miserably on
aarch64 machines

Change-Id: Ia795cf1dc012bd8f06b429d97184b88c80dd72cb
2025-12-07 11:40:03 +01:00
Tom Hubrecht 543ae33d26 functional2: Increase timeout in the cycles detection test
On a busy CI the 1s timeout is not always enough

Change-Id: Ie39be01ae8818df8c9026d28907f5b145a452e3b
2025-12-06 22:33:19 +00:00
Raito Bezarius 6928c6f952 libstore/store-api: offer more information about drv parsing errors
We offer the three usual options: verify, repair then delete and direct
people to report us corruption so we can fix if this is induced by us.

Fixes #447.

Change-Id: I0df61769d732d227333c206f312857c0593f7bce
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-12-06 20:30:38 +00:00
Raito Bezarius 7f32b71b6b libstore/derivations: expand error message for the first misparse
If the derivation does not start with D, do not return a simple
"expected string 'D'" error but a full error message.

This contributes towards #447.

Change-Id: Iee05f3918e4cc43e79f244ab2fd64a52cf2bb6d2
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-12-06 20:30:38 +00:00
Raito Bezarius d2ca1810b1 nix/upgrade-nix: disallow daemon connections for the store
Prior to I6a6a6964d2b5ad47ae5ea9eb11af9b6373ce2141 — `sudo nix
upgrade-nix` would perform direct store access.

This ensured a certain number of desireable properties for upgrading the
Lix binary itself.

We re-introduce direct store access for upgrading Lix binaries.

Fixes #1060.

Change-Id: I523c4d3023ed5fe9eff8fde9a266c56a0de47d8c
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-12-06 20:30:15 +00:00
Raito Bezarius 7fbb366ce2 lix/legacy/nix-build: save up 30ish chars in $NIX_BUILD_TOP
Alternative to cl/4661 discussed in #1044.

It can be assumed that `$tmpdir/build-top` can be created safely without
any risk, this way, we don't need to reuse the random directory creation
primitive.

Fixes #1044.

Change-Id: Iec52477f3047fc40959b183c607312d5a40fc8c9
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-12-06 19:48:08 +01:00
56 changed files with 647 additions and 320 deletions
+2 -7
View File
@@ -8,16 +8,12 @@ Checks:
- -bugprone-narrowing-conversions
# kind of nonsense
- -bugprone-easily-swappable-parameters
# too many warnings for now
- -bugprone-implicit-widening-of-multiplication-result
# Lix's exception handling is Questionable
- -bugprone-empty-catch
# many warnings
- -bugprone-unchecked-optional-access
# many warnings, seems like a questionable lint
- -bugprone-branch-clone
# extremely noisy before clang 19: https://github.com/llvm/llvm-project/issues/93959
- -bugprone-multi-level-implicit-pointer-conversion
# we don't compile out our asserts
- -bugprone-assert-side-effect
# FIXME(jade): figure out if this warning is any good
@@ -29,9 +25,6 @@ Checks:
# crimes must be appropriately declared as crimes
- cppcoreguidelines-pro-type-cstyle-cast
- lix-*
# This can not yet be applied to Lix itself since we need to do source
# reorganization so that lix/ include paths work.
- -lix-fixincludes
# This lint is included as an example, but the lib function it replaces is
# already gone.
- -lix-hasprefixsuffix
@@ -40,3 +33,5 @@ Checks:
CheckOptions:
bugprone-reserved-identifier.AllowedIdentifiers: '__asan_default_options'
bugprone-unused-return-value.AllowCastToVoid: true
ExtraArgs: ["-Werror=unnecessary-virtual-specifier"]
@@ -0,0 +1,13 @@
---
synopsis: "Nix shells' $NIX_BUILD_TOP are shorter"
cls: [4663]
issues: [fj#1044]
category: "Fixes"
credits: [raito]
---
Following the changes in 2.94.0 to shorten build directory paths, aimed at [resolving UNIX domain socket length issues](https://gerrit.lix.systems/c/lix/+/4168/13) and [improving nix-shell](https://git.lix.systems/lix-project/lix/issues/940), we inadvertently introduced an excessively long path for the `$NIX_BUILD_TOP` environment variable used by Nix shells (their effective temporary `/build` directory).
To fix this, we replaced the `build-top-$HASH` directory name with simply `build-top`, reducing these paths by at least 30 characters.
We also added a test to ensure that Nix shells do not introduce more than 50 extra characters relative to their base directory (e.g., `/tmp` when `$TMPDIR` is not set).
@@ -0,0 +1,14 @@
---
synopsis: "Shells supports $NIX_LOG_FD now"
cls: [4694, 4695]
issues: [fj#336]
category: "Improvements"
credits: [raito]
---
Lix's "debugging" shells (`nix3-develop` and `nix-shell`) now supports
`$NIX_LOG_FD` environment variable.
This means that [hook logging in
stdenv](https://github.com/NixOS/nixpkgs/pull/310387) appears while debugging
derivations via `nix3-develop` or `nix-shell`.
+1 -1
View File
@@ -527,7 +527,7 @@ kj::Promise<void> AcceptedBuild::runImpl(RunContext context)
AIO().timeoutAfter(15 * kj::MINUTES, lockFileAsync(uploadLock.get(), ltWrite))
);
if (!result) {
printError("somebody is hogging the upload lock for '%s', continuing...");
printError("somebody is hogging the upload lock for '%s', continuing...", storeUri);
}
}
+9 -1
View File
@@ -192,7 +192,11 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
throw UsageError("'-p' and '-E' are mutually exclusive");
AutoDelete tmpDir(createTempDir(myName));
AutoDelete buildTopTmpDir(createTempSubdir(tmpDir, "build-top"));
// NOTE: we assume there's no `build-top` directory created inside of `tmpDir` and we have
// ownership of this.
auto buildTopTmpDir = tmpDir + "/build-top";
createDirs(buildTopTmpDir);
if (outLink.empty())
outLink = (Path) tmpDir + "/result";
@@ -434,6 +438,10 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
+ 1
);
// We re-export similarly to what occurs inside of a derivation goal `NIX_LOG_FD` to stderr.
// So that stdenv hooks that logs information can be observed inside this debugging tool.
env["NIX_LOG_FD"] = "2";
// Don't use defaultTempDir() here! We want to preserve the user's TMPDIR for the shell
env["NIX_BUILD_TOP"] = env["TMPDIR"] = env["TEMPDIR"] = env["TMP"] = env["TEMP"] =
getEnvNonEmpty("TMPDIR").value_or(buildTopTmpDir);
-2
View File
@@ -15,8 +15,6 @@ namespace nix {
extern std::string programPath;
extern char * * savedArgv;
class EvalState;
struct Pos;
class Store;
+17 -3
View File
@@ -5,18 +5,32 @@
#include <functional>
#include <list>
#include <map>
#include <span>
#include <string>
namespace nix {
typedef std::function<int(AsyncIoRoot &, std::string, std::list<std::string>)> MainFunction;
struct LegacyCommandRegistry
{
using LegacyCommandMap = std::map<std::string, MainFunction>;
typedef std::function<int(AsyncIoRoot &, std::string, std::list<std::string>)> MainFunction;
typedef std::function<
int(AsyncIoRoot &, std::string, std::list<std::string>, std::span<char *>)>
RawMainFunction;
using LegacyCommandMap = std::map<std::string, RawMainFunction>;
static LegacyCommandMap * commands;
static void add(const std::string & name, MainFunction fun)
{
addWithRaw(
name,
[fun](AsyncIoRoot & aio, std::string name, std::list<std::string> args, std::span<char *>) {
return fun(aio, name, args);
}
);
}
static void addWithRaw(const std::string & name, RawMainFunction fun)
{
if (!commands) commands = new LegacyCommandMap;
(*commands)[name] = fun;
+1 -1
View File
@@ -48,7 +48,7 @@ char ** copyCompletions(const StringSet& possible)
if (vp) {
while (--ac >= 0)
free(vp[ac]);
free(vp);
free(static_cast<void *>(vp));
}
throw Error("allocation failure");
}
+1 -1
View File
@@ -46,7 +46,7 @@ public:
*
* This function logs but ignores errors from readline's write_history().
*/
virtual void writeHistory();
void writeHistory();
virtual ~ReadlineLikeInteracter() override;
};
+1 -3
View File
@@ -121,7 +121,6 @@ public:
private:
Bindings * bindings;
EvalMemory & mem;
SymbolTable & symbols;
Size capacity;
@@ -129,9 +128,8 @@ public:
// needed by std::back_inserter
using value_type = Attr;
BindingsBuilder(EvalMemory & mem, SymbolTable & symbols, Bindings * bindings, Size capacity)
BindingsBuilder(SymbolTable & symbols, Bindings * bindings, Size capacity)
: bindings(bindings)
, mem(mem)
, symbols(symbols)
, capacity(capacity)
{
+4 -4
View File
@@ -210,9 +210,9 @@ void initLibExpr()
(resident) memory to be allocated. This might be a problem on
systems that don't overcommit. */
if (!getEnv("GC_INITIAL_HEAP_SIZE")) {
int64_t size = 32 * 1024 * 1024;
int64_t size = 32l * 1024 * 1024;
#if HAVE_SYSCONF && defined(_SC_PAGESIZE) && defined(_SC_PHYS_PAGES)
int64_t maxSize = 384 * 1024 * 1024;
int64_t maxSize = 384l * 1024 * 1024;
int64_t pageSize = sysconf(_SC_PAGESIZE);
int64_t pages = sysconf(_SC_PHYS_PAGES);
if (pageSize != -1) {
@@ -291,14 +291,14 @@ EvalMemory::EvalMemory()
{
assert(libexprInitialised);
#if HAVE_BOEHMGC
GC_add_roots(gcCache, gcCache + CACHES);
GC_add_roots(static_cast<void *>(gcCache), static_cast<void *>(gcCache + CACHES));
#endif
}
EvalMemory::~EvalMemory()
{
#if HAVE_BOEHMGC
GC_remove_roots(gcCache, gcCache + CACHES);
GC_remove_roots(static_cast<void *>(gcCache), static_cast<void *>(gcCache + CACHES));
#endif
}
+1 -1
View File
@@ -216,7 +216,7 @@ public:
BindingsBuilder buildBindings(SymbolTable & symbols, size_t capacity)
{
return BindingsBuilder(*this, symbols, allocBindings(capacity), capacity);
return BindingsBuilder(symbols, allocBindings(capacity), capacity);
}
const Statistics getStats() const { return stats; }
-2
View File
@@ -33,8 +33,6 @@
namespace nix {
char * * savedArgv;
static bool gcWarning = true;
void printGCWarning()
+1 -1
View File
@@ -48,7 +48,7 @@ void detectStackOverflow()
requires an alternative stack, otherwise the signal cannot be
delivered when we're out of stack space. */
stack_t stack;
stack.ss_size = 4096 * 4 + MINSIGSTKSZ;
stack.ss_size = 4096ul * 4 + MINSIGSTKSZ;
static auto stackBuf = std::make_unique<std::vector<char>>(stack.ss_size);
stack.ss_sp = stackBuf->data();
if (!stack.ss_sp) throw Error("cannot allocate alternative stack");
+18 -15
View File
@@ -227,18 +227,20 @@ retry:
}
auto & localStore = getLocalStore();
bool sandboxFallbackAllowed = true;
if (localStore.config().storeDir != localStore.config().realStoreDir.get()) {
#if __linux__
const bool sandboxFallbackAllowed = [&]() {
if (localStore.config().storeDir != localStore.config().realStoreDir.get()) {
#if __linux__
if (!useChroot) {
printTaggedWarning("auto-enabling the sandbox due to using a diverted store");
}
useChroot = true;
sandboxFallbackAllowed = false;
#else
return false;
#else
throw Error("building using a diverted store is not supported on this platform");
#endif
}
#endif
}
return true;
}();
if (useBuildUsers()) {
if (!buildUser)
@@ -1832,15 +1834,18 @@ try {
);
}
outputGraph[scratchOutputs.at(name)] = StorePathSet{};
std::visit(
overloaded{/* Since we'll use the already installed versions of these, we
can treat them as leaves and ignore any references they
have. */
[&](const AlreadyRegistered &) {
outputGraph[scratchOutputs.at(name)] = StorePathSet{};
},
[&](const AlreadyRegistered &) {},
[&](const PerhapsNeedToRegister & refs) {
outputGraph[scratchOutputs.at(name)] = refs.refs;
for (auto & ref : refs.refs) {
if (inverseOutputMap.find(ref) != inverseOutputMap.end()) {
outputGraph[scratchOutputs.at(name)].insert(ref);
}
}
}
},
*orifu
@@ -1851,10 +1856,8 @@ try {
topoSort(outputsToSort, {[&](const std::string & name) {
StringSet dependencies;
for (auto & path : outputGraph.at(scratchOutputs.at(name))) {
auto outputName = inverseOutputMap.find(path);
if (outputName != inverseOutputMap.end()) {
dependencies.insert(outputName->second);
}
auto outputName = inverseOutputMap.at(path);
dependencies.insert(outputName);
}
return dependencies;
}});
+14 -4
View File
@@ -142,6 +142,13 @@ static void expect(StringViewStream & str, std::string_view s)
str.remaining.remove_prefix(s.size());
}
static void expectWithErrorMsg(StringViewStream & str, std::string_view s, const char * errorMsg)
{
if (!str.remaining.starts_with(s)) {
throw FormatError(errorMsg);
}
str.remaining.remove_prefix(s.size());
}
/* Read a C-style string from stream `str'. */
static BackedStringView parseString(StringViewStream & str)
@@ -276,13 +283,16 @@ Derivation parseDerivation(
drv.name = name;
StringViewStream str{s};
expect(str, "D");
const char * genericErrorMsg = "derivation does not start with 'Derive' or 'DrvWithVersion'";
expectWithErrorMsg(str, "D", genericErrorMsg);
switch (str.peek()) {
case 'e':
expect(str, "erive(");
expectWithErrorMsg(str, "erive(", genericErrorMsg);
break;
case 'r': {
expect(str, "rvWithVersion(");
expectWithErrorMsg(str, "rvWithVersion(", genericErrorMsg);
auto versionS = parseString(str);
throw FormatError("Unknown derivation ATerm format version '%s'", *versionS);
}
@@ -342,7 +352,7 @@ Derivation parseDerivation(
*/
static void printString(std::string & res, std::string_view s)
{
boost::container::small_vector<char, 64 * 1024> buffer;
boost::container::small_vector<char, 64ul * 1024> buffer;
buffer.reserve(s.size() * 2 + 2);
char * buf = buffer.data();
char * p = buf;
+1 -1
View File
@@ -333,7 +333,7 @@ struct TransferItem
// when the buffer is full (as determined by a historical magic value) we
// pause the transfer and wait for the receiver to unpause it when ready.
if (successfulStatuses.count(getHTTPStatus()) && state->data.size() > 1024 * 1024) {
if (successfulStatuses.count(getHTTPStatus()) && state->data.size() > 1024ul * 1024) {
return CURL_WRITEFUNC_PAUSE;
}
+1 -1
View File
@@ -265,7 +265,7 @@ static std::map<StorePath, Node> mkGraph(
for (auto & node : graph_data) {
for (auto & ref : node.second.dependencies) {
graph_data.find(ref)->second.dependents.insert(node.first);
graph_data.at(ref).dependents.insert(node.first);
}
}
+1 -1
View File
@@ -253,7 +253,7 @@ time_t parseOlderThanTimeSpec(std::string_view timeSpec)
time_t curTime = time(0);
auto strDays = timeSpec.substr(0, timeSpec.size() - 1);
auto days = string2Int<int>(strDays);
auto days = string2Int<time_t>(strDays);
if (!days || *days < 1)
throw UsageError("invalid number of days specifier '%1%'", timeSpec);
+7 -4
View File
@@ -195,8 +195,8 @@ ref<Aws::Client::ClientConfiguration> S3Helper::makeConfig(
if (!endpoint.empty()) {
res->endpointOverride = endpoint;
}
res->requestTimeoutMs = 600 * 1000;
res->connectTimeoutMs = 5 * 1000;
res->requestTimeoutMs = 600l * 1000;
res->connectTimeoutMs = 5l * 1000;
res->retryStrategy = std::make_shared<RetryStrategy>();
res->caFile = settings.caFile;
// Use the system proxy env-vars in curl for s3, which is off by default for some reason
@@ -312,8 +312,11 @@ struct S3BinaryCacheStoreConfig final : BinaryCacheStoreConfig
"Whether to use multi-part uploads."};
const Setting<uint64_t> bufferSize{
this, 5 * 1024 * 1024, "buffer-size",
"Size (in bytes) of each part in multi-part uploads."};
this,
5ul * 1024 * 1024,
"buffer-size",
"Size (in bytes) of each part in multi-part uploads."
};
const std::string name() override { return "S3 Binary Cache Store"; }
+13 -1
View File
@@ -1338,7 +1338,19 @@ try {
Derivation::nameFromPath(drvPath)
);
} catch (FormatError & e) {
throw Error("error parsing derivation '%s': %s", store.printStorePath(drvPath), e.msg());
auto drvPathS = store.printStorePath(drvPath);
throw Error(
fmt("error parsing derivation '%1%': %2%\n"
"This can occur when the derivation is corrupted.\n"
"You can check this with `nix-store --verify-path %1%` and possibly repair with "
"`nix-store --repair-path %1%`.\n"
"In case the repair cannot be done, `nix-store --delete %1%` may be able "
"to remove the broken path.\n"
"We would appreciate a bug report at "
"https://git.lix.systems/lix-project/lix/issues if you think this is a bug.",
drvPathS,
e.msg())
);
}
} catch (...) {
co_return result::current_exception();
+2 -2
View File
@@ -121,7 +121,7 @@ public:
{
}
AsyncBufferedInputStream(AsyncInputStream & inner, size_t bufSize = 32 * 1024)
AsyncBufferedInputStream(AsyncInputStream & inner, size_t bufSize = 32ul * 1024)
: AsyncBufferedInputStream(inner, make_ref<IoBuffer>(bufSize))
{
}
@@ -173,7 +173,7 @@ public:
{
}
AsyncBufferedOutputStream(AsyncOutputStream & inner, size_t bufSize = 32 * 1024)
AsyncBufferedOutputStream(AsyncOutputStream & inner, size_t bufSize = 32ul * 1024)
: AsyncBufferedOutputStream(inner, make_ref<IoBuffer>(bufSize))
{
}
+8 -7
View File
@@ -16,7 +16,6 @@
#include <archive.h>
#include <archive_entry.h>
#include <cerrno>
#include <chrono>
#include <cstdio>
#include <cstring>
@@ -38,7 +37,7 @@ static const int COMPRESSION_LEVEL_DEFAULT = -1;
// Don't feed brotli too much at once.
struct ChunkedCompressionSink : CompressionSink
{
uint8_t outbuf[32 * 1024];
uint8_t outbuf[32ul * 1024];
void writeUnbuffered(std::string_view data) override
{
@@ -63,13 +62,15 @@ struct ArchiveDecompressionSource : Source
struct archive_entry * ae;
if (!archive) {
archive = std::make_unique<TarArchive>(*src, true);
this->archive->check(archive_read_next_header(this->archive->archive, &ae),
"failed to read header (%s)");
if (archive_filter_count(this->archive->archive) < 2) {
this->archive->check(
archive_read_next_header(this->archive->archive.get(), &ae),
"failed to read header (%s)"
);
if (archive_filter_count(this->archive->archive.get()) < 2) {
throw CompressionError("input compression not recognized");
}
}
ssize_t result = archive_read_data(this->archive->archive, data, len);
ssize_t result = archive_read_data(this->archive->archive.get(), data, len);
if (result > 0) return result;
if (result == 0) {
throw EndOfFile("reached end of compressed file");
@@ -167,7 +168,7 @@ struct NoneSink : CompressionSink
struct BrotliDecompressionSource : Source
{
static constexpr size_t BUF_SIZE = 32 * 1024;
static constexpr size_t BUF_SIZE = 32ul * 1024;
std::unique_ptr<char[]> buf;
size_t avail_in = 0;
const uint8_t * next_in;
+1 -1
View File
@@ -133,7 +133,7 @@ Generator<Bytes> drainFDSource(int fd, bool block)
}
});
std::array<unsigned char, 64 * 1024> buf;
std::array<unsigned char, 64ul * 1024> buf;
while (1) {
checkInterrupt();
ssize_t rd = read(fd, buf.data(), buf.size());
+3 -3
View File
@@ -443,7 +443,7 @@ void writeFile(const Path & path, Source & source, mode_t mode)
{
AutoCloseFD fd = openForWrite(path, mode);
std::vector<char> buf(64 * 1024);
std::vector<char> buf(64ul * 1024);
try {
while (true) {
@@ -463,7 +463,7 @@ void writeFileExcl(const Path & path, Source & source, mode_t mode)
{
AutoCloseFD fd = openForWriteExcl(path, mode);
std::vector<char> buf(64 * 1024);
std::vector<char> buf(64ul * 1024);
try {
while (true) {
@@ -483,7 +483,7 @@ kj::Promise<Result<void>> writeFile(const Path & path, AsyncInputStream & source
try {
AutoCloseFD fd = openForWrite(path, mode);
std::vector<char> buf(64 * 1024);
std::vector<char> buf(64ul * 1024);
try {
while (true) {
+2 -3
View File
@@ -191,9 +191,8 @@ Hash::Hash(std::string_view rest, HashType type, bool isSRI)
};
for (unsigned int i = 0; i < hashSize; i++) {
hash[i] =
parseHexDigit(rest[i * 2]) << 4
| parseHexDigit(rest[i * 2 + 1]);
const size_t j = i << 1;
hash[i] = parseHexDigit(rest[j]) << 4 | parseHexDigit(rest[j + 1]);
}
}
+1 -1
View File
@@ -19,7 +19,7 @@ class IoBuffer
std::unique_ptr<char[]> buffer;
public:
explicit IoBuffer(size_t bufSize = 32 * 1024) : bufSize(bufSize) {}
explicit IoBuffer(size_t bufSize = 32ul * 1024) : bufSize(bufSize) {}
size_t size() const
{
+2 -2
View File
@@ -107,8 +107,8 @@ struct RpcLogger : Logger
}
buffer->sizeEstimate += sizeof(e) + extraSize;
buffer->items.emplace_back(std::move(e));
return buffer->sizeEstimate >= 1024 * 1024 ? BufferState::NeedsFlush
: BufferState::HasSpace;
return buffer->sizeEstimate >= static_cast<size_t>(1024 * 1024) ? BufferState::NeedsFlush
: BufferState::HasSpace;
}
BufferState log(Verbosity lvl, std::string_view s) override
+1 -1
View File
@@ -214,7 +214,7 @@ Pid startProcess(std::function<void()> fun, const ProcessOptions & options)
// Not supported, since then we don't know when to free the stack.
assert(!(options.cloneFlags & CLONE_VM));
size_t stackSize = 1 * 1024 * 1024;
size_t stackSize = 1ul * 1024 * 1024;
auto stack = static_cast<char *>(mmap(0, stackSize,
PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0));
if (stack == MAP_FAILED) throw SysError("allocating stack");
+2 -2
View File
@@ -53,7 +53,7 @@ struct BufferedSink : virtual Sink
{
ref<IoBuffer> buffer;
BufferedSink(size_t bufSize = 32 * 1024) : buffer(make_ref<IoBuffer>(bufSize)) {}
BufferedSink(size_t bufSize = 32ul * 1024) : buffer(make_ref<IoBuffer>(bufSize)) {}
explicit BufferedSink(ref<IoBuffer> buffer) : buffer(std::move(buffer)) {}
void operator () (std::string_view data) override;
@@ -105,7 +105,7 @@ struct BufferedSource : Source
{
ref<IoBuffer> buffer;
BufferedSource(size_t bufSize = 32 * 1024) : buffer(make_ref<IoBuffer>(bufSize)) {}
BufferedSource(size_t bufSize = 32ul * 1024) : buffer(make_ref<IoBuffer>(bufSize)) {}
explicit BufferedSource(ref<IoBuffer> buffer) : buffer(std::move(buffer)) {}
size_t read(char * data, size_t len) override;
+75 -30
View File
@@ -1,9 +1,11 @@
#include <archive.h>
#include <archive_entry.h>
#include <kj/async.h>
#include <map>
#include "async-io.hh"
#include "file-descriptor.hh"
#include "lix/libstore/temporary-dir.hh"
#include "lix/libutil/c-calls.hh"
#include "lix/libutil/charptr-cast.hh"
#include "lix/libutil/file-system.hh"
@@ -45,67 +47,110 @@ void TarArchive::check(int err, const std::string & reason)
if (err == ARCHIVE_EOF)
throw EndOfFile("reached end of archive");
else if (err != ARCHIVE_OK)
throw Error(reason, archive_error_string(this->archive));
throw Error(reason, archive_error_string(this->archive.get()));
}
TarArchive::TarArchive(Source & source, bool raw) : buffer(65536)
TarArchive::TarArchive(Source & source, bool raw)
: archive{archive_read_new(), archive_read_free}
, source(&source)
, buffer(65536)
{
this->archive = archive_read_new();
this->source = &source;
if (!raw) {
archive_read_support_filter_all(archive);
archive_read_support_format_all(archive);
archive_read_support_filter_all(archive.get());
archive_read_support_format_all(archive.get());
} else {
archive_read_support_filter_all(archive);
archive_read_support_format_raw(archive);
archive_read_support_format_empty(archive);
archive_read_support_filter_all(archive.get());
archive_read_support_format_raw(archive.get());
archive_read_support_format_empty(archive.get());
}
archive_read_set_option(archive, nullptr, "mac-ext", nullptr);
check(archive_read_open(archive, (void *)this, callback_open, callback_read, callback_close), "Failed to open archive (%s)");
archive_read_set_option(archive.get(), nullptr, "mac-ext", nullptr);
check(
archive_read_open(
archive.get(), (void *) this, callback_open, callback_read, callback_close
),
"Failed to open archive (%s)"
);
}
TarArchive::TarArchive(const Path & path)
TarArchive::TarArchive(const Path & path) : archive{archive_read_new(), archive_read_free}
{
this->archive = archive_read_new();
archive_read_support_filter_all(archive);
archive_read_support_format_all(archive);
archive_read_set_option(archive, nullptr, "mac-ext", nullptr);
archive_read_support_filter_all(archive.get());
archive_read_support_format_all(archive.get());
archive_read_set_option(archive.get(), nullptr, "mac-ext", nullptr);
check(
archive_read_open_filename(archive, requireCString(path), 16384),
archive_read_open_filename(archive.get(), requireCString(path), 16384),
"failed to open archive: %s"
);
}
void TarArchive::close()
{
check(archive_read_close(this->archive), "Failed to close archive (%s)");
check(archive_read_close(this->archive.get()), "Failed to close archive (%s)");
}
TarArchive::~TarArchive()
namespace {
struct IndexEntry
{
if (this->archive) archive_read_free(this->archive);
struct IndexDirectory
{
std::map<std::string, IndexEntry> entries;
};
struct IndexFile
{
uint64_t start;
uint64_t size;
};
std::variant<IndexDirectory, IndexFile> innerData;
int mode;
};
/** Extract a TAR archive into an in-memory index with data stored on disk,
* because fundamentally all we have to do when importing archives to the store
* is to sort the entries and then send it into a NAR serializer.
*
* This deals with macOS performance problems by not materializing the files to
* disk in the first place.
*
* https://git.lix.systems/lix-project/lix/issues/1072
*/
struct InMemoryIndex
{
Path tempDir;
AutoDelete tempDirDeleter;
AutoCloseFD backingFile;
std::ofstream writer;
IndexEntry topLevel;
InMemoryIndex() : tempDir(createTempDir()), tempDirDeleter(tempDir, true)
{
backingFile = sys::open(tempDir + "/extracted", O_RDWR | O_TRUNC | O_CLOEXEC);
}
};
}
static void extract_archive(TarArchive & archive, const Path & destDir)
{
requireCString(destDir);
int flags = ARCHIVE_EXTRACT_TIME
| ARCHIVE_EXTRACT_SECURE_SYMLINKS
| ARCHIVE_EXTRACT_SECURE_NODOTDOT;
int flags = ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_SECURE_SYMLINKS
| ARCHIVE_EXTRACT_SECURE_NODOTDOT | ARCHIVE_EXTRACT_NO_HFS_COMPRESSION;
for (;;) {
struct archive_entry * entry;
int r = archive_read_next_header(archive.archive, &entry);
int r = archive_read_next_header(archive.archive.get(), &entry);
if (r == ARCHIVE_EOF) break;
auto name = archive_entry_pathname(entry);
if (!name)
throw Error("cannot get archive member name: %s", archive_error_string(archive.archive));
throw Error(
"cannot get archive member name: %s", archive_error_string(archive.archive.get())
);
if (r == ARCHIVE_WARN)
printTaggedWarning("%1%", Uncolored(archive_error_string(archive.archive)));
printTaggedWarning("%1%", Uncolored(archive_error_string(archive.archive.get())));
else
archive.check(r);
@@ -125,7 +170,7 @@ static void extract_archive(TarArchive & archive, const Path & destDir)
(destDir + "/" + original_hardlink).c_str());
}
archive.check(archive_read_extract(archive.archive, entry, flags));
archive.check(archive_read_extract(archive.archive.get(), entry, flags));
}
archive.close();
+1 -3
View File
@@ -8,7 +8,7 @@
namespace nix {
struct TarArchive {
struct archive * archive;
std::unique_ptr<struct archive, decltype(&archive_read_free)> archive;
Source * source;
std::vector<unsigned char> buffer;
@@ -22,8 +22,6 @@ struct TarArchive {
TarArchive(const TarArchive &) = delete;
void close();
~TarArchive();
};
kj::Promise<Result<void>> unpackTarfile(AsyncInputStream & source, const Path & destDir);
+1 -33
View File
@@ -55,7 +55,6 @@
#endif
static constexpr int SUBDAEMON_CONNECTION_FD = 0;
static constexpr int SUBDAEMON_SETTINGS_FD = 3;
namespace nix {
@@ -346,9 +345,6 @@ try {
peer.pidKnown ? fmt("pid %1%", peer.pid) : "unknown peer"
);
Pipe settings;
settings.create();
// Fork a child to handle the connection. make sure it's called with
// argv0 `nix-daemon` so we don't try to run `nix --for` when called
// from more modern scripts that assume nix-command being available.
@@ -363,11 +359,7 @@ try {
fmt("%1%", int(verbosity)),
},
.dieWithParent = false,
.redirections =
{
{.dup = SUBDAEMON_CONNECTION_FD, .from = remote.get()},
{.dup = SUBDAEMON_SETTINGS_FD, .from = settings.readSide.get()},
}
.redirections = {{.dup = SUBDAEMON_CONNECTION_FD, .from = remote.get()}}
};
if (forceTrustClientOpt) {
options.args.push_back(
@@ -375,15 +367,6 @@ try {
);
}
runProgram2(options).release();
FdSink sink(settings.writeSide.get());
std::map<std::string, Config::SettingInfo> overriddenSettings;
globalConfig.getSettings(overriddenSettings, true);
for (auto & setting : overriddenSettings) {
sink << 1 << setting.first << setting.second.value;
}
sink << 0;
sink.flush();
} catch (Error & error) {
auto ei = error.info();
// FIXME: add to trace?
@@ -417,21 +400,6 @@ static void daemonInstance(AsyncIoRoot & aio, std::optional<TrustedFlag> forceTr
forceTrustClientOpt ? " by override" : ""
);
{
FdSource source(SUBDAEMON_SETTINGS_FD);
/* Read the parent's settings. */
while (readNum<unsigned>(source)) {
auto name = readString(source);
auto value = readString(source);
settings.set(name, value);
}
if (close(SUBDAEMON_SETTINGS_FD) < 0) {
throw SysError("preparing subdaemon connection");
}
}
// Background the daemon.
if (setsid() == -1) {
throw SysError("creating a new session");
+3
View File
@@ -364,6 +364,9 @@ struct Common : InstallableCommand, MixProfile
out << fmt("%s=\"$%s${nix_saved_%s:+:$nix_saved_%s}\"\n", var, var, var, var);
out << "export NIX_BUILD_TOP=\"$(mktemp -d -t nix-shell.XXXXXX)\"\n";
// We re-export similarly to what occurs inside of a derivation goal `NIX_LOG_FD` to stderr.
// So that stdenv hooks that logs information can be observed inside this debugging tool.
out << "export NIX_LOG_FD=2\n";
for (auto & i : {"TMP", "TMPDIR", "TEMP", "TEMPDIR"})
out << fmt("export %s=\"$NIX_BUILD_TOP\"\n", i);
+1 -1
View File
@@ -152,7 +152,7 @@ void renderDiffInfo(
const std::string_view indent)
{
for (auto & [name, item] : diff) {
auto showDelta = std::abs(item.sizeDelta) >= 8 * 1024;
auto showDelta = std::abs(item.sizeDelta) >= 8l * 1024;
std::vector<std::string> line;
if (!item.removedVersions.empty() || !item.addedVersions.empty())
+7 -5
View File
@@ -455,8 +455,6 @@ void registerNixHelp()
int mainWrapped(AsyncIoRoot & aio, int argc, char ** argv)
{
savedArgv = argv;
/* The chroot helper needs to be run before any threads have been
started. */
if (argc > 0 && argv[0] == chrootHelperName) {
@@ -493,7 +491,12 @@ int mainWrapped(AsyncIoRoot & aio, int argc, char ** argv)
registerLegacyCommands();
auto legacy = (*LegacyCommandRegistry::commands)[programName];
if (legacy) {
return legacy(aio, std::string(baseNameOf(argv[0])), Strings(argv + 1, argv + argc));
return legacy(
aio,
std::string(baseNameOf(argv[0])),
Strings(argv + 1, argv + argc),
{argv + 1, argv + argc}
);
}
}
@@ -645,7 +648,6 @@ int mainWrapped(AsyncIoRoot & aio, int argc, char ** argv)
return 0;
}
}
int main(int argc, char * * argv)
@@ -657,7 +659,7 @@ int main(int argc, char * * argv)
// Increase the default stack size for the evaluator and for
// libstdc++'s std::regex.
nix::setStackSize(64 * 1024 * 1024);
nix::setStackSize(64ul * 1024 * 1024);
return nix::handleExceptions(argv[0], [&]() {
nix::AsyncIoRoot aio;
+13
View File
@@ -49,6 +49,19 @@ struct CmdUpgradeNix : MixDryRun, EvalCommand
});
}
// NOTE(Raito): we override the store creation
// to prevent any store daemon connection.
//
// An upgrade, by nature, requires a direct store access
// to avoid having the daemon die in the middle of changing the binary.
//
// If more commands needs that, we can move it into a mixin. This was deliberately not done
// here.
virtual ref<Store> createStore(AsyncIoRoot & aio) override
{
return aio.blockOn(openStore(settings.storeUri.get(), {}, AllowDaemon::Disallow));
}
/**
* This command is stable before the others
*/
+1
View File
@@ -358,6 +358,7 @@ stdenv.mkDerivation (finalAttrs: {
++ lib.optional (!officialRelease && buildUnreleasedNotes) build-release-notes
++ lib.optional internalApiDocs doxygen
++ lib.optionals lintInsteadOfBuild [
llvmPackages.libllvm
# required for a wrapped clang-tidy
llvmPackages.clang-tools
# load-bearing order (just as below); the actual stdenv wrapped clang
+1 -1
View File
@@ -1,7 +1,7 @@
project('lix-perl', 'cpp',
version : run_command('bash', '-c', 'echo -n $(jq -r .version < ../version.json)$VERSION_SUFFIX', check : true).stdout().strip(),
default_options : [
'cpp_std=c++2a',
'cpp_std=c++23',
# TODO(Qyriad): increase the warning level
'debug=true',
# FIXME(Qyriad): should this be -O2? The main nix build was switched to -O2 in 3c5234430
+9 -7
View File
@@ -22,7 +22,7 @@ void CharPtrCastCheck::check(
const auto ReinterpretCastExpr =
Result.Nodes.getNodeAs<CXXReinterpretCastExpr>("reinterpret-cast-expr");
const auto ToTypeSpan = ReinterpretCastExpr->getAngleBrackets();
const auto & SM = Result.Context->getSourceManager();
const auto &SM = Result.Context->getSourceManager();
auto Diag =
diag(ReinterpretCastExpr->getExprLoc(),
@@ -32,14 +32,16 @@ void CharPtrCastCheck::check(
auto Inside = tooling::getText(*ReinterpretCastExpr->getSubExprAsWritten(),
*Result.Context);
Diag << Inserter.createIncludeInsertion(SM.getFileID(ReinterpretCastExpr->getExprLoc()), "charptr-cast.hh");
Diag << Inserter.createIncludeInsertion(
SM.getFileID(ReinterpretCastExpr->getExprLoc()), "charptr-cast.hh");
llvm::Twine Replacement =
"charptr_cast" +
tooling::getText(CharSourceRange(ToTypeSpan, true), *Result.Context) +
"(" + Inside + ")";
auto Replacement =
("charptr_cast" +
tooling::getText(CharSourceRange(ToTypeSpan, true), *Result.Context) +
"(" + Inside + ")")
.str();
Diag << FixItHint::CreateReplacement(ReinterpretCastExpr->getSourceRange(),
Replacement.str());
Replacement);
}
} // namespace nix::clang_tidy
+1 -1
View File
@@ -4,7 +4,7 @@ project('nix-eval-jobs', 'cpp',
default_options : [
'debug=true',
'optimization=2',
'cpp_std=c++20',
'cpp_std=c++23',
],
)
@@ -56,3 +56,38 @@ LineReader::LineReader(LineReader &&other) {
// Remove trailing newline
return std::string_view(buffer, read - 1);
}
AsyncLineReader::AsyncLineReader(nix::AutoCloseFD fd)
: stream{std::move(fd)}, readBuffer{kj::heapArray<char>(4096)} {}
kj::Promise<nix::Result<std::optional<std::string>>> AsyncLineReader::readLine()
try {
auto pos = buffer.find('\n');
if (pos != std::string::npos) {
std::string result = buffer.substr(0, pos);
memmove(buffer.data(), buffer.data() + pos + 1, buffer.size() - pos - 1);
buffer.resize(buffer.size() - pos - 1);
co_return result;
}
// No full line was buffered, read until we have one.
while (true) {
auto nRead = LIX_TRY_AWAIT(stream.read(readBuffer.begin(), readBuffer.size()));
if (!nRead && !buffer.empty()) {
// File has ended, but not everything has been read out of the buffer yet.
co_return std::move(buffer);
} else if (!nRead) {
co_return std::nullopt;
}
std::string_view readStr{readBuffer.begin(), *nRead};
auto pos = readStr.find('\n');
if (pos != std::string_view::npos) {
buffer.append(readStr.substr(0, pos));
co_return std::exchange(buffer, readStr.substr(pos + 1));
}
buffer.append(readStr);
}
} catch (...) {
co_return nix::result::current_exception();
}
@@ -1,4 +1,6 @@
#pragma once
#include <lix/libutil/async-io.hh>
#include <lix/libutil/file-descriptor.hh>
#include <cstdio>
#include <string>
#include <string_view>
@@ -18,3 +20,15 @@ class LineReader {
char *buffer = nullptr;
size_t len = 0;
};
class AsyncLineReader {
public:
AsyncLineReader(nix::AutoCloseFD fd);
kj::Promise<nix::Result<std::optional<std::string>>> readLine();
private:
nix::AsyncFdIoStream stream;
std::string buffer;
kj::Array<char> readBuffer;
};
+147 -137
View File
@@ -4,6 +4,8 @@
#include <lix/libexpr/eval-settings.hh>
#include <lix/libmain/shared.hh>
#include <lix/libutil/async.hh>
#include <lix/libutil/async-collect.hh>
#include <lix/libutil/async-semaphore.hh>
#include <lix/libutil/sync.hh>
#include <lix/libexpr/eval.hh>
#include <lix/libutil/json.hh>
@@ -25,14 +27,11 @@
#include <lix/libutil/terminal.hh>
#include <lix/libutil/ref.hh>
#include <lix/libstore/store-api.hh>
#include <condition_variable>
#include <filesystem>
#include <exception>
#include <functional>
#include <iostream>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <string_view>
#include <thread>
@@ -75,8 +74,8 @@ class Collector {
using Response = std::variant<Next, JsonResponse, Restart>;
RunningProgram child;
AutoCloseFD to;
std::optional<LineReader> from;
std::optional<AsyncFdIoStream> to;
std::optional<AsyncLineReader> from;
Strings workerCmdline;
@@ -105,11 +104,12 @@ class Collector {
};
child = runProgram2(options);
to = std::move(toPipe.writeSide);
from.emplace(fromPipe.readSide.release());
to.emplace(std::move(toPipe.writeSide));
from.emplace(std::move(fromPipe.readSide));
}
void waitForWorkerReady() {
kj::Promise<Result<void>> waitForWorkerReady()
try {
assert(child);
std::visit(overloaded{
[](const Next &) {},
@@ -121,53 +121,66 @@ class Collector {
to.reset();
from.reset();
},
}, readResponse("checking worker process"));
}, LIX_TRY_AWAIT(readResponse("checking worker process")));
co_return result::success();
} catch (...) {
co_return result::current_exception();
}
void makeWorkerReady() {
kj::Promise<Result<void>> makeWorkerReady()
try {
if (child) {
waitForWorkerReady();
LIX_TRY_AWAIT(waitForWorkerReady());
}
if (!child) {
startWorker();
waitForWorkerReady();
LIX_TRY_AWAIT(waitForWorkerReady());
}
if (!child) {
throw Error("worker exited immediately");
}
co_return result::success();
} catch (...) {
co_return result::current_exception();
}
Response readResponse(std::string_view msg) {
kj::Promise<Result<Response>> readResponse(std::string_view msg)
try {
assert(from);
auto line = from->readLine();
if (line.empty()) {
auto line = LIX_TRY_AWAIT(from->readLine());
if (!line) {
handleBrokenPipe(msg);
} else if (line == "next") {
return Next{};
co_return Next{};
} else if (line == "restart") {
return Restart{};
co_return Restart{};
} else {
try {
return JsonResponse{JSON::parse(line)};
co_return JsonResponse{JSON::parse(*line)};
} catch (const json::ParseError &e) {
throw Error(
"Received invalid JSON from worker: %s\n json: '%s'",
e.what(), line);
e.what(), *line);
}
}
} catch (...) {
co_return result::current_exception();
}
void writeRequest(Request request) {
kj::Promise<Result<void>> writeRequest(Request request)
try {
assert(to);
auto line = std::visit(overloaded{
[](const Do &request) {
return fmt("do %s", request.attrPath.dump());
return fmt("do %s\n", request.attrPath.dump());
},
[](const Exit &) {
return std::string{"exit"};
return std::string{"exit\n"};
},
}, request);
if (tryWriteLine(to.get(), line) < 0) {
try {
LIX_TRY_AWAIT(to->writeFull(line.data(), line.size()));
} catch (SysError &err) {
auto msg = std::visit(overloaded{
[](const Do &request) {
return fmt("sending attrPath '%s'", joinAttrPath(request.attrPath));
@@ -178,6 +191,9 @@ class Collector {
}, request);
handleBrokenPipe(msg);
}
co_return result::success();
} catch (...) {
co_return result::current_exception();
}
[[noreturn]] void handleBrokenPipe(std::string_view msg) {
@@ -240,10 +256,11 @@ public:
}
}
JSON evaluate(JSON attrPath) {
makeWorkerReady();
writeRequest(Do{attrPath});
return std::visit(overloaded{
kj::Promise<Result<JSON>> evaluate(JSON attrPath)
try {
LIX_TRY_AWAIT(makeWorkerReady());
LIX_TRY_AWAIT(writeRequest(Do{attrPath}));
co_return std::visit(overloaded{
[](const Next &) -> JSON {
throw Error("unexpected response from worker: next");
},
@@ -253,93 +270,101 @@ public:
[](const Restart &) -> JSON {
throw Error("unexpected response from worker: restart");
},
}, readResponse(fmt("reading result for attrPath '%s'", joinAttrPath(attrPath))));
}
void exit() {
if (child) {
waitForWorkerReady();
}
if (child) {
writeRequest(Exit{});
// The worker will print "restart" when exiting cleanly, even if due to an explicit exit request.
waitForWorkerReady();
}
}
};
struct State {
std::set<JSON> todo = JSON::array({JSON::array()});
std::set<JSON> active;
std::exception_ptr exc;
std::map<std::string, JSON> jobs;
};
void collectorThread(MyArgs &myArgs, Sync<State> &state_, std::condition_variable &wakeup) {
try {
Collector collector{myArgs.cmdline};
while (true) {
/* Wait for a job name to become available. */
JSON attrPath;
while (true) {
checkInterrupt();
auto state(state_.lock());
if ((state->todo.empty() && state->active.empty()) ||
state->exc) {
collector.exit();
return;
}
if (!state->todo.empty()) {
attrPath = *state->todo.begin();
state->todo.erase(state->todo.begin());
state->active.insert(attrPath);
break;
} else
state.wait(wakeup);
}
/* Tell the worker to evaluate it. */
auto response = collector.evaluate(attrPath);
/* Handle the response. */
std::vector<JSON> newAttrs;
if (response.find("attrs") != response.end()) {
for (auto &i : response["attrs"]) {
JSON newAttr = JSON(response["attrPath"]);
newAttr.emplace_back(i);
newAttrs.push_back(newAttr);
}
} else {
auto state(state_.lock());
state->jobs.insert_or_assign(response["attr"], response);
if (nix::settings.readOnlyMode) {
response.erase("namedConstituents");
response.erase("constituents");
}
auto named = response.find("namedConstituents");
if (named == response.end() || named->empty()) {
response.erase("namedConstituents");
logger->writeToStdout(response.dump());
}
}
/* Add newly discovered job names to the queue. */
{
auto state(state_.lock());
state->active.erase(attrPath);
for (auto p : newAttrs) {
state->todo.insert(p);
}
wakeup.notify_all();
}
}
}, LIX_TRY_AWAIT(readResponse(fmt("reading result for attrPath '%s'", joinAttrPath(attrPath)))));
} catch (...) {
auto state(state_.lock());
state->exc = std::current_exception();
wakeup.notify_all();
co_return result::current_exception();
}
}
kj::Promise<Result<void>> exit()
try {
if (child) {
LIX_TRY_AWAIT(waitForWorkerReady());
}
if (child) {
LIX_TRY_AWAIT(writeRequest(Exit{}));
// The worker will print "restart" when exiting cleanly, even if due to an explicit exit request.
LIX_TRY_AWAIT(waitForWorkerReady());
}
co_return result::success();
} catch (...) {
co_return result::current_exception();
}
};
class Coordinator {
AsyncSemaphore semaphore;
kj::Array<Collector> workers;
std::vector<Collector *> idleWorkers;
kj::Promise<Result<void>> evaluateRecursively(JSON attrPath, std::map<std::string, JSON> &jobs)
try {
JSON response;
{
auto _token = co_await semaphore.acquire();
auto *worker = idleWorkers.back();
idleWorkers.pop_back();
Finally _returnWorker{[&]() {
idleWorkers.push_back(worker);
}};
response = LIX_TRY_AWAIT(worker->evaluate(attrPath));
}
std::vector<JSON> newAttrs;
if (response.find("attrs") != response.end()) {
for (auto &i : response["attrs"]) {
JSON newAttr = JSON(response["attrPath"]);
newAttr.emplace_back(i);
newAttrs.push_back(newAttr);
}
} else {
jobs.insert_or_assign(response["attr"], response);
if (nix::settings.readOnlyMode) {
response.erase("namedConstituents");
response.erase("constituents");
}
auto named = response.find("namedConstituents");
if (named == response.end() || named->empty()) {
response.erase("namedConstituents");
nix::logger->writeToStdout(response.dump());
}
}
LIX_TRY_AWAIT(asyncSpread(newAttrs, [&](const JSON &newAttr) {
return evaluateRecursively(newAttr, jobs);
}));
co_return result::success();
} catch (...) {
co_return result::current_exception();
}
public:
Coordinator(size_t nrWorkers, const Strings &cmdline)
: semaphore{static_cast<unsigned>(nrWorkers)} {
if (nrWorkers >= std::numeric_limits<unsigned>::max() - 1) {
throw Error("nix-eval-jobs cannot handle %d workers, please choose a reasonable number");
}
auto builder = kj::heapArrayBuilder<Collector>(nrWorkers);
for (unsigned i = 0; i < nrWorkers; ++i) {
auto worker = &builder.add(cmdline);
idleWorkers.push_back(worker);
}
workers = builder.finish();
}
kj::Promise<Result<std::map<std::string, JSON>>> run()
try {
std::map<std::string, JSON> jobs;
LIX_TRY_AWAIT(evaluateRecursively(JSON::array(), jobs));
for (auto &worker : workers) {
LIX_TRY_AWAIT(worker.exit());
}
co_return jobs;
} catch (...) {
co_return result::current_exception();
}
};
int main(int argc, char **argv) {
return handleExceptions(argv[0], [&]() {
@@ -390,23 +415,8 @@ int main(int argc, char **argv) {
return 0;
}
Sync<State> state_;
/* Start a collector thread per worker process. */
std::vector<std::thread> threads;
std::condition_variable wakeup;
for (size_t i = 0; i < myArgs.nrWorkers; i++) {
threads.emplace_back(std::bind(collectorThread, std::ref(myArgs),
std::ref(state_), std::ref(wakeup)));
}
for (auto &thread : threads)
thread.join();
auto state(state_.lock());
if (state->exc)
std::rethrow_exception(state->exc);
Coordinator coordinator{myArgs.nrWorkers, myArgs.cmdline};
auto jobs = aio.blockOn(coordinator.run());
if (myArgs.constituents) {
auto store = aio.blockOn(myArgs.evalStoreUrl
@@ -415,28 +425,28 @@ int main(int argc, char **argv) {
std::visit(
nix::overloaded{
[&](const std::vector<AggregateJob> &namedConstituents) {
rewriteAggregates(state->jobs, namedConstituents, store,
rewriteAggregates(jobs, namedConstituents, store,
myArgs.gcRootsDir, aio);
},
[&](const DependencyCycle &e) {
printError(
"Found dependency cycle between jobs '%s' and '%s'",
e.a, e.b);
state->jobs[e.a]["error"] = e.message();
state->jobs[e.b]["error"] = e.message();
jobs[e.a]["error"] = e.message();
jobs[e.b]["error"] = e.message();
logger->writeToStdout(state->jobs[e.a].dump());
logger->writeToStdout(state->jobs[e.b].dump());
nix::logger->writeToStdout(jobs[e.a].dump());
nix::logger->writeToStdout(jobs[e.b].dump());
for (const auto &jobName : e.remainingAggregates) {
state->jobs[jobName]["error"] =
jobs[jobName]["error"] =
"Skipping aggregate because of a dependency "
"cycle";
logger->writeToStdout(state->jobs[jobName].dump());
nix::logger->writeToStdout(jobs[jobName].dump());
}
},
},
resolveNamedConstituents(state->jobs));
resolveNamedConstituents(jobs));
}
return 0;
-1
View File
@@ -160,7 +160,6 @@ functional_tests_scripts = [
'regression-reference-checks.sh',
'redirected-filter-source.sh',
'daemon-trust.sh',
'output-cycles.sh',
]
# Plugin tests require shared libraries support.
+44
View File
@@ -75,6 +75,47 @@ output=$(NIX_PATH=nixpkgs="$shellDotNix" nix-shell --pure -p foo --argstr fooCon
test ! -d "$(dirname $IN_SHELL_TEMPDIR)" && echo "nix-shell deleted the parent directory of \$TEMPDIR: clean up successful" || { echo "nix-shell did not delete the parent directory of \$TEMPDIR: clean up failure"; exit 1; }
)
# Test whether the added length w.r.t. base directories is reasonable enough.
# This prevents regressions that can affect negatively things like opening UNIX
# domain sockets in non-isolated builds.
(
unset TMPDIR
BASE_DIR="$(realpath /tmp)"
LENGTH_RESULT=$(
NIX_PATH=nixpkgs="$shellDotNix" nix-shell --pure -p foo --run "
BASE_DIR_ENV=\"$BASE_DIR\"
ADDED_LENGTH=\$(( \${#NIX_BUILD_TOP} - \${#BASE_DIR_ENV} ))
echo \"\$ADDED_LENGTH:\$NIX_BUILD_TOP\"
"
)
ADDED_LENGTH="${LENGTH_RESULT%%:*}"
IN_SHELL_NIX_BUILD_TOP="${LENGTH_RESULT#*:}"
# The idea of this value is len("nix-shell-$hash/build-top") + ≤5 chars of margin.
MAX_ALLOWED=50
# The added length must be minimum 2 chars.
MIN_ALLOWED=2
if (( ADDED_LENGTH < MIN_ALLOWED )); then
echo "Added length ($ADDED_LENGTH) is impossibly low. The test is not correct."
exit 1
fi
if (( ADDED_LENGTH <= MAX_ALLOWED )); then
echo "Added length to \$NIX_BUILD_TOP in shells ($ADDED_LENGTH chars) is within acceptable limit ($MAX_ALLOWED chars)."
else
echo "ERROR: Added length too large ($ADDED_LENGTH chars > $MAX_ALLOWED chars)."
echo "Base: $BASE_DIR"
echo "NIX_BUILD_TOP: $IN_SHELL_NIX_BUILD_TOP"
exit 1
fi
)
# Test $NIX_LOG_FD
expectStderr 0 nix-shell --pure $shellDotNix -A shellDrv --run 'echo hello > $NIX_LOG_FD' |& grepQuiet 'hello'
# FIXME: testing that Ctrl-C to a (non-)interactive nix-shell stops it would be appreciated,
# but it is hard to send accurately the signal to the right process group.
@@ -135,6 +176,9 @@ shellDrv=$(nix-instantiate "$shellDotNix" -A shellDrv.out)
nix develop $shellDrv -c bash -c '[[ -n $stdenv ]]'
# Test $NIX_LOG_FD
expectStderr 0 nix develop $shellDrv -c bash -c 'echo hello > $NIX_LOG_FD' |& grepQuiet 'hello'
nix print-dev-env $shellDrv > $TEST_ROOT/dev-env2.sh
nix print-dev-env $shellDrv --json > $TEST_ROOT/dev-env2.json
-18
View File
@@ -1,18 +0,0 @@
source common.sh
clearStore
error="$(! nix-build check-outputs.nix -A cycle 2>&1)"
grepQuiet "cycle detected in build of '.*' in the references of output 'bar' from output 'foo'" <<<"$error"
if [[ "$(uname -s)" = Linux ]]; then
<<<"$error" grepQuiet "/store/.*-cycle-bar"
<<<"$error" grepQuiet "└───lib/libfoo: ….*cycle-baz.*"
<<<"$error" grepQuiet " →.*/store/.*-cycle-baz"
<<<"$error" grepQuiet " └───share/lalala:.*-cycle-foo.*"
fi
error="$(! nix-build check-outputs.nix -A as_dependency 2>&1)"
grepQuiet "cycle detected in build of '.*' in the references of output 'bar' from output 'foo'" <<<"$error"
grepQuiet "error: 1 dependencies of derivation" <<<"$error"
@@ -15,6 +15,18 @@ rec {
'';
};
cycle-with-deps = mkDerivation {
name = "cycle-with-deps";
inherit dep;
outputs = [ "foo" "bar" ];
builder = builtins.toFile "builder.sh" ''
mkdir -p $foo/bin $bar/lib
ln -sf $dep $bar/lib
echo $foo > $bar/txt
echo $bar > $foo/txt
'';
};
as_dependency = mkDerivation {
name = "depends-on-cycle";
inherit cycle;
@@ -0,0 +1,78 @@
import re
import sys
from functional2.testlib.fixtures.file_helper import with_files, CopyFile
from functional2.testlib.fixtures.nix import Nix
from functional2.testlib.utils import get_global_asset_pack
_files = {
"output-cycles.nix": CopyFile("assets/test_build/output-cycles.nix"),
**get_global_asset_pack("dependencies"),
}
def _assert_cycle_tree(output: str, regexes: list[str]):
lines = output.splitlines()
start = next((k for k, v in enumerate(lines) if "Shown below are the files inside" in v), None)
assert start is not None
for line, regex in enumerate(regexes, start=start + 1):
assert re.search(regex, lines[line])
def _assert_cycle_message(err: str):
assert (
len(
re.findall(
r"cycle detected in build of '.*' in the references of output 'bar' from output 'foo'",
err,
)
)
== 1
)
@with_files(_files)
def test_cycle(nix: Nix):
res = nix.nix_build(["output-cycles.nix", "-A", "cycle"]).run().expect(1)
err = res.stderr_plain
_assert_cycle_message(err)
if sys.platform == "linux":
_assert_cycle_tree(
err,
[
r"/store/.*-cycle-bar",
r"└───lib/libfoo: ….*cycle-baz.*",
r" →.*/store/.*-cycle-baz",
r" └───share/lalala:.*-cycle-foo.*",
],
)
@with_files(_files)
def test_cycle_in_dependency(nix: Nix):
res = nix.nix_build(["output-cycles.nix", "-A", "as_dependency"]).run().expect(1)
err = res.stderr_plain
_assert_cycle_message(err)
assert "error: 1 dependencies of derivation" in err
@with_files(_files)
def test_cycle_with_deps(nix: Nix):
res = nix.nix_build(["output-cycles.nix", "-A", "cycle-with-deps"]).run().expect(1)
err = res.stderr_plain
_assert_cycle_message(err)
if sys.platform == "linux":
_assert_cycle_tree(
err,
[
r"/store/.*-cycle-with-deps-bar",
r"└───txt: ….*cycle-with-deps-foo.*",
r" →.*/store/.*-cycle-with-deps-foo",
r" └───txt:.*-cycle-with-deps-bar.*",
],
)
@@ -32,7 +32,7 @@ def custom_sub_command(request: pytest.FixtureRequest, custom_sub_command_path:
import os, sys
# Start with args[0] set to the actual nix command used for testing
# as we are not making Lix variants of those.
os.execvp("nix-{lix_cmd}", [ "nix-{lix_cmd}" ] + sys.argv[1:])
os.execvp("nix", [ "nix-{lix_cmd}" ] + sys.argv[1:])
""")
)
executable.chmod(stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR)
+1 -1
View File
@@ -117,7 +117,7 @@ def test_valid_restricted_toFile(nix: Nix): # noqa: N802 # builtin name
@with_files({"cycle.nix": Symlink("cycle.nix")})
# timeout given in seconds
@pytest.mark.timeout(1)
@pytest.mark.timeout(30)
def test_invalid_no_hang_symlink_cycle(nix: Nix):
"""Check that symlink cycles don't cause a hang."""
res = nix.nix(["eval", "--file", "cycle.nix"]).run().expect(1)
+2 -1
View File
@@ -14,7 +14,7 @@ xdist_opts = [
functional2_env = environment()
# set the bin dir for us to know where to find the lix binaries for our declarative path
# without leaking all of PATH or env
functional2_env.set('NIX_BIN_DIR', bindir)
functional2_env.set('NIX_BIN_DIR', fs.parent(nix.full_path()))
if build_test_shell != ''
functional2_env.set('BUILD_TEST_SHELL', build_test_shell)
endif
@@ -32,6 +32,7 @@ test(
meson.current_source_dir()
],
env : functional2_env,
depends : nix,
# FIXME: Although we can trivially use TAP here with pytest-tap, due to a meson bug, it is unusable.
# (failure output does not get displayed to the console. at all. someone should go fix it):
# https://github.com/mesonbuild/meson/issues/11185
@@ -83,6 +83,7 @@ class CommandResult:
class Command:
argv: list[str]
_env: ManagedEnv
exe: Path | None = None
stdin: bytes | None = None
cwd: Path = dataclasses.field(default=None)
_logger: logging.Logger = dataclasses.field(default=logger, init=False)
@@ -107,6 +108,7 @@ class Command:
self._logger.debug("Running Command with args: %s", self.argv)
proc = subprocess.Popen(
self.argv,
executable=self.exe,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE if self.stdin else subprocess.DEVNULL,
+54 -2
View File
@@ -5,6 +5,7 @@ from pathlib import Path
from textwrap import dedent
from typing import Any, Literal
from collections.abc import Callable, Generator
import shutil
import pytest
@@ -81,7 +82,7 @@ class NixSettings:
(env.dirs.nix_conf_dir / "nix.conf").write_text(cfg)
env.set_env("NIX_CONFIG", cfg)
if self.nix_store_dir:
env.dirs.nix_store_dir = str(self.nix_store_dir)
env.dirs.nix_store_dir = self.nix_store_dir
@dataclasses.dataclass
@@ -89,6 +90,18 @@ class Nix:
env: ManagedEnv
_settings: NixSettings | None = dataclasses.field(init=False, default=None)
@property
def _nix_executable(self) -> Path:
if nix_bin_dir := self.env.dirs.nix_bin_dir:
return Path(nix_bin_dir) / "nix"
if from_path := shutil.which("nix"):
return Path(from_path)
raise ValueError(
"Couldn't find a Nix command to execute! Set NIX_BIN_DIR or fix your environment"
)
@property
def settings(self) -> NixSettings:
"""
@@ -124,7 +137,7 @@ class Nix:
settings.nix_store_dir = self.env.dirs.nix_store_dir
settings.to_env_overlay(self.env)
return Command(argv=argv, _env=self.env)
return Command(argv=argv, exe=self._nix_executable, _env=self.env)
def nix(
self,
@@ -160,6 +173,45 @@ class Nix:
self._settings = orig
return cmd.run()
@property
def store_dir(self) -> Path:
"""
The actual NIX_STORE_DIR this Nix command uses.
"""
assert self.env.dirs.nix_store_dir is not None, "bug in ManagedEnv"
return self.env.dirs.nix_store_dir
def physical_store_path_for(self, path: str | Path) -> Path:
"""
Takes a /nix/store/ path and rewrites it to be relative to this Nix's NIX_STORE_DIR.
Nix accepts and returns store paths as `/nix/store` even when that's not where `NIX_STORE_DIR`
physically is on the filesystem. Since we move the conceptual root for Nix to `test_root`,
these "virtual" paths differ from the physical ones. So this function will convert `/nix/store`
"virtual" paths to their real, physical location on the system.
Basically, if you're passing it to `nix build` or `nix-store` or whatever, you want the
`/nix/store` version. If you're passing it to a Python API (like pathlib.Path.exists()) or a
command that operates on arbitrary files instead of store paths, you want the output of this
function.
:param path: a string or Path to convert
:return: a Path object holding the rewritten, physical system path to the store entry
"""
return Path(str(path).replace("/nix/store", self.store_dir.as_posix()))
def hash_path(self, store_path: str | Path, *args: str) -> str:
"""
Shortcut to use `nix hash path {store_path}`, converting "virtual" store paths returned
from Nix to their physical system paths including the test root.
:param store_path: store path of the derivation or entry to hash
"""
actual_path = self.physical_store_path_for(store_path).as_posix()
res = self.nix(["hash", "path", actual_path, *args], flake=True).run().ok()
return res.stdout_plain
@pytest.fixture
def nix(tmp_path: Path, env: ManagedEnv) -> Generator[Nix, Any, None]:
@@ -74,4 +74,4 @@ def test_nix_settings_to_env_overlay_store_dir(tmp_path: Path):
settings.to_env_overlay(env)
assert "store = " not in env._env["NIX_CONFIG"]
assert env.dirs.nix_store_dir == "/some/path"
assert str(env.dirs.nix_store_dir) == "/some/path"
+1 -1
View File
@@ -256,7 +256,7 @@ TEST(FileTransfer, NOT_ON_DARWIN(defersFailures))
// initial wait for header data will also wait for the the response to
// complete (the source is only woken when curl returns data, and curl
// might only do so once its internal buffer has already been filled.)
return std::string(1024 * 1024, ' ');
return std::string(static_cast<size_t>(1024 * 1024), ' ');
});
AsyncIoRoot aio;
auto ft = makeFileTransfer(0);