Compare commits

..
Author SHA1 Message Date
Raito Bezarius 24df5d98f9 lix/libstore/linux: rename cgroup with drvHash
Change-Id: I238d0568a3e4b1ff3057781c0639528d666b4d37
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-12-12 01:30:44 +01:00
eldritch horrors 286f540ce8 nix/daemon: socket-activate single connections
the cgroups experimental feature does not work properly without this
because we do not stop subdaemons when the main daemon is shut down.
systemd needs the assigned cgroups to be empty to restart the daemon
and thus cannot cleanly restart the daemon if any connections exist.
starting a fresh unit for each connection creates a new cgroup every
time instead of sharing any delegations and thus solves the problem.

fixes #1030

Change-Id: Id6c458aad30eaa08c3609ac8280a7dde8e8f3cf9
2025-12-06 19:44:28 +01:00
eldritch horrors acb85fb910 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-06 19:44:28 +01:00
eldritch horrors 775832d7f0 libcmd: remove unused savedArgv
this was only used in the pre-exec daemon days.

Change-Id: I3bbb113f9940e6980f01af60e6614a9656b0fd03
2025-12-06 19:44:28 +01:00
eldritch horrors 6cec6929b2 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 changed 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-06 19:44:28 +01:00
814 changed files with 12271 additions and 24155 deletions
+3 -4
View File
@@ -4,10 +4,9 @@ AccessModifierOffset: -4
AlignAfterOpenBracket: BlockIndent
AlignEscapedNewlines: Left
AlignOperands: DontAlign
AlignTrailingComments: false
AllowShortBlocksOnASingleLine: Empty
AllowShortBlocksOnASingleLine: Always
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortIfStatementsOnASingleLine: WithoutElse
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
@@ -36,7 +35,7 @@ BreakAfterAttributes: Always
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Custom
BreakConstructorInitializers: BeforeComma
ColumnLimit: 110
ColumnLimit: 100
EmptyLineAfterAccessModifier: Leave
EmptyLineBeforeAccessModifier: Leave
FixNamespaceComments: false
+7 -2
View File
@@ -8,12 +8,16 @@ 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
@@ -25,6 +29,9 @@ 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
@@ -33,5 +40,3 @@ Checks:
CheckOptions:
bugprone-reserved-identifier.AllowedIdentifiers: '__asan_default_options'
bugprone-unused-return-value.AllowCastToVoid: true
ExtraArgs: ["-Werror=unnecessary-virtual-specifier"]
-3
View File
@@ -41,6 +41,3 @@ buildtime.bin
*.pyc
**/.idea
# Yeah, I've got no clue.
/subprojects/.wraplock
-3
View File
@@ -1,5 +1,2 @@
Fiona Behrens <me@kloenk.dev>
Fiona Behrens <me@kloenk.dev> <me@kloenk.de>
rootile <lix@rootile.de>
rootile <lix@rootile.de> <commentator2.0@crystal-cavern.systems>
rootile <lix@rootile.de> <lix@crystal-cavern.systems>
Generated
-4
View File
@@ -39,10 +39,6 @@ dependencies = [
"rowan",
]
[[package]]
name = "lixutil-rs"
version = "0.0.0"
[[package]]
name = "once_cell"
version = "1.19.0"
+1 -1
View File
@@ -1,6 +1,6 @@
[workspace]
resolver = "2"
members = ["lix/lix-doc", "lix/libutil"]
members = ["lix/lix-doc"]
[workspace.package]
edition = "2021"
-19
View File
@@ -1,19 +0,0 @@
# Darwin: don't link liblix* into plugins (host process provides them at runtime).
# Explicitly link curl so it binds to Nix-store libcurl, not /usr/lib/libcurl.
if is_darwin
plugin_deps = [
liblix.partial_dependency(includes : true, compile_args : true),
curl,
]
else
plugin_deps = [liblix, curl]
endif
plugin_mtls_store = shared_module(
'plugin_mtls_store',
'plugin_mtls_store.cc',
dependencies : plugin_deps,
install : false,
build_by_default : true,
link_args : is_darwin ? shared_module_link_args : strict_shared_module_link_args,
)
@@ -1,14 +0,0 @@
R"(
**Store URL format**: `https+mtls://...`
This store allows a binary cache to be accessed via HTTPS with mutual TLS (client certificate authentication).
Both parameters are required:
- `tls-certificate`, a path to the TLS client certificate
- `tls-private-key`, a path to the TLS private key backing the client certificate
If you don't need mTLS, use `https://` instead.
)"
-102
View File
@@ -1,102 +0,0 @@
#include "lix/libstore/store-api.hh"
#include "lix/libutil/config.hh"
#include "lix/libstore/http-binary-cache-store.hh"
#include <stdlib.h>
#include <curl/curl.h>
namespace nix {
struct mTLSBinaryCacheStoreConfig : HttpBinaryCacheStoreConfig
{
using HttpBinaryCacheStoreConfig::HttpBinaryCacheStoreConfig;
const std::string name() override
{
return "mTLS HTTP Binary Cache Store";
}
std::string doc() override
{
return
#include "mtls-http-binary-cache-store.md"
;
}
PathsSetting<nix::Path> tlsCertificate{
this,
"",
"tls-certificate",
"Path of the TLS client certificate in PEM format as expected by CURLOPT_SSLCERT"
};
PathsSetting<nix::Path> tlsKey{
this,
"",
"tls-private-key",
"Path of the TLS client certificate private key in PEM format as expected by CURLOPT_SSLKEY"
};
};
struct mTLSBinaryCacheStoreImpl : public HttpBinaryCacheStore
{
struct Keyring
{
nix::Path tlsCertificate;
nix::Path tlsKey;
};
mTLSBinaryCacheStoreConfig config_;
std::shared_ptr<Keyring> keyring;
mTLSBinaryCacheStoreConfig & config() override
{
return config_;
}
const mTLSBinaryCacheStoreConfig & config() const override
{
return config_;
}
mTLSBinaryCacheStoreImpl(
const std::string & uriScheme, const Path & _cacheUri, mTLSBinaryCacheStoreConfig config
)
: Store(config)
, HttpBinaryCacheStore("https", _cacheUri, config)
, config_(std::move(config))
, keyring(std::make_shared<Keyring>(config_.tlsCertificate.get(), config_.tlsKey.get()))
{
}
FileTransferOptions makeOptions(Headers && headers = {}) override
{
auto options = HttpBinaryCacheStore::makeOptions(std::move(headers));
auto baseExtraSetup = std::move(options.extraSetup);
auto keyring = this->keyring;
options.extraSetup = [keyring, baseExtraSetup{std::move(baseExtraSetup)}](CURL * req) {
if (baseExtraSetup) {
baseExtraSetup(req);
}
const bool haveCert = !keyring->tlsCertificate.empty();
const bool haveKey = !keyring->tlsKey.empty();
if (!(haveCert && haveKey)) {
throw Error("https+mtls requires both tls-certificate and tls-private-key");
}
curl_easy_setopt(req, CURLOPT_SSLCERT, keyring->tlsCertificate.c_str());
curl_easy_setopt(req, CURLOPT_SSLKEY, keyring->tlsKey.c_str());
};
return options;
}
static std::set<std::string> uriSchemes()
{
return {"https+mtls"};
}
};
}
extern "C" void nix_plugin_entry()
{
nix::StoreImplementations::add<nix::mTLSBinaryCacheStoreImpl, nix::mTLSBinaryCacheStoreConfig>();
}
+9 -15
View File
@@ -1,15 +1,9 @@
let
lockFile = builtins.fromJSON (builtins.readFile ./flake.lock);
flake-compat-node = lockFile.nodes.${lockFile.nodes.root.inputs.flake-compat};
flake-compat = builtins.fetchTarball {
inherit (flake-compat-node.locked) url;
sha256 = flake-compat-node.locked.narHash;
};
flake = (
import flake-compat {
src = ./.;
}
);
in
flake.defaultNix
(import (
let
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
in
fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
sha256 = lock.nodes.flake-compat.locked.narHash;
}
) { src = ./.; }).defaultNix
+5 -28
View File
@@ -57,14 +57,15 @@ blitz:
display_name: Julian Stecklina
github: blitz
blokyk:
display_name: blokyk
github: blokyk
cole-h:
display_name: Cole Helbling
github: cole-h
commentator2.0:
display_name: Commentator2.0 (Rutile)
forgejo: commentatorforall
github: CommentatorForAll
delan:
display_name: delan
forgejo: delan
@@ -103,9 +104,6 @@ goldstein:
forgejo: goldstein
github: GoldsteinE
gustavderdrache:
github: gustavderdrache
horrors:
display_name: eldritch horrors
forgejo: pennae
@@ -200,9 +198,6 @@ nan-git:
ncfavier:
github: ncfavier
nkk0:
github: nkk0
not-my-profile:
display_name: Martin Fischer
github: not-my-profile
@@ -249,20 +244,9 @@ roberth:
display_name: Robert Hensing
github: roberth
rootile:
display_name: rootile (Rutile)
forgejo: rootile
seppel3210:
github: Seppel3210
sterni:
forgejo: sterni
github: sternenseemann
stevalkr:
github: stevalkr
teofilc:
forgejo: teofilc
github: TeofilC
@@ -289,9 +273,6 @@ vigress8:
forgejo: vigress8
github: vigress8
vlaci:
github: vlaci
vlinkz:
display_name: Victor Fuentes
forgejo: vlinkz
@@ -307,10 +288,6 @@ xanderio:
xokdvium:
github: xokdvium
xyenon:
forgejo: xyenon
github: xyenon
yorickvp:
github: yorickvp
+1 -1
View File
@@ -69,7 +69,7 @@ let
let
result = squash ''
- ${
if inlineHTML then ''<span id="conf-${name}">[`${name}`](#conf-${name})</span>'' else "`${name}`"
if inlineHTML then ''<span id="conf-${name}">[`${name}`](#conf-${name})</span>'' else ''`${name}`''
}
${indent " " body}
+4 -7
View File
@@ -41,13 +41,10 @@ manual = custom_target(
'-euo', 'pipefail',
'-c',
'''
@0@ @INPUT0@ @3@ > @DEPFILE@
# Needs to be in lix/doc/manual for e.g. substitute.py
pushd @3@
@1@ build . -d @2@
popd
@0@ @INPUT0@ @CURRENT_SOURCE_DIR@ > @DEPFILE@
cd @3@
@1@ build . -d @2@ | { grep -Fv "because fragment resolution isn't implemented" || :; }
cd @SOURCE_ROOT@
rm -rf @2@/manual
mv @2@/html @2@/manual
find @2@/manual -iname meson.build -delete
-10
View File
@@ -1,10 +0,0 @@
---
synopsis: "allow setting nested attributes via `--arg`/`--argstr`"
cls: [5338]
category: "Features"
credits: [ma27]
issues: [fj#496]
---
Passing `--arg config.allowUnfree true` to e.g. `nix-build` now results in `config` with value
`{ allowUnfree = true; }` passed to the expression.
+15
View File
@@ -0,0 +1,15 @@
---
synopsis: "Add `builtins.warn` for emitting warnings from Nix code"
cls: [2248]
category: "Features"
credits: [milibopp, Qyriad]
---
Lix now has a builtin function for emitting warnings.
Like `builtins.trace`, it takes two arguments: the message to emit, and the expression to return.
_Unlike_ `builtins.trace`, `builtins.warn` requires the first argument — the message — to be a string.
In the future we may extend `builtins.warn` to accept a more structured API.
To go along with this, we also have two new config settings:
- [`debugger-on-warn`](@docroot@/command-ref/conf-file.md#conf-debugger-on-warn), which, when used with `--debugger`, makes `builtins.warn` also function like [`builtins.break`](@docroot@/language/builtins.md#builtins-break).
- [`abort-on-warn`](@docroot@/command-ref/conf-file.md#conf-abort-on-warn), which aborts evaluation entirely after the warning is emitted.
@@ -0,0 +1,13 @@
---
synopsis: "Deprecate shadowing internal files through the Nix search path"
issues: [998]
cls: [4632]
category: "Breaking Changes"
credits: [thubrecht]
---
As Lix uses the path `<nix/fetchurl.nix>` for bootstrapping purposes, the ability to shadow it by adding `nix=/some/path` (or `/other/path` that contains a `nix` directory) to the search path is not desirable.
To alleviate potential issues, Lix now emits a warning when the Nix search path contains potential shadows for internal files, which will be changed to an error in a future release.
The warning can be disabled by enabling the deprecated feature `nix-path-shadow`.
-10
View File
@@ -1,10 +0,0 @@
---
synopsis: "libexpr: allow empty attr-names in parseAttrPath if they are quoted"
cls: [5375]
category: "Miscellany"
credits: [ma27]
---
Empty strings are now allowed in attribute paths as consumed by e.g. `nix-build`.
I.e. `nix-build -A 'foo."".bar'` works now.
The quotes are necessary, i.e. `nix-build -A foo..bar` will throw an error.
@@ -0,0 +1,10 @@
---
synopsis: "Warn instead of erroring when the final destination of a transfer changes in-flight"
cls: [4641]
issues: [fj#1004]
category: "Miscellany"
credits: [thubrecht]
---
Lix will now emit a warning during downloads where the final destination changes suddently mid-transfer instead of throwing an error.
This transfer behavior has been known to happen very rarely while fetching from some CDNs.
@@ -0,0 +1,10 @@
---
synopsis: 'functional lang migration'
issues: [lix#856]
cls: [3213, 3214, 3215, 3224, 4092, 4093, 4094, 4095, 4096, 4097, 4098, 4099, 4100, 4101, 4102, 4103, 4104, 4105, 4106, 4107, 4108, 4109, 4110, 4111, 4112, 4113, 4114, 4115, 4116, 4117, 4122, 4123, 4269, 4270, 4271, 4272, 4273, 4274, 4347, 4348, 4349, 4350, 4351, 4352, 4569, 4570, 4571, 4572, 4573, 4574, 4596, 4597, 4598, 4599, 4600, 4601, 4602, 4603, 4604, 4605]
category: Development
credits: [piegames, commentator2.0]
---
We have done it! The functional/lang framework has now been fully migrated to functional2/lang.
This means: no more `just clean` and `just install` mess and whatever because one removed a test.
The lang test suite is also getting a face lift, with an improved folder structure and restructuring of many tests.
-14
View File
@@ -1,14 +0,0 @@
---
synopsis: "builtins.break doesn't break expression anymore"
issues: [1165]
cls: [5422]
category: "Fixes"
credits: [blokyk]
---
Wrapping an expression in `builtins.break` used to break some builtins like
`map` and the `is*` functions, which could modify the execution path of code
inadvertently, made debugging nix harder than it already is, and in some cases
even crashed the interpreter. Now, using `break` should be completely
transparent to whatever function receives it as an input, preventing the
above-mentioned issues.
-9
View File
@@ -1,9 +0,0 @@
---
synopsis: "flake config warnings are now printed to stderr"
issues: [1155]
cls: [5379]
category: "Fixes"
credits: [lheckemann]
---
The settings listed in a flake-config confirmation prompt are now printed to stderr rather than stdout, which allows `nix print-dev-env` to emit valid bash again even in the presence of untrusted settings.
+10
View File
@@ -0,0 +1,10 @@
---
synopsis: "Allow remote builders to be configured using TOML"
cls: [4533]
category: "Features"
credits: [commentator2.0, Qyriad]
---
Lix now supports configuring remote builders using a TOML file instead of the old, very cursed and incomprehensible format.
This comes with not only a human-understandable file, but also with better messages and error reports on misconfiguration.
A more detailed Documentation can be found on the [distributed-builds](@docroot@/advanced-topics/distributed-builds.md) Wiki-page
-10
View File
@@ -1,10 +0,0 @@
---
synopsis: "Lix now requires lowdown 1.4.0 or later"
issues: []
cls: [5374]
category: Packaging
credits: [sterni]
---
Support for linking against `lowdown < 1.4.0` has been removed from Lix since
all supported Nixpkgs channels distribute lowdown 2.0.4 or later.
+8
View File
@@ -0,0 +1,8 @@
---
synopsis: "Default to showing build logs in the new-style (nix3) CLI"
cls: [4674]
category: "Miscellany"
credits: [k900]
---
Lix will now show logs by default, in addition to the progress bar, when invoked through the new-style "nix3" CLI (`nix build`, etc)
@@ -0,0 +1,31 @@
---
synopsis: "Move /root/.cache/nix to /var/cache/nix by default"
cls: [4671]
issues: [fj#634]
category: "Breaking Changes"
credits: [raito]
---
By default, Lix attempts to locate a cache directory for its operations (such
as the narinfo cache) by checking the value of `$XDG_CACHE_DIR`.
However, since the Nix daemon is a system service, using `$XDG_CACHE_DIR` is
not typical in this context.
To address this, systemd provides a better solution. Specifically, when
`CacheDirectory=` is set in the `[Service]` section of a systemd unit, it
automatically sets the `$CACHE_DIRECTORY` environment variable and systemd will
manage that cache directory for us.
Now, our systemd unit includes `CacheDirectory=nix`, which sets the
`$CACHE_DIRECTORY` and takes precedence over `$XDG_CACHE_DIR`.
If the daemon is run under user units, systemd will automatically set
`$XDG_CACHE_DIR`.
If neither of these variables is set, Lix falls back to its default behavior.
By default, Lix will try to find a cache directory for its various operations
(e.g. narinfo cache) by looking into `$XDG_CACHE_DIR`.
In summary, what was stored in `/root/.cache/nix` is now moved to
`/var/cache/nix/nix`.
@@ -1,11 +0,0 @@
---
synopsis: "Shadowing internal files through the Nix search path is now an error"
issues: [998]
cls: [4632, 5370]
category: "Breaking Changes"
credits: [thubrecht, jade, horrors]
---
As Lix uses the path `<nix/fetchurl.nix>` for bootstrapping purposes, the ability to shadow it by adding `nix=/some/path` (or `/other/path` that contains a `nix` directory) to the search path is not desirable.
Lix 2.95 deprecated this behavior with a warning, Lix 2.96 now turns it into a hard error if the `nix-path-shadow` deprecated feature isn't enabled. This deprecated feature is slated to be removed in Lix 2.98.
+9
View File
@@ -0,0 +1,9 @@
---
synopsis: "Add an indication of nix-shell nesting depth"
cls: [4657]
issues: [fj#826]
category: "Improvements"
credits: [thubrecht]
---
When in a nix shell (either via a `nix-shell` or a `nix develop` invocation), a variable `NIX_SHELL_LEVEL` is exported to indicate the nesting depth of nix shells.
+20
View File
@@ -0,0 +1,20 @@
---
synopsis: Derivations can now be printed in detail in `nix repl`
cls: [3842]
category: Improvements
credits: [Lunaphied]
---
Traditionally derivations printed in the REPL would only print a formatted object
representing the path of the derivation file it refers to. This makes inspecting
the enhanced derivation attribute sets encountered from `mkDerivation` or similar
wrappers more difficult. Even the `:p`/`:print` command would not elaborate attribute sets
tagged as a derivation.
With this change you can now use `:p`/`:print` to directly inspect a derivation
by providing one as the top-level object. Derivation attribute sets will only be
printed two levels deep and internal derivation attrsets will remain in unexpanded
path form as before. `drvAttrs` will also be elided as these attributes are already
present in the top-level attribute set of the derivation. These heuristics provide
a balance between readability and functionality. When the `:p`/`:print` is omitted,
a bare derivation is printed in the path format as before.
@@ -0,0 +1,37 @@
---
synopsis: Remove `fetch-closure` experimental feature
issues: [fj#1010]
cls: [4595]
category: "Breaking Changes"
credits: [just1602]
---
The `fetch-closure` experimental feature has been removed.
Outside of allowing the user to import closure from binary cache,
`fetchClosure` also allow you to do the following:
* rewrite non-CA path to CA
* reject non-CA paths at fetching time
* reject CA paths at fetching time
Some people are using those mechanism to prevent users from having to build any
package and force going via the declared cache or as a way to use ancient/old
software without paying the evaluation cost of a second nixpkgs.
Both use cases are somewhat of an antipattern in Nix semantics. If the user
cannot fetch a program directly via the substituter mechanism and fall back to
local build, this is a feature AND a misconfiguration. If the user cannot build
certain derivations because they are too expensive, the build directives should
pass `-j0` or similar.
As for the second usecase, there's a different way to do it that also allows to
have a way to reproduce the paths that are hardcoded in that file, perform
`import (fetchurl "https://my-cache/${hashparts storepath}.drv")` rather, i.e.
an IFD to a possibly well known name. The backend can generate them on the fly
or once, and possess stable names.
Finally, as for the non-CA → CA features, Lix removed ca-derivations.
fetchClosure offers ca-derivations-like features which suffers from similar
shortcomings albeit lessened. It only follows that we should rather deprecate
and remove these capabilities.
-18
View File
@@ -1,18 +0,0 @@
---
synopsis: "Allow moving between stack frames relative to current debugger frame"
issues: [1156]
cls: [5411]
category: "Improvements"
credits: [blokyk]
---
Debugging functional programs often involve switching between a bunch of stack
frames to get the full context of what's happening and who's calling who.
Before this change, going up or down the stack in the nix debugger with `:st`
meant remembering the absolute index of each stack frame, instead of their
positions relative to one another; this got tiring *fast*.
Now, you can prepend `:st`'s argument with a + or - sign to indicate you want to
move relative to the current stack frame. For example, typing `:st +3` when you
were on frame `10` will go frame `13`; vice-versa, typing `:st -4` on frame `6`
will go to frame `2`.
-14
View File
@@ -1,14 +0,0 @@
---
synopsis: "invalid arguments to :st now print an error"
cls: [5386]
category: "Improvements"
credits: [blokyk]
---
When using the debugger, the `:st` command used to traverse the call stack would
silently fail and put the debugger in an invalid state if the argument given to
it wasn't a valid stack frame index.
This change adds an error message warning the user if the given index wasn't a
valid frame (telling them the range of valid indices), as well as if it wasn't
even a valid integer to begin with.
+15
View File
@@ -0,0 +1,15 @@
---
synopsis: "Lix daemons are now fully socket-activated on systemd setups"
cls: []
issues: [1030]
category: "Miscellany"
credits: [horrors]
---
When launched by systemd, Lix no longer uses a persistent daemon process and uses systemd socket
activation instead. This is necessary to support the `cgroups` and `auto-allocate-uids` features
and may improve observability of daemon behavior with common systemd-based monitoring solutions.
The old behavior with a single persistent daemon is still available, but disabled by default. It
is not possible to enable both a persistent daemon and socket activation, starting one stops the
other automatically. Existing installations should not require any changes when they're updated.
-1
View File
@@ -200,7 +200,6 @@
- [Release Notes](release-notes/release-notes.md)
- [Upcoming release](release-notes/rl-next.md)
<!-- RELENG-AUTO-INSERTION-MARKER (see releng/release_notes.py) -->
- [Lix 2.95 (2026-03-13)](release-notes/rl-2.95.md)
- [Lix 2.94 (2025-11-17)](release-notes/rl-2.94.md)
- [Lix 2.93 (2025-05-09)](release-notes/rl-2.93.md)
- [Lix 2.92 (2025-01-18)](release-notes/rl-2.92.md)
@@ -135,7 +135,7 @@ How those are combined within the configuration file differs for the formats, an
8. `ssh-public-host-key` (**optional**)
The public host key of the remote machine.
Defaults to basic ssh behavior (checking contents of the known-hosts file)
Defaults to basic ssh behavior (checking contests of the known-hosts file)
### Using a TOML configuration
-6
View File
@@ -177,12 +177,6 @@ Most commands in Lix accept the following command-line options:
You can override this using `--arg`, e.g., `nix-env --install --attr pkgname --arg system \"i686-freebsd\"`.
(Note that since the argument is a Nix string literal, you have to escape the quotes.)
Additionally, dots are interpreted as attribute-path separators.
I.e. `nix-instantiate '<nixpkgs>' -A hello-unfree --arg config.allowUnfree true` will result in an argument `config` with value `{ allowUnfree = true; }` being passed to `<nixpkgs>`.
Please note that merging of different arguments is rejected.
I.e. `--arg config '{ cudaSupport = true; }' --arg config.allowUnfree true` will not work whereas `--arg config.cudaSupport true --arg config.allowUnfree true` is accepted.
- <span id="opt-argstr">[`--argstr`](#opt-argstr)</span> *name* *value*
This option is like `--arg`, only the value is not a Nix expression but a string.
@@ -19,7 +19,7 @@ This description is not normative, but a feature removal may roughly happen like
1. Add a warning when the feature is being used.
2. Disable the feature by default, putting it behind a deprecated feature flag.
- If disabling the feature started out as an opt-in experimental feature, turn that experimental flag into a no-op or remove it entirely.
For example, `--extra-experimental-features no-url-literals` becomes `--extra-deprecated-features url-literals`.
For example, `--extra-experimental-features=no-url-literals` becomes `--extra-deprecated-features=url-literals`.
3. Decide on a time frame for how long that feature will still be supported for backwards compatibility, and clearly communicate that in the error messages.
- Sometimes, automatic migration to alternatives is possible, and such should be provided if possible
- At least one NixOS release cycle should be the minimum
+12 -60
View File
@@ -51,64 +51,28 @@ $ nix-shell -A native-clangStdenvPackages
### Building from the development shell
We have a [justfile](just.systems) for extra convenient building.
It defaults to using `./build` as the build directory, and `$out` (`./outputs/out`) as the install directory.
For most cases, you can clean-build, install, and run the tests with:
```bash
$ just setup --wipe && just test
```
> **Note**
>
> The `--wipe` argument to `meson setup` conveniently works whether you have an existing build directory or not.
>
> However, it is *mostly*, but not *exactly* equivalent to deleting the build directory first.
> In particular, previously specified `-D` build options are **preserved** with `--wipe` (for some reason).
> For example, if you fetch and checkout a new version of Lix, and that new version *removes* a Meson build option from `./meson.options`, *and* a previous invocation in that build directory explicitly set that option, then `meson setup --wipe build` will error, complaining about the unknown option.
> For these cases, `just clean` will give you a well-and-truly-this-time-for-real clean build.
Because the integration tests require installation to work, `just test` automatically also calls `just install`, and Meson helpfully will automatically build any targets that need building when trying to install them.
You can override the build directory or install directory by setting the justfile [variables](https://just.systems/man/en/setting-variables-from-the-command-line.html) `outdir` and `builddir` on the command-line:
```bash
$ just builddir=build-before-bisect outdir=out-before-bisect setup
$ just builddir=build-before-bisect test
```
You'll have to set `builddir` for every target, but `outdir` only needs to be set for `setup`.
Run a clean build and test with `just clean setup build install test`.
You can also run the unit tests and integration tests separately:
```bash
$ just setup
$ just test-unit
$ just test-integration
$ just setup build test-unit
$ just install test-integration
```
Most justfile targets forward all further arguments to the underlying Meson invocation.
Many justfile aliases have a `-custom` variant which pass extra arguments to `meson`.
For example, to work on both Lix and nix-eval-jobs you can run:
```bash
$ just setup -Dnix-eval-jobs=enabled
```
$ just setup-custom -Dnix-eval-jobs=enabled
$ # or
$ mesonFlags=-Dnix-eval-jobs=enabled just setup
```
Note that only targets which *don't* accept extra arguments can have other targets following them.
`just clean setup` is equivalent to `just clean && just setup`, but `just build test` runs the `build` target with the argument `test`.
This means that if you want to, for example, build with lower parallelism, and then test, you will have to do something like this:
```bash
$ just build -j4
$ just test
```
Finally, the rewrite of the integration test suite, functional2, also has its own justfile target which allows passing extra arguments to pytest.
For example, to collect and list all functional2 tests without running them, you can pass pytest's `--collect-only` argument:
```bash
$ just test-functional2 --collect-only
```
Note that only targets which don't accept extra arguments can be used when
running multiple targets at once; `just setup build` is fine, but `just
setup-custom build` is an error. The `test` target is usually the last one to
run, so it always accepts extra arguments.
You can also build Lix manually:
@@ -444,18 +408,6 @@ You can build it yourself:
# xdg-open ./result/coverage/index.html
```
Or, in a dev shell, set `-Dcoverage=true` when running `meson setup`.
Coverage data goes into `build/profraw` when you run executables in the dev shell.
Then, run `ninja -C build coverage-report` to produce an HTML report of coverage in `build/coverage/index.html` alongside a LLVM `.lcov` file.
> [!NOTE]
> We use the [llvm source-based coverage], which has better precision than using clang with gcov, which is debuginfo based (but likely worse performance, which is fine).
>
> It should be noted that Meson [allegedly has coverage support][meson-coverage], but it only supports gcov-style coverage, so we don't use it.
[llvm source-based coverage]: https://clang.llvm.org/docs/SourceBasedCodeCoverage.html
[meson-coverage]: https://mesonbuild.com/Unit-tests.html#coverage
Metrics about the change in line/function coverage over time will be available in the future (FIXME(lix-hydra)).
## Add a release note {#release-notes}
+1 -4
View File
@@ -383,10 +383,7 @@ I grepped `lix/` for `get[eE]nv\("` to find the mentions in Lix code.
Overrides compile-time configuration of various locations used by Lix. See `lix/libstore/globals.cc`.
**Expected value**: a directory
- `LIX_DAEMON_SOCKET_DIR` (optional) - Overrides the daemon socket directory from `$NIX_STATE_DIR/daemon-socket`.
**Expected value**: a directory
- `NIX_DAEMON_SOCKET_PATH` (optional) - Overrides the daemon socket path from `$NIX_STATE_DIR/daemon-socket/socket`. Ignored if `LIX_DAEMON_SOCKET_DIR` is set.
- `NIX_DAEMON_SOCKET_PATH` (optional) - Overrides the daemon socket path from `$NIX_STATE_DIR/daemon-socket/socket`.
**Expected value**: path to a socket
- `NIX_LOG_FD` (output) - An FD number for logs in `internal-json` format to be sent to.
+1 -1
View File
@@ -89,7 +89,7 @@
[store path]: #gloss-store-path
- [file system object]{#gloss-file-system-object}
- [file system object]{#gloss-store-object}
The Nix data model for representing simplified file system data.
-6
View File
@@ -17,12 +17,6 @@ the attributes of which specify the inputs of the build.
string. This is used as a symbolic name for the package by
`nix-env`, and it is appended to the output paths of the derivation.
> **Note**
>
> Names can only contain alphanumerical characters (0-9, a-z, A-Z)
> as well as `+`, `-`, `.`, `_`, `?` and `=`. Names must be neither
> `.` nor `..`, and must not start with `.-` or `..-`.
- There must be an attribute named [`builder`]{#attr-builder} that identifies the
program that is executed to perform the build. It can be either a
derivation or a source (a local file reference, e.g.,
+1 -1
View File
@@ -5,7 +5,7 @@
FIXME(Lix): This chapter is quite outdated with respect to recommended practices in 2024 and needs updating.
The commands in here will work, however, and the installation section is up to date.
For more updated guidance, see the links on <https://wiki.lix.systems/books/lix-users/page/nix-resources>
For more updated guidance, see the links on <https://lix.systems/resources/>
</div>
-546
View File
@@ -1,546 +0,0 @@
# Lix 2.95 "Kakigōri" (2026-03-13)
# Lix 2.95.0 (2026-03-13)
## Breaking Changes
- Deprecate shadowing internal files through the Nix search path [lix#998](https://git.lix.systems/lix-project/lix/issues/998) [cl/4632](https://gerrit.lix.systems/c/lix/+/4632)
As Lix uses the path `<nix/fetchurl.nix>` for bootstrapping purposes, the ability to shadow it by adding `nix=/some/path` (or `/other/path` that contains a `nix` directory) to the search path is not desirable.
To alleviate potential issues, Lix now emits a warning when the Nix search path contains potential shadows for internal files, which will be changed to an error in a future release.
The warning can be disabled by enabling the deprecated feature `nix-path-shadow`.
Many thanks to [Tom Hubrecht](https://git.lix.systems/tom-hubrecht) for this.
- More deprecated features [cl/2092](https://gerrit.lix.systems/c/lix/+/2092) [cl/2310](https://gerrit.lix.systems/c/lix/+/2310) [cl/2311](https://gerrit.lix.systems/c/lix/+/2311) [cl/4638](https://gerrit.lix.systems/c/lix/+/4638) [cl/4652](https://gerrit.lix.systems/c/lix/+/4652) [cl/4764](https://gerrit.lix.systems/c/lix/+/4764)
This release cycle features a new batch of deprecated (anti-)features.
You can opt in into the old behavior with `--extra-deprecated-features` or any equivalent configuration option.
- `broken-string-indentation` indented strings (those starting with `''`) might produce unintended results due to how the whitespace stripping is done. Those cases will now warn the user.
- `broken-string-escape` "escaped" characters without a properly defined escape sequence evaluate to "themselves". This is in most cases unintended behaviour, both for writing regexes, and using legacy or uncommon escape sequences like `\f`. The user will now be warned, if those are present.
- `floating-without-zero` so far, one was able to declare a float using something like `.123`. This can cause confusion about accessing attributes. Floating point numbers must now always include the leading zero, i.e. `0.123`
- `rec-set-merges` Attribute sets like `{ foo = {}; foo.bar = 42;}` implicitly merge at parse time, however if one of them is marked as recursive but not the others then the recursive attribute may get lost (order-dependent). Therefore, merging attrs with mixed-`rec` is now forbidden.
- `rec-set-dynamic-attrs` Dynamic attributes have weird semantics in the presence of recursive attrsets (they evaluate *after* the rest of the set). This is now forbidden.
- `or-as-identifier` `or` as an identifier has always been weird since the `or` (almost-)keyword has been introduced. We are deprecating the backcompat hacks from the early days of Nix in favor of making `or` a full and proper keyword.
- `tokens-no-whitespace` Function applications without space around the arguments like `0a`, `0.00.0` or `foo"1"2` are now forbidden. The same applies to list elements. The primary reason for this deprecation is to remove foot guns around surprising tokenization rules regarding number literals, but this will also free up some syntax for other purposes (e.g. `r""` strings) for reuse at some point in the future.
- `shadow-internal-symbols` has been expanded to also forbid shadowing `null`, `true` and `false`.
- `ancient-let` deprecation has been turned into a full parser error instead of a warning.
- `rec-set-overrides` deprecation has been turned into a full parser error instead of a warning.
Many thanks to [piegames](https://git.lix.systems/piegames), [rootile (Rutile)](https://git.lix.systems/rootile), and [eldritch horrors](https://git.lix.systems/pennae) for this.
- Move `/root/.cache/nix` to `/var/cache/nix` by default [lix#634](https://git.lix.systems/lix-project/lix/issues/634) [cl/4671](https://gerrit.lix.systems/c/lix/+/4671)
By default, Lix attempts to locate a cache directory for its operations (such
as the narinfo cache) by checking the value of `$XDG_CACHE_DIR`.
However, since the Nix daemon is a system service, using `$XDG_CACHE_DIR` is
not typical in this context.
To address this, systemd provides a better solution. Specifically, when
`CacheDirectory=` is set in the `[Service]` section of a systemd unit, it
automatically sets the `$CACHE_DIRECTORY` environment variable and systemd will
manage that cache directory for us.
Now, our systemd unit includes `CacheDirectory=nix`, which sets the
`$CACHE_DIRECTORY` and takes precedence over `$XDG_CACHE_DIR`.
If the daemon is run under user units, systemd will automatically set
`$XDG_CACHE_DIR`.
If neither of these variables is set, Lix falls back to its default behavior.
By default, Lix will try to find a cache directory for its various operations
(e.g. narinfo cache) by looking into `$XDG_CACHE_DIR`.
In summary, what was stored in `/root/.cache/nix` is now moved to
`/var/cache/nix/nix`.
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
- Remove `fetch-closure` experimental feature [lix#1010](https://git.lix.systems/lix-project/lix/issues/1010) [cl/4595](https://gerrit.lix.systems/c/lix/+/4595)
The `fetch-closure` experimental feature has been removed.
Outside of allowing the user to import closure from binary cache,
`fetchClosure` also allowed you to do the following:
* rewrite non-CA path to CA
* reject non-CA paths at fetching time
* reject CA paths at fetching time
Some people are using those mechanism to prevent users from having to build any
package and force going via the declared cache or as a way to use ancient/old
software without paying the evaluation cost of a second nixpkgs.
Both use cases are somewhat of an antipattern in Nix semantics. If the user
cannot fetch a program directly via the substituter mechanism and fall back to
local build, this is a feature *and* a misconfiguration. If the user cannot build
certain derivations because they are too expensive, the build directives should
pass `-j0` or similar.
As for the second usecase, there's a different way to do it that also allows to
have a way to reproduce the paths that are hardcoded in that file, perform
`import (fetchurl "https://my-cache/${hashparts storepath}.drv")` rather, i.e.
an IFD to a possibly well known name. The backend can generate them on the fly
or once, and possess stable names.
Finally, as for the non-CA → CA features, Lix removed ca-derivations.
fetchClosure offers ca-derivations-like features which suffers from similar
shortcomings albeit lessened. It only follows that we should deprecate
and remove these capabilities.
Many thanks to [just1602](https://git.lix.systems/just1602) for this.
## Features
- `nix store add-path` now supports references [cl/5205](https://gerrit.lix.systems/c/lix/+/5205)
Lix supports two categories of hashes in store paths: input-addressed and output-addressed.
Currently, in Nix language, there is no way to produce output-addressed paths with references, as fixed-output derivations forbid references.
However, the Nix store actually *supports* references in output-addressed paths.
This is very useful for importing build products created outside of Lix that reference dependency store paths since such build products have no associated derivation so don't make any sense to input-address.
Previously, output-addressed paths with references could only be created by writing a custom client to the rather-baroque Nix daemon protocol; now it's available in the CLI.
Using `nix store add-path --references-list-json REFS_LIST_FILE SOME_PATH` with a JSON list of string store paths, you can now create such paths with the Lix CLI.
They may be consumed from Nix language using something like `builtins.storePath` or the following which also works in pure evaluation mode:
```nix
# Hack from https://git.lix.systems/lix-project/lix/issues/402#issuecomment-5889
path:
builtins.appendContext path {
${path} = {
path = true;
};
}
```
Many thanks to [jade](https://git.lix.systems/jade) for this.
- Add `builtins.warn` for emitting warnings from Nix code [cl/2248](https://gerrit.lix.systems/c/lix/+/2248)
Lix now has a builtin function for emitting warnings.
Like `builtins.trace`, it takes two arguments: the message to emit, and the expression to return.
_Unlike_ `builtins.trace`, `builtins.warn` requires the first argument — the message — to be a string.
In the future we may extend `builtins.warn` to accept a more structured API.
To go along with this, we also have two new config settings:
- [`debugger-on-warn`](@docroot@/command-ref/conf-file.md#conf-debugger-on-warn), which, when used with `--debugger`, makes `builtins.warn` also function like [`builtins.break`](@docroot@/language/builtins.md#builtins-break).
- [`abort-on-warn`](@docroot@/command-ref/conf-file.md#conf-abort-on-warn), which aborts evaluation entirely after the warning is emitted.
Many thanks to [Emilia Bopp](https://git.lix.systems/milibopp) and [Qyriad](https://git.lix.systems/Qyriad) for this.
- `keep-env-derivations` is now supported for nix3 CLI (`nix profile`) [lix#1095](https://git.lix.systems/lix-project/lix/issues/1095) [cl/5332](https://gerrit.lix.systems/c/lix/+/5332)
The `keep-env-derivations` feature is now available for `nix profile`. This allows users to prevent the garbage collection of derivations used to install a profile, even when `keep-derivations = false` (set to `true` by default).
Previously, `nix-env` supported this feature, but `nix profile` **never** did. This caused issues when garbage collection removed the associated `.drv` files, which are required, for example, by vulnerability management tools (e.g. [vulnix](https://github.com/nix-community/vulnix)) for proper operation.
This issue has now been resolved.
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
- Make `log-format` a setting [cl/4686](https://gerrit.lix.systems/c/lix/+/4686)
The [`--log-format` CLI option](@docroot@/command-ref/opt-common.md#opt-log-format) can now be set in [`nix.conf`](@docroot@/command-ref/conf-file.md#conf-log-format)!
For example, you can now persistently enable the `multiline-with-logs` log format [added in Lix 2.91](@docroot@/release-notes/rl-2.91.md) by adding the following to your `nix.conf`:
```conf
log-format = multiline-with-logs
```
Or the equivalent in a NixOS configuration:
```nix
{
nix.settings.log-format = "multiline-with-logs";
}
```
Many thanks to [Qyriad](https://git.lix.systems/Qyriad) for this.
- Allow remote builders to be configured using TOML [cl/4533](https://gerrit.lix.systems/c/lix/+/4533)
Lix now supports configuring remote builders using a TOML file instead of the old, very cursed and incomprehensible format.
This comes with not only a human-understandable file, but also with better messages and error reports on misconfiguration.
A more detailed Documentation can be found on the [distributed-builds](@docroot@/advanced-topics/distributed-builds.md) documentation page.
Many thanks to [rootile (Rutile)](https://git.lix.systems/rootile) and [Qyriad](https://git.lix.systems/Qyriad) for this.
- Emit warnings when encountering IFD with `warn-import-from-derivation` [nix#13279](https://github.com/NixOS/nix/pull/13279) [cl/3879](https://gerrit.lix.systems/c/lix/+/3879)
Instead of only being able to toggle the use of [Import from
Derivation](https://nix.dev/manual/nix/stable/language/import-from-derivation) with
`allow-import-from-derivation`, Lix is now able to warn users whenever IFD is encountered with
`warn-import-from-derivation`.
Many thanks to [Seth Flynn](https://git.lix.systems/getchoo), [gustavderdrache](https://github.com/gustavderdrache), and [Eelco Dolstra](https://github.com/edolstra) for this.
## Improvements
- Collect Flakes untrusted settings into one prompt [lix#682](https://git.lix.systems/lix-project/lix/issues/682) [cl/2921](https://gerrit.lix.systems/c/lix/+/2921)
When working with Flakes containing untrusted settings, a prompt is shown for each setting, asking whether to vet or approve it. This looks like:
```
nix flake lock
warning: ignoring untrusted flake configuration setting 'allow-dirty', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
warning: ignoring untrusted flake configuration setting 'sandbox', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
The following settings require your decision:
- allow-dirty = false
- sandbox = false
Do you want to allow configuration settings to be applied?
This may allow the flake to gain root, see the nix.conf manual page (yes for now/Allow always/no/No to all)
```
In Flakes with a large number of settings to approve or reject, this process can become tedious as each option must be handled individually.
To address this, all untrusted settings are now consolidated into a single prompt: allowing for bulk acceptance permanently or not, rejection, or detailed review. For example:
### Scrutiny scenario
```console
nix flake lock
warning: ignoring untrusted flake configuration setting 'allow-dirty', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
warning: ignoring untrusted flake configuration setting 'sandbox', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
The following settings require your decision:
- allow-dirty = false
- sandbox = false
Do you want to allow configuration settings to be applied?
This may allow the flake to gain root, see the nix.conf manual page (yes for now/Allow always/no/No to all) n
warning: you can set 'accept-flake-config' to 'false' to automatically reject configuration options supplied by flakes
Do you want to allow setting 'allow-dirty = false'? (yes for now/Allow always/no for now) y
Do you want to allow setting 'sandbox = false'? (yes for now/Allow always/no for now) n
```
### Reject everything scenario
```console
nix flake lock
warning: ignoring untrusted flake configuration setting 'allow-dirty', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
warning: ignoring untrusted flake configuration setting 'sandbox', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
The following settings require your decision:
- allow-dirty = false
- sandbox = false
Do you want to allow configuration settings to be applied?
This may allow the flake to gain root, see the nix.conf manual page (yes for now/Allow always/no/No to all) N
Rejecting all untrusted nix.conf entries
warning: you can set 'accept-flake-config' to 'false' to automatically reject configuration options supplied by flakes
```
### Accept everything scenario
```console
nix flake lock
warning: ignoring untrusted flake configuration setting 'allow-dirty', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
warning: ignoring untrusted flake configuration setting 'sandbox', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
The following settings require your decision:
- allow-dirty = false
- sandbox = false
Do you want to allow configuration settings to be applied?
This may allow the flake to gain root, see the nix.conf manual page (yes for now/Allow always/no/No to all) y
```
### Accept everything PERMANENTLY scenario
Note that accepting everything permanently will authorize these options for any
further operations.
The file containing this trust information is usually located in
`~/.local/share/nix/trusted-settings.json` and can be edited manually to revoke
this permission until Lix provides a first-class command for this manipulation.
```console
nix flake lock
warning: ignoring untrusted flake configuration setting 'allow-dirty', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
warning: ignoring untrusted flake configuration setting 'sandbox', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
The following settings require your decision:
- allow-dirty = false
- sandbox = false
Do you want to allow configuration settings to be applied?
This may allow the flake to gain root, see the nix.conf manual page (yes for now/Allow always/no/No to all) A
```
Many thanks to [isabelroses](https://git.lix.systems/isabelroses), [Raito Bezarius](https://git.lix.systems/raito), and [eldritch horrors](https://git.lix.systems/pennae) for this.
- `--check` or `--rebuild` is clearer about a missing path [lix#485](https://git.lix.systems/lix-project/lix/issues/485)
Previously, when running Lix with --check or --rebuild, failures often surfaced
as an unhelpful error:
> "some outputs of '...' are not valid, so checking is not possible"
This message could mean two different things:
- The requested output paths don't exist at all, or,
- Some outputs exist but are not known to Lix
Lix cannot reliably distinguish these cases, so it treated them the same.
We've updated the error messages to clarify what Lix can determine: whether any
valid outputs (> 0) are present or whether no outputs are available.
When no valid outputs can be found, Lix will now suggest building the derivation
normally (without --check or --rebuild) before trying again.
When some valid outputs are present, Lix now reports which ones are valid,
shows the full list of known outputs, and also suggests building the derivation
normally.
In the future, Lix may automate this recovery step when it knows how to rebuild
the paths, but implementing that safely requires more extensive changes to the
codebase.
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
- `nix develop` no longer ignores the env variable `SSL_CERT_FILE` [cl/5042](https://gerrit.lix.systems/c/lix/+/5042)
Running `nix develop` and `nix print-dev-env` on shells that define the environment variable `SSL_CERT_FILE` now works correctly by exporting that variable inside the built shell.
Many thanks to [Tom Hubrecht](https://git.lix.systems/tom-hubrecht) for this.
- Linux sandbox launch overhead greatly reduced [cl/5030](https://gerrit.lix.systems/c/lix/+/5030) [cl/5073](https://gerrit.lix.systems/c/lix/+/5073) [cl/5074](https://gerrit.lix.systems/c/lix/+/5074)
Sandboxed builds are now much cheaper to launch on Linux, with constant management
overhead. This will mostly be noticeable when building derivation trees containing
many small derivations like nixpkgs' `writeFile` or `runCommand` with scripts that
exit quickly. In synthetic tests we have seen build times of 3000 small runCommand
drop from 80 seconds to 14 seconds, which is the most optimistic case in practice.
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
- mTLS store connections via a plugin [cl/3754](https://gerrit.lix.systems/c/lix/+/3754) [cl/3696](https://gerrit.lix.systems/c/lix/+/3696) [cl/3697](https://gerrit.lix.systems/c/lix/+/3697) [cl/3698](https://gerrit.lix.systems/c/lix/+/3698)
To support use cases requiring mutual TLS (mTLS) authentication when connecting
to remote Nix stores, e.g. private stores, we have introduced a **contributed**
mTLS plugin extending the Lix store interface.
This design follows an extensibility model which was brought up [by a proposal
of making Kerberos authentication possible in Lix
directly](https://gerrit.lix.systems/c/lix/+/3637).
This mTLS plugin serves as a concrete example of how store connection
mechanisms can be modularized through external plugins, without extending Lix
core. This idea can be generalized to integrate automatic certificate renewal
or advanced integrations with secrets engine or posture checks.
It enables custom TLS client certificates to be used for authenticating against
a remote store that enforces mTLS.
To use the plugin, configure Lix manually by setting in your `nix.conf`:
```
plugin-files = /a/path/to/libplugin_mtls_store.so
```
Currently, this must be done explicitly. In the future, Nixpkgs will provide a
mechanism to reference an up-to-date and curated set of plugins automatically.
Making plugins easily consumable outside of Nixpkgs (e.g., from external plugin
registries or binary distributions) remains an open question and will require
further design.
Contributed plugins come with significantly reduced **stability** and
**maintenance** guarantees compared to the Lix core. We encourage users who
depend on a given plugin to take on maintenance responsibilities and apply for
ownership within the Lix mono-repository. These plugins are subject to removal
at any time.
Many thanks to [Raito Bezarius](https://git.lix.systems/raito), [eldritch horrors](https://git.lix.systems/pennae), [mic92](https://github.com/mic92), [vlaci](https://github.com/vlaci), and [nkk0](https://github.com/nkk0) for this.
- Add an indication of nix-shell nesting depth [lix#826](https://git.lix.systems/lix-project/lix/issues/826) [cl/4657](https://gerrit.lix.systems/c/lix/+/4657)
When in a nix shell (either via a `nix-shell` or a `nix develop` invocation), a variable `NIX_SHELL_LEVEL` is exported to indicate the nesting depth of nix shells.
Many thanks to [Tom Hubrecht](https://git.lix.systems/tom-hubrecht) for this.
- `nix store delete` can now unlink a GC root before deleting its closure [cl/4660](https://gerrit.lix.systems/c/lix/+/4660)
Ever build something, and then you want to delete it and whatever dependencies it downloaded?
Before you had to resolve the `result` symlink and copy it, then delete it, *then* `nix store delete --delete-closure --skip-live` on the path you copied.
Now you can just pass `--unlink` and the `result` symlink itself.
Many thanks to [Qyriad](https://git.lix.systems/Qyriad) for this.
- `nix path-info` no longer lies to the user about fetching paths [lix#323](https://git.lix.systems/lix-project/lix/issues/323) [cl/4866](https://gerrit.lix.systems/c/lix/+/4866)
When running `nix path-info` with an installable that is not present in the store, Lix no longer
tells the user which paths are missing and that they will be fetched, as the documentation clearly
states that this command does not fetch missing paths.
Many thanks to [Tom Hubrecht](https://git.lix.systems/tom-hubrecht) for this.
- Derivations can now be printed in detail in `nix repl` [cl/3842](https://gerrit.lix.systems/c/lix/+/3842)
Traditionally derivations printed in the REPL would only print a formatted object
representing the path of the derivation file it refers to. This makes inspecting
the enhanced derivation attribute sets encountered from `mkDerivation` or similar
wrappers more difficult. Even the `:p`/`:print` command would not elaborate attribute sets
tagged as a derivation.
With this change you can now use `:p`/`:print` to directly inspect a derivation
by providing one as the top-level object. Derivation attribute sets will only be
printed two levels deep and internal derivation attrsets will remain in unexpanded
path form as before. `drvAttrs` will also be elided as these attributes are already
present in the top-level attribute set of the derivation. These heuristics provide
a balance between readability and functionality. When the `:p`/`:print` is omitted,
a bare derivation is printed in the path format as before.
Many thanks to [Lunaphied](https://git.lix.systems/Lunaphied) for this.
- Reject `__json` in structured attributes derivations [lix#380](https://git.lix.systems/lix-project/lix/issues/380) [cl/5286](https://gerrit.lix.systems/c/lix/+/5286)
In structured attributes derivations, `__json` is used internally to store the
JSON representation of the `env` attribute field that users can set.
Unfortunately, a user can set `__json` *and* enable structured attributes,
resulting in a broken derivation from a semantic point of view.
As no user can benefit from setting `__json` *and* enable structured attributes,
we disallow that possibility and throw an error from now on.
This is not seen as a breaking change because there's no user code that can
benefit from this behavior, hence, it's an improvement to user experience.
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
- Shells support `$NIX_LOG_FD` now [lix#336](https://git.lix.systems/lix-project/lix/issues/336) [cl/4694](https://gerrit.lix.systems/c/lix/+/4694) [cl/4695](https://gerrit.lix.systems/c/lix/+/4695)
Lix's "debugging" shells (`nix3-develop` and `nix-shell`) now set the
`$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`.
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
- Supplementary groups are now supported for daemon authentication [lix#968](https://git.lix.systems/lix-project/lix/issues/968) [cl/5021](https://gerrit.lix.systems/c/lix/+/5021)
macOS, FreeBSD and Linux now support receiving supplementary groups during UNIX domain authentication to a Lix daemon.
This change is particularly beneficial for systemd units with `DynamicUser=true` that need to connect to a Lix daemon, using a `SupplementaryGroups=` allocated by systemd in the context of the process. This is desirable if you wish to harden Lix clients.
Many thanks to [Raito Bezarius](https://git.lix.systems/raito), [Tom Hubrecht](https://git.lix.systems/tom-hubrecht), [alois31](https://git.lix.systems/alois31), and [eldritch horrors](https://git.lix.systems/pennae) for this.
## Fixes
- Nix shells' `$NIX_BUILD_TOP` are shorter [lix#1044](https://git.lix.systems/lix-project/lix/issues/1044) [cl/4663](https://gerrit.lix.systems/c/lix/+/4663)
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).
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
- Fix resolving of symlinks in flake paths [lix#106](https://git.lix.systems/lix-project/lix/issues/106) [lix#12286](https://git.lix.systems/lix-project/lix/pulls/12286) [cl/4783](https://gerrit.lix.systems/c/lix/+/4783)
Flake paths are now canonicalized to resolve symlinks. This ensures that when a flake is accessed via a symlink, paths are resolved relative to the target directory, not the symlink's location.
Many thanks to [stevalkr](https://github.com/stevalkr) and [xyenon](https://git.lix.systems/xyenon) for this.
- The REPL no longer considers failed loads for `:reload` [lix#50](https://git.lix.systems/lix-project/lix/issues/50) [cl/4864](https://gerrit.lix.systems/c/lix/+/4864) [cl/4865](https://gerrit.lix.systems/c/lix/+/4865) [cl/4700](https://gerrit.lix.systems/c/lix/+/4700) [cl/4889](https://gerrit.lix.systems/c/lix/+/4889)
The [REPL](@docroot@/command-ref/new-cli/nix3-repl.md) allows "loading" files, flakes, and expressions into the environment, with the commands `:load`/`:l`, `:load-flake`/`:lf`, and `:add`/`:a` respectively.
The results of those stay in the environment as-is even if their sources change, until the `:reload` command is used.
However `:reload` would re-perform *all* instances of `:l`/`:lf`/`:a`, meaning you would get things like this:
```nix
nix-repl> :l /tmp/texting.nix
error: getting status of '/tmp/texting.nix': No such file or directory
# oops, typo.
nix-repl> :l /tmp/testing.nix
# Do some stuff…
nix-repl> :reload
error: getting status of '/tmp/texting.nix': No such file or directory
```
This is pretty silly, but also *incredibly* annoying, as it would stop there and *not* reload the correct files anymore.
This effectively meant typoing any of the load commands would make `:reload` useless for the rest of the entire `nix repl` session!
This has been fixed, so now only *successful* loads count towards `:reload`.
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) and [Qyriad](https://git.lix.systems/Qyriad) for this.
- Consistently use commit hash as rev when locking git inputs [cl/4762](https://gerrit.lix.systems/c/lix/+/4762)
Lix will now use commit hashes instead of tag object hashes in the `rev` field
when fetching git inputs by tag in `flake.lock` and `builtins.fetchTree` output.
Note that this means that Lix may change some `flake.lock` files on re-locking. Old `flake.lock` files still remain valid.
Many thanks to [goldstein](https://git.lix.systems/goldstein) for this.
## Development
- Functional lang migration [lix#856](https://git.lix.systems/lix-project/lix/issues/856) [cl/3213](https://gerrit.lix.systems/c/lix/+/3213)
We have done it! The functional/lang framework has now been fully migrated to functional2/lang.
This means: no more `just clean` and `just install` mess and whatever because one removed a test.
The lang test suite is also getting a face lift, with an improved folder structure and restructuring of many tests.
Only the first CL of the chain is provided but there's way more changes associated to this project.
Many thanks to [piegames](https://git.lix.systems/piegames) and [rootile (Rutile)](https://git.lix.systems/rootile) for this.
## Miscellany
- Warn instead of erroring when the final destination of a transfer changes in-flight [lix#1004](https://git.lix.systems/lix-project/lix/issues/1004) [cl/4641](https://gerrit.lix.systems/c/lix/+/4641)
Lix will now emit a warning during downloads where the final destination changes suddently mid-transfer instead of throwing an error.
This transfer behavior has been known to happen very rarely while fetching from some CDNs.
Many thanks to [Tom Hubrecht](https://git.lix.systems/tom-hubrecht) for this.
- `impersonate-linux-26` setting removed [cl/5047](https://gerrit.lix.systems/c/lix/+/5047)
Linux 3.0 was released 15 years ago. The `impersonate-linux-26` setting was added
14 years ago with no mention of it being necessary to build anything, only saying
that it improves determinism—which isn't accurate since impersonating Linux 2.6.x
still allows the version string to change, and the final component of the version
does still change with each Linux release. Since this setting should be no longer
necessary in modern systems and workarounds for building old code exist (by using
e.g. `setarch --uname-2.6` to wrap builds) we are removing this setting from Lix.
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
- Default to showing build logs in the new-style (nix3) CLI [cl/4674](https://gerrit.lix.systems/c/lix/+/4674)
Lix will now show logs by default, in addition to the progress bar, when invoked through the new-style "nix3" CLI (`nix build`, etc)
Many thanks to [K900](https://git.lix.systems/K900) for this.
- Lix daemons are now fully socket-activated on systemd setups [lix#1030](https://git.lix.systems/lix-project/lix/issues/1030)
When launched by systemd, Lix no longer uses a persistent daemon process and uses systemd socket
activation instead. This is necessary to support the `cgroups` and `auto-allocate-uids` features
and may improve observability of daemon behavior with common systemd-based monitoring solutions.
The old behavior with a single persistent daemon is still available, but disabled by default. It
is not possible to enable both a persistent daemon and socket activation, starting one stops the
other automatically. Existing installations should not require any changes when they're updated.
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
- Plugin interfaces have changed (again) [lix#359](https://git.lix.systems/lix-project/lix/issues/359) [cl/4933](https://gerrit.lix.systems/c/lix/+/4933) [cl/4934](https://gerrit.lix.systems/c/lix/+/4934)
The `RegisterPrimOp` class used to register builtins has been removed. Plugins
must now call `PluginPrimOps::add` from their `nix_plugin_entry` with the same
parameters previously passed to `RegisterRrimOp` to register any new builtins.
The `GlobalConfig::Register` helper class has also been removed. Adding config
options to the system is now done with `GlobalConfig::registerGlobalConfig`; a
plugin can add config values by calling this function from `nix_plugin_entry`.
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
+1 -1
View File
@@ -381,7 +381,7 @@ image
pkgs.buildPackages.runCommand "docker-image-tarball-${pkgs.nix.version}"
{
nativeBuildInputs = [ pkgs.buildPackages.bubblewrap ];
meta.description = "Docker image tarball with Lix for ${pkgs.stdenv.hostPlatform.system}";
meta.description = "Docker image tarball with Lix for ${pkgs.system}";
}
''
mkdir -p $out/nix-support
Generated
+19 -17
View File
@@ -3,15 +3,17 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1751685974,
"narHash": "sha256-NKw96t+BgHIYzHUjkTK95FqYRVKB8DHpVhefWSz/kTw=",
"rev": "549f2762aebeff29a2e5ece7a7dc0f955281a1d1",
"type": "tarball",
"url": "https://git.lix.systems/api/v1/repos/lix-project/flake-compat/archive/549f2762aebeff29a2e5ece7a7dc0f955281a1d1.tar.gz"
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"type": "tarball",
"url": "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz"
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"lowdown-src": {
@@ -33,11 +35,11 @@
"nix2container": {
"flake": false,
"locked": {
"lastModified": 1767195068,
"narHash": "sha256-+OMnL79ZjqM/PCz2hoQ12MnXNoSSfBGnsYBOZnA9XbI=",
"lastModified": 1724996935,
"narHash": "sha256-njRK9vvZ1JJsP8oV2OgkBrpJhgQezI03S7gzskCcHos=",
"owner": "nlewo",
"repo": "nix2container",
"rev": "bb6801be998ba857a62c002cb77ece66b0a57298",
"rev": "fa6bb0a1159f55d071ba99331355955ae30b3401",
"type": "github"
},
"original": {
@@ -106,16 +108,16 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1773082486,
"narHash": "sha256-TKUDrM0nKUo5s/b8jhjXa2prcu5KU5Cck3HBTRLDjfo=",
"lastModified": 1757198069,
"narHash": "sha256-m3VUcOD4rTs8J7S+3dOjWMrAjw6RcITC3XYQ98zhEFs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "7f8b8875bdb38a70c7b5ceb9ba6a6a8d69859e16",
"rev": "0747026fc57ecb9c28901c7f7a2b5dc40e8af43c",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.11-small",
"ref": "nixos-25.05-small",
"repo": "nixpkgs",
"type": "github"
}
@@ -123,11 +125,11 @@
"pre-commit-hooks": {
"flake": false,
"locked": {
"lastModified": 1769939035,
"narHash": "sha256-Fok2AmefgVA0+eprw2NDwqKkPGEI5wvR+twiZagBvrg=",
"lastModified": 1733318908,
"narHash": "sha256-SVQVsbafSM1dJ4fpgyBqLZ+Lft+jcQuMtEL3lQWx2Sk=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "a8ca480175326551d6c4121498316261cbb5b260",
"rev": "6f4e2a2112050951a314d2733a994fbab94864c6",
"type": "github"
},
"original": {
+81 -80
View File
@@ -2,7 +2,7 @@
description = "Lix: A modern, delicious implementation of the Nix package manager";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11-small";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05-small";
nixpkgs-regression.url = "github:NixOS/nixpkgs/215d4d0fd80ca5163643b03a33fde804a29cc1e2";
# Required because Nix 2.18 is not in Nixpkgs ≥ 25.05 anymore.
@@ -24,7 +24,7 @@
flake = false;
};
flake-compat = {
url = "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz";
url = "github:edolstra/flake-compat";
flake = false;
};
};
@@ -177,14 +177,18 @@
nixStable = prev.nix;
nixVersions = prev.nixVersions // {
nix_2_3 = prev.nixVersions.nix_2_3.overrideAttrs (old: {
meta = old.meta // {
knownVulnerabilities = [ ];
};
});
# Nix 2.18 has been removed from Nixpkgs ≥ 25.05, so we need to reintroduce it ourselves for our tests.
nix_2_18 =
nix_2_18.outputs.packages.${currentStdenv.hostPlatform.system}.default.overrideAttrs
(_: {
pname = "nix";
});
nix_2_18 = nix_2_18.outputs.packages.${currentStdenv.hostPlatform.system}.default;
};
# Forward from the previous stage as we dont want it to pick the lowdown override
nixUnstable = prev.nixUnstable;
check-headers = final.buildPackages.callPackage ./maintainers/check-headers.nix { };
check-syscalls = final.buildPackages.callPackage ./maintainers/check-syscalls.nix { };
@@ -216,12 +220,12 @@
inherit versionSuffix officialRelease;
stdenv = currentStdenv;
busybox-sandbox-shell = final.busybox-sandbox-shell or final.default-busybox-sandbox-shell;
# See below
lowdown = final.lowdown_3_0;
lowdown-unsandboxed = final.lowdown_3_0.override { enableDarwinSandbox = false; };
};
lix-clang-tidy = final.callPackage ./subprojects/lix-clang-tidy { };
lix-clang-tidy = final.callPackage ./subprojects/lix-clang-tidy {
# FIXME: To be removed when switching to nixos-25.11-small
llvmPackages = final.llvmPackages_20;
};
nix-eval-jobs = final.callPackage ./subprojects/nix-eval-jobs {
stdenv = currentStdenv;
@@ -245,23 +249,23 @@
# And same thing for our build-release-notes package.
build-release-notes = final.nix.passthru.build-release-notes;
# As soon as Nixpkgs updates to >= 3.0.0, change to lowdown_2_0!
# We don't change the default version in order to not change the hash
# of Nix/Lix from upstream Nixpkgs.
lowdown_3_0 =
if (lib.versions.major prev.lowdown.version == "3") then
prev.lowdown
else
prev.lowdown.overrideAttrs (
finalAttrs: _prevAttrs: {
version = "3.0.0";
src = final.fetchurl {
url = "https://kristaps.bsd.lv/lowdown/snapshots/lowdown-${finalAttrs.version}.tar.gz";
sha512 = "94e97234d598382c3c3dc27f9bfdb3a3a2fcf7dbb6a8df3c85ee09f27f792449034a41d49d9cfd3d8450d2de01b8562c20c3d120e65c81af4d7d6c9454119e93";
};
}
);
lowdown_1_3 =
# If the stable channel we are using ships lowdown >= 1.4, we need
# to swap this around, take the default lowdown from the stable
# channel and add an overridden one for the legacy version.
assert lib.versionOlder prev.lowdown.version "1.4.0";
prev.lowdown;
lowdown = prev.lowdown.overrideAttrs (prevAttrs: rec {
version = "2.0.2";
src = final.fetchurl {
url = "https://kristaps.bsd.lv/lowdown/snapshots/lowdown-${version}.tar.gz";
sha512 = "2a4d0rqh8gkw4ca3gkzddp0hjpmmw74cbks8k0inhh0vizmgbn188zdv6m1kgmr019b99g7insli8js3ci1ji7y4n5nk704bswf3z3i";
};
nativeBuildInputs = prevAttrs.nativeBuildInputs ++ [ final.buildPackages.bmake ];
postInstall = lib.replaceStrings [ "lowdown.so.1" ] [ "lowdown.so.2" ] (
prevAttrs.postInstall or ""
);
});
capnproto = prev.capnproto.overrideAttrs (old: {
patches =
@@ -286,57 +290,32 @@
overlays.default = overlayFor (p: p.clangStdenv);
hydraJobs = {
# Aggregate job that is finished in Hydra _after_ all constituent jobs (here: grouped by system)
# succeed.
# This is used to run CD scripts once all builds are finished on Hydra.
release = forAllSystems (
system:
let
pkgs = nixpkgsFor.${system}.native;
in
pkgs.runCommand "release"
{
_hydraAggregate = true;
constituents = lib.filter (x: x != null) (
lib.mapAttrsToListRecursiveCond
(_: val: !(lib.isDerivation val || builtins.any (system': val ? ${system'}) systems))
(
path: drv:
if drv ? ${system} then
lib.concatStringsSep "." (path ++ [ system ])
else if drv.system or null == system then
lib.concatStringsSep "." path
else
null
)
(
removeAttrs self.hydraJobs [
"devShell"
"release"
"rl-next"
]
)
);
}
''
touch $out
''
);
# Binary package for various platforms.
build = forAllSystems (system: self.packages.${system}.nix);
# Ensure support for lowdown < 3.0 doesn't regress for NixOS 25.11
build-lowdown_2_0 = lib.genAttrs [ "aarch64-linux" ] (
# Building Lix twice in CI is expensive, but we can catch a lot of static
# build regressions by at least making sure it evals and configures.
configure-static = lib.genAttrs linux64BitSystems (
system:
assert lib.versionOlder nixpkgsFor.${system}.native.lowdown.version "3.0.0";
self.packages.${system}.nix.override {
lowdown = nixpkgsFor.${system}.native.lowdown;
lowdown-unsandboxed = nixpkgsFor.${system}.native.lowdown-unsandboxed;
self.packages.${system}.nix-static.overrideAttrs {
dontBuild = true;
installPhase = ''
runHook preInstall
echo "configure-static complete. exiting with success"
mkdir -p "$out"
exit 0
'';
}
);
buildStatic = lib.genAttrs linux64BitSystems (system: self.packages.${system}.nix-static);
# Ensure support for lowdown < 1.4 doesn't regress
build-lowdown_1_3 = forAllSystems (
system:
self.packages.${system}.nix.override {
lowdown = nixpkgsFor.${system}.native.lowdown_1_3;
}
);
devShell = forAllSystems (system: {
default = self.devShells.${system}.default;
@@ -462,6 +441,8 @@
pkgs.callPackage ./package.nix {
# Required since we don't support gcc stdenv
stdenv = pkgs.clangStdenv;
# FIXME: To be removed when switching to nixos-25.11-small
llvmPackages = pkgs.llvmPackages_20;
versionSuffix = "";
lintInsteadOfBuild = true;
}
@@ -469,15 +450,35 @@
# Make sure that nix-env still produces the exact same result
# on a particular version of Nixpkgs.
evalNixpkgs = nixpkgsFor.x86_64-linux.native.callPackage ./tests/nixpkgs/eval.nix {
inherit nixpkgs-regression;
};
evalNixpkgs =
with nixpkgsFor.x86_64-linux.native;
runCommand "eval-nixos" { buildInputs = [ nix ]; } ''
type -p nix-env
# Note: we're filtering out nixos-install-tools because https://github.com/NixOS/nixpkgs/pull/153594#issuecomment-1020530593.
time nix-env --store dummy:// -f ${nixpkgs-regression} -qaP --drv-path | sort | grep -v nixos-install-tools > packages
[[ $(sha1sum < packages | cut -c1-40) = 402242fca90874112b34718b8199d844e8b03d12 ]]
mkdir $out
'';
nixpkgsLibTests = forAllSystems (
system:
nixpkgsFor.${system}.native.callPackage ./tests/nixpkgs/lib.nix {
inherit nixpkgs system;
let
inherit (self.packages.${system}) nix;
pkgs = nixpkgsFor.${system}.native;
testWithNix = import (nixpkgs + "/lib/tests/test-with-nix.nix") { inherit pkgs lib nix; };
in
pkgs.symlinkJoin {
name = "nixpkgs-lib-tests";
paths = [
testWithNix
]
# NOTE: nixpkgs 25.05 is being ... *creative*, and requires this dance to override
# the evaluator used for the test. it will break again in the future, don't worry.
++ lib.optionals pkgs.stdenv.isLinux [
((pkgs.callPackage "${nixpkgs}/ci/eval" { inherit nix; }).attrpathsSuperset {
evalSystem = system;
})
];
}
);
};
@@ -526,6 +527,7 @@
# devShells and packages already get checked by nix flake check, so
# this is just jobs that are special
build-lowdown_1_3 = self.hydraJobs.build-lowdown_1_3.${system};
binaryTarball = self.hydraJobs.binaryTarball.${system};
perlBindings = self.hydraJobs.perlBindings.${system};
nix-eval-jobs = self.hydraJobs.nix-eval-jobs.${system};
@@ -557,7 +559,7 @@
dockerImage =
let
pkgs = nixpkgsFor.${system}.native;
nix2container' = import nix2container { inherit pkgs; };
nix2container' = import nix2container { inherit pkgs system; };
in
import ./docker.nix {
inherit pkgs;
@@ -589,11 +591,10 @@
inherit stdenv versionSuffix;
busybox-sandbox-shell = pkgs.busybox-sandbox-shell or pkgs.default-busybox-sandbox;
internalApiDocs = false;
includeSanitizerLibs = true;
# Use LLD in the dev shell by default for faster link times.
useLld = stdenv.hostPlatform.isLinux;
};
pre-commit = self.hydraJobs.pre-commit.${pkgs.stdenv.hostPlatform.system} or { };
pre-commit = self.hydraJobs.pre-commit.${pkgs.system} or { };
in
pkgs.callPackage nix.mkDevShell {
pre-commit-checks = pre-commit;
+23 -21
View File
@@ -1,10 +1,4 @@
# https://just.systems/man/en/
#
# Take a look at ./doc/manual/src/contributing/hacking.md for a detailed
# explanation on how to use this file!
outdir := x"${out:-$PWD/outputs/out}"
builddir := "build"
# List all available targets
list:
@@ -12,36 +6,44 @@ list:
# Clean build artifacts
clean:
rm -rf {{ builddir }}
rm -rf build
# Prepare meson for building.
setup *OPTIONS:
meson setup {{ builddir }} --reconfigure --prefix="{{outdir}}" $mesonFlags {{ OPTIONS }}
# Prepare meson for building with extra options
setup-custom *OPTIONS:
meson setup build --prefix="$PWD/outputs/out" $mesonFlags {{ OPTIONS }}
# Prepare meson for building
setup: (setup-custom)
# Build lix with extra options
build *OPTIONS:
meson compile -C {{ builddir }} {{ OPTIONS }}
build-custom *OPTIONS:
meson compile -C build {{ OPTIONS }}
# Build lix
build: (build-custom)
alias compile := build
# `meson install` will automatically build anything that needs to be built to install it.
[doc("Install Lix for local development")]
install *OPTIONS:
meson install --quiet -C {{ builddir }} {{ OPTIONS }}
# Install lix for local development with extra options
install-custom *OPTIONS: (build-custom OPTIONS)
meson install -C build
# Run all tests tests (installs first).
test *OPTIONS: (install)
meson test -C {{ builddir }} --print-errorlogs --max-lines 10000 {{ OPTIONS }}
# Install lix for local development
install: (install-custom)
# Run tests (usually requires `install`) with extra options
test *OPTIONS:
meson test -C build --print-errorlogs {{ OPTIONS }}
# Run unit tests only
test-unit *OPTIONS: (test "--suite" "check")
# Run integration tests only
test-integration *OPTIONS: (test "--suite" "installcheck" OPTIONS)
test-integration *OPTIONS: install (test "--suite" "installcheck")
# Run functional2 tests using pytest directly, allowing for additional arguments to be passed to pytest e.g. for more granular test selection
test-functional2 *OPTIONS:
cd tests/functional2 && python -m pytest -v {{ OPTIONS }}
cd tests && python -m pytest -v {{ OPTIONS }} functional2
alias clang-tidy := lint
-1
View File
@@ -1 +0,0 @@
# noqa: N999 # consistency with rest of the codebase
+84 -104
View File
@@ -1,113 +1,93 @@
import dataclasses
from enum import Enum
from textwrap import dedent, indent
from typing import NamedTuple
from typing import List, NamedTuple
from common import cxx_literal, generate_file, load_data
from common import cxx_literal, generate_file, load_data, get_argument_parser
KNOWN_KEYS = set([
'name',
'type',
'constructorArgs',
'implementation',
'impure',
'renameInGlobalScope',
])
IMPURE_NOTE = """
> **Note**
class BuiltinConstant(NamedTuple):
name: str
type: str
implementation: str
impure: bool
rename_in_global_scope: bool
documentation: str
def parse(datum):
unknown_keys = set(datum.keys()) - KNOWN_KEYS
if unknown_keys:
raise Exception('unknown keys', unknown_keys)
return BuiltinConstant(
name = datum['name'],
type = datum['type'],
implementation = ('{' + ', '.join([f'NewValueAs::{datum["type"]}', *datum['constructorArgs']]) + '}') if 'constructorArgs' in datum else datum['implementation'],
impure = datum.get('impure', False),
rename_in_global_scope = datum.get('renameInGlobalScope', True),
documentation = datum.content,
)
VALUE_TYPES = {
'attrs': 'nAttrs',
'boolean': 'nBool',
'integer': 'nInt',
'list': 'nList',
'null': 'nNull',
'string': 'nString',
}
HUMAN_TYPES = {
'attrs': 'set',
'boolean': 'Boolean',
'integer': 'integer',
'list': 'list',
'null': 'null',
'string': 'string',
}
def main():
import argparse
ap = argparse.ArgumentParser()
ap.add_argument('--header', help='Path of the header to generate')
ap.add_argument('--docs', help='Path of the documentation file to generate')
ap.add_argument('defs', help='Builtin definition files', nargs='+')
args = ap.parse_args()
builtin_constants = load_data(args.defs, BuiltinConstant.parse)
generate_file(args.header, builtin_constants, lambda constant:
# `builtins` is magic and must come first
'' if constant.name == 'builtins' else constant.name,
lambda constant:
f'''{'if (!evalSettings.pureEval) ' if constant.impure else ''}{{
addConstant({cxx_literal(('__' if constant.rename_in_global_scope else '') + constant.name)}, {constant.implementation}, {{
.type = {VALUE_TYPES[constant.type]},
.doc = {cxx_literal(constant.documentation)},
.impureOnly = {cxx_literal(constant.impure)},
}});
}}
''')
generate_file(args.docs, builtin_constants, lambda constant: constant.name, lambda constant:
f'''<dt id="builtins-{constant.name}">
<a href="#builtins-{constant.name}"><code>{constant.name}</code></a> ({HUMAN_TYPES[constant.type]})
</dt>
<dd>
{constant.documentation}
''' + ('''> **Note**
>
> Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval).
"""
''' if constant.impure else '') + '''</dd>
''')
class TypeName(NamedTuple):
human: str
code: str
class BuiltinType(TypeName, Enum):
attrs = TypeName("set", "nAttrs")
boolean = TypeName("boolean", "nBool")
integer = TypeName("integer", "nInt")
list = TypeName("list", "nList")
null = TypeName("null", "nNull")
string = TypeName("string", "nString")
@classmethod
def from_string(cls, t_name: str) -> "BuiltinType":
for t in cls:
if t_name == t.name:
return t
msg = f"Invalid builtin type: {t_name}"
raise ValueError(msg)
@dataclasses.dataclass
class BuiltinConstant:
name: str
documentation: str
# Fields with different name in the Post than in here
# our fields
type: BuiltinType = dataclasses.field(init=False)
# Post fields
type_str: dataclasses.InitVar[str]
constructor_args: dataclasses.InitVar[list[str] | None] = None
implementation: str = ""
impure: bool = False
rename_in_global_scope: bool = True
def __post_init__(self, type_str: str, constructor_args: list[str] | None):
self.type = BuiltinType.from_string(type_str)
if constructor_args is not None:
args = [f"NewValueAs::{type_str}"] + constructor_args
self.implementation = f"{{{','.join(args)}}}"
@property
def code(self) -> str:
cond = "if (!evalSettings.pureEval) " if self.impure else ""
return dedent(f"""
{cond} {{
addConstant(
{cxx_literal(("__" if self.rename_in_global_scope else "") + self.name)},
{self.implementation},
{{
.type = {self.type.code},
.doc = {cxx_literal(self.documentation)},
.impureOnly = {cxx_literal(self.impure)},
}}
);
}}
""")
@property
def docs(self) -> str:
indentation = " " * 3
return dedent(f"""
<dt id="builtins-{self.name}">
<a href="#builtins-{self.name}"><code>{self.name}</code></a> ({self.type.human})
</dt>
<dd>
{indent(self.documentation, indentation)}
{indent(IMPURE_NOTE, indentation) if self.impure else ""}
</dd>
""")
def main():
args = get_argument_parser().parse_args()
builtin_constants = load_data(args.defs, BuiltinConstant)
generate_file(
args.header,
builtin_constants,
lambda constant:
# `builtins` is magic and must come first
"" if constant.name == "builtins" else constant.name,
lambda b: b.code,
)
generate_file(args.docs, builtin_constants, lambda constant: constant.name, lambda b: b.docs)
if __name__ == "__main__":
if __name__ == '__main__':
main()
+66 -74
View File
@@ -1,90 +1,82 @@
import dataclasses
from textwrap import dedent, indent
from typing import List, NamedTuple, Optional
from common import (
cxx_literal,
generate_file,
load_data,
get_argument_parser,
get_experimental_features,
)
from build_experimental_features import ExperimentalFeature
from common import cxx_literal, generate_file, load_data
KNOWN_KEYS = set([
'name',
'implementation',
'renameInGlobalScope',
'args',
'experimentalFeature',
])
@dataclasses.dataclass
class Builtin:
class Builtin(NamedTuple):
name: str
implementation: str
rename_in_global_scope: bool
args: List[str]
experimental_feature: Optional[str]
documentation: str
args: list[str]
experimental_feature: str | None = None
implementation: str = ""
rename_in_global_scope: bool = True
def __post_init__(self):
self.implementation = self.implementation or f"prim_{self.name}"
def generate_code(self, experimental_features: dict[str, str]) -> str:
xf = experimental_features[self.experimental_feature]
cond = (
f"if (experimentalFeatureSettings.isEnabled({xf})) "
if self.experimental_feature
else ""
def parse(datum):
unknown_keys = set(datum.keys()) - KNOWN_KEYS
if unknown_keys:
raise Exception('unknown keys', unknown_keys)
return Builtin(
name = datum['name'],
implementation = datum['implementation'] if 'implementation' in datum else f'prim_{datum["name"]}',
rename_in_global_scope = datum.get('renameInGlobalScope', True),
args = datum['args'],
experimental_feature = datum.get('experimentalFeature', None),
documentation = datum.content,
)
return dedent(f"""
{cond}{{
addPrimOp({{
.name = {cxx_literal(("__" if self.rename_in_global_scope else "") + self.name)},
.args = {cxx_literal(self.args)},
.arity = {len(self.args)},
.doc = {cxx_literal(self.documentation)},
.fun = {self.implementation},
.experimentalFeature = {xf},
}});
}}
""")
@property
def docs(self) -> str:
return dedent(f"""
<dt id="builtins-{self.name}">
<a href="#builtins-{self.name}"><code>{self.name} {
" ".join([f"<var>{arg}</var>" for arg in self.args])
}</code></a>
</dt>
<dd>
{indent(self.documentation, " " * 3)}
{
f"This function is only available if the [{self.experimental_feature}](@docroot@/contributing/experimental-features.md#xp-feature-{self.experimental_feature}) experimental feature is enabled."
if self.experimental_feature is not None
else ""
}
</dd>
""")
def main():
ap = get_argument_parser()
ap.add_argument(
"--experimental-features", help="Directory containing the experimental feature definitions"
)
import argparse
ap = argparse.ArgumentParser()
ap.add_argument('--header', help='Path of the header to generate')
ap.add_argument('--docs', help='Path of the documentation file to generate')
ap.add_argument('--experimental-features', help='Directory containing the experimental feature definitions')
ap.add_argument('defs', help='Builtin definition files', nargs='+')
args = ap.parse_args()
builtins = load_data(args.defs, Builtin)
builtins = load_data(args.defs, Builtin.parse)
experimental_features = get_experimental_features(
args.experimental_features, [b.experimental_feature for (_, b) in builtins]
)
experimental_feature_names = set([builtin.experimental_feature for (_, builtin) in builtins])
experimental_feature_names.discard(None)
experimental_feature_files = [f'{args.experimental_features}/{name}.md' for name in experimental_feature_names]
experimental_features = load_data(experimental_feature_files, ExperimentalFeature.parse)
experimental_features = dict(map(lambda path_and_feature:
(path_and_feature[1].name, f'Xp::{path_and_feature[1].internal_name}'), experimental_features))
experimental_features[None] = 'std::nullopt'
generate_file(
args.header,
builtins,
lambda builtin: builtin.name,
lambda b: b.generate_code(experimental_features),
)
generate_file(args.docs, builtins, lambda builtin: builtin.name, lambda b: b.docs)
generate_file(args.header, builtins, lambda builtin: builtin.name, lambda builtin:
f'''{'' if builtin.experimental_feature is None else f'if (experimentalFeatureSettings.isEnabled({experimental_features[builtin.experimental_feature]})) '}{{
addPrimOp({{
.name = {cxx_literal(('__' if builtin.rename_in_global_scope else '') + builtin.name)},
.args = {cxx_literal(builtin.args)},
.arity = {len(builtin.args)},
.doc = {cxx_literal(builtin.documentation)},
.fun = {builtin.implementation},
.experimentalFeature = {experimental_features[builtin.experimental_feature]},
}});
}}
''')
generate_file(args.docs, builtins, lambda builtin: builtin.name, lambda builtin:
f'''<dt id="builtins-{builtin.name}">
<a href="#builtins-{builtin.name}"><code>{builtin.name} {' '.join([f'<var>{arg}</var>' for arg in builtin.args])}</code></a>
</dt>
<dd>
{builtin.documentation}
if __name__ == "__main__":
''' + (f'''This function is only available if the [{builtin.experimental_feature}](@docroot@/contributing/experimental-features.md#xp-feature-{builtin.experimental_feature}) experimental feature is enabled.
''' if builtin.experimental_feature is not None else '') + '''</dd>
''')
if __name__ == '__main__':
main()
@@ -0,0 +1,58 @@
from typing import NamedTuple
from common import cxx_literal, generate_file, load_data
KNOWN_KEYS = set([
'name',
'internalName',
])
class ExperimentalFeature(NamedTuple):
name: str
internal_name: str
description: str
def parse(datum):
unknown_keys = set(datum.keys()) - KNOWN_KEYS
if unknown_keys:
raise ValueError('unknown keys', unknown_keys)
return ExperimentalFeature(
name = datum['name'],
internal_name = datum['internalName'],
description = datum.content,
)
def main():
import argparse
ap = argparse.ArgumentParser()
ap.add_argument('--deprecated', action='store_true', help='Generate deprecated features')
ap.add_argument('--header', help='Path of the declaration header to generate')
ap.add_argument('--impl-header', help='Path of the implementation header to generate')
ap.add_argument('--descriptions', help='Path of the description file to generate')
ap.add_argument('--shortlist', help='Path of the shortlist file to generate')
ap.add_argument('defs', help='Experimental feature definition files', nargs='+')
args = ap.parse_args()
features = load_data(args.defs, ExperimentalFeature.parse)
generate_file(args.header, features, lambda feature: feature.name, lambda feature:
f' {feature.internal_name},\n')
generate_file(args.impl_header, features, lambda feature: feature.name, lambda feature:
f''' {{
.tag = {"Dep" if args.deprecated else "Xp"}::{feature.internal_name},
.name = {cxx_literal(feature.name)},
.description = {cxx_literal(feature.description)},
}},
''')
generate_file(args.descriptions, features, lambda feature: feature.name, lambda feature:
f'''## [`{feature.name}`]{{#{"dp" if args.deprecated else "xp"}-feature-{feature.name}}}
{feature.description}
''')
generate_file(args.shortlist, features, lambda feature: feature.name, lambda feature:
f' - [`{feature.name}`](@docroot@/contributing/{"deprecated" if args.deprecated else "experimental"}-features.md#{"dp" if args.deprecated else "xp"}-feature-{feature.name})\n')
if __name__ == '__main__':
main()
-105
View File
@@ -1,105 +0,0 @@
import dataclasses
from enum import Enum
from textwrap import dedent
from typing import ClassVar, NamedTuple
from common import cxx_literal, generate_file, load_data, get_argument_parser
class FeatureTypeNames(NamedTuple):
code_tag: str
doc_tag: str
class TimelineEvent(NamedTuple):
date: str
release: str
message: str
cls: list[int]
class FeatureType(FeatureTypeNames, Enum):
experimental = FeatureTypeNames("Xp", "xp")
deprecated = FeatureTypeNames("Dep", "dp")
@dataclasses.dataclass
class ExtraFeature:
name: str
internal_name: str
documentation: str
timeline: list[TimelineEvent] = dataclasses.field(default_factory=list)
type: ClassVar[FeatureType]
@property
def code(self) -> str:
return dedent(f"""
{{
.tag = {ExtraFeature.type.code_tag}::{self.internal_name},
.name = {cxx_literal(self.name)},
.description = {cxx_literal(self.documentation)},
}},
""")
@property
def docs(self) -> str:
timeline = (
f"""
### Timeline
{
"\n ".join(
[
f"- {event.date}, {event.release}: {event.message} [{", ".join([f'[CL {cl}](https://git.lix.systems/c/lix/+/{cl})' for cl in event.cls])}]"
for event in self.timeline
]
)
}
"""
if self.timeline
else ""
)
return dedent(f"""
## [`{self.name}`]{{#{ExtraFeature.type.doc_tag}-feature-{self.name}}}
{self.documentation.replace("\n", f"\n{' ' * 3}")}
{timeline}
""")
@property
def short_docs(self) -> str:
return f" - [`{self.name}`](@docroot@/contributing/{ExtraFeature.type.name}-features.md#{ExtraFeature.type.doc_tag}-feature-{self.name})\n"
def main():
ap = get_argument_parser()
ap.add_argument("--deprecated", action="store_true", help="Generate deprecated features")
ap.add_argument("--impl-header", help="Path of the implementation header to generate")
ap.add_argument("--shortlist", help="Path of the shortlist file to generate")
args = ap.parse_args()
ExtraFeature.type = FeatureType.deprecated if args.deprecated else FeatureType.experimental
def load(**kwargs) -> ExtraFeature:
kwargs["timeline"] = [TimelineEvent(**args) for args in kwargs.get("timeline", [])]
return ExtraFeature(**kwargs)
features = load_data(args.defs, load)
generate_file(
args.header,
features,
lambda feature: feature.name,
lambda feature: f" {feature.internal_name},\n",
)
generate_file(args.impl_header, features, lambda feature: feature.name, lambda f: f.code)
generate_file(args.docs, features, lambda feature: feature.name, lambda f: f.docs)
generate_file(args.shortlist, features, lambda feature: feature.name, lambda f: f.short_docs)
if __name__ == "__main__":
main()
+120 -136
View File
@@ -1,157 +1,141 @@
import dataclasses
from textwrap import dedent
from typing import Any
from typing import List, NamedTuple, Optional
from common import (
cxx_literal,
generate_file,
load_data,
get_experimental_features,
get_argument_parser,
)
from build_experimental_features import ExperimentalFeature
from common import cxx_literal, generate_file, load_data
KNOWN_KEYS = set([
'name',
'internalName',
'platforms',
'type',
'settingType',
'default',
'defaultExpr',
'defaultText',
'aliases',
'experimentalFeature',
'deprecated',
])
PLATFORM_WARNING = """
> **Note**
> This setting is only available on {platforms} systems.
class Setting(NamedTuple):
name: str
internal_name: str
description: str
platforms: Optional[List[str]]
setting_type: str
default_expr: str
default_text: str
aliases: List[str]
experimental_feature: Optional[str]
deprecated: bool
"""
def parse(datum):
unknown_keys = set(datum.keys()) - KNOWN_KEYS
if unknown_keys:
raise ValueError('unknown keys', unknown_keys)
default_text = f'`{nix_conf_literal(datum["default"])}`' if 'default' in datum else datum['defaultText']
if default_text == '``':
default_text = '*empty*'
return Setting(
name = datum['name'],
internal_name = datum['internalName'],
description = datum.content,
platforms = datum.get('platforms', None),
setting_type = f'Setting<{datum["type"]}>' if 'type' in datum else datum['settingType'],
default_expr = cxx_literal(datum['default']) if 'default' in datum else datum['defaultExpr'],
default_text = default_text,
aliases = datum.get('aliases', []),
experimental_feature = datum.get('experimentalFeature', None),
deprecated = datum.get('deprecated', False),
)
XP_WARNING = """
> **Warning**
platform_names = {
'darwin': 'Darwin',
'linux': 'Linux',
}
def nix_conf_literal(v):
if v is None:
return ''
elif isinstance(v, bool) and v == False: # 0 == False
return 'false'
elif isinstance(v, bool) and v == True: # 1 == True
return 'true'
elif isinstance(v, int):
return str(v)
elif isinstance(v, str):
return v
elif isinstance(v, list):
return ' '.join([nix_conf_literal(item) for item in v])
else:
raise NotImplementedError(f'Cannot represent {repr(v)} in nix.conf')
def indent(prefix, body):
return ''.join(['\n' if line == '' else f'{prefix}{line}\n' for line in body.split('\n')])
def main():
import argparse
ap = argparse.ArgumentParser()
ap.add_argument('--kernel', help='Name of the kernel Lix will run on')
ap.add_argument('--header', help='Path of the header to generate')
ap.add_argument('--docs', help='Path of the documentation file to generate')
ap.add_argument('--experimental-features', help='Directory containing the experimental feature definitions')
ap.add_argument('defs', help='Setting definition files', nargs='+')
args = ap.parse_args()
settings = load_data(args.defs, Setting.parse)
experimental_feature_names = set([setting.experimental_feature for (_, setting) in settings])
experimental_feature_names.discard(None)
experimental_feature_files = [f'{args.experimental_features}/{name}.md' for name in experimental_feature_names]
experimental_features = load_data(experimental_feature_files, ExperimentalFeature.parse)
experimental_features = dict(map(lambda path_and_feature:
(path_and_feature[1].name, f'Xp::{path_and_feature[1].internal_name}'), experimental_features))
experimental_features[None] = 'std::nullopt'
generate_file(args.header, settings, lambda setting: setting.name, lambda setting:
f'''{setting.setting_type} {setting.internal_name} {{
this,
{setting.default_expr},
{cxx_literal(setting.name)},
{cxx_literal(setting.description)},
{cxx_literal(setting.aliases)},
true,
{experimental_features[setting.experimental_feature]},
{cxx_literal(setting.deprecated)}
}};
''' if setting.platforms is None or args.kernel in setting.platforms else '')
generate_file(args.docs, settings, lambda setting: setting.name, lambda setting:
f'''- <span id="conf-{setting.name}">[`{setting.name}`](#conf-{setting.name})</span>
{indent(" ", setting.description)}
''' + (f''' > **Note**
> This setting is only available on {', '.join([platform_names[platform] for platform in setting.platforms])} systems.
''' if setting.platforms is not None else '') + (f''' > **Warning**
> This setting is part of an
> [experimental feature](@docroot@/contributing/experimental-features.md).
To change this setting, you need to make sure the corresponding experimental feature,
[`{feature}`](@docroot@/contributing/experimental-features.md#xp-feature-{feature}),
[`{setting.experimental_feature}`](@docroot@/contributing/experimental-features.md#xp-feature-{setting.experimental_feature}),
is enabled.
For example, include the following in [`nix.conf`](#):
```
extra-experimental-features = {feature}
{name} = ...
extra-experimental-features = {setting.experimental_feature}
{setting.name} = ...
```
"""
DEPR_WARNING = """
> **Warning**
''' if setting.experimental_feature is not None else '') + (''' > **Warning**
> This setting is deprecated and will be removed in a future version of Lix.
"""
''' if setting.deprecated else '') + f''' **Default:** {setting.default_text}
''' + (f''' **Deprecated alias:** {', '.join([f'`{item}`' for item in setting.aliases])}
@dataclasses.dataclass
class Setting:
name: str
internal_name: str
documentation: str
''' if setting.aliases != [] else ''))
default_text: str = ""
setting_type: str = ""
default_expr: str = ""
platforms: list[str] = dataclasses.field(default_factory=list)
aliases: list[str] = dataclasses.field(default_factory=list)
experimental_feature: str | None = None
deprecated: bool = False
default: dataclasses.InitVar[str | None] = None
type_str: dataclasses.InitVar[str | None] = None
def __post_init__(self, default: Any, type_str: str | None):
if default is not None: # is not None nor an empty String
self.default_text = f"`{nix_conf_literal(default)}`"
self.default_expr = self.default_expr or cxx_literal(default)
self.default_text = self.default_text or "*empty*"
if type_str is not None:
self.setting_type = f"Setting<{type_str}>"
def generate_code(self, experimental_features: dict[str | None, str]) -> str:
indentation = " " * 4
expr = (indent(indentation, self.default_expr) + indentation) if "\n" in self.default_expr else self.default_expr
return dedent(f"""
{self.setting_type} {self.internal_name} {{
this,
{expr},
{cxx_literal(self.name)},
{cxx_literal(self.documentation)},
{cxx_literal(self.aliases)},
true,
{experimental_features[self.experimental_feature]},
{cxx_literal(self.deprecated)}
}};
""")
@property
def docs(self) -> str:
indentation = " " * 3
platforms = [p.capitalize() for p in self.platforms]
aliases = [f"`{item}`" for item in self.aliases]
description = dedent(f"""
{indent(indentation, self.documentation)}
{indent(indentation, PLATFORM_WARNING.format(platforms=str(platforms)[1:-1])) if self.platforms else ""}
{indent(indentation, XP_WARNING.format(feature=self.experimental_feature, name=self.name)) if self.experimental_feature is not None else ""}
{indent(indentation, DEPR_WARNING) if self.deprecated else ""}
**Default:** {self.default_text}
{f"**Deprecated alias:** {str(aliases)[1:-1]}\n" if self.aliases else ""}
""")
return f'- <span id="conf-{self.name}">[`{self.name}`](#conf-{self.name})</span>' + indent(
" ", # indent by two space to make it part of the list point
description,
)
platform_names = {"darwin": "Darwin", "linux": "Linux"}
def nix_conf_literal(v: Any) -> str:
if v is None:
return ""
if v is False:
return "false"
if v is True:
return "true"
if isinstance(v, int):
return str(v)
if isinstance(v, str):
return v
if isinstance(v, list):
return " ".join([nix_conf_literal(item) for item in v])
msg = f"Cannot represent {v!r} in nix.conf"
raise NotImplementedError(msg)
def indent(prefix: str, body: str) -> str:
return "".join(["\n" if not line else f"{prefix}{line}\n" for line in body.split("\n")])
def main():
ap = get_argument_parser()
ap.add_argument("--kernel", help="Name of the kernel Lix will run on")
ap.add_argument(
"--experimental-features", help="Directory containing the experimental feature definitions"
)
args = ap.parse_args()
settings = load_data(args.defs, Setting)
experimental_features = get_experimental_features(
args.experimental_features, [s.experimental_feature for (_, s) in settings]
)
generate_file(
args.header,
settings,
lambda setting: setting.name,
lambda setting: setting.generate_code(experimental_features)
if not setting.platforms or args.kernel in setting.platforms
else "",
)
generate_file(args.docs, settings, lambda setting: setting.name, lambda setting: setting.docs)
if __name__ == "__main__":
if __name__ == '__main__':
main()
+30 -34
View File
@@ -1,5 +1,4 @@
#!@python@
# ruff: noqa: SIM112 # ignore lowercase env variable names for capnpc as we have them in lower case as arguments
import argparse
import capnp
@@ -8,61 +7,58 @@ import os
import subprocess
import sys
if lang := os.environ.get("lix_capnp_lang"):
outputs = os.environ["lix_capnp_outputs"].split()
old_cwd = os.environ["lix_capnp_old_cwd"]
schema = capnp.load("@capnp_include@/capnp/schema.capnp", imports=["@capnp_include@"])
if lang := os.environ.get('lix_capnp_lang'):
outputs = os.environ['lix_capnp_outputs'].split()
old_cwd = os.environ['lix_capnp_old_cwd']
schema = capnp.load('@capnp_include@/capnp/schema.capnp', imports=['@capnp_include@'])
request = schema.CodeGeneratorRequest.read(sys.stdin)
subprocess.run([lang], input=request.as_builder().to_bytes()).check_returncode()
base_dir = Path.cwd()
base_dir = os.getcwd()
os.chdir(old_cwd)
include = [str(Path(p).resolve()) for p in os.environ["lix_capnp_include"].split(":")]
include = [ str(Path(p).resolve()) for p in os.environ['lix_capnp_include'].split(':') ]
if depfile := os.environ["lix_capnp_depfile"]:
if depfile := os.environ['lix_capnp_depfile']:
deps = ""
for input_file in request.requestedFiles:
deps += " ".join(f"{input_file.filename}.{o}" for o in outputs)
for input in request.requestedFiles:
deps += " ".join(f"{input.filename}.{o}" for o in outputs)
deps += ":"
for dep in input_file.imports:
for dep in input.imports:
if dep.name.startswith("/"):
for candidate in (Path(i + dep.name) for i in include):
if candidate.exists():
deps += " " + str(candidate)
break
else:
msg = "not handling relative includes"
raise RuntimeError(msg)
raise RuntimeError("not handling relative includes")
deps += "\n\n"
Path(depfile).write_text(deps)
else:
parser = argparse.ArgumentParser()
parser.add_argument("--language")
parser.add_argument("--outdir")
parser.add_argument("--src-prefix")
parser.add_argument("--depfile", default="")
parser.add_argument("-I", "--include", action="append", default=["@capnp_include@"])
parser.add_argument("inputs", nargs="+")
parser.add_argument('--language')
parser.add_argument('--outdir')
parser.add_argument('--src-prefix')
parser.add_argument('--depfile', default="")
parser.add_argument('-I', '--include', action='append', default=['@capnp_include@'])
parser.add_argument('inputs', nargs='+')
args = parser.parse_args()
for infile in args.inputs:
os.environ["lix_capnp_lang"] = f"capnpc-{args.language}"
os.environ["lix_capnp_include"] = ":".join(args.include)
os.environ["lix_capnp_depfile"] = args.depfile
os.environ["lix_capnp_old_cwd"] = str(Path.cwd())
os.environ['lix_capnp_lang'] = f"capnpc-{args.language}"
os.environ['lix_capnp_include'] = ':'.join(args.include)
os.environ['lix_capnp_depfile'] = args.depfile
os.environ['lix_capnp_old_cwd'] = os.getcwd()
if args.language == "c++":
os.environ["lix_capnp_outputs"] = "c++ h"
os.environ['lix_capnp_outputs'] = "c++ h"
else:
raise RuntimeError("unknown language " + args.language)
subprocess.run(
[
"@capnp@",
"compile",
f"-o{sys.argv[0]}:{args.outdir}",
f"--src-prefix={args.src_prefix}",
*(f"-I{i}" for i in args.include),
infile,
]
).check_returncode()
subprocess.run([
'@capnp@',
'compile',
f'-o{sys.argv[0]}:{args.outdir}',
f'--src-prefix={args.src_prefix}',
*(f"-I{i}" for i in args.include),
infile
]).check_returncode()
+42 -95
View File
@@ -1,113 +1,60 @@
import argparse
import re
from collections.abc import Callable
from pathlib import Path
from typing import Any
import frontmatter
import pathlib
from collections import defaultdict
def cxx_escape_character(c: str) -> str:
if 0x20 <= ord(c) < 0x7F and c != '"' and c != "?" and c != "\\":
def cxx_escape_character(c):
if ord(c) >= 0x20 and ord(c) < 0x7f and c != '"' and c != '?' and c != '\\':
return c
if c == "\t":
return r"\t"
if c == "\n":
return r"\n"
if c == "\r":
return r"\r"
if c == '"':
return r"\""
if c == "?":
return r"\?"
if c == "\\":
return r"\\"
if ord(c) <= 0xFFFF:
return str.format(r"\u{:04x}", ord(c))
return str.format(r"\U{:08x}", ord(c))
elif c == '\t':
return r'\t'
elif c == '\n':
return r'\n'
elif c == '\r':
return r'\r'
elif c == '"':
return r'\"'
elif c == '?':
return r'\?'
elif c == '\\':
return r'\\'
elif ord(c) <= 0xffff:
return str.format(r'\u{:04x}', ord(c))
else:
return str.format(r'\U{:08x}', ord(c))
def cxx_literal(v: Any) -> str:
def cxx_literal(v):
if v is None:
return "std::nullopt"
if v is False:
return "false"
if v is True:
return "true"
if isinstance(v, int):
return 'std::nullopt'
elif isinstance(v, bool) and v == False: # 0 == False
return 'false'
elif isinstance(v, bool) and v == True: # 1 == True
return 'true'
elif isinstance(v, int):
return str(v)
if isinstance(v, str):
return "".join(['"', *(cxx_escape_character(c) for c in v), '"'])
if isinstance(v, list):
return f"{{{', '.join([cxx_literal(item) for item in v])}}}"
msg = f"cannot represent {v!r} in C++"
raise NotImplementedError(msg)
elif isinstance(v, str):
return ''.join(['"', *(cxx_escape_character(c) for c in v), '"'])
elif isinstance(v, list):
return f'{{{", ".join([cxx_literal(item) for item in v])}}}'
else:
raise NotImplementedError(f'cannot represent {repr(v)} in C++')
def get_experimental_features(
base_path: str, human_names: list[str | None]
) -> dict[str | None, str]:
experimental_feature_files = {
f"{base_path}/{xp_name}.md" for xp_name in human_names if xp_name is not None
}
from build_extra_features import ExtraFeature # noqa: PLC0415 # Avoid cyclic import
experimental_features_data = load_data(list(experimental_feature_files), ExtraFeature)
experimental_features: dict[str | None, str] = {
xf.name: f"Xp::{xf.internal_name}" for _, xf in experimental_features_data
}
experimental_features[None] = "std::nullopt"
return experimental_features
FIELD_RENAMES = {"type": "type_str", "content": "documentation"}
def load_data[T](defs: list[str], parse_function: type[T]) -> list[tuple[str, T]]:
def load_data(defs, parse_function):
data = []
for path in defs:
try:
datum = {
# convert camelCase to snake_case
re.sub(r"(?<=.)([A-Z])", lambda m: f"_{m.group(1).lower()}", k): v
for k, v in frontmatter.load(path).to_dict().items()
}
for post_name, field_name in FIELD_RENAMES.items():
if post_name in datum:
datum[field_name] = datum.pop(post_name)
data.append((path, parse_function(**datum)))
datum = frontmatter.load(path)
data.append((path, parse_function(datum)))
except Exception as e:
e.add_note(f"in {path}")
e.add_note(f'in {path}')
raise
return data
def generate_file[T](
path: str | None,
data: list[T],
sort_key_function: Callable[[T], str],
generate_function: Callable[[T], str],
):
def generate_file(path, data, sort_key_function, generate_function):
if path is not None:
with Path(path).open("w") as out:
for path, datum in sorted(
data, key=lambda path_and_datum: sort_key_function(path_and_datum[1])
):
with open(path, 'w') as out:
for path, datum in sorted(data, key=lambda pathAndDatum: sort_key_function(pathAndDatum[1])):
try:
text = generate_function(datum)
out.write(text)
out.write(generate_function(datum))
except Exception as e:
e.add_note(f"in {path}")
e.add_note(f'in {path}')
raise
def get_argument_parser() -> argparse.ArgumentParser:
ap = argparse.ArgumentParser()
ap.add_argument("--header", help="Path of the header to generate")
ap.add_argument("--docs", help="Path of the documentation file to generate")
ap.add_argument("defs", help="Builtin definition files", nargs="+")
return ap
+3 -5
View File
@@ -69,7 +69,7 @@ static std::string makeLockFilename(const std::string & storeUri) {
// This avoids issues with the escaped URI being very long and causing
// path too long errors, while also avoiding any possibility of collision
// caused by simple truncation.
auto hash = hashString(HashType::SHA256, storeUri).to_string(HashFormat::Base32, false);
auto hash = hashString(HashType::SHA256, storeUri).to_string(Base::Base32, false);
return escapeUri(storeUri).substr(0, 48) + "-" + hash.substr(0, 16);
}
@@ -405,7 +405,7 @@ kj::Promise<void> Instance::init(InitContext context)
}
kj::Promise<void> Instance::buildImpl(BuildContext context)
try {
{
if (!initialized) {
throw Error("build hook not fully initialized");
}
@@ -461,8 +461,6 @@ try {
auto ac = context.getResults().initResult().initGood().initAccept();
ac.setMachine(kj::heap<AcceptedBuild>(store, drvPath, std::move(*builder)));
} catch (...) {
RPC_FILL(context.getResults(), initResult, std::current_exception());
}
kj::Promise<void> Instance::build(BuildContext context)
@@ -529,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...", storeUri);
printError("somebody is hogging the upload lock for '%s', continuing...");
}
}
-103
View File
@@ -1,103 +0,0 @@
#include "lix/libcmd/legacy.hh"
#include "lix/libstore/builtins.hh"
#include "lix/libstore/builtins/buildenv.hh"
#include "lix/libutil/async.hh"
#include "lix/libutil/error.hh"
#include "lix/libutil/file-system.hh"
#include "lix/libutil/logging.hh"
#include "lix/libutil/strings.hh"
#include "lix/libutil/types.hh"
#include <string_view>
using std::literals::operator""sv;
namespace nix {
static int main_builtin_builder(AsyncIoRoot & aio, std::string programName, Strings argv)
{
logger = makeJSONLogger(*logger);
std::map<std::string, std::string> env;
auto argvIt = argv.begin();
const auto argvEnd = argv.end();
// we do not use the argument parsing functions we have in libmain here, neither
// the legacy versions nor the newer ones. the legacy version could work, but we
// want to provide two sets of arguments separated by `--` and would need rather
// unpleasant state handling to use the legacy parser. the more modern parser is
// entirely incapable of doing this for us since it's all statically configured.
const auto getArg = [&](std::string_view desc) {
if (argvIt == argvEnd) {
throw Error("expected a value for %s", desc);
}
return *argvIt++;
};
if (auto val = string2Int<int>(getArg("verbosity"))) {
verbosity = verbosityFromIntClamped(*val);
} else {
throw Error("expected a verbosity argument");
}
while (argvIt != argvEnd) {
const auto arg = getArg("option");
if (arg == "--") {
break;
} else if (!arg.starts_with("--")) {
throw Error("unexpected builtin option %s", arg);
}
auto value = unescapeNul(getArg(arg));
globalConfig.set(arg.substr(2), value);
}
while (argvIt != argvEnd) {
const auto key = getArg("builder argument");
if (!key.starts_with("--")) {
throw Error("unexpected builtin builder argument %s", key);
}
env[unescapeNul(key.substr(2))] = unescapeNul(getArg(key));
}
auto getAttr = [&](const std::string & name) {
auto i = env.find(name);
if (i == env.end()) {
throw Error("attribute '%s' missing", name);
}
return i->second;
};
const auto builder = getAttr("builder");
if (builder == "builtin:fetchurl") {
const auto outputHashMode = getAttr("outputHashMode");
const auto hash = outputHashMode == "flat" ? [&] -> std::optional<Hash> {
const auto ht = parseHashTypeOpt(getAttr("outputHashAlgo"));
return newHashAllowEmpty(getAttr("outputHash"), ht);
}()
: std::nullopt;
BuiltinFetchurl{
.storePath = getAttr("out"),
.mainUrl = getAttr("url"),
.unpack = getOr(env, "unpack", "0") == "1",
.executable = getOr(env, "executable", "0") == "1",
.hash = hash,
}
.run(aio);
} else if (builder == "builtin:buildenv") {
builtinBuildenv(getAttr("out"), tokenizeString<Strings>(getAttr("derivations")), getAttr("manifest"));
} else if (builder == "builtin:unpack-channel") {
builtinUnpackChannel(getAttr("out"), getAttr("channelName"), getAttr("src"));
} else {
throw Error("unknown builtin builder %s", builder);
}
return 0;
}
void registerLegacyBuiltinBuilder()
{
LegacyCommandRegistry::add("builtin-builder", main_builtin_builder);
}
}
-6
View File
@@ -1,6 +0,0 @@
#pragma once
///@file
namespace nix {
void registerLegacyBuiltinBuilder();
}
+10 -8
View File
@@ -4,7 +4,9 @@
#include "lix/libutil/result.hh"
#include <iostream>
#include <sstream>
using std::cout;
namespace nix {
@@ -40,31 +42,31 @@ static std::string makeNode(std::string_view id, std::string_view label,
dotQuote(id), dotQuote(label), dotQuote(colour));
}
kj::Promise<Result<std::string>> formatDotGraph(ref<Store> store, StorePathSet && roots)
kj::Promise<Result<void>> printDotGraph(ref<Store> store, StorePathSet && roots)
try {
StorePathSet workList(std::move(roots));
StorePathSet doneSet;
std::stringstream result;
result << "digraph G {\n";
cout << "digraph G {\n";
while (!workList.empty()) {
auto path = std::move(workList.extract(workList.begin()).value());
if (!doneSet.insert(path).second) continue;
result << makeNode(std::string(path.to_string()), path.name(), "#ff0000");
cout << makeNode(std::string(path.to_string()), path.name(), "#ff0000");
for (auto & p : TRY_AWAIT(store->queryPathInfo(path))->references) {
if (p != path) {
workList.insert(p);
result << makeEdge(std::string(p.to_string()), std::string(path.to_string()));
cout << makeEdge(std::string(p.to_string()), std::string(path.to_string()));
}
}
}
result << "}\n";
co_return result.str();
cout << "}\n";
co_return result::success();
} catch (...) {
co_return result::current_exception();
}
+2 -1
View File
@@ -5,5 +5,6 @@
namespace nix {
kj::Promise<Result<std::string>> formatDotGraph(ref<Store> store, StorePathSet && roots);
kj::Promise<Result<void>> printDotGraph(ref<Store> store, StorePathSet && roots);
}
+18 -16
View File
@@ -5,7 +5,9 @@
#include "lix/libutil/result.hh"
#include <iostream>
#include <sstream>
using std::cout;
namespace nix {
@@ -45,21 +47,21 @@ static std::string makeNode(const ValidPathInfo & info)
(info.path.isDerivation() ? "derivation" : "output-path"));
}
kj::Promise<Result<std::string>> formatGraphML(ref<Store> store, StorePathSet && roots)
kj::Promise<Result<void>> printGraphML(ref<Store> store, StorePathSet && roots)
try {
StorePathSet workList(std::move(roots));
StorePathSet doneSet;
std::pair<StorePathSet::iterator, bool> ret;
std::stringstream result;
result << "<?xml version='1.0' encoding='utf-8'?>\n"
<< "<graphml xmlns='http://graphml.graphdrawing.org/xmlns'\n"
<< " xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'\n"
<< " xsi:schemaLocation='http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd'>\n"
<< "<key id='narSize' for='node' attr.name='narSize' attr.type='long'/>"
<< "<key id='name' for='node' attr.name='name' attr.type='string'/>"
<< "<key id='type' for='node' attr.name='type' attr.type='string'/>"
<< "<graph id='G' edgedefault='directed'>\n";
cout << "<?xml version='1.0' encoding='utf-8'?>\n"
<< "<graphml xmlns='http://graphml.graphdrawing.org/xmlns'\n"
<< " xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'\n"
<< " xsi:schemaLocation='http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd'>\n"
<< "<key id='narSize' for='node' attr.name='narSize' attr.type='long'/>"
<< "<key id='name' for='node' attr.name='name' attr.type='string'/>"
<< "<key id='type' for='node' attr.name='type' attr.type='string'/>"
<< "<graph id='G' edgedefault='directed'>\n";
while (!workList.empty()) {
auto path = std::move(workList.extract(workList.begin()).value());
@@ -68,20 +70,20 @@ try {
if (ret.second == false) continue;
auto info = TRY_AWAIT(store->queryPathInfo(path));
result << makeNode(*info);
cout << makeNode(*info);
for (auto & p : info->references) {
if (p != path) {
workList.insert(p);
result << makeEdge(path.to_string(), p.to_string());
cout << makeEdge(path.to_string(), p.to_string());
}
}
}
result << "</graph>\n";
result << "</graphml>\n";
co_return result.str();
cout << "</graph>\n";
cout << "</graphml>\n";
co_return result::success();
} catch (...) {
co_return result::current_exception();
}
+2 -1
View File
@@ -5,5 +5,6 @@
namespace nix {
kj::Promise<Result<std::string>> formatGraphML(ref<Store> store, StorePathSet && roots);
kj::Promise<Result<void>> printGraphML(ref<Store> store, StorePathSet && roots);
}
-2
View File
@@ -4,7 +4,6 @@ legacy_sources = files(
# `build-remote` is not really legacy (it powers all remote builds), but it's
# not a `nix3` command.
'build-remote.cc',
'builtin-builder.cc',
'dotgraph.cc',
'graphml.cc',
'nix-build.cc',
@@ -20,7 +19,6 @@ legacy_sources = files(
legacy_headers = files(
'build-remote.hh',
'builtin-builder.hh',
'nix-build.hh',
'nix-channel.hh',
'nix-collect-garbage.hh',
+13 -18
View File
@@ -192,11 +192,7 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
throw UsageError("'-p' and '-E' are mutually exclusive");
AutoDelete tmpDir(createTempDir(myName));
// 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);
AutoDelete buildTopTmpDir(createTempSubdir(tmpDir, "build-top"));
if (outLink.empty())
outLink = (Path) tmpDir + "/result";
@@ -213,7 +209,7 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
auto autoArgsWithInNixShell = autoArgs;
if (runEnv) {
auto newArgs = evaluator->buildBindings(autoArgsWithInNixShell->size() + 1);
newArgs.insert("inNixShell", {NewValueAs::boolean, true});
newArgs.alloc("inNixShell").mkBool(true);
for (auto & i : *autoArgs) newArgs.insert(i);
autoArgsWithInNixShell = newArgs.finish();
}
@@ -272,7 +268,8 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
if (attrPaths.empty()) attrPaths = {""};
for (auto e : exprs) {
Value vRoot = state->eval(e);
Value vRoot;
state->eval(e, vRoot);
std::function<bool(const Value & v)> takesNixShellAttr;
takesNixShellAttr = [&](const Value & v) {
@@ -355,7 +352,8 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
"(import <nixpkgs> {}).bashInteractive",
CanonPath::fromCwd());
Value v = state->eval(expr);
Value v;
state->eval(expr, v);
auto drv = getDerivation(*state, v, false);
if (!drv)
@@ -436,10 +434,6 @@ 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);
@@ -543,12 +537,13 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
printMsg(lvlChatty, "running shell: %s", concatMapStringsSep(" ", args, shellEscape));
RunningProgram proc = runProgram2({
.program = *shell,
.searchPath = true,
.args = args,
.environment = env,
});
RunningProgram proc = runProgram2(
{.program = *shell,
.searchPath = true,
.args = args,
.environment = env,
.dieWithParent = true}
);
// NOTE: we wait and return the status check immediately.
// If there's interruption, we will swallow it and wait again for termination.
+209 -251
View File
@@ -151,10 +151,12 @@ static void getAllExprs(Evaluator & state,
continue;
}
/* Load the expression on demand. */
Value vArg = {NewValueAs::string, path2.canonical().abs()};
Value vArg;
vArg.mkString(path2.canonical().abs());
if (seen.size() == maxAttrs)
throw Error("too many Nix expressions in directory '%1%'", path);
attrs.insert(attrName, {NewValueAs::app, state.mem, state.builtins.get("import"), vArg});
attrs.alloc(attrName
) = {NewValueAs::app, state.mem, state.builtins.get("import"), vArg};
}
else if (st.type == InputAccessor::tDirectory)
/* `path2' is a directory (with no default.nix in it);
@@ -163,13 +165,15 @@ static void getAllExprs(Evaluator & state,
}
}
static Value loadSourceExpr(EvalState & state, const SourcePath & path_)
static void loadSourceExpr(EvalState & state, const SourcePath & path_, Value & v)
{
auto path = state.ctx.paths.checkSourcePath(path_);
auto st = path.stat();
if (isNixExpr(state.ctx.paths, path, st))
return state.evalFile(path);
state.evalFile(path, v);
/* The path is a directory. Put the Nix expressions in the
directory in a set, with the file name of each expression as
@@ -179,10 +183,10 @@ static Value loadSourceExpr(EvalState & state, const SourcePath & path_)
directory). */
else if (st.type == InputAccessor::tDirectory) {
auto attrs = state.ctx.buildBindings(maxAttrs);
attrs.insert("_combineChannels", Value::EMPTY_LIST);
attrs.alloc("_combineChannels") = Value::EMPTY_LIST;
StringSet seen;
getAllExprs(state.ctx, path, seen, attrs);
return {NewValueAs::attrs, attrs};
v.mkAttrs(attrs);
}
else throw Error("path '%s' is not a directory or a Nix expression", path);
@@ -193,7 +197,8 @@ static void loadDerivations(EvalState & state, const SourcePath & nixExprPath,
std::string systemFilter, Bindings & autoArgs,
const std::string & pathPrefix, DrvInfos & elems)
{
Value vRoot = loadSourceExpr(state, nixExprPath);
Value vRoot;
loadSourceExpr(state, nixExprPath, vRoot);
Value v(findAlongAttrPath(state, pathPrefix, autoArgs, vRoot).first);
@@ -415,12 +420,14 @@ static void queryInstSources(EvalState & state,
(import ./foo.nix)' = `(import ./foo.nix).bar'. */
case srcNixExprs: {
Value vArg = loadSourceExpr(state, *instSource.nixExprPath);
Value vArg;
loadSourceExpr(state, *instSource.nixExprPath, vArg);
for (auto & i : args) {
Expr & eFun = state.ctx.parseExprFromString(i, CanonPath::fromCwd());
Value vFun = state.eval(eFun);
Value vTmp = {NewValueAs::app, state.ctx.mem, vFun, vArg};
Value vFun, vTmp;
state.eval(eFun, vFun);
vTmp = {NewValueAs::app, state.ctx.mem, vFun, vArg};
getDerivations(state, vTmp, "", *instSource.autoArgs, elems, true);
}
@@ -472,7 +479,8 @@ static void queryInstSources(EvalState & state,
}
case srcAttrPath: {
Value vRoot = loadSourceExpr(state, *instSource.nixExprPath);
Value vRoot;
loadSourceExpr(state, *instSource.nixExprPath, vRoot);
for (auto & i : args) {
Value v(findAlongAttrPath(state, i, *instSource.autoArgs, vRoot).first);
getDerivations(state, v, "", *instSource.autoArgs, elems, true);
@@ -509,7 +517,8 @@ static bool keep(EvalState & state, DrvInfo & drv)
static void setMetaFlag(EvalState & state, DrvInfo & drv,
const std::string & name, const std::string & value)
{
Value v = {NewValueAs::string, value};
Value v;
v.mkString(value);
drv.setMeta(state, name, v);
}
@@ -883,7 +892,8 @@ static bool cmpElemByName(EvalState & state, DrvInfo & a, DrvInfo & b)
typedef std::list<Strings> Table;
std::string formatTable(Table & table)
void printTable(Table & table)
{
auto nrColumns = table.size() > 0 ? table.front().size() : 0;
@@ -898,22 +908,18 @@ std::string formatTable(Table & table)
if (j->size() > widths[column]) widths[column] = j->size();
}
std::stringstream result;
for (auto & i : table) {
Strings::iterator j;
size_t column;
for (j = i.begin(), column = 0; j != i.end(); ++j, ++column) {
std::string s = *j;
replace(s.begin(), s.end(), '\n', ' ');
result << s;
cout << s;
if (column < nrColumns - 1)
result << std::string(widths[column] - s.size() + 2, ' ');
cout << std::string(widths[column] - s.size() + 2, ' ');
}
result << std::endl;
cout << std::endl;
}
return result.str();
}
@@ -1124,250 +1130,209 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
return;
}
withPager([&](Pager & pager) {
Table table;
std::ostringstream xmlStream;
XMLWriter xml(true, xmlStream);
xml.openElement("items");
RunPager pager;
for (auto & i : elems) {
try {
if (i.hasFailed()) {
continue;
}
Table table;
std::ostringstream dummy;
XMLWriter xml(true, *(xmlOutput ? &cout : &dummy));
XMLOpenElement xmlRoot(xml, "items");
// Activity act(*logger, lvlDebug, "outputting query result '%1%'", i.attrPath);
for (auto & i : elems) {
try {
if (i.hasFailed()) continue;
if (globals.prebuiltOnly && !validPaths.count(i.queryOutPath(*state))
&& !substitutablePaths.count(i.queryOutPath(*state)))
{
continue;
}
//Activity act(*logger, lvlDebug, "outputting query result '%1%'", i.attrPath);
/* For table output. */
Strings columns;
if (globals.prebuiltOnly &&
!validPaths.count(i.queryOutPath(*state)) &&
!substitutablePaths.count(i.queryOutPath(*state)))
continue;
/* For XML output. */
XMLAttrs attrs;
/* For table output. */
Strings columns;
if (printStatus) {
auto outPath = i.queryOutPath(*state);
bool hasSubs = substitutablePaths.count(outPath);
bool isInstalled = installed.count(outPath);
bool isValid = validPaths.count(outPath);
if (xmlOutput) {
attrs["installed"] = isInstalled ? "1" : "0";
attrs["valid"] = isValid ? "1" : "0";
attrs["substitutable"] = hasSubs ? "1" : "0";
} else {
columns.push_back(
(std::string) (isInstalled ? "I" : "-") + (isValid ? "P" : "-")
+ (hasSubs ? "S" : "-")
);
}
/* For XML output. */
XMLAttrs attrs;
if (printStatus) {
auto outPath = i.queryOutPath(*state);
bool hasSubs = substitutablePaths.count(outPath);
bool isInstalled = installed.count(outPath);
bool isValid = validPaths.count(outPath);
if (xmlOutput) {
attrs["installed"] = isInstalled ? "1" : "0";
attrs["valid"] = isValid ? "1" : "0";
attrs["substitutable"] = hasSubs ? "1" : "0";
} else
columns.push_back(
(std::string) (isInstalled ? "I" : "-")
+ (isValid ? "P" : "-")
+ (hasSubs ? "S" : "-"));
}
if (xmlOutput)
attrs["attrPath"] = i.attrPath;
else if (printAttrPath)
columns.push_back(i.attrPath);
if (xmlOutput) {
auto drvName = DrvName(i.queryName(*state));
attrs["name"] = drvName.fullName;
attrs["pname"] = drvName.name;
attrs["version"] = drvName.version;
} else if (printName) {
columns.push_back(i.queryName(*state));
}
if (compareVersions) {
/* Compare this element against the versions of the
same named packages in either the set of available
elements, or the set of installed elements. !!!
This is O(N * M), should be O(N * lg M). */
std::string version;
VersionDiff diff = compareVersionAgainstSet(*state, i, otherElems, version);
char ch;
switch (diff) {
case cvLess: ch = '>'; break;
case cvEqual: ch = '='; break;
case cvGreater: ch = '<'; break;
case cvUnavail: ch = '-'; break;
default: abort();
}
if (xmlOutput) {
attrs["attrPath"] = i.attrPath;
} else if (printAttrPath) {
columns.push_back(i.attrPath);
if (diff != cvUnavail) {
attrs["versionDiff"] = ch;
attrs["maxComparedVersion"] = version;
}
} else {
auto column = (std::string) "" + ch + " " + version;
if (diff == cvGreater && shouldANSI(StandardOutputStream::Stdout))
column = ANSI_RED + column + ANSI_NORMAL;
columns.push_back(column);
}
}
if (xmlOutput) {
if (i.querySystem(*state) != "") attrs["system"] = i.querySystem(*state);
}
else if (printSystem)
columns.push_back(i.querySystem(*state));
if (printDrvPath) {
auto drvPath = i.queryDrvPath(*state);
if (xmlOutput) {
auto drvName = DrvName(i.queryName(*state));
attrs["name"] = drvName.fullName;
attrs["pname"] = drvName.name;
attrs["version"] = drvName.version;
} else if (printName) {
columns.push_back(i.queryName(*state));
}
if (drvPath) attrs["drvPath"] = store.printStorePath(*drvPath);
} else
columns.push_back(drvPath ? store.printStorePath(*drvPath) : "-");
}
if (compareVersions) {
/* Compare this element against the versions of the
same named packages in either the set of available
elements, or the set of installed elements. !!!
This is O(N * M), should be O(N * lg M). */
std::string version;
VersionDiff diff = compareVersionAgainstSet(*state, i, otherElems, version);
if (xmlOutput)
attrs["outputName"] = i.queryOutputName(*state);
char ch;
switch (diff) {
case cvLess:
ch = '>';
break;
case cvEqual:
ch = '=';
break;
case cvGreater:
ch = '<';
break;
case cvUnavail:
ch = '-';
break;
default:
abort();
}
if (xmlOutput) {
if (diff != cvUnavail) {
attrs["versionDiff"] = ch;
attrs["maxComparedVersion"] = version;
}
} else {
auto column = (std::string) "" + ch + " " + version;
if (diff == cvGreater && shouldANSI(StandardOutputStream::Stdout)) {
column = ANSI_RED + column + ANSI_NORMAL;
}
columns.push_back(column);
}
if (printOutPath && !xmlOutput) {
DrvInfo::Outputs outputs = i.queryOutputs(*state);
std::string s;
for (auto & j : outputs) {
if (!s.empty()) s += ';';
if (j.first != "out") { s += j.first; s += "="; }
s += store.printStorePath(*j.second);
}
columns.push_back(s);
}
if (printDescription) {
auto descr = i.queryMetaString(*state, "description");
if (xmlOutput) {
if (i.querySystem(*state) != "") {
attrs["system"] = i.querySystem(*state);
}
} else if (printSystem) {
columns.push_back(i.querySystem(*state));
}
if (descr != "") attrs["description"] = descr;
} else
columns.push_back(descr);
}
if (printDrvPath) {
auto drvPath = i.queryDrvPath(*state);
if (xmlOutput) {
if (drvPath) {
attrs["drvPath"] = store.printStorePath(*drvPath);
}
} else {
columns.push_back(drvPath ? store.printStorePath(*drvPath) : "-");
}
if (xmlOutput) {
XMLOpenElement item(xml, "item", attrs);
DrvInfo::Outputs outputs = i.queryOutputs(*state, printOutPath);
for (auto & j : outputs) {
XMLAttrs attrs2;
attrs2["name"] = j.first;
if (j.second)
attrs2["path"] = store.printStorePath(*j.second);
xml.writeEmptyElement("output", attrs2);
}
if (xmlOutput) {
attrs["outputName"] = i.queryOutputName(*state);
}
if (printOutPath && !xmlOutput) {
DrvInfo::Outputs outputs = i.queryOutputs(*state);
std::string s;
for (auto & j : outputs) {
if (!s.empty()) {
s += ';';
}
if (j.first != "out") {
s += j.first;
s += "=";
}
s += store.printStorePath(*j.second);
}
columns.push_back(s);
}
if (printDescription) {
auto descr = i.queryMetaString(*state, "description");
if (xmlOutput) {
if (descr != "") {
attrs["description"] = descr;
}
} else {
columns.push_back(descr);
}
}
if (xmlOutput) {
XMLOpenElement item(xml, "item", attrs);
DrvInfo::Outputs outputs = i.queryOutputs(*state, printOutPath);
for (auto & j : outputs) {
if (printMeta) {
StringSet metaNames = i.queryMetaNames(*state);
for (auto & j : metaNames) {
XMLAttrs attrs2;
attrs2["name"] = j.first;
if (j.second) {
attrs2["path"] = store.printStorePath(*j.second);
}
xml.writeEmptyElement("output", attrs2);
}
if (printMeta) {
StringSet metaNames = i.queryMetaNames(*state);
for (auto & j : metaNames) {
XMLAttrs attrs2;
attrs2["name"] = j;
Value * v = i.queryMeta(*state, j);
if (!v) {
printError(
"derivation '%s' has invalid meta attribute '%s'", i.queryName(*state), j
);
} else {
if (v->type() == nString) {
attrs2["type"] = "string";
attrs2["value"] = v->str();
xml.writeEmptyElement("meta", attrs2);
} else if (v->type() == nInt) {
attrs2["type"] = "int";
attrs2["value"] = fmt("%1%", v->integer());
xml.writeEmptyElement("meta", attrs2);
} else if (v->type() == nFloat) {
attrs2["type"] = "float";
attrs2["value"] = fmt("%1%", v->fpoint());
xml.writeEmptyElement("meta", attrs2);
} else if (v->type() == nBool) {
attrs2["type"] = "bool";
attrs2["value"] = v->boolean() ? "true" : "false";
xml.writeEmptyElement("meta", attrs2);
} else if (v->type() == nList) {
attrs2["type"] = "strings";
XMLOpenElement m(xml, "meta", attrs2);
for (auto & elem : v->listItems()) {
if (elem.type() != nString) {
continue;
}
XMLAttrs attrs3;
attrs3["value"] = elem.str();
xml.writeEmptyElement("string", attrs3);
}
} else if (v->type() == nAttrs) {
attrs2["type"] = "strings";
XMLOpenElement m(xml, "meta", attrs2);
Bindings & attrs = *v->attrs();
for (auto & i : attrs) {
const Attr & a(*attrs.get(i.name));
if (a.value.type() != nString) {
continue;
}
XMLAttrs attrs3;
attrs3["type"] = globals.state->symbols[i.name];
attrs3["value"] = a.value.str();
xml.writeEmptyElement("string", attrs3);
attrs2["name"] = j;
Value * v = i.queryMeta(*state, j);
if (!v)
printError(
"derivation '%s' has invalid meta attribute '%s'",
i.queryName(*state), j);
else {
if (v->type() == nString) {
attrs2["type"] = "string";
attrs2["value"] = v->str();
xml.writeEmptyElement("meta", attrs2);
} else if (v->type() == nInt) {
attrs2["type"] = "int";
attrs2["value"] = fmt("%1%", v->integer());
xml.writeEmptyElement("meta", attrs2);
} else if (v->type() == nFloat) {
attrs2["type"] = "float";
attrs2["value"] = fmt("%1%", v->fpoint());
xml.writeEmptyElement("meta", attrs2);
} else if (v->type() == nBool) {
attrs2["type"] = "bool";
attrs2["value"] = v->boolean() ? "true" : "false";
xml.writeEmptyElement("meta", attrs2);
} else if (v->type() == nList) {
attrs2["type"] = "strings";
XMLOpenElement m(xml, "meta", attrs2);
for (auto & elem : v->listItems()) {
if (elem.type() != nString) {
continue;
}
XMLAttrs attrs3;
attrs3["value"] = elem.str();
xml.writeEmptyElement("string", attrs3);
}
} else if (v->type() == nAttrs) {
attrs2["type"] = "strings";
XMLOpenElement m(xml, "meta", attrs2);
Bindings & attrs = *v->attrs();
for (auto &i : attrs) {
const Attr & a(*attrs.get(i.name));
if (a.value.type() != nString) {
continue;
}
XMLAttrs attrs3;
attrs3["type"] = globals.state->symbols[i.name];
attrs3["value"] = a.value.str();
xml.writeEmptyElement("string", attrs3);
}
}
}
}
} else {
table.push_back(columns);
}
} else
table.push_back(columns);
cout.flush();
cout.flush();
} catch (AssertionError & e) {
printMsg(
lvlTalkative,
"skipping derivation named '%1%' which gives an assertion failure",
i.queryName(*state)
);
} catch (Error & e) {
e.addTrace(nullptr, "while querying the derivation named '%1%'", i.queryName(*state));
throw;
}
} catch (AssertionError & e) {
printMsg(lvlTalkative, "skipping derivation named '%1%' which gives an assertion failure", i.queryName(*state));
} catch (Error & e) {
e.addTrace(nullptr, "while querying the derivation named '%1%'", i.queryName(*state));
throw;
}
}
// </items>
xml.closeElement();
if (!xmlOutput) {
pager << formatTable(table);
} else {
pager << xmlStream.str();
}
});
if (!xmlOutput) printTable(table);
}
static void opSwitchProfile(Globals & globals, Strings opFlags, Strings opArgs)
{
if (opFlags.size() > 0)
@@ -1418,27 +1383,20 @@ static void opListGenerations(Globals & globals, Strings opFlags, Strings opArgs
auto [gens, curGen] = findGenerations(globals.profile);
withPager([&](Pager & pager) {
for (auto & i : gens) {
tm t;
if (!localtime_r(&i.creationTime, &t)) {
throw Error("cannot convert time");
}
pager << fmt(
"%|4| %|4|-%|02|-%|02| %|02|:%|02|:%|02| %||\n",
i.number,
t.tm_year + 1900,
t.tm_mon + 1,
t.tm_mday,
t.tm_hour,
t.tm_min,
t.tm_sec,
i.number == curGen ? "(current)" : ""
);
}
});
RunPager pager;
for (auto & i : gens) {
tm t;
if (!localtime_r(&i.creationTime, &t)) throw Error("cannot convert time");
logger->cout("%|4| %|4|-%|02|-%|02| %|02|:%|02|:%|02| %||",
i.number,
t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,
t.tm_hour, t.tm_min, t.tm_sec,
i.number == curGen ? "(current)" : "");
}
}
static void opDeleteGenerations(Globals & globals, Strings opFlags, Strings opArgs)
{
if (opFlags.size() > 0)
+7 -2
View File
@@ -34,7 +34,8 @@ void processExpr(EvalState & state, const Strings & attrPaths,
return;
}
Value vRoot = state.eval(e);
Value vRoot;
state.eval(e, vRoot);
for (auto & i : attrPaths) {
Value v(findAlongAttrPath(state, i, autoArgs, vRoot).first);
@@ -42,7 +43,11 @@ void processExpr(EvalState & state, const Strings & attrPaths,
NixStringContext context;
if (evalOnly) {
Value vRes = autoArgs.empty() ? v : state.autoCallFunction(autoArgs, v, noPos);
Value vRes;
if (autoArgs.empty())
vRes = v;
else
state.autoCallFunction(autoArgs, v, vRes, noPos);
if (output == okRaw)
std::cout << *state.coerceToString(noPos, vRes, context, "while generating the nix-instantiate output", StringCoercionMode::Strict);
// We intentionally don't output a newline here. The default PS1 for Bash in NixOS starts with a newline
+77 -100
View File
@@ -25,9 +25,7 @@
#include <iostream>
#include <algorithm>
#include <ostream>
#include <ranges>
#include <sstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
@@ -290,7 +288,6 @@ try {
graph. Topological sorting is used to keep the tree relatively
flat. */
static void printTree(
std::ostream & ostream,
std::shared_ptr<Store> store,
AsyncIoRoot & aio,
const StorePath & path,
@@ -300,11 +297,11 @@ static void printTree(
)
{
if (!done.insert(path).second) {
ostream << fmt("%s%s [...]\n", firstPad, store->printStorePath(path));
cout << fmt("%s%s [...]\n", firstPad, store->printStorePath(path));
return;
}
ostream << fmt("%s%s\n", firstPad, store->printStorePath(path));
cout << fmt("%s%s\n", firstPad, store->printStorePath(path));
auto info = aio.blockOn(store->queryPathInfo(path));
@@ -318,7 +315,6 @@ static void printTree(
for (const auto &[n, i] : enumerate(sorted)) {
bool last = n + 1 == sorted.size();
printTree(
ostream,
store,
aio,
i,
@@ -377,15 +373,17 @@ opQuery(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
if (!query) query = qOutputs;
withPager([&](Pager & pager) {
switch (*query) {
RunPager pager;
switch (*query) {
case qOutputs: {
for (auto & i : opArgs) {
auto outputs =
aio.blockOn(maybeUseOutputs(store, store->followLinksToStorePath(i), true, forceRealise));
for (auto & outputPath : outputs) {
pager << fmt("%1%\n", store->printStorePath(outputPath));
}
auto outputs = aio.blockOn(
maybeUseOutputs(store, store->followLinksToStorePath(i), true, forceRealise)
);
for (auto & outputPath : outputs)
cout << fmt("%1%\n", store->printStorePath(outputPath));
}
break;
}
@@ -396,55 +394,54 @@ opQuery(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
case qReferrersClosure: {
StorePathSet paths;
for (auto & i : opArgs) {
auto ps = aio.blockOn(
maybeUseOutputs(store, store->followLinksToStorePath(i), useOutput, forceRealise)
);
auto ps = aio.blockOn(maybeUseOutputs(
store, store->followLinksToStorePath(i), useOutput, forceRealise
));
for (auto & j : ps) {
if (query == qRequisites) {
aio.blockOn(store->computeFSClosure(j, paths, false, includeOutputs));
} else if (query == qReferences) {
for (auto & p : aio.blockOn(store->queryPathInfo(j))->references) {
}
else if (query == qReferences) {
for (auto & p : aio.blockOn(store->queryPathInfo(j))->references)
paths.insert(p);
}
} else if (query == qReferrers) {
}
else if (query == qReferrers) {
StorePathSet tmp;
aio.blockOn(store->queryReferrers(j, tmp));
for (auto & i : tmp) {
for (auto & i : tmp)
paths.insert(i);
}
} else if (query == qReferrersClosure) {
aio.blockOn(store->computeFSClosure(j, paths, true));
}
else if (query == qReferrersClosure)
aio.blockOn(store->computeFSClosure(j, paths, true));
}
}
auto sorted = aio.blockOn(store->topoSortPaths(paths));
for (StorePaths::reverse_iterator i = sorted.rbegin(); i != sorted.rend(); ++i) {
pager << fmt("%s\n", store->printStorePath(*i));
}
for (StorePaths::reverse_iterator i = sorted.rbegin();
i != sorted.rend(); ++i)
cout << fmt("%s\n", store->printStorePath(*i));
break;
}
case qDeriver:
for (auto & i : opArgs) {
auto info = aio.blockOn(store->queryPathInfo(store->followLinksToStorePath(i)));
pager << fmt(
"%s\n", info->deriver ? store->printStorePath(*info->deriver) : "unknown-deriver"
);
cout << fmt("%s\n", info->deriver ? store->printStorePath(*info->deriver) : "unknown-deriver");
}
break;
case qValidDerivers: {
StorePathSet result;
for (auto & i : opArgs) {
auto derivers = aio.blockOn(store->queryValidDerivers(store->followLinksToStorePath(i)));
auto derivers =
aio.blockOn(store->queryValidDerivers(store->followLinksToStorePath(i)));
for (const auto & i : derivers) {
result.insert(i);
}
}
auto sorted = aio.blockOn(store->topoSortPaths(result));
for (StorePaths::reverse_iterator i = sorted.rbegin(); i != sorted.rend(); ++i) {
pager << fmt("%s\n", store->printStorePath(*i));
}
for (StorePaths::reverse_iterator i = sorted.rbegin();
i != sorted.rend(); ++i)
cout << fmt("%s\n", store->printStorePath(*i));
break;
}
@@ -453,112 +450,95 @@ opQuery(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
auto path = aio.blockOn(useDeriver(store, store->followLinksToStorePath(i)));
Derivation drv = aio.blockOn(store->derivationFromPath(path));
StringPairs::iterator j = drv.env.find(bindingName);
if (j == drv.env.end()) {
throw Error(
"derivation '%s' has no environment binding named '%s'",
store->printStorePath(path),
bindingName
);
}
pager << fmt("%s\n", j->second);
if (j == drv.env.end())
throw Error("derivation '%s' has no environment binding named '%s'",
store->printStorePath(path), bindingName);
cout << fmt("%s\n", j->second);
}
break;
case qHash:
case qSize:
for (auto & i : opArgs) {
for (auto & j : aio.blockOn(
maybeUseOutputs(store, store->followLinksToStorePath(i), useOutput, forceRealise)
))
for (auto & j : aio.blockOn(maybeUseOutputs(
store, store->followLinksToStorePath(i), useOutput, forceRealise
)))
{
auto info = aio.blockOn(store->queryPathInfo(j));
if (query == qHash) {
assert(info->narHash.type == HashType::SHA256);
pager << fmt("%s\n", info->narHash.to_string(HashFormat::Base32));
} else if (query == qSize) {
pager << fmt("%d\n", info->narSize);
}
cout << fmt("%s\n", info->narHash.to_string(Base::Base32, true));
} else if (query == qSize)
cout << fmt("%d\n", info->narSize);
}
}
break;
case qTree: {
StorePathSet done;
for (auto & i : opArgs) {
std::stringstream tmp;
printTree(tmp, store, aio, store->followLinksToStorePath(i), "", "", done);
pager << tmp.str();
}
for (auto & i : opArgs)
printTree(store, aio, store->followLinksToStorePath(i), "", "", done);
break;
}
case qGraph: {
StorePathSet roots;
for (auto & i : opArgs) {
for (auto & j : aio.blockOn(
maybeUseOutputs(store, store->followLinksToStorePath(i), useOutput, forceRealise)
))
for (auto & i : opArgs)
for (auto & j : aio.blockOn(maybeUseOutputs(
store, store->followLinksToStorePath(i), useOutput, forceRealise
)))
{
roots.insert(j);
}
}
pager << aio.blockOn(formatDotGraph(ref<Store>::unsafeFromPtr(store), std::move(roots)));
aio.blockOn(printDotGraph(ref<Store>::unsafeFromPtr(store), std::move(roots)));
break;
}
case qGraphML: {
StorePathSet roots;
for (auto & i : opArgs) {
for (auto & j : aio.blockOn(
maybeUseOutputs(store, store->followLinksToStorePath(i), useOutput, forceRealise)
))
for (auto & i : opArgs)
for (auto & j : aio.blockOn(maybeUseOutputs(
store, store->followLinksToStorePath(i), useOutput, forceRealise
)))
{
roots.insert(j);
}
}
pager << aio.blockOn(formatGraphML(ref<Store>::unsafeFromPtr(store), std::move(roots)));
aio.blockOn(printGraphML(ref<Store>::unsafeFromPtr(store), std::move(roots)));
break;
}
case qResolve: {
for (auto & i : opArgs) {
pager << fmt("%s\n", store->printStorePath(store->followLinksToStorePath(i)));
}
for (auto & i : opArgs)
cout << fmt("%s\n", store->printStorePath(store->followLinksToStorePath(i)));
break;
}
case qRoots: {
StorePathSet args;
for (auto & i : opArgs) {
for (auto & p : aio.blockOn(
maybeUseOutputs(store, store->followLinksToStorePath(i), useOutput, forceRealise)
))
for (auto & i : opArgs)
for (auto & p : aio.blockOn(maybeUseOutputs(
store, store->followLinksToStorePath(i), useOutput, forceRealise
)))
{
args.insert(p);
}
}
StorePathSet referrers;
aio.blockOn(store->computeFSClosure(
args, referrers, true, settings.gcKeepOutputs, settings.gcKeepDerivations
));
args, referrers, true, settings.gcKeepOutputs, settings.gcKeepDerivations));
auto & gcStore = require<GcStore>(*store);
Roots roots = aio.blockOn(gcStore.findRoots(false));
for (auto & [target, links] : roots) {
if (referrers.find(target) != referrers.end()) {
for (auto & link : links) {
pager << fmt("%1% -> %2%\n", link, gcStore.printStorePath(target));
}
}
}
for (auto & [target, links] : roots)
if (referrers.find(target) != referrers.end())
for (auto & link : links)
cout << fmt("%1% -> %2%\n", link, gcStore.printStorePath(target));
break;
}
default:
abort();
}
});
}
}
static void
@@ -594,16 +574,15 @@ opReadLog(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Stri
auto & logStore = require<LogStore>(*store);
withPager([&](Pager & pager) {
for (auto & i : opArgs) {
auto path = logStore.followLinksToStorePath(i);
auto log = aio.blockOn(logStore.getBuildLog(path));
if (!log) {
throw Error("build log of derivation '%s' is not available", logStore.printStorePath(path));
}
pager << *log;
}
});
RunPager pager;
for (auto & i : opArgs) {
auto path = logStore.followLinksToStorePath(i);
auto log = aio.blockOn(logStore.getBuildLog(path));
if (!log)
throw Error("build log of derivation '%s' is not available", logStore.printStorePath(path));
std::cout << *log;
}
}
static void
@@ -877,12 +856,10 @@ opVerifyPath(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, S
aio.blockOn(aio.blockOn(store->narFromPath(path))->drainInto(sink));
auto current = sink.finish();
if (current.first != info->narHash) {
printError(
"path '%s' was modified! expected hash '%s', got '%s'",
printError("path '%s' was modified! expected hash '%s', got '%s'",
store->printStorePath(path),
info->narHash.to_string(),
current.first.to_string()
);
info->narHash.to_string(Base::SRI, true),
current.first.to_string(Base::SRI, true));
status = 1;
}
}
+21 -27
View File
@@ -46,31 +46,24 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
auto attrs = state.ctx.buildBindings(7 + outputs.size());
attrs.insert(state.ctx.symbols.sym_type, {NewValueAs::string, "derivation"});
attrs.insert(state.ctx.symbols.sym_name, {NewValueAs::string, i.queryName(state)});
attrs.alloc(state.ctx.s.type).mkString("derivation");
attrs.alloc(state.ctx.s.name).mkString(i.queryName(state));
auto system = i.querySystem(state);
if (!system.empty())
attrs.insert(state.ctx.symbols.sym_system, {NewValueAs::string, system});
attrs.insert(
state.ctx.symbols.sym_outPath,
{NewValueAs::string, state.ctx.store->printStorePath(i.queryOutPath(state))}
);
attrs.alloc(state.ctx.s.system).mkString(system);
attrs.alloc(state.ctx.s.outPath).mkString(state.ctx.store->printStorePath(i.queryOutPath(state)));
if (drvPath)
attrs.insert(
state.ctx.symbols.sym_drvPath, {NewValueAs::string, state.ctx.store->printStorePath(*drvPath)}
);
attrs.alloc(state.ctx.s.drvPath).mkString(state.ctx.store->printStorePath(*drvPath));
// Copy each output meant for installation.
auto & vOutputs = attrs.alloc(state.ctx.s.outputs);
auto outputsList = state.ctx.mem.newList(outputs.size());
attrs.insert(state.ctx.symbols.sym_outputs, {NewValueAs::list, outputsList});
vOutputs = {NewValueAs::list, outputsList};
for (const auto & [m, j] : enumerate(outputs)) {
outputsList->elems[m] = {NewValueAs::string, j.first};
outputsList->elems[m].mkString(j.first);
auto outputAttrs = state.ctx.buildBindings(2);
outputAttrs.insert(
state.ctx.symbols.sym_outPath,
{NewValueAs::string, state.ctx.store->printStorePath(*j.second)}
);
attrs.insert(j.first, {NewValueAs::attrs, outputAttrs});
outputAttrs.alloc(state.ctx.s.outPath).mkString(state.ctx.store->printStorePath(*j.second));
attrs.alloc(j.first).mkAttrs(outputAttrs);
/* This is only necessary when installing store paths, e.g.,
`nix-env -i /nix/store/abcd...-foo'. */
@@ -88,9 +81,9 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
meta.insert(state.ctx.symbols.create(j), *v);
}
attrs.insert(state.ctx.symbols.sym_meta, {NewValueAs::attrs, meta});
attrs.alloc(state.ctx.s.meta).mkAttrs(meta);
manifest->elems[n++] = {NewValueAs::attrs, attrs};
manifest->elems[n++].mkAttrs(attrs);
if (drvPath) references.insert(*drvPath);
}
@@ -104,17 +97,18 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
str.str(), references));
/* Get the environment builder expression. */
Value envBuilder = state.eval(state.ctx.parseExprFromString(
#include "buildenv.nix.gen.hh"
, CanonPath::root
));
Value envBuilder;
state.eval(state.ctx.parseExprFromString(
#include "buildenv.nix.gen.hh"
, CanonPath::root), envBuilder);
/* Construct a Nix expression that calls the user environment
builder with the manifest as argument. */
auto attrs = state.ctx.buildBindings(3);
attrs.insert("manifest", state.ctx.paths.mkStorePathString(manifestFile));
state.ctx.paths.mkStorePathString(manifestFile, attrs.alloc("manifest"));
attrs.insert(state.ctx.symbols.create("derivations"), vManifest);
Value args = {NewValueAs::attrs, attrs};
Value args;
args.mkAttrs(attrs);
Value topLevel{NewValueAs::app, state.ctx.mem, envBuilder, args};
@@ -122,9 +116,9 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
debug("evaluating user environment builder");
state.forceValue(topLevel, noPos);
NixStringContext context;
const Attr & aDrvPath(*topLevel.attrs()->get(state.ctx.symbols.sym_drvPath));
const Attr & aDrvPath(*topLevel.attrs()->get(state.ctx.s.drvPath));
auto topLevelDrv = state.coerceToStorePath(aDrvPath.pos, aDrvPath.value, context, "");
const Attr & aOutPath(*topLevel.attrs()->get(state.ctx.symbols.sym_outPath));
const Attr & aOutPath(*topLevel.attrs()->get(state.ctx.s.outPath));
auto topLevelOut = state.coerceToStorePath(aOutPath.pos, aOutPath.value, context, "");
/* Realise the resulting store expression. */
+2 -5
View File
@@ -20,7 +20,8 @@ DrvInfos queryInstalled(EvalState & state, const Path & userEnv)
throw Error("profile '%s' is incompatible with 'nix-env'; please use 'nix profile' instead", userEnv);
auto manifestFile = userEnv + "/manifest.nix";
if (pathExists(manifestFile)) {
Value v = state.evalFile(CanonPath(manifestFile));
Value v;
state.evalFile(CanonPath(manifestFile), v);
Bindings & bindings(*state.ctx.mem.allocBindings(0));
getDerivations(state, v, "", bindings, elems, false);
}
@@ -98,10 +99,6 @@ void ProfileElement::updateStorePaths(
for (auto & output : bfd.outputs) {
storePaths.insert(output.second);
}
if (settings.envKeepDerivations) {
storePaths.insert(bfd.drvPath.path);
}
},
},
buildable.raw()
+4 -2
View File
@@ -171,7 +171,7 @@ struct RawInstallablesCommand : virtual Args, SourceExprCommand
std::vector<FlakeRef> getFlakeRefsForCompletion() override;
protected:
private:
std::vector<std::string> rawInstallables;
};
@@ -220,11 +220,13 @@ struct MixOperateOnOptions : virtual Args
*/
struct BuiltPathsCommand : InstallablesCommand, virtual MixOperateOnOptions
{
protected:
private:
bool recursive = false;
bool all = false;
protected:
Realise realiseMode = Realise::Derivation;
public:
+41 -87
View File
@@ -1,6 +1,3 @@
#include "libexpr/value.hh"
#include "libutil/strings.hh"
#include "lix/libexpr/attr-path.hh"
#include "lix/libexpr/eval-settings.hh"
#include "lix/libcmd/common-eval-args.hh"
#include "lix/libmain/shared.hh"
@@ -13,10 +10,31 @@
#include "lix/libcmd/command.hh"
#include "lix/libutil/async.hh"
#include "lix/libutil/error.hh"
#include <deque>
#include "lix/libutil/regex.hh"
#include <regex>
namespace nix {
static std::regex const identifierRegex = regex::parse("^[A-Za-z_][A-Za-z0-9_'-]*$");
static void checkValidNixIdentifier(const std::string & name)
{
std::smatch match;
if (!std::regex_match(name, match, identifierRegex)) {
throw UsageError(
"This invocation specifies a value for argument '%s' "
"which isn't a valid Nix identifier. "
"The project is dropping support for this so that it's possible to make e.g. "
"'%s' evaluating to '%s' in the future. "
"If you depend on this behavior, please reach out in "
"<https://git.lix.systems/lix-project/lix/issues/496> so we can discuss your use-case.",
name,
"--arg config.allowUnfree true",
"{ config.allowUnfree = true; }"
);
}
}
MixEvalArgs::MixEvalArgs()
{
addFlag(
@@ -24,7 +42,10 @@ MixEvalArgs::MixEvalArgs()
.description = "Pass the value *expr* as the argument *name* to Nix functions.",
.category = category,
.labels = {"name", "expr"},
.handler = {[&](std::string name, std::string expr) { autoArgs[name] = ExprArgument(expr); }}}
.handler = {[&](std::string name, std::string expr) {
checkValidNixIdentifier(name);
autoArgs[name] = 'E' + expr;
}}}
);
addFlag({
@@ -32,7 +53,10 @@ MixEvalArgs::MixEvalArgs()
.description = "Pass the string *string* as the argument *name* to Nix functions.",
.category = category,
.labels = {"name", "string"},
.handler = {[&](std::string name, std::string s) { autoArgs[name] = StringArgument(s); }},
.handler = {[&](std::string name, std::string s) {
checkValidNixIdentifier(name);
autoArgs[name] = 'S' + s;
}},
});
addFlag({
@@ -155,90 +179,20 @@ MixEvalArgs::MixEvalArgs()
});
}
struct AutoArgsContainer
{
std::map<Symbol, std::variant<Value, AutoArgsContainer>> data;
Bindings * toBindings(Evaluator & state)
{
auto bb = state.buildBindings(data.size());
for (auto & [sym, v] : data) {
bb.insert(
sym,
std::visit(
overloaded{
[&](Value & v) { return v; },
[&](AutoArgsContainer & aac) -> Value {
return {NewValueAs::attrs, aac.toBindings(state)};
}
},
v
)
);
}
return bb.finish();
}
};
static void addAutoArgRecursive(
AutoArgsContainer & container,
Evaluator & state,
std::vector<std::string> && path,
Value & val,
const std::string_view pathStr
)
{
auto * data = &container.data;
auto size = path.size();
for (auto [i, pathCmp] : enumerate(path)) {
auto next = state.symbols.create(pathCmp);
auto entry = data->find(next);
if (entry == data->end()) {
if (i == size - 1) {
(*data)[next] = val;
} else {
(*data)[next] = AutoArgsContainer{};
data = &std::get<AutoArgsContainer>((*data)[next]).data;
}
} else {
std::visit(
overloaded{
[&](Value & v) {
throw Error(
"Cannot set %s via --arg/--argstr when it's the path-extension of another "
"auto-argument!",
pathStr
);
},
[&](AutoArgsContainer & v) { data = &v.data; }
},
entry->second
);
}
}
}
Bindings * MixEvalArgs::getAutoArgs(Evaluator & state)
{
AutoArgsContainer aac;
for (auto & [name, value] : autoArgs) {
Value v = std::visit(
overloaded{
[&](StringArgument & str) -> Value { return {NewValueAs::string, (std::string_view) str.value}; },
[&](ExprArgument & e) -> Value {
return state.evalLazily(state.parseExprFromString(e.expr, CanonPath::fromCwd()));
}
},
value
);
addAutoArgRecursive(aac, state, parseAttrPath(name, false), v, name);
auto res = state.buildBindings(autoArgs.size());
for (auto & i : autoArgs) {
Value v;
if (i.second[0] == 'E')
state.evalLazily(
state.parseExprFromString(i.second.substr(1), CanonPath::fromCwd()), v
);
else
v.mkString(((std::string_view) i.second).substr(1));
res.insert(state.symbols.create(i.first), v);
}
return aac.toBindings(state);
return res.finish();
}
kj::Promise<Result<EvalPaths::PathResult<SourcePath, ThrownError>>>
+1 -10
View File
@@ -14,15 +14,6 @@ class EvalState;
class Bindings;
struct SourcePath;
struct StringArgument
{
std::string value;
};
struct ExprArgument
{
std::string expr;
};
struct MixEvalArgs : virtual Args, virtual MixRepair
{
static constexpr auto category = "Common evaluation options";
@@ -36,7 +27,7 @@ struct MixEvalArgs : virtual Args, virtual MixRepair
std::optional<std::string> evalStoreUrl;
private:
std::map<std::string, std::variant<StringArgument, ExprArgument>> autoArgs;
std::map<std::string, std::string> autoArgs;
};
/** @brief Resolve an argument that is generally a file, but could be something that
-76
View File
@@ -1,76 +0,0 @@
#pragma once
///@file
#include <string_view>
#include <type_traits>
#include <utility>
#include <optional>
#include <ranges>
#include "lix/libutil/args.hh"
namespace nix::cli {
template<typename Enum>
struct enum_cli_traits;
template<typename Enum>
constexpr std::string_view toString(Enum value)
{
for (const auto & [name, val] : enum_cli_traits<Enum>::values) {
if (val == value) {
return name;
}
}
std::terminate();
}
template<typename Enum>
std::optional<Enum> fromString(std::string_view str)
{
for (const auto & [name, val] : enum_cli_traits<Enum>::values) {
if (name == str) {
return val;
}
}
return std::nullopt;
}
template<typename Enum>
void completeAmongEnumChoices(AddCompletions & completions, size_t, std::string_view prefix)
{
for (const auto & [name, _] : enum_cli_traits<Enum>::values) {
if (name.starts_with(prefix)) {
completions.add(name);
}
}
}
template<typename Enum>
Enum parseEnumArg(std::string text)
{
auto valueOpt = fromString<Enum>(text);
if (valueOpt) {
return *valueOpt;
} else {
auto names = std::ranges::views::keys(enum_cli_traits<Enum>::values)
| std::ranges::to<std::set<std::string>>();
auto suggestions = Suggestions::bestMatches(names, text);
throw UsageError(suggestions, "'%s' is not a recognised '%s'", text, enum_cli_traits<Enum>::typeName);
}
}
template<typename Enum>
std::optional<Enum> parseOptionalEnumArg(std::string text)
{
auto target = fromString<Enum>(text);
if (!target && text != "") {
auto names = std::ranges::views::keys(enum_cli_traits<Enum>::values) | std::ranges::to<std::set>();
auto suggestions = Suggestions::bestMatches(names, text);
throw UsageError(suggestions, "'%s' is not a recognised '%s'", text, enum_cli_traits<Enum>::typeName);
}
return target;
}
}
+2
View File
@@ -7,6 +7,8 @@
#include "lix/libexpr/flake/flake.hh"
#include "lix/libexpr/eval-cache.hh"
#include <nlohmann/json.hpp>
namespace nix {
std::vector<std::string> InstallableFlake::getActualAttrPaths()
+17 -14
View File
@@ -219,7 +219,8 @@ void SourceExprCommand::completeInstallable(EvalState & state, AddCompletions &
state.aio.blockOn(lookupFileArg(*evaluator, *file)).unwrap()
));
Value root = state.eval(e);
Value root;
state.eval(e, root);
auto autoArgs = getAutoArgs(*evaluator);
@@ -236,7 +237,8 @@ void SourceExprCommand::completeInstallable(EvalState & state, AddCompletions &
auto [v1, pos] = findAlongAttrPath(state, prefix_, *autoArgs, root);
state.forceValue(v1, pos);
Value v2 = state.autoCallFunction(*autoArgs, v1, pos);
Value v2;
state.autoCallFunction(*autoArgs, v1, v2, pos);
if (v2.type() == nAttrs) {
for (auto & i : *v2.attrs()) {
@@ -409,7 +411,8 @@ ref<eval_cache::EvalCache> openEvalCache(
if (getEnv("NIX_ALLOW_EVAL").value_or("1") == "0")
throw Error("not everything is cached, but evaluation is not allowed");
Value vFlake = flake::callFlake(state, *lockedFlake);
Value vFlake;
flake::callFlake(state, *lockedFlake, vFlake);
state.forceAttrs(vFlake, noPos, "while parsing cached flake data");
@@ -446,18 +449,18 @@ Installables SourceExprCommand::parseInstallables(
throw UsageError("'--file' and '--expr' are exclusive");
auto evaluator = getEvaluator();
Value vFile;
Value vFile = [&](NeverAsync = {}) {
if (file == "-") {
auto & e = evaluator->parseStdin();
return state.eval(e);
} else if (file) {
return state.evalFile(state.aio.blockOn(lookupFileArg(*evaluator, *file)).unwrap());
} else {
auto & e = evaluator->parseExprFromString(*expr, CanonPath::fromCwd());
return state.eval(e);
}
}();
if (file == "-") {
auto & e = evaluator->parseStdin();
state.eval(e, vFile);
}
else if (file)
state.evalFile(state.aio.blockOn(lookupFileArg(*evaluator, *file)).unwrap(), vFile);
else {
auto & e = evaluator->parseExprFromString(*expr, CanonPath::fromCwd());
state.eval(e, vFile);
}
for (auto & s : ss) {
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(s);
-1
View File
@@ -5,7 +5,6 @@
#include <functional>
#include <list>
#include <map>
#include <span>
#include <string>
namespace nix {
+3 -1
View File
@@ -5,4 +5,6 @@ includedir=@includedir@
Name: Lix (libcmd)
Description: Lix Package Manager (libcmd)
Version: @PACKAGE_VERSION@
Requires: lix
Requires: lix-base lix-util lix-store
Requires.private: lix-fetchers lix-expr lix-main @BOEHM_IF_FOUND@ libeditline lowdown ncurses
Libs: -L${libdir} @LIBLIX_DOC_IF_STATIC@ -llixcmd
+8 -4
View File
@@ -55,6 +55,7 @@ std::string renderMarkdownToTerminal(std::string_view markdown, StandardOutputSt
struct lowdown_opts opts{
.type = LOWDOWN_TERM,
#ifdef LOWDOWN_SEPARATE_TERM_OPTS
.term =
{
.cols = lowdown_cols,
@@ -64,13 +65,16 @@ std::string renderMarkdownToTerminal(std::string_view markdown, StandardOutputSt
.vmargin = 0,
.centre = 0,
},
// maxdepth needs to be part of the ifdefs to match declaration order
.maxdepth = 20,
.feat = LOWDOWN_COMMONMARK | LOWDOWN_FENCED | LOWDOWN_DEFLIST | LOWDOWN_TABLES,
#ifdef LOWDOWN_CONSOLIDATED_OFLAGS
.oflags = LOWDOWN_NOLINK,
#else
.maxdepth = 20,
.cols = lowdown_cols,
.hmargin = 0,
.vmargin = 0,
#endif /* LOWDOWN_SEPARATE_TERM_OPTS */
.feat = LOWDOWN_COMMONMARK | LOWDOWN_FENCED | LOWDOWN_DEFLIST | LOWDOWN_TABLES,
.oflags = LOWDOWN_TERM_NOLINK,
#endif /* LOWDOWN_CONSOLIDATED_OFLAGS */
};
if (!shouldANSI(fileno)) {
opts.oflags |= LOWDOWN_TERM_NOANSI;
+62 -3
View File
@@ -1,4 +1,4 @@
liblix_sources += files(
libcmd_sources = files(
'built-path.cc',
'cmd-profiles.cc',
'command.cc',
@@ -21,7 +21,6 @@ libcmd_headers = files(
'command.hh',
'common-eval-args.hh',
'editor-for.hh',
'enum-traits.hh',
'installable-attr-path.hh',
'installable-derived-path.hh',
'installable-flake.hh',
@@ -33,8 +32,68 @@ libcmd_headers = files(
'repl.hh',
)
liblix_generated_headers += [
libcmd_generated_headers = [
gen_header.process('repl-overlays.nix', preserve_path_from: meson.current_source_dir()),
]
libcmd = library(
'lixcmd',
libcmd_generated_headers,
libcmd_sources,
dependencies : [
liblixutil,
liblixstore,
liblixfetchers,
liblixexpr,
liblixmain,
liblix_doc,
boehm,
editline,
kj,
lowdown,
ncurses,
nlohmann_json,
],
# '../..' for self references like "lix/libcmd/*.hh"
include_directories : [ '../..' ],
cpp_pch : cpp_pch,
install : true,
# FIXME(Qyriad): is this right?
install_rpath : libdir,
)
install_headers(libcmd_headers, subdir : 'lix/libcmd', preserve_path : true)
custom_target(
command : [ 'cp', '@INPUT@', '@OUTPUT@' ],
input : libcmd_generated_headers,
output : '@PLAINNAME@',
install : true,
install_dir : includedir / 'lix/libcmd',
)
liblixcmd = declare_dependency(
include_directories : include_directories('../..'),
dependencies : [
liblixutil,
liblixstore,
kj,
],
link_with : libcmd,
)
meson.override_dependency('lix-cmd', liblixcmd)
# FIXME: not using the pkg-config module because it creates way too many deps
# while meson migration is in progress, and we want to not include boost here
configure_file(
input : 'lix-cmd.pc.in',
output : 'lix-cmd.pc',
install_dir : libdir / 'pkgconfig',
configuration : {
'prefix' : prefix,
'libdir' : libdir,
'includedir' : includedir,
'PACKAGE_VERSION' : meson.project_version(),
'BOEHM_IF_FOUND' : boehm.found() ? 'bdw-gc' : '',
'LIBLIX_DOC_IF_STATIC' : is_static ? '-llix_doc' : '',
},
)
+1 -1
View File
@@ -48,7 +48,7 @@ char ** copyCompletions(const StringSet& possible)
if (vp) {
while (--ac >= 0)
free(vp[ac]);
free(static_cast<void *>(vp));
free(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().
*/
void writeHistory();
virtual void writeHistory();
virtual ~ReadlineLikeInteracter() override;
};
+454 -990
View File
File diff suppressed because it is too large Load Diff
-86
View File
@@ -1,86 +0,0 @@
#include "common.hh"
#include <csignal>
#include <cstdlib>
#include <cstring>
#include <format>
#include <sched.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/wait.h>
LIBEXEC_HELPER(0)
static int waitFor(pid_t child)
{
int status;
while (true) {
if (waitpid(child, &status, 0) == -1) {
if (errno != EINTR) {
DIE_UNLESS_SYS("waitpid()", -1);
}
} else if (WIFEXITED(status)) {
return WEXITSTATUS(status);
} else if (WIFSIGNALED(status)) {
die(std::format("child died with signal {}", WTERMSIG(status)));
} else {
die(std::format("child exited {}", status));
}
}
}
int helperMain(const char * name, std::span<char *> args) noexcept
{
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) {
die(std::format("mmap(): {}", strerror(errno)));
}
const bool haveUserNS = [&] {
auto child = clone([](void *) { return 0; }, stack + stackSize, CLONE_NEWUSER | SIGCHLD, nullptr);
if (child == -1) {
printf("user %s\n", strerror(errno));
return false;
} else if (auto status = waitFor(child)) {
die(std::format("userns check child failed unexpectedly with status {}", status));
} else {
printf("user\n");
return true;
}
}();
{
auto child = clone(
[](void *) {
/* Make sure we don't remount the parent's /proc. */
if (mount(0, "/", 0, MS_PRIVATE | MS_REC, 0) == -1) {
return 1;
}
/* Test whether we can remount /proc. The kernel disallows
this if /proc is not fully visible, i.e. if there are
filesystems mounted on top of files inside /proc. See
https://lore.kernel.org/lkml/87tvsrjai0.fsf@xmission.com/T/. */
if (mount("none", "/proc", "proc", 0, 0) == -1) {
return 2;
}
return 0;
},
stack + stackSize,
CLONE_NEWNS | CLONE_NEWPID | (haveUserNS ? CLONE_NEWUSER : 0) | SIGCHLD,
nullptr
);
if (child == -1) {
printf("mount-pid %s\n", strerror(errno));
} else if (waitFor(child) != 0) {
printf("mount-pid failed to remount /proc\n");
} else {
printf("mount-pid\n");
}
}
return 0;
}
-103
View File
@@ -1,103 +0,0 @@
#pragma once
///@file common setup/utility header for libexec helpers
#include <cctype>
#include <cerrno>
#include <cinttypes>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <format> // IWYU pragma: keep
#include <limits>
#include <span>
#include <string> // IWYU pragma: keep
#include <string_view>
#include <type_traits>
#include <unistd.h>
/// file descriptor of the error reporting pipe. anything written to this pipe
/// will be treated as a fatal error message regardless of helper exit status.
/// an empty line (a single `\n` byte) will be treated as successful startup,
/// any errors encountered later can be retrieved by the parent in due course.
inline int ERR_PIPE;
inline void writeErrPipe(std::string_view msg)
{
while (!msg.empty()) {
if (auto wrote = write(ERR_PIPE, msg.data(), msg.size()); wrote >= 0) {
msg.remove_prefix(size_t(wrote));
} else {
break;
}
}
}
/// immediately terminate helper execution with a fatal error.
[[noreturn]]
inline void die(std::string_view msg)
{
writeErrPipe(msg);
exit(252);
}
/// converts an argument to an integer or dies with a message.
template<typename T, size_t N>
requires std::is_integral_v<T>
T argToInt(const char (&argName)[N], const char * str)
{
// this should really just wrap std::from_chars, but macos doesn't have it.
for (const auto c : std::string_view(str)) {
if (c != '-' && !std::isdigit(c)) {
die(std::format("invalid {} argument", argName));
}
}
char * end = nullptr;
const auto tmp = [&] {
if constexpr (std::is_signed_v<T>) {
return std::strtoimax(str, &end, 10); // NOLINT(lix-unsafe-c-calls): str is a C string
} else {
return std::strtoumax(str, &end, 10); // NOLINT(lix-unsafe-c-calls): str is a C string
}
}();
if (!end || *end || tmp < std::numeric_limits<T>::min() || tmp > std::numeric_limits<T>::max()) {
die(std::format("invalid {} argument", argName));
}
return tmp;
}
/// check syscall result and immediately terminate with a message on failure.
#define DIE_UNLESS_SYS(name, expr) \
([&] { \
if ((expr) == -1) { \
die(std::format("{}: {}", name, strerror(errno))); \
} \
}())
/// declare the TU expanding this as a libexec helper with at least `expectedArgs`
/// arguments. more arguments may be passed, fewer args will be treated as a fatal
/// error and reported immediately. a valid ERR_PIPE pipe must be passed as as the
/// first argument and will be set to close-on-exec to not pass it on to children.
#define LIBEXEC_HELPER(expectedArgs) \
int main(int argc, char * argv[]) \
{ \
if (argc < (expectedArgs) + 2) { \
_exit(254); \
} \
\
try { \
/* NOTE: we purposely accept imperfect conversion, only errors are fatal. \
if our parent messes this up we have *much* bigger problems than this. */ \
ERR_PIPE = std::stoi(argv[1]); \
} catch (...) { \
_exit(253); \
} \
\
DIE_UNLESS_SYS("error pipe fcntl", fcntl(ERR_PIPE, F_SETFD, FD_CLOEXEC)); \
return helperMain(argv[0], {argv + 2, argv + argc}); \
}
int helperMain(const char * name, std::span<char *> args) noexcept;
-60
View File
@@ -1,60 +0,0 @@
#include "common.hh"
#include <charconv>
#include <cstring>
#include <format>
#include <signal.h>
#include <unistd.h>
#if __APPLE__
#include <sys/syscall.h>
#endif
LIBEXEC_HELPER(1)
int helperMain(const char * name, std::span<char *> args) noexcept
{
std::string_view uidArg = args[0];
uid_t uid;
if (auto res = std::from_chars(uidArg.begin(), uidArg.end(), uid);
res.ptr != uidArg.end() || res.ec != std::errc())
{
die("invalid uid argument");
}
/* The system call kill(-1, sig) sends the signal `sig' to all
users to which the current process can send signals. So we
switch to that uid and send a mass kill once we've done so. */
if (setuid(uid) == -1) {
die(std::format("setuid(): {}", strerror(errno)));
}
while (true) {
#ifdef __APPLE__
/* OSX's kill syscall takes a third parameter that, among
other things, determines if kill(-1, signo) affects the
calling process. In the OSX libc, it's set to true,
which means "follow POSIX", which we don't want here */
if (syscall(SYS_kill, -1, SIGKILL, false) == 0) {
break;
}
#else
if (kill(-1, SIGKILL) == 0) {
break;
}
#endif
if (errno == ESRCH || errno == EPERM) {
break; /* no more processes */
}
if (errno != EINTR) {
die(std::format("cannot kill processes for uid {}: {}", uid, strerror(errno)));
}
}
/* !!! We should really do some check to make sure that there are
no processes left running under `uid', but there is no portable
way to do so (I think). The most reliable way may be `ps -eo
uid | grep -q $uid'. */
return 0;
}
-90
View File
@@ -1,90 +0,0 @@
#include "launch-builder.hh"
#include "lix/libstore/build/request.capnp.h"
#include "lix/libutil/rpc.hh"
#include <cstdlib>
#include <spawn.h>
#include <string_view>
#include <sys/sysctl.h>
#include <unistd.h>
namespace nix {
/* This definition is undocumented but depended upon by all major browsers. */
extern "C" int sandbox_init_with_parameters(
const char * profile, uint64_t flags, const char * const parameters[], char ** errorbuf
);
bool prepareChildSetup(build::Request::Reader request)
{
return true;
}
void finishChildSetup(build::Request::Reader request)
{
const auto config = request.getPlatform().getDarwin();
/* The tmpDir in scope points at the temporary build directory for our derivation. Some packages try
different mechanisms to find temporary directories, so we want to open up a broader place for them
to put their files, if needed. */
auto globalTmpDir = rpc::to<std::string>(config.getGlobalTempDir());
/* They don't like trailing slashes on subpath directives */
if (globalTmpDir.back() == '/') {
globalTmpDir.pop_back();
}
if (auto env = getenv("_NIX_TEST_NO_SANDBOX"); env && env != std::string_view("1")) {
std::vector<const char *> sandboxArgs;
sandboxArgs.push_back("_NIX_BUILD_TOP");
sandboxArgs.push_back(config.getTempDir().cStr());
sandboxArgs.push_back("_GLOBAL_TMP_DIR");
sandboxArgs.push_back(globalTmpDir.c_str());
if (config.getAllowLocalNetworking()) {
sandboxArgs.push_back("_ALLOW_LOCAL_NETWORKING");
sandboxArgs.push_back("1");
}
sandboxArgs.push_back(nullptr);
// NOLINTNEXTLINE(lix-unsafe-c-calls): all of these are env names or paths
if (sandbox_init_with_parameters(config.getSandboxProfile().cStr(), 0, sandboxArgs.data(), nullptr)) {
writeFull(STDERR_FILENO, "failed to configure sandbox\n");
_exit(1);
}
}
}
[[noreturn]]
void execBuilder(build::Request::Reader request)
{
const auto config = request.getPlatform().getDarwin();
posix_spawnattr_t attrp;
if (posix_spawnattr_init(&attrp)) {
throw SysError("failed to initialize builder");
}
if (posix_spawnattr_setflags(&attrp, POSIX_SPAWN_SETEXEC)) {
throw SysError("failed to initialize builder");
}
const auto platform = rpc::to<std::string_view>(config.getPlatform());
if (platform == "aarch64-darwin") {
// Unset kern.curproc_arch_affinity so we can escape Rosetta
int affinity = 0;
sysctlbyname("kern.curproc_arch_affinity", nullptr, nullptr, &affinity, sizeof(affinity));
cpu_type_t cpu = CPU_TYPE_ARM64;
posix_spawnattr_setbinpref_np(&attrp, 1, &cpu, nullptr);
} else if (platform == "x86_64-darwin") {
cpu_type_t cpu = CPU_TYPE_X86_64;
posix_spawnattr_setbinpref_np(&attrp, 1, &cpu, nullptr);
}
ExecRequest req{request};
posix_spawn(nullptr, req.builder.c_str(), nullptr, &attrp, req.args.data(), req.envs.data());
throw SysError(errno, std::format("running {}", req.builder));
}
}
-24
View File
@@ -1,24 +0,0 @@
#include "launch-builder.hh"
#include "lix/libstore/build/request.capnp.h"
#include <format>
#include <string>
#include <unistd.h>
namespace nix {
bool prepareChildSetup(build::Request::Reader config)
{
return true;
}
void finishChildSetup(build::Request::Reader config) {}
void execBuilder(build::Request::Reader config)
{
ExecRequest req{config};
execve(req.builder.data(), req.args.data(), req.envs.data());
throw SysError("running %s", req.builder);
}
}
-451
View File
@@ -1,451 +0,0 @@
#include "launch-builder.hh"
#include "lix/libstore/build/request.capnp.h"
#include "lix/libutil/rpc.hh"
#include <cassert>
#include <csignal>
#include <fcntl.h>
#include <filesystem>
#include <format>
#include <kj/io.h>
#include <net/if.h>
#include <netinet/in.h>
#include <set>
#include <stdexcept>
#include <string>
#include <string_view>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/personality.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <unistd.h>
#if HAVE_SECCOMP
#include <linux/filter.h>
#include <sys/syscall.h>
#include <seccomp.h>
#endif
namespace fs = std::filesystem;
namespace nix {
// TODO dedup with libutil
static void setPersonality(std::string_view system)
{
/* Change the personality to 32-bit if we're doing an
i686-linux build on an x86_64-linux machine. */
struct utsname utsbuf;
uname(&utsbuf);
if ((system == "i686-linux"
&& (std::string_view(SYSTEM) == "x86_64-linux"
|| (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "x86_64"))))
|| system == "armv7l-linux" || system == "armv6l-linux" || system == "armv5tel-linux")
{
if (personality(PER_LINUX32) == -1) {
throw SysError("cannot set 32-bit personality");
}
}
/* Disable address space randomization for improved
determinism. */
int cur = personality(0xffffffff);
if (cur != -1) {
personality(cur | ADDR_NO_RANDOMIZE);
}
}
bool pathExists(const fs::path & path)
{
return fs::exists(fs::symlink_status(path));
}
void bindPath(const fs::path & source, const fs::path & target, bool optional = false)
{
debug("bind mounting %1% to %2%", source, target);
auto bindMount = [&]() {
if (mount(source.c_str(), target.c_str(), "", MS_BIND | MS_REC, 0) == -1) {
throw SysError("bind mount from %1% to %2% failed", source, target);
}
};
auto st = fs::symlink_status(source);
if (st.type() == fs::file_type::not_found) {
if (optional) {
return;
} else {
throw SysError("getting attributes of path %1%", source);
}
}
if (st.type() == fs::file_type::directory) {
fs::create_directories(target);
bindMount();
} else if (st.type() == fs::file_type::symlink) {
// Symlinks can (apparently) not be bind-mounted, so just copy it
fs::create_directories(target.parent_path());
fs::copy_symlink(source, target);
} else {
fs::create_directories(target.parent_path());
if (kj::AutoCloseFd file{open(target.c_str(), O_RDWR | O_CREAT, 0644)}; file == nullptr) {
throw SysError("could not create %s", target);
}
bindMount();
}
}
bool prepareChildSetup(build::Request::Reader request)
{
auto config = request.getPlatform().getLinux();
// Set the NO_NEW_PRIVS prctl flag.
// This both makes loading seccomp filters work for unprivileged users,
// and is an additional security measure in its own right.
if (prctl(PR_SET_NO_NEW_PRIVS, 1L, 0L, 0L, 0L) == -1) {
throw SysError("PR_SET_NO_NEW_PRIVS failed");
}
#if HAVE_SECCOMP
if (config.hasSeccompFilters()) {
const auto seccompBPF = config.getSeccompFilters();
const auto entries = seccompBPF.size() / sizeof(struct sock_filter);
assert(entries <= std::numeric_limits<unsigned short>::max());
struct sock_fprog fprog = {
.len = static_cast<unsigned short>(entries),
// the kernel does not actually write to the filter, and doesn't care about alignment
.filter = const_cast<struct sock_filter *>(
reinterpret_cast<const struct sock_filter *>(seccompBPF.begin())
),
};
if (syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER, 0, &fprog) != 0) {
throw SysError("unable to load seccomp BPF program");
}
}
#endif
KJ_DEFER(setPersonality(rpc::to<std::string_view>(config.getPlatform())));
if (!config.hasSandbox()) {
return true;
}
auto sandbox = config.getSandbox();
// NOLINTBEGIN(lix-unsafe-c-calls): we trust the parent that all sandbox config is correct.
// no strings in the linux sandbox config can be set by normal users or derivation authors,
// except (in single-user instances) storeDir and chrootRootDir, which must be valid paths.
//
// NOLINTBEGIN(lix-foreign-exceptions): they're all properly caught by the builder main fn.
const fs::path chrootRootDir{rpc::to<std::string_view>(sandbox.getChrootRootDir())};
if (sandbox.getPrivateNetwork()) {
/* Initialise the loopback interface. */
kj::AutoCloseFd fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP));
if (fd == nullptr) {
throw SysError("cannot open IP socket");
}
struct ifreq ifr;
strcpy(ifr.ifr_name, "lo");
ifr.ifr_flags = IFF_UP | IFF_LOOPBACK | IFF_RUNNING;
if (ioctl(fd.get(), SIOCSIFFLAGS, &ifr) == -1) {
throw SysError("cannot set loopback interface flags");
}
}
/* Set the hostname etc. to fixed values. */
char hostname[] = "localhost";
if (sethostname(hostname, sizeof(hostname)) == -1) {
throw SysError("cannot set host name");
}
char domainname[] = "(none)"; // kernel default
if (setdomainname(domainname, sizeof(domainname)) == -1) {
throw SysError("cannot set domain name");
}
/* Make all filesystems private. This is necessary
because subtrees may have been mounted as "shared"
(MS_SHARED). (Systemd does this, for instance.) Even
though we have a private mount namespace, mounting
filesystems on top of a shared subtree still propagates
outside of the namespace. Making a subtree private is
local to the namespace, though, so setting MS_PRIVATE
does not affect the outside world. */
const fs::path storeDir{rpc::to<std::string>(sandbox.getStoreDir())};
const auto chrootStoreDir = chrootRootDir / storeDir.relative_path();
if (mount(0, "/", 0, MS_PRIVATE | MS_REC, 0) == -1) {
throw SysError("unable to make '/' private");
}
/* Bind-mount chroot directory to itself, to treat it as a
different filesystem from /, as needed for pivot_root. */
if (mount(chrootRootDir.c_str(), chrootRootDir.c_str(), "", MS_BIND, 0) == -1) {
throw SysError("unable to bind mount %1%", chrootRootDir);
}
/* Bind-mount the sandbox's Nix store onto itself so that
we can mark it as a "shared" subtree, allowing bind
mounts made in *this* mount namespace to be propagated
into the child namespace created by the
unshare(CLONE_NEWNS) call below.
Marking chrootRootDir as MS_SHARED causes pivot_root()
to fail with EINVAL. Don't know why. */
if (mount(chrootStoreDir.c_str(), chrootStoreDir.c_str(), "", MS_BIND, 0) == -1) {
throw SysError("unable to bind mount the Nix store");
}
if (mount("", chrootStoreDir.c_str(), "", MS_SHARED, 0) == -1) {
throw SysError("unable to make %s shared", chrootStoreDir);
}
bool devMounted = false;
bool devPtsMounted = false;
/* Bind-mount all the directories from the "host"
filesystem that we want in the chroot
environment. */
for (auto path : sandbox.getPaths()) {
const fs::path source{rpc::to<std::string_view>(path.getSource())};
const fs::path target{rpc::to<std::string_view>(path.getTarget())};
devMounted |= target == "/dev";
devPtsMounted |= target == "/dev/pts";
if (source == "/proc") {
continue; // backwards compatibility
}
#if HAVE_EMBEDDED_SANDBOX_SHELL
if (source == "__embedded_sandbox_shell__") {
static unsigned char sh[] = {
#include "embedded-sandbox-shell.gen.hh"
};
const fs::path dst = chrootRootDir / target.relative_path();
fs::create_directories(dst.parent_path());
kj::AutoCloseFd fd(open(dst.c_str(), O_RDWR | O_CREAT, 0755));
if (fd == nullptr) {
throw SysError("cannot create sandbox shell");
}
writeFull(fd.get(), std::string_view((const char *) sh, sizeof(sh)));
fs::permissions(dst, fs::perms(0555));
} else
#endif
bindPath(source, chrootRootDir / target.relative_path(), path.getOptional());
}
/* Set up a nearly empty /dev, unless the user asked to
bind-mount the host /dev. */
if (!devMounted) {
const auto bind = [&](fs::path item) { bindPath(item, chrootRootDir / item.relative_path()); };
fs::create_directories(chrootRootDir / "dev/shm");
fs::create_directories(chrootRootDir / "dev/pts");
bind("/dev/full");
if (sandbox.getWantsKvm() && pathExists("/dev/kvm")) {
bind("/dev/kvm");
}
bind("/dev/null");
bind("/dev/random");
bind("/dev/tty");
bind("/dev/urandom");
bind("/dev/zero");
fs::create_symlink("/proc/self/fd", chrootRootDir / "dev/fd");
fs::create_symlink("/proc/self/fd/0", chrootRootDir / "dev/stdin");
fs::create_symlink("/proc/self/fd/1", chrootRootDir / "dev/stdout");
fs::create_symlink("/proc/self/fd/2", chrootRootDir / "dev/stderr");
}
/* Bind a new instance of procfs on /proc. */
fs::create_directories(chrootRootDir / "proc");
if (mount("none", (chrootRootDir / "proc").c_str(), "proc", 0, 0) == -1) {
throw SysError("mounting /proc");
}
/* Mount sysfs on /sys. */
if (request.hasCredentials() && request.getCredentials().getUidCount() != 1) {
fs::create_directories(chrootRootDir / "sys");
if (mount("none", (chrootRootDir / "sys").c_str(), "sysfs", 0, 0) == -1) {
throw SysError("mounting /sys");
}
}
/* Mount a new tmpfs on /dev/shm to ensure that whatever
the builder puts in /dev/shm is cleaned up automatically. */
if (pathExists("/dev/shm")
&& mount("none", (chrootRootDir / "dev/shm").c_str(), "tmpfs", 0, sandbox.getSandboxShmFlags().cStr())
== -1)
{
throw SysError("mounting /dev/shm");
}
/* Mount a new devpts on /dev/pts. Note that this
requires the kernel to be compiled with
CONFIG_DEVPTS_MULTIPLE_INSTANCES=y (which is the case
if /dev/ptx/ptmx exists). */
if (pathExists("/dev/pts/ptmx") && !pathExists(chrootRootDir / "dev/ptmx") && !devPtsMounted) {
if (mount("none", (chrootRootDir / "dev/pts").c_str(), "devpts", 0, "newinstance,mode=0620") == 0) {
fs::create_symlink("/dev/pts/ptmx", chrootRootDir / "dev/ptmx");
/* Make sure /dev/pts/ptmx is world-writable. With some
Linux versions, it is created with permissions 0. */
fs::permissions(chrootRootDir / "dev/pts/ptmx", fs::perms(0666));
} else {
if (errno != EINVAL) {
throw SysError("mounting /dev/pts");
}
bindPath("/dev/pts", chrootRootDir / "dev/pts");
bindPath("/dev/ptmx", chrootRootDir / "dev/ptmx");
}
}
/* Make /etc unwritable */
if (!sandbox.getUseUidRange()) {
fs::permissions(chrootRootDir / "etc", fs::perms(0555));
}
/* The comment below is now outdated. Recursive Nix has been removed.
* So there's no need to make path appear in the sandbox.
* TODO(Raito): cleanup before a merge.
*/
/* Unshare this mount namespace. This is necessary because
pivot_root() below changes the root of the mount
namespace. This means that the call to setns() in
addDependency() would hide the host's filesystem,
making it impossible to bind-mount paths from the host
Nix store into the sandbox. Therefore, we save the
pre-pivot_root namespace in
sandboxMountNamespace. Since we made /nix/store a
shared subtree above, this allows addDependency() to
make paths appear in the sandbox. */
if (unshare(CLONE_NEWNS) == -1) {
throw SysError("unsharing mount namespace");
}
/* Creating a new cgroup namespace is independent of whether we enabled the cgroup experimental feature.
* We always create a new cgroup namespace from a sandboxing perspective. */
/* Unshare the cgroup namespace. This means
/proc/self/cgroup will show the child's cgroup as '/'
rather than whatever it is in the parent. */
if (unshare(CLONE_NEWCGROUP) == -1) {
throw SysError("unsharing cgroup namespace");
}
/* Do the chroot(). */
if (chdir(chrootRootDir.c_str()) == -1) {
throw SysError("cannot change directory to %1%", chrootRootDir);
}
if (mkdir("real-root", 0) == -1) {
throw SysError("cannot create real-root directory");
}
if (syscall(SYS_pivot_root, ".", "real-root") == -1) {
throw SysError("cannot pivot old root directory onto %1%", chrootRootDir / "real-root");
}
if (chroot(".") == -1) {
throw SysError("cannot change root directory to %1%", chrootRootDir);
}
if (umount2("real-root", MNT_DETACH) == -1) {
throw SysError("cannot unmount real root filesystem");
}
if (rmdir("real-root") == -1) {
throw SysError("cannot remove real-root directory");
}
/* Switch to the sandbox uid/gid in the user namespace,
which corresponds to the build user or calling user in
the parent namespace. */
if (setgid(sandbox.getGid()) == -1) {
throw SysError("setgid failed");
}
if (setuid(sandbox.getUid()) == -1) {
throw SysError("setuid failed");
}
if (sandbox.hasWaitForInterface()) {
// wait for the pasta interface to appear. pasta can't signal us when
// it's done setting up the namespace, so we have to wait for a while
kj::AutoCloseFd fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP));
if (fd == nullptr) {
throw SysError("cannot open IP socket");
}
struct ifreq ifr;
strncpy(ifr.ifr_name, sandbox.getWaitForInterface().cStr(), sizeof(ifr.ifr_name));
// wait two minutes for the interface to appear. if it does not do so
// we are either grossly overloaded, or pasta startup failed somehow.
static constexpr int SINGLE_WAIT_US = 1000;
static constexpr int TOTAL_WAIT_US = 120'000'000;
for (unsigned tries = 0;; tries++) {
if (tries > TOTAL_WAIT_US / SINGLE_WAIT_US) {
throw std::runtime_error(
"sandbox network setup timed out, please check daemon logs for possible error output."
);
} else if (ioctl(fd.get(), SIOCGIFFLAGS, &ifr) == 0) {
if ((ifr.ifr_ifru.ifru_flags & IFF_UP) != 0) {
break;
}
} else if (errno == ENODEV) {
usleep(SINGLE_WAIT_US);
} else {
throw SysError("cannot get loopback interface flags");
}
}
}
// NOLINTEND(lix-foreign-exceptions)
// NOLINTEND(lix-unsafe-c-calls)
return false;
}
void finishChildSetup(build::Request::Reader request)
{
// clear all capabilities when not running as root in the sandbox.
// we always clear ambient capabilities because they survive exec.
if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0L, 0L, 0L) == -1) {
throw SysError("clearing ambient caps");
}
if (!request.getPlatform().getLinux().getSandbox().getUseUidRange()) {
static constexpr uint32_t LINUX_CAPABILITY_VERSION_3 = 0x20080522;
static constexpr uint32_t LINUX_CAPABILITY_U32S_3 = 2;
struct user_cap_header_struct
{
uint32_t version;
int pid;
} hdr = {LINUX_CAPABILITY_VERSION_3, 0};
struct user_cap_data_struct
{
uint32_t effective;
uint32_t permitted;
uint32_t inheritable;
} data[LINUX_CAPABILITY_U32S_3] = {};
if (syscall(SYS_capset, &hdr, data)) {
throw SysError("couldn't set capabilities");
}
}
if (prctl(PR_SET_PDEATHSIG, SIGKILL) == -1) {
throw SysError("setting death signal");
}
if (getppid() != request.getPlatform().getLinux().getParentPid()) {
raise(SIGKILL);
}
}
[[noreturn]]
void execBuilder(build::Request::Reader request)
{
ExecRequest req{request};
execve(req.builder.data(), req.args.data(), req.envs.data());
throw SysError("running %s", req.builder);
}
}
-225
View File
@@ -1,225 +0,0 @@
#include "launch-builder.hh"
#include "lix/libstore/build/request.capnp.h"
#include "lix/libutil/rpc.hh"
#include <capnp/message.h>
#include <capnp/serialize.h>
#include <csignal>
#include <cstdint>
#include <exception>
#include <fcntl.h>
#include <filesystem>
#include <grp.h>
#include <limits>
#include <sys/resource.h>
#include <unistd.h>
#include <vector>
namespace nix {
bool printDebugLogs = false;
static void requireCString(const char * context, const std::string & s)
{
if (s.contains('\0')) {
std::string p{s};
for (auto pos = p.find('\0'); pos != p.npos; pos = p.find('\0')) {
p.replace(pos, 1, "");
}
// NOLINTNEXTLINE(lix-foreign-exceptions)
throw std::runtime_error(std::format("derivation {} {} contains NUL bytes", context, p));
}
}
ExecRequest::ExecRequest(build::Request::Reader request)
{
const auto fill = [](auto context, auto & strings, auto & pointers, auto from) {
strings.reserve(from.size());
for (auto arg : from) {
strings.push_back(rpc::to<std::string>(arg));
requireCString(context, strings.back());
pointers.push_back(strings.back().data());
}
pointers.push_back(nullptr);
};
builder = rpc::to<std::string>(request.getBuilder());
requireCString("derivation builder", builder);
fill("derivation argument", argsStorage, args, request.getArgs());
fill("derivation environment entry", envsStorage, envs, request.getEnvironment());
}
void writeFull(int fd, std::string_view data)
{
while (!data.empty()) {
const auto wrote = ::write(fd, data.data(), data.size());
if (wrote < 0) {
throw SysError("write()");
} else {
data.remove_prefix(size_t(wrote));
}
}
}
static void closeExtraFDs()
{
constexpr int MAX_KEPT_FD = 2;
static_assert(std::max({STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}) == MAX_KEPT_FD);
// Both Linux and FreeBSD support close_range.
#if __linux__ || __FreeBSD__
auto closeRange = [](unsigned int first, unsigned int last, int flags) -> int {
// musl does not have close_range as of 2024-08-10
// patch: https://www.openwall.com/lists/musl/2024/08/01/9
#if HAVE_CLOSE_RANGE
return close_range(first, last, flags);
#else
return syscall(SYS_close_range, first, last, flags);
#endif
};
// first try to close_range everything we don't care about. if this
// returns an error with these parameters we're running on a kernel
// that does not implement close_range (i.e. pre 5.9) and fall back
// to the old method. we should remove that though, in some future.
if (closeRange(3, ~0U, 0) == 0) {
return;
}
#endif
#if __linux__
try {
for (auto & s : std::filesystem::directory_iterator("/proc/self/fd")) {
auto fd = std::stoi(s.path().filename().c_str());
if (fd > MAX_KEPT_FD) {
debug("closing leaked FD %d", fd);
close(fd);
}
}
return;
} catch (std::exception &) { // NOLINT(lix-foreign-exceptions): that's what std::filesystem throws
}
#endif
int maxFD = 0;
maxFD = sysconf(_SC_OPEN_MAX);
for (int fd = MAX_KEPT_FD + 1; fd < maxFD; ++fd) {
close(fd); /* ignore result */
}
}
}
int main(int argc, char * argv[])
{
using namespace nix;
if (argc < 1) {
return 255;
}
bool sendException = true;
try {
capnp::MallocMessageBuilder buf;
capnp::readMessageCopyFromFd(
STDIN_FILENO, buf, {.traversalLimitInWords = std::numeric_limits<uint64_t>::max()}
);
auto request = buf.getRoot<build::Request>().asReader();
printDebugLogs = request.getDebug();
{
sigset_t set;
sigemptyset(&set);
if (sigprocmask(SIG_SETMASK, &set, nullptr)) {
throw SysError("failed to unmask signals");
}
}
/* Put the child in a separate session (and thus a separate
process group) so that it has no controlling terminal (meaning
that e.g. ssh cannot open /dev/tty) and it doesn't receive
terminal signals. */
if (setsid() == -1) {
throw SysError("creating a new session");
}
/* Dup stderr to stdout. */
if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1) {
throw SysError("cannot dup stderr into stdout");
}
const bool setUser = prepareChildSetup(request);
// NOLINTNEXTLINE(lix-unsafe-c-calls): we trust the parent here
if (chdir(rpc::to<std::string>(request.getWorkingDir()).c_str()) == -1) {
throw SysError("changing into %s", rpc::to<std::string>(request.getWorkingDir()));
}
/* Disable core dumps by default. */
struct rlimit limit = {0, RLIM_INFINITY};
if (request.getEnableCoreDumps()) {
limit.rlim_cur = RLIM_INFINITY;
}
setrlimit(RLIMIT_CORE, &limit);
// FIXME: set other limits to deterministic values?
/* If we are running in `build-users' mode, then switch to the
user we allocated above. Make sure that we drop all root
privileges. Note that above we have closed all file
descriptors except std*, so that's safe. Also note that
setuid() when run as root sets the real, effective and
saved UIDs. */
if (setUser && request.hasCredentials()) {
auto creds = request.getCredentials();
/* Preserve supplementary groups of the build user, to allow
admins to specify groups such as "kvm". */
std::vector<gid_t> gids;
std::copy(
creds.getSupplementaryGroups().begin(),
creds.getSupplementaryGroups().end(),
std::back_inserter(gids)
);
if (setgroups(gids.size(), gids.data()) == -1) {
throw SysError("cannot set supplementary groups of build user");
}
if (setgid(creds.getGid()) == -1 || getgid() != creds.getGid() || getegid() != creds.getGid()) {
throw SysError("setgid failed");
}
if (setuid(creds.getUid()) == -1 || getuid() != creds.getUid() || geteuid() != creds.getUid()) {
throw SysError("setuid failed");
}
}
finishChildSetup(request);
/* Close all other file descriptors. */
closeExtraFDs();
// Reroute stdin to /dev/null. closing the setup socket fd also signals
// successful setup of the builder, all other errors must go to stderr.
kj::AutoCloseFd fdDevNull{open("/dev/null", O_RDWR | O_CLOEXEC)};
if (fdDevNull == nullptr) {
throw SysError("cannot open /dev/null");
}
if (dup2(fdDevNull.get(), STDIN_FILENO) == -1) {
throw SysError("cannot dup null device into stdin");
}
sendException = false;
execBuilder(request);
} catch (std::exception & e) { // NOLINT(lix-foreign-exceptions)
if (sendException) {
capnp::MallocMessageBuilder builder;
auto error = builder.getRoot<build::SetupResponse>();
RPC_FILL(error, setFatalError, e.what());
capnp::writeMessageToFd(STDIN_FILENO, builder);
} else {
writeFull(STDERR_FILENO, e.what());
}
return 1;
}
}
-79
View File
@@ -1,79 +0,0 @@
#pragma once
///@file
#include "lix/libstore/build/request.capnp.h"
#include "lix/libutil/rpc.hh"
#include <boost/format.hpp>
#include <capnp/message.h>
#include <capnp/serialize.h>
#include <cstring>
#include <exception>
#include <memory>
#include <string>
#include <string_view>
#include <unistd.h>
#include <vector>
namespace nix {
bool prepareChildSetup(nix::build::Request::Reader request);
void finishChildSetup(nix::build::Request::Reader request);
[[noreturn]]
void execBuilder(nix::build::Request::Reader request);
// silence the foreign exception lint for this helper
class BaseException : public std::exception
{};
class SysError : public BaseException
{
private:
std::shared_ptr<std::string> msg;
public:
explicit SysError(auto fmt, const auto &... args) : SysError(errno, fmt, args...) {}
SysError(int error, auto fmt, const auto &... args)
{
const auto errstr = strerror(error);
auto format = boost::format(fmt);
((format % args), ...);
msg = std::make_shared<std::string>(format.str() + ": " + errstr);
}
const char * what() const noexcept override
{
return msg->c_str();
}
};
struct ExecRequest
{
std::string builder;
std::vector<std::string> argsStorage, envsStorage;
std::vector<char *> args, envs;
ExecRequest(nix::build::Request::Reader request);
};
void writeFull(int fd, std::string_view data);
extern bool printDebugLogs;
inline void printDebugLog(auto fmt, const auto &... args)
{
auto format = boost::format(fmt);
((format % args), ...);
capnp::MallocMessageBuilder builder;
auto log = builder.getRoot<build::SetupResponse>();
RPC_FILL(log, setLogLine, format.str());
capnp::writeMessageToFd(STDIN_FILENO, builder);
}
#define debug(msg, ...) \
do { \
if (::nix::printDebugLogs) { \
printDebugLog(msg, __VA_ARGS__); \
} \
} while (0)
}
-66
View File
@@ -1,66 +0,0 @@
if is_linux
check_namespace_support = executable(
'check-namespace-support',
files('check-namespace-support.cc'),
install : true,
install_dir : libexecdir / 'lix',
)
endif
kill_user = executable(
'kill-user',
files('kill-user.cc'),
install : true,
install_dir : libexecdir / 'lix',
)
if is_linux
launch_builder_impl = 'linux'
elif is_darwin
launch_builder_impl = 'darwin'
else
launch_builder_impl = 'fallback'
endif
launch_builder = executable(
'launch-builder',
files(
'launch-builder.cc',
f'launch-builder-@launch_builder_impl@.cc',
),
liblix_generated_headers,
include_directories : [ '../..' ],
dependencies : [
capnp,
],
install : true,
install_dir : libexecdir / 'lix',
)
run_build_hook = executable(
'run-build-hook',
files('run-build-hook.cc'),
install : true,
install_dir : libexecdir / 'lix',
)
run_diff_hook = executable(
'run-diff-hook',
files('run-diff-hook.cc'),
install : true,
install_dir : libexecdir / 'lix',
)
run_pager = executable(
'run-pager',
files('run-pager.cc'),
install : true,
install_dir : libexecdir / 'lix',
)
unix_bind_connect = executable(
'unix-bind-connect',
files('unix-bind-connect.cc'),
install : true,
install_dir : libexecdir / 'lix',
)
-17
View File
@@ -1,17 +0,0 @@
#include "common.hh"
#include <unistd.h>
LIBEXEC_HELPER(2)
int helperMain(const char * name, std::span<char *> args) noexcept
{
DIE_UNLESS_SYS("chdir", chdir("/"));
DIE_UNLESS_SYS("setsid", setsid());
static_assert(STDIN_FILENO == 0);
DIE_UNLESS_SYS("close(stdin)", close(STDIN_FILENO));
DIE_UNLESS_SYS("stdin = open(/dev/null)", open("/dev/null", O_RDWR));
execv(args[0], args.subspan(1).data());
die("exec failed");
}
-27
View File
@@ -1,27 +0,0 @@
#include "common.hh"
#include <grp.h>
using std::literals::operator""sv;
LIBEXEC_HELPER(3)
int helperMain(const char * name, std::span<char *> args) noexcept
{
const auto uid = args[0];
const auto gid = args[1];
const auto hook = args.subspan(2);
DIE_UNLESS_SYS("chdir", chdir("/"));
if (gid != "-"sv) {
DIE_UNLESS_SYS("setgid", setgid(argToInt<gid_t>("gid", gid)));
/* Drop all other groups if we're setgid. */
DIE_UNLESS_SYS("setgroups", setgroups(0, 0));
}
if (uid != "-"sv) {
DIE_UNLESS_SYS("setuid", setuid(argToInt<uid_t>("uid", uid)));
}
execvp(hook[0], hook.data());
die("exec failed");
}
-19
View File
@@ -1,19 +0,0 @@
#include "common.hh"
LIBEXEC_HELPER(0)
int helperMain(const char * name, std::span<char *> args) noexcept
{
auto pager = args.empty() ? nullptr : args[0];
if (!getenv("LESS")) {
setenv("LESS", "FRSXMK", 1);
}
if (pager) {
execl("/bin/sh", "sh", "-c", pager, nullptr);
}
execlp("pager", "pager", nullptr);
execlp("less", "less", nullptr);
execlp("more", "more", nullptr);
die("could not find a pager to run, please set PAGER or NIX_PAGER");
}
-34
View File
@@ -1,34 +0,0 @@
#include "common.hh"
#include <sys/socket.h>
#include <sys/un.h>
LIBEXEC_HELPER(4)
int helperMain(const char *, std::span<char *> args) noexcept
{
int socket = argToInt<int>("socket", args[0]);
std::string_view method = args[1];
const auto dir = args[2];
const auto name = args[3];
DIE_UNLESS_SYS("chdir", chdir(dir));
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
if (auto nameLen = strlen(name); nameLen + 1 >= sizeof(addr.sun_path)) {
die(std::format("socket path {}/{} is too long", dir, name));
} else {
memcpy(addr.sun_path, name, nameLen + 1);
}
if (method == "bind") {
DIE_UNLESS_SYS("bind", bind(socket, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)));
} else if (method == "connect") {
DIE_UNLESS_SYS("connect", connect(socket, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)));
} else {
die(std::format("invalid method %s", method));
}
return 0;
}
+16 -31
View File
@@ -7,7 +7,8 @@
namespace nix {
std::vector<std::string> parseAttrPath(std::string_view const s, bool allowRhsTrailingDot)
std::vector<std::string> parseAttrPath(std::string_view const s)
{
std::vector<std::string> res;
std::string cur;
@@ -15,22 +16,6 @@ std::vector<std::string> parseAttrPath(std::string_view const s, bool allowRhsTr
auto i = s.begin();
while (i != s.end()) {
if (*i == '.') {
if (!haveData) {
if (res.empty()) {
throw ParseError(
"Leading dot in attribute selection path '%1%' is not allowed! If the attribute name "
"is an empty string, use '\"\".foo.bar'",
s
);
} else {
throw ParseError(
"consecutive dots not allowed in selection path '%1%', use 'foo.\"\".bar' to denote "
"an "
"empty attribute name",
s
);
}
}
res.push_back(cur);
haveData = false;
cur.clear();
@@ -51,11 +36,7 @@ std::vector<std::string> parseAttrPath(std::string_view const s, bool allowRhsTr
}
++i;
}
if (haveData) {
res.push_back(cur);
} else if (!allowRhsTrailingDot) {
throw ParseError("Trailing dot on the right-hand side of path expr '%1%' is not allowed!", s);
};
if (haveData) res.push_back(cur);
return res;
}
@@ -102,13 +83,18 @@ findAlongAttrPath(EvalState & state, const std::string & attrPath, Bindings & au
auto attrIndex = string2Int<unsigned int>(attr);
/* Evaluate the expression. */
v = state.autoCallFunction(autoArgs, v, pos);
Value vNew;
state.autoCallFunction(autoArgs, v, vNew, pos);
v = vNew;
state.forceValue(v, noPos);
/* It should evaluate to either a set or an expression,
according to what is specified in the attrPath. */
if (!attrIndex) {
if (attr.empty())
throw Error("empty attribute name in selection path '%1%'", attrPath);
if (v.type() != nAttrs) {
auto pathPart =
std::vector<std::string>(tokens.begin(), tokens.begin() + attrPathIdx);
@@ -179,14 +165,13 @@ findAlongAttrPath(EvalState & state, const std::string & attrPath, Bindings & au
std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what)
{
Value v2 = [&]() {
try {
auto dummyArgs = state.ctx.mem.allocBindings(0);
return findAlongAttrPath(state, "meta.position", *dummyArgs, v).first;
} catch (Error &) {
throw NoPositionInfo("package '%s' has no source location information", what);
}
}();
Value v2;
try {
auto dummyArgs = state.ctx.mem.allocBindings(0);
v2 = findAlongAttrPath(state, "meta.position", *dummyArgs, v).first;
} catch (Error &) {
throw NoPositionInfo("package '%s' has no source location information", what);
}
// FIXME: is it possible to extract the Pos object instead of doing this
// toString + parsing?
+1 -1
View File
@@ -25,7 +25,7 @@ std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v
* Such an attr path is a dot-separated sequence of attribute names, which are possibly quoted.
* No escaping is performed; attribute names containing double quotes are unrepresentable.
*/
std::vector<std::string> parseAttrPath(std::string_view const s, bool allowRhsTrailingDot = true);
std::vector<std::string> parseAttrPath(std::string_view const s);
/**
* Converts an attr path from a list of strings into a string once more.
+20 -2
View File
@@ -23,13 +23,31 @@ Bindings * EvalMemory::allocBindings(size_t capacity)
return new (allocBytes(sizeof(Bindings) + sizeof(Attr) * capacity)) Bindings();
}
void BindingsBuilder::insert(std::string_view name, Value value, PosIdx pos)
Value & BindingsBuilder::alloc(Symbol name, PosIdx pos)
{
return insert(symbols.create(name), value, pos);
bindings->push_back(Attr(name, {}, pos));
return (bindings->end() - 1)->value;
}
Value & BindingsBuilder::alloc(std::string_view name, PosIdx pos)
{
return alloc(symbols.create(name), pos);
}
void Bindings::sort()
{
if (size_) std::sort(begin(), end());
}
Value & Value::mkAttrs(BindingsBuilder & bindings)
{
mkAttrs(bindings.finish());
return *this;
}
}
+10 -8
View File
@@ -25,8 +25,7 @@ struct Attr
PosIdx pos;
mutable Value value;
Attr(Symbol name, Value value, PosIdx pos = noPos) : name(name), pos(pos), value(value) {}
[[deprecated]]
Attr() {};
Attr() { };
bool operator < (const Attr & a) const
{
return name < a.name;
@@ -73,9 +72,8 @@ public:
const Attr * get(Symbol name)
{
iterator i = std::lower_bound(begin(), end(), name, [](const Attr & value, const Symbol & compare) {
return value.name < compare;
});
Attr key(name, {});
iterator i = std::lower_bound(begin(), end(), key);
if (i != end() && i->name == name) return &*i;
return nullptr;
}
@@ -123,6 +121,7 @@ public:
private:
Bindings * bindings;
EvalMemory & mem;
SymbolTable & symbols;
Size capacity;
@@ -130,8 +129,9 @@ public:
// needed by std::back_inserter
using value_type = Attr;
BindingsBuilder(SymbolTable & symbols, Bindings * bindings, Size capacity)
BindingsBuilder(EvalMemory & mem, SymbolTable & symbols, Bindings * bindings, Size capacity)
: bindings(bindings)
, mem(mem)
, symbols(symbols)
, capacity(capacity)
{
@@ -142,8 +142,6 @@ public:
insert(Attr(name, value, pos));
}
void insert(std::string_view name, Value value, PosIdx pos = noPos);
void insert(const Attr & attr)
{
push_back(attr);
@@ -155,6 +153,10 @@ public:
bindings->push_back(attr);
}
Value & alloc(Symbol name, PosIdx pos = noPos);
Value & alloc(std::string_view name, PosIdx pos = noPos);
[[nodiscard("must use created bindings")]]
Bindings * finish()
{
+1 -2
View File
@@ -10,8 +10,7 @@ present in *args*. All are optional except `path`:
- name\
The name of the path when added to the store. This can used to
reference paths that have
[nix-illegal characters in their names](./derivations.md),
reference paths that have nix-illegal characters in their names,
like `@`.
- filter\
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: scopedImport
implementation: "[](EvalState & state, Value ** args) -> Value { return import(state, *args[1], args[0]); }"
implementation: "[](EvalState & state, Value ** args, Value & v) { import(state, *args[1], args[0], v); }"
args: [scope, path]
renameInGlobalScope: false
---

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