Compare commits

..
Author SHA1 Message Date
Raito Bezarius 5e9551e662 gosh
Change-Id: I92dba73610fbdc9fc67ba6590e828a7bed869d28
Signed-off-by: Raito Bezarius <raito@lix.systems>
2025-07-06 16:24:40 +02:00
730 changed files with 8452 additions and 14753 deletions
-2
View File
@@ -20,8 +20,6 @@ Checks:
- -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
- -bugprone-exception-escape
# all thrown exceptions must derive from std::exception
- hicpp-exception-baseclass
# capturing async lambdas are dangerous
Generated
+3 -3
View File
@@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
version = 3
[[package]]
name = "countme"
@@ -47,9 +47,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "rnix"
version = "0.12.0"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f15e00b0ab43abd70d50b6f8cd021290028f9b7fdd7cdfa6c35997173bc1ba9"
checksum = "bb35cedbeb70e0ccabef2a31bcff0aebd114f19566086300b8f42c725fc2cb5f"
dependencies = [
"rowan",
]
-26
View File
@@ -1,26 +0,0 @@
# Docs
per-file README.md=*
per-file CONTRIBUTING.md=*
# DevX
per-file justfile=*
per-file .envrc=*
per-file .gitignore=*
per-file .github=*
per-file .mailmap=*
# Build
per-file meson.build=*
per-file meson.options=*
per-file flake.nix=*
per-file flake.lock=*
per-file *.nix=*
per-file Cargo.lock=*
per-file Cargo.toml=*
per-file version.json=*
# Code style
per-file .clang-tidy=*
per-file .clang-format=*
per-file .editorconfig=*
per-file treefmt.toml=*
-1
View File
@@ -1 +0,0 @@
*
+36 -157
View File
@@ -7,44 +7,15 @@ import os
import json
import tempfile
import platform
import shlex
import textwrap
import dataclasses
flake_args = ["--extra-experimental-features", "nix-command flakes"]
flake_args = ["--extra-experimental-features","'nix-command flakes'"]
# hyperfine has its own variable substitution, so we use that and pass build="{BUILD}" here.
# perf doesn't have variable substitution, so we call these with build being the actual build directory.
cases = {
"search": lambda build: [
f"{build}/bin/nix",
*flake_args,
"search",
"--no-eval-cache",
"github:nixos/nixpkgs/e1fa12d4f6c6fe19ccb59cac54b5b3f25e160870",
"hello",
],
"rebuild": lambda build: [
f"{build}/bin/nix",
*flake_args,
"eval",
"--raw",
"--impure",
"--expr",
textwrap.dedent("""
(import <nixpkgs/nixos> {
configuration = ./bench/nixpkgs/nixos/modules/installer/cd-dvd/installation-cd-graphical-calamares-plasma6.nix;
}).config.system.build.toplevel
""").replace("\n", " "),
],
"rebuild_lh": lambda build: [
"GC_INITIAL_HEAP_SIZE=10g",
*cases['rebuild'](build),
],
"parse": lambda build: [
f"{build}/bin/nix",
*flake_args,
"eval",
"-f",
"bench/nixpkgs/pkgs/development/haskell-modules/hackage-packages.nix",
],
"search": lambda build: [f"{build}/bin/nix", *flake_args, "search", "--no-eval-cache", "github:nixos/nixpkgs/e1fa12d4f6c6fe19ccb59cac54b5b3f25e160870", "hello"],
"rebuild": lambda build: [f"{build}/bin/nix", *flake_args, "eval", "--raw", "--impure", "--expr", "'with import <nixpkgs/nixos> {}; system'"],
"rebuild_lh": lambda build: ["GC_INITIAL_HEAP_SIZE=10g", f"{build}/bin/nix", *flake_args, "eval", "--raw", "--impure", "--expr", "'with import <nixpkgs/nixos> {}; system'"],
"parse": lambda build: [f"{build}/bin/nix", *flake_args, "eval", "-f", "bench/nixpkgs/pkgs/development/haskell-modules/hackage-packages.nix"],
}
arg_parser = argparse.ArgumentParser()
@@ -53,81 +24,46 @@ arg_parser = argparse.ArgumentParser()
# mode, we would have to combine the JSON ourselves to support that, which
# would probably be better done by writing a benchmarking script in
# not-bash.
arg_parser.add_argument(
'builds',
nargs='+',
help="At least two build directories to compare, containing bin/nix",
)
arg_parser.add_argument(
'--cases',
type=str,
help="A comma-separated list of cases you want to run. Defaults to running all",
)
arg_parser.add_argument(
'--mode',
nargs='+',
choices=[ "walltime", "memory" ] + [ "icount" ] if platform.system() == 'Linux' else [], # perf doesn't run on Darwin
default=[ "walltime" ],
)
arg_parser.add_argument(
'--daemon',
action='store_true',
help='Run a temporary daemon for the benchmark instead of using a local store directly',
)
arg_parser.add_argument('builds', nargs='+', help="At least two build directories to compare, containing bin/nix")
arg_parser.add_argument('--cases', type=str, help="A comma-separated list of cases you want to run. Defaults to running all")
available_modes = [ "walltime" ] + [ "icount" ] if platform.system() == 'Linux' else [] # perf doesn't run on Darwin
arg_parser.add_argument('--mode', choices=available_modes, default="walltime")
args = arg_parser.parse_args()
if len(args.builds) < 1:
raise ValueError("need at least one build directory to benchmark")
if len(args.builds) < 2:
raise ValueError("need at least two build directories to compare")
benchmarks: list[str] = []
if args.cases is None:
benchmarks = list(cases.keys())
else:
for case in args.cases.split(","):
if case not in cases:
raise ValueError(f"no such case: {case}")
if case not in cases: raise ValueError(f"no such case: {case}")
benchmarks.append(case)
def make_full_command(build, case):
cmd = " ".join(map(shlex.quote, cases[case](build)))
if args.daemon:
return " ".join([
f"{build}/bin/nix --extra-experimental-features nix-command daemon &",
"trap 'kill %1' EXIT;",
f"NIX_REMOTE=daemon {cmd}",
])
else:
return cmd
def bench_walltime(env):
hyperfine_args = ["--parameter-list", "BUILD", ','.join(args.builds), "--warmup", "2", "--runs", "10"]
for case in benchmarks:
for build in args.builds:
subprocess.run([
"taskset", "-c", "2,3",
"chrt", "-f","50",
*[
"hyperfine", "--warmup", "2", "--runs", "10",
"--export-json", f"bench/bench-{case}-{build}.json",
"--export-markdown", f"bench/bench-{case}-{build}.md",
"--", make_full_command(build, case),
],
], env=env, check=True)
case_command = cases[case]("{BUILD}") # see the comment on cases
subprocess.run([
"taskset", "-c", "2,3",
"chrt", "-f","50",
"hyperfine", *hyperfine_args, "--export-json", f"bench/bench-{case}.json", "--export-markdown", f"bench/bench-{case}.md", "--", " ".join(case_command)
], env=env, check=True)
print("Benchmarks summary\n---\n")
for case in benchmarks:
results = []
for build in args.builds:
with open(f"bench/bench-{case}-{build}.json") as fd:
results.append(json.load(fd)["results"][0])
for result in results:
fd = open(f"bench/bench-{case}.json")
result_json = json.load(fd)
fd.close()
for result in result_json["results"]:
print(result["command"])
print("-" * min(80,len(result["command"])))
def attr_rounded(attr):
return f"{result[attr]:.3f}"
attr_rounded = lambda attr: f"{result[attr]:.3f}"
print(" mean: ", attr_rounded("mean"), "±", attr_rounded("stddev"))
print(" user:", attr_rounded("user"), "| system", attr_rounded("system"))
print(" median: ", attr_rounded("median"))
print(" range: ", attr_rounded("min") + "s.." + attr_rounded("max")+"s")
print(" relative:", f"{result["mean"]/results[0]["mean"]:.3f}")
print(" relative:", f"{result["mean"]/result_json["results"][0]["mean"]:.3f}")
print("\n")
@@ -135,14 +71,12 @@ def bench_icount(env):
perf_results_for: dict[str, list[tuple[str, float]]] = {}
for case in benchmarks:
for build in args.builds:
case_command = cases[case](build)
# the perf stat -j output (incorrectly) localizes numbers, which will trip up the json parser.
env["LC_ALL"]="C"
case_command = make_full_command(build, case)
commandline = [
"perf", "stat", "-o", f"bench/perf-{case}.json", "-j",
"sh", "-c", case_command,
"perf", "stat", "-o", f"bench/perf-{case}.json", "-j", "sh", "-c", " ".join(case_command)
]
print("running", case_command)
subprocess.run(commandline, env=env, check=True, stdout=subprocess.DEVNULL) # warmup run
subprocess.run(commandline, env=env, check=True, stdout=subprocess.DEVNULL)
perf_fd = open(f"bench/perf-{case}.json")
@@ -150,9 +84,8 @@ def bench_icount(env):
perf_fd.close()
instr = next(x for x in perf_data if x["event"] in ["instructions", "instructions:u"]) # an implementation of a find_first iterator
if case not in perf_results_for:
perf_results_for[case] = []
perf_results_for[case].append((case_command, float(instr["counter-value"])))
if case not in perf_results_for: perf_results_for[case] = []
perf_results_for[case].append((" ".join(case_command), float(instr["counter-value"])))
print("Benchmarks summary\n---\n")
for (case, entries) in perf_results_for.items():
@@ -164,54 +97,6 @@ def bench_icount(env):
print(" relative instructions:", int(instr)/perf_results_for[case][0][1])
print("\n")
@dataclasses.dataclass
class MemoryStatistics:
envBytes: int
listBytes: int
setBytes: int
valueBytes: int
heapBytes: int
heapSize: int
def bench_memory(env):
path = "bench/bench-memory.json"
env = env | {
'NIX_SHOW_STATS': '1',
'NIX_SHOW_STATS_PATH': path,
}
results: dict[str, list[tuple[str, MemoryStatistics]]] = {}
for case in benchmarks:
for build in args.builds:
case_command = make_full_command(build, case)
commandline = [ "sh", "-c", case_command ]
print("running", case_command)
subprocess.run(commandline, env=env, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
with open(path) as fd:
stats = json.load(fd)
results.setdefault(case, []).append((case_command, MemoryStatistics(
envBytes=stats['envs']['bytes'],
listBytes=stats['list']['bytes'],
setBytes=stats['sets']['bytes'],
valueBytes=stats['values']['bytes'],
heapSize=stats['gc']['heapSize'],
heapBytes=stats['gc']['totalBytes'],
)))
print("Benchmarks summary\n---\n")
for (case, entries) in results.items():
for cmd, stats in entries:
print(cmd)
print("-" * min(80, len(cmd)))
print(f" env bytes: {stats.envBytes :15d} | {(stats.envBytes / entries[0][1].envBytes) :.3f}x")
print(f" list bytes: {stats.listBytes :15d} | {(stats.listBytes / entries[0][1].listBytes) :.3f}x")
print(f" set bytes: {stats.setBytes :15d} | {(stats.setBytes / entries[0][1].setBytes) :.3f}x")
if not entries[0][1].valueBytes:
print(f" value bytes: {0:15d}")
else:
print(f" value bytes: {stats.valueBytes:15d} | {(stats.valueBytes / entries[0][1].valueBytes):.3f}x")
print(f" heap alloc'd: {stats.heapBytes :15d} | {(stats.heapBytes / entries[0][1].heapBytes) :.3f}x")
print(f" heap size: {stats.heapSize :15d} | {(stats.heapSize / entries[0][1].heapSize) :.3f}x")
print("\n")
with tempfile.TemporaryDirectory() as tmp_dir:
subprocess.run([
@@ -223,15 +108,9 @@ with tempfile.TemporaryDirectory() as tmp_dir:
subenv = os.environ.copy()
subenv["NIX_CONF_DIR"] = "/var/empty"
subenv["NIX_REMOTE"] = tmp_dir
subenv["NIX_PATH"] = ":".join([
"nixpkgs=bench/nixpkgs",
])
subenv["NIX_DAEMON_SOCKET_PATH"] = f"{tmp_dir}/daemon"
subenv["NIX_PATH"] = "nixpkgs=bench/nixpkgs:nixos-config=bench/configuration.nix"
for mode in args.mode:
if mode == "walltime":
bench_walltime(subenv)
elif mode == "memory":
bench_memory(subenv)
else:
bench_icount(subenv)
if args.mode == "walltime":
bench_walltime(subenv)
else:
bench_icount(subenv)
+314
View File
@@ -0,0 +1,314 @@
{
config,
pkgs,
lib,
...
}:
{
boot = {
initrd = {
availableKernelModules = [
"xhci_pci"
"ahci"
];
kernelModules = [ "dm-snapshot" ];
luks.devices = {
croot = {
device = "/dev/sdb";
allowDiscards = true;
};
};
};
kernelModules = [ "kvm-intel" ];
kernelPackages = pkgs.linuxPackages_latest;
loader = {
systemd-boot.enable = true;
efi.canTouchEfiVariables = true;
};
};
hardware = {
enableRedistributableFirmware = true;
cpu.intel.updateMicrocode = true;
graphics.enable32Bit = true;
graphics.extraPackages = with pkgs; [
vaapiIntel
intel-media-driver
intel-compute-runtime
];
};
fileSystems = {
"/" = {
device = "/dev/sda2";
fsType = "xfs";
options = [ "noatime" ];
};
"/boot" = {
device = "/dev/sda1";
fsType = "vfat";
};
"/nas" = {
device = "nas:/";
fsType = "nfs4";
options = [
"ro"
"x-systemd.automount"
];
};
};
swapDevices = [ { device = "/dev/swap"; } ];
networking = {
useDHCP = false;
hostName = "host";
wireless = {
enable = true;
interfaces = [ "eth1" ];
};
interfaces = {
eth0.useDHCP = true;
eth1.useDHCP = true;
};
wg-quick.interfaces = {
wg0 = {
address = [ "2001:db8::1" ];
privateKeyFile = "/etc/secrets/wg0.key";
peers = [
{
publicKey = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
endpoint = "[2001:db8::2]:61021";
allowedIPs = [ "2001::db8:1::/64" ];
}
];
};
};
firewall.allowedUDPPorts = [ 4567 ];
};
i18n = {
defaultLocale = "en_US.UTF-8";
inputMethod.enable = true;
inputMethod.type = "ibus";
};
services = {
libinput.enable = true;
xserver = {
enable = true;
xkb.layout = "us";
xkb.variant = "altgr-intl";
xkb.options = "ctrl:nocaps";
wacom.enable = true;
videoDrivers = [ "modesetting" ];
modules = [ pkgs.xf86_input_wacom ];
displayManager.sx.enable = true;
windowManager.i3.enable = true;
};
udev.extraHwdb = ''
# not like this mattered at all
# we're not running udev from here
'';
udev.extraRules = ''
# ACTION=="add", SUBSYSTEM=="input", ...
'';
};
programs = {
light.enable = true;
wireshark = {
enable = true;
package = pkgs.wireshark-qt;
};
gnupg.agent = {
enable = true;
};
};
fonts.packages = with pkgs; [
font-awesome
noto-fonts
noto-fonts-cjk-sans
noto-fonts-emoji
noto-fonts-extra
dejavu_fonts
powerline-fonts
source-code-pro
cantarell-fonts
];
users = {
mutableUsers = false;
users = {
user = {
isNormalUser = true;
group = "user";
extraGroups = [
"wheel"
"video"
"audio"
"dialout"
"users"
"kvm"
"wireshark"
];
password = "unimportant";
};
};
groups = {
user = { };
};
};
security = {
pam.loginLimits = [
{
domain = "@audio";
item = "memlock";
type = "-";
value = "unlimited";
}
{
domain = "@audio";
item = "rtprio";
type = "-";
value = "99";
}
{
domain = "@audio";
item = "nofile";
type = "soft";
value = "99999";
}
{
domain = "@audio";
item = "nofile";
type = "hard";
value = "99999";
}
];
sudo.extraRules = [
{
users = [ "user" ];
commands = [
{
command = "${pkgs.linuxPackages.cpupower}/bin/cpupower";
options = [ "NOPASSWD" ];
}
];
}
];
};
environment.systemPackages = with pkgs; [
a2jmidid
age
ardour
bemenu
blender
breeze-icons
breeze-qt5
bubblewrap
calf
claws-mail
darktable
duperemove
emacs
feh
file
firefox
fluidsynth
adwaita-icon-theme
gnuplot
graphviz
helm
i3status-rust
inkscape
jack2
jq
krita
ldns
libqalculate
libreoffice
man-pages
nix-diff
nix-index
nix-output-monitor
open-music-kontrollers.patchmatrix
pamixer
pavucontrol
pciutils
picom
pwgen
redshift
ripgrep
rlwrap
silver-searcher
soundfont-fluid
whois
wol
xclip
xdot
xdotool
xorg.xkbcomp
yt-dlp
zathura
borgbackup
linuxPackages.cpupower
mtr
kitty
xf86_input_wacom
];
environment.pathsToLink = [ "/share/soundfonts" ];
systemd.user.services.run-python = {
after = [ "network-online.target" ];
script = ''
exec ${pkgs.python3}/bin/python
'';
serviceConfig = {
CapabilityBoundingSet = [ "" ];
KeyringMode = "private";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateTmp = true;
PrivateUsers = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
RestrictAddressFamilies = "AF_INET AF_INET6";
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~ @resources @privileged"
];
UMask = "077";
};
};
system.stateVersion = "23.11";
}
-1
View File
@@ -1 +0,0 @@
*
-1
View File
@@ -1 +0,0 @@
*
-16
View File
@@ -77,11 +77,6 @@ edolstra:
display_name: Eelco Dolstra
github: edolstra
emilazy:
display_name: Emily
forgejo: emilazy
github: emilazy
ericson:
display_name: John Ericson
github: ericson2314
@@ -120,11 +115,6 @@ jade:
just1602:
forgejo: just1602
kasimeka:
display_name: ورد
forgejo: janw4ld
github: kasimeka
kfears:
display_name: KFears
forgejo: kfearsoff
@@ -163,9 +153,6 @@ ma27:
matthewbauer:
github: matthewbauer
mic92:
github: mic92
midnightveil:
display_name: julia
forgejo: midnightveil
@@ -265,9 +252,6 @@ winter:
xanderio:
github: xanderio
xokdvium:
github: xokdvium
yorickvp:
github: yorickvp
+1 -1
View File
@@ -225,7 +225,7 @@ let
showCategory = cat: ''
${optionalString (cat != "") "**${cat}:**"}
${listOptions (filterAttrs (n: v: v.category == cat && !v.hidden) allOptions)}
${listOptions (filterAttrs (n: v: v.category == cat) allOptions)}
'';
listOptions = opts: concatStringsSep "\n" (attrValues (mapAttrs showOption opts));
showOption =
@@ -1,60 +0,0 @@
---
synopsis: "Global certificate authorities are copied inside the builder's environment"
issues: [gh#12698, fj#885]
cls: [3765]
category: Fixes
credits: [raito, emilazy]
---
Previously, CA certificates were only installed at
`/etc/ssl/certs/ca-certificates.crt` for sandboxed builds on Linux.
This setup was insufficient in light of recent changes in `nixpkgs`, which now
enforce HTTPS usage for `fetchurl`, even for fixed-output derivations, to
mitigate confidentiality risks such as `netrc` or credentials leakage.
`nixpkgs` still make use of a special package called `cacerts` which contains a
copy of the CA certificates maintained by Nixpkgs and added as a reference for
TLS-enabled fetchers.
As a result, having a consistent and trusted certificate authority in all
builder environments is becoming more essential.
On `nix-darwin`, the `NIX_SSL_CERT_FILE` environment variable is always
explicitly defined, but it is ignored by the sandbox setup.
Simultaneously, Nix evaluates and propagates impure environment variables via
`lib.proxyImpureEnvVars`, meaning that if `NIX_SSL_CERT_FILE` is set (which
influences the default value for `ssl-cert-file`), it will be forwarded
unchanged into the builder environment.
However, on Linux, Nix also *copies* the CA file into the sandbox, creating a
discrepancy between the value of `NIX_SSL_CERT_FILE` and the actual trusted
certificate path used during the build.
This divergence caused confusion and was partially addressed by attempts to
whitelist the CA path in the Darwin sandbox (see cl/2906), but that approach
involved a non-trivial path canonicalization step and is not as general as this one.
To address this properly, we now emit a warning and override
`NIX_SSL_CERT_FILE` inside the builder, explicitly pointing it to the CA file
copied into the sandbox.
This eliminates ambiguity between `NIX_SSL_CERT_FILE`
and `ssl-cert-file`, ensuring consistent trust anchors across platforms.
This warning might become a hard error as we figure out what to do regarding
`lib.proxyImpureEnvVars` in nixpkgs.
The behavior has been verified across sandboxed and unsandboxed builds on both
Linux and Darwin.
As a consequence of this change, approximately 500KB of CA certificate data is
now unconditionally copied into the build directory for fixed-output
derivations.
While this ensures consistent trust verification without having to restart the
daemon after system upgrades, it may introduce a slight overhead in build
performance. At present, no optimizations have been implemented to avoid this
copy, but if this overhead proves noticeable in your workflows, please open an
issue so we can evaluate and possibly implement different strategies to render
trust anchors visible.
-25
View File
@@ -1,25 +0,0 @@
---
synopsis: "Hitting Control-C twice always terminates Lix"
cls: [3574]
issues: []
category: "Improvements"
credits: [horrors]
---
Hitting Control-C or sending `SIGINT` to Lix now prints an informational message
if it is still running after on second, the second Control-C/`SIGINT` terminates
Lix immediately without waiting for any shutdown code to finish running. Lix did
not treat the second such event differently from first in the past; this made it
impossible to easily terminate running Lix processes that got stuck in e.g. very
expensive Nixlang code that never interacted with the store. We now terminate as
soon as the user hits Control-C again without waiting any more, to much the same
effect as putting Lix into the background and killing it immediately afterwards.
This means you can now more conveniently break out of stuck Nixlang evaluations:
```
nix-instantiate --eval --expr 'let f = n: if n == 0 then 0 else f (n - 1) + f (n - 1); in f 32'
^CStill shutting down. Press ^C again to abort all operations immediately.
^C
❌130
```
-12
View File
@@ -1,12 +0,0 @@
---
synopsis: "libstore: exponential backoff for downloads"
issues: [lix#932]
cls: [3856]
category: Fixes
credits: [ma27]
---
The connection timeout when downloading from e.g. a binary cache is exponentially
increased per failure. The option `connect-timeout` is now an alias to `max-connect-timeout`
which is the maximum value for a timeout. The start value is controlled
by `initial-connect-timeout` which is `5` by default.
-9
View File
@@ -1,9 +0,0 @@
---
synopsis: Fix develop shells for derivations with escape codes
issues: [fj#991]
cls: [4154, 4155]
category: Fixes
credits: [Qyriad]
---
ASCII control characters (including `\e`, used for ANSI escape codes) in derivation variables are now correctly escaped for `nix develop` and `nix print-dev-env`, instead of erroring.
-16
View File
@@ -1,16 +0,0 @@
---
synopsis: Add `inputs.self.submodules` flake attribute
issues: [fj#942]
cls: [3839]
category: Features
credits: [edolstra, kasimeka]
---
A port of <https://github.com/NixOS/nix/pull/12421> to Lix, which:
- adds a general `inputs.self` flake attribute that retroactively applies
configurations to a flake after it's been fetched, then triggers a refetch of
the flake with the new config.
- implements `inputs.self.submodules` that allows a flake to declare its need
for submodules, which are then fetched automatically with no need to pass
`?submodules=1` anywhere.
@@ -1,16 +0,0 @@
---
synopsis: Add hyperlinks in attr set printing
issues: []
cls: [3790]
category: Features
credits: [jade]
---
The attribute set printer, such as is seen in `nix repl` or in type errors, now prints hyperlinks on each attribute name to its definition site if it is known.
Example: all of the attributes shown here are hyperlinks to the exact definition site of the attribute in question:
```
$ nix eval -f '<nixpkgs>' lib.licenses.mit
{ deprecated = false; free = true; fullName = "MIT License"; redistributable = true; shortName = "mit"; spdxId = "MIT"; url = "https://spdx.org/licenses/MIT.html"; }
```
@@ -1,16 +0,0 @@
---
synopsis: Parse overflowing JSON number literals as floatingpoint
issues: []
cls: [3919]
category: "Fixes"
credits: [emilazy]
---
Previously, `builtins.fromJSON "-9223372036854775809"` would
return a floatingpoint number, while `builtins.fromJSON
"9223372036854775808"` would cause an evaluation error. This was
introduced with the banning of integer overflow in Lix 2.91; previously
the latter would result in C++ undefined behaviour. These cases are
now treated consistently with JSONs model of a single numeric type,
and JSON number literals that do not fit in a Nixlanguage integer
will be parsed as floatingpoint numbers.
-13
View File
@@ -1,13 +0,0 @@
---
synopsis: "`--keep-failed` chowns the build directory to the user that request the build"
issues: []
cls: []
category: Improvements
credits: [horrors]
---
Running a build with `--keep-failed` now chowns the temporary directory from the
builder user and group to the user that request the build if the build came from
a local user connected to the daemon. This makes inspecting failed derivations a
lot easier. On Linux the build directory made visible to the user will not be in
the same path as it was in the sandbox and continuing builds will usually break.
-10
View File
@@ -1,10 +0,0 @@
---
synopsis: "nix-eval-jobs: support `--no-instantiate` flag"
issues: [fj#987]
category: Features
credits: [mic92,ma27]
---
`nix-eval-jobs` now supports a flag called `--no-instantiate`. With this enabled,
no write operations on the eval store are performed. That means, only evaluation is
performed, but derivations (and their gcroots) aren't created.
@@ -1,29 +0,0 @@
---
synopsis: "Fix nix develop for derivations that rejects dependencies with structured attrs"
issues: [fj#997]
cls: [4182]
category: Fixes
credits: [raito]
---
For the sake of concision, we refer to `disallowedReferences` in what follows,
but all output checks were equally fixed:
`{dis,}allowed{References,Requisites}`.
Derivations can define *output checks* to reject unwanted dependencies, such as
interpreters like `bash` or compilers like `gcc`. This can be done in two ways:
* **Legacy style**: `disallowedReferences = [ ... ]` in the environment.
* **Structured attrs**: `outputChecks.<output>.disallowedReferences = [ ... ]`,
typically used in `__json`.
Only the structured form supports derivations with multiple outputs.
`nix develop` internally rewrites derivations to create development shells. It
relied on the legacy `disallowedReferences`, and failed to honor the structured
variant. This led to broken shells in cases where `bashInteractive` was
explicitly disallowed using structured output checks, e.g. `nix develop
nixpkgs#systemd` after the "bash-less NixOS" changes.
This fix teaches `nix develop` to respect structured output checks, restoring
support for such derivations.
@@ -1,10 +0,0 @@
---
synopsis: "nix-eval-jobs: retain NIX_PATH"
issues: []
cls: [3859]
category: Fixes
credits: [ma27,mic92]
---
`nix-eval-jobs` doesn't clear the `NIX_PATH` from the environment anymore. This matches the behavior
of [upstream version `2.30`](https://github.com/nix-community/nix-eval-jobs/releases/tag/v2.30.0).
-25
View File
@@ -1,25 +0,0 @@
---
synopsis: "show tree with references that lead to an output cycle"
issues: [fj#551]
category: Improvements
credits: [ma27]
---
When Lix determines a cyclic dependency between several outputs of a derivation,
it now displays which files in which outputs lead to an output cycle:
```
error: cycle detected in build of '/nix/store/gc5h2whz3rylpf34n99nswvqgkjkigmy-demo.drv' in the references of output 'bar' from output 'foo'.
Shown below are the files inside the outputs leading to the cycle:
/nix/store/3lrgm74j85nzpnkz127rkwbx3fz5320q-demo-bar
└───lib/libfoo: …stuffbefore /nix/store/h680k7k53rjl9p15g6h7kpym33250w0y-demo-baz andafter.…
→ /nix/store/h680k7k53rjl9p15g6h7kpym33250w0y-demo-baz
└───share/snenskek: …???? /nix/store/dm24c76p9y2mrvmwgpmi64rryw6x5qmm-demo-foo ....…
→ /nix/store/dm24c76p9y2mrvmwgpmi64rryw6x5qmm-demo-foo
└───bin/alarm: …textexttext/nix/store/3lrgm74j85nzpnkz127rkwbx3fz5320q-demo-bar abcabcabc.…
→ /nix/store/3lrgm74j85nzpnkz127rkwbx3fz5320q-demo-bar
```
Please note that showing the files and its contents while displaying the cycles only works
on Linux.
+2 -2
View File
@@ -1,13 +1,13 @@
---
synopsis: Remove support for daemon protocols before 2.18
issues: [fj#510]
issues: []
cls: [3249]
significance: significant
category: "Breaking Changes"
credits: [horrors]
---
Support for daemon wire protocols belonging to Nix 2.17 or older have been
Support for daemon wire protocols belonging to Nix 2.18 or older have been
removed. This impacts clients connecting to the local daemon socket or any
remote builder configured using the `ssh-ng` protocol. Builders configured
with the `ssh` protocol are still accessible from clients such as Nix 2.3.
-14
View File
@@ -1,14 +0,0 @@
---
synopsis: "`nix eval --write-to` has been removed"
cls: [4045]
issues: [fj#974, fj#227]
category: "Breaking Changes"
credits: [horrors]
---
`nix eval --write-to` has been removed since it was underspecified, not widely
useful, and prone to security-sensitive misbehaviors. The feature was added in
Nix 2.4 purely for internal use in the build system. According to our research
it hasn't found any use outside of some distribution packaging scripts. Please
use structured outputs formats (such as JSON) instead as they have better type
fidelity, don't conflate attributes with paths, and are useful to other tools.
@@ -1,17 +0,0 @@
---
synopsis: Remove the `parse-toml-timestamps` experimental feature
category: "Breaking Changes"
credits: [emilazy]
---
The `parse-toml-timestamps` experimental feature has been removed.
This feature used inband signalling to mark timestamps, making it
impossible to unambiguously parse TOML documents. It also exposed
implementationdefined behaviour in the TOML specification that
changed in the toml11 parser library.
Any interface for parsing TOML timestamps suitable for future
stabilization would necessarily involve breaking changes, and there
is no evidence this experimental feature is being relied upon in the
wild, so it has been removed.
@@ -1,19 +0,0 @@
---
synopsis: "`disallowedRequisites` now reports chains of disallowed requisites"
issues: [fj#334,fj#626,gh#10877]
category: Improvements
credits: [ma27,roberth]
---
When a build fails because of [`disallowedRequisites`](@docroot@/language/advanced-attributes.md#adv-attr-disallowedRequisites), the error message now includes the chain of references that led to the failure. This makes it easier to see in which derivations the chain can be broken, to resolve the problem.
Example:
```
$ nix-build -A hello
error: output '/nix/store/0b7k85gg5r28gb54px9nq7iv5986mns9-hello-2.12.2' is not allowed to refer to the following paths:
/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-glibc-2.40-66
Shown below are chains that lead to the forbidden path(s).
/nix/store/0b7k85gg5r28gb54px9nq7iv5986mns9-hello-2.12.2
└───/nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-glibc-2.40-66
```
@@ -1,17 +0,0 @@
---
synopsis: "libstore/binary-cache-store: don't cache narinfo on nix copy, remove negative entry"
issues: []
cls: [3789]
category: Fixes
credits: [ma27]
---
When using e.g. [Snix's nar-bridge](https://snix.dev/docs/components/overview/#nar-bridge) via
an `http`-store, Lix would create cache entries with a wrong URL to the NAR when uploading
a store-path.
This caused hard build failures for Hydra.
Lix doesn't create these entries on upload anymore. Instead, it only removes negative cache entries.
The cache entry for a narinfo is now created the first time, Lix queries the cache
for the previously uploaded store-path again.
-10
View File
@@ -1,10 +0,0 @@
---
synopsis: "Lix libraries can now be linked statically"
issues: [fj#789]
cls: [3775, 3778]
category: Fixes
credits: [alois31]
---
Previously the pkg-config files distributed with Lix were only suitable for dynamic linkage, causing "undefined reference to…" linker errors when trying to link statically.
Private dependency information has now been added to make static linkage work as expected without user intervention.
In addition, relevant static libraries are now prelinked to avoid strange failures due to missing static initializers.
+1 -3
View File
@@ -3,7 +3,7 @@ synopsis: Symbols reuses once-allocated Value to reduce garbage collected alloca
issues: []
cls: [3308, 3300, 3314, 3310, 3312, 3313]
category: Improvements
credits: [raito, horrors, thubrecht, xokdvium, nan-git]
credits: [raito, horrors, thubrecht, nan-git]
---
In the Lix evaluator, **symbols** represent immutable strings, like those used
@@ -30,5 +30,3 @@ As a result, this reduces the number of allocations, leading to:
* A slight decrease in CPU usage during Nix evaluations.
This change is inspired by https://github.com/NixOS/nix/pull/13258 but the approach is different.
**Note** : [`xokdvium`](https://github.com/xokdvium) is the rightful author of https://gerrit.lix.systems/c/lix/+/3300 and the credit was missed on our end during the development process. We are deeply sorry for this mistake.
@@ -1,14 +0,0 @@
---
synopsis: Reject overflowing TOML integer literals
issues: []
cls: [3916]
category: "Breaking Changes"
credits: [emilazy]
---
The toml11 library used by Lix was updated. The new
version aligns with the [TOML v1.0.0 specifications
requirement](https://toml.io/en/v1.0.0#integer) to reject integer
literals that cannot be losslessly parsed. This means that code like
`builtins.fromTOML "v=0x8000000000000000"` will now produce an error
rather than silently saturating the integer result.
@@ -1,10 +0,0 @@
---
synopsis: add description to zsh completions
issues: [fj#910]
cls: [3632]
category: "Fixes"
credits: [matthewbauer]
---
Emit descriptions when completing args in zsh completions. This uses the descriptions we already
provided in NIX\_GET\_COMPLETIONS.
+7 -14
View File
@@ -107,27 +107,20 @@ See that section for complete details (`nix-build --help`), but in summary, a pa
> This option can cause non-termination, because lazy data
> structures can be infinitely large.
- `--raw`\
- `--raw`
When used with `--eval`, the result must be coercible to a string, i.e.,
something that can be converted using `${...}`.
Integers will always generate an error when output via `--raw`, regardless of
[`coerce-integers`](../contributing/experimental-features.md#xp-feature-coerce-integers) being enabled, to avoid ambiguity.
The output is printed exactly as-is, with no quotes, escaping, or trailing
newline.
something that can be converted using `${...}`. The output is
printed exactly as-is, with no quotes, escaping, or trailing newline.
- `--json`\
When used with `--eval`, print the resulting value as an JSON
representation of the resulting value rather than as a Nix expression.
The conversion behaviour, if `--strict` is passed, is the same as
[`builtins.toJSON`](../language/builtins.md#builtins-toJSON).
representation of the abstract syntax tree rather than as a Nix expression.
- `--xml`\
When used with `--eval`, print the resulting value as an XML
representation of the resulting value rather than as a Nix expression.
The schema is the same as that used by [`builtins.toXML`](../language/builtins.md#builtins-toXML).
representation of the abstract syntax tree rather than as a Nix expression.
The schema is the same as that used by the [`toXML`
built-in](../language/builtins.md).
- `--read-write-mode`\
When used with `--eval`, perform evaluation in read/write mode so
+2 -2
View File
@@ -661,8 +661,8 @@ Verbosity levels are:
The default level that the command starts is `ERROR`. The simplest way to
increase the verbosity by stacking `-v` option (eg: `-vvv == level 3 == INFO`).
Use `--quiet` to decrease verbosity by one level.
There is one shortcut, `--debug` to run in `DEBUG` verbosity level.
There are also two shortcuts, `--debug` to run in `DEBUG` verbosity level and
`--quiet` to run in `ERROR` verbosity level.
----------
+19 -65
View File
@@ -11,19 +11,7 @@ The following instructions assume you already have some version of Nix or Lix in
[installation instructions]: ../installation/installation.md
A typical development flow for simple changes in Lix looks like:
- [Set up and build Lix](#building)
- For large changes, check in regarding design and possibly create an RFD issue on Forgejo
- Make the changes in your editor
- [Send the changes to Gerrit](#sending-to-gerrit)
- Once you have the number for the CL from Gerrit to put in the changelog, [write a changelog entry](#release-notes) and amend it into the commit
- Update the Gerrit change by submitting it with the same command as the first time
- Request and receive a code review
- Address feedback from the review
- Amend commits, send to Gerrit again
- Submit the approved change
## Building Lix in a development shell {#building}
## Building Lix in a development shell
### Setting up the development shell
@@ -60,7 +48,7 @@ $ just setup build test-unit
$ just install test-integration
```
Many justfile aliases have a `-custom` variant which pass extra arguments to `meson`.
Many targets have a `-custom` variant which pass extra arguments to `meson`.
For example, to work on both Lix and nix-eval-jobs you can run:
```
@@ -141,36 +129,7 @@ To inspect the canonical source of truth on what the state of the buildsystem co
$ meson introspect
```
## Sending changes to Gerrit for review {#sending-to-gerrit}
We use Gerrit for all our code review in Lix.
Our instance is at <https://gerrit.lix.systems>.
There's much more information about how to use Gerrit in the [wiki section on Gerrit][wiki-gerrit] including how to use Jujutsu, how to use the UI and more.
The Snix project also has some Gerrit information [in their contributing docs][snix-gerrit].
[wiki-gerrit]: https://wiki.lix.systems/books/lix-contributors/chapter/gerrit
[snix-gerrit]: https://snix.dev/docs/guides/contributing/
The gist is that once you have your SSH key and git remote set up, you can send commits for review with:
```
$ git remote set-url origin ssh://YOURUSERNAME@gerrit.lix.systems:2022/lix
$ git push origin HEAD:refs/for/main
```
Then, you can request a review via the "Reply" button on the web UI.
If you click "Suggest Owners", it will try to suggest the maintainers of the area of the code change to send review requests to.
Requesting reviews from multiple people is normal.
We do our best to respond to directly sent reviews in a few days, so feel free to request another reviewer or ask on Matrix if you've not got a response for a while.
Keep in mind that Lix is a volunteer project and we have limited bandwidth, so some changes aren't feasible to shepherd through; please check in on Matrix at design time when doing large changes.
Once you get a `Code-Review+2` vote on your change, it's rebased on `main` and CI marks it `Verified+1`, you're able (and usually expected, so you can have a second chance to check it over) to hit the Submit button to merge it.
If the change appears as "Rebase Required", you need to rebase it on `main` locally or via the Gerrit UI and wait for `Verified+1` before the Submit button is made active
The `Code-Review+2` from before will stick around through trivial rebases so no need to re-request review for a mere rebase.
## Building Lix with `nix`
## Building Lix outside of development shells
To build a release version of Lix for the current operating system and CPU architecture:
@@ -327,10 +286,10 @@ Configure your editor to use the `clangd` from the shell, either by running it i
> Some other editors (e.g. Emacs, Vim) need a plugin to support LSP servers in general (e.g. [lsp-mode](https://github.com/emacs-lsp/lsp-mode) for Emacs and [vim-lsp](https://github.com/prabirshrestha/vim-lsp) for vim).
> Editor-specific setup is typically opinionated, so we will not cover it here in more detail.
# Manual and documentation
## Building the manual
### Checking links in the manual
The build checks for broken internal links.
This happens late in the process, so `nix build` is not suitable for iterating.
To build the manual incrementally, run:
```console
@@ -342,20 +301,15 @@ meson compile -C build manual
[`mdbook-linkcheck`]: https://github.com/Michael-F-Bryan/mdbook-linkcheck
[URI fragments]: https://en.wikipedia.org/wiki/URI_fragment
The built manual is in `build/doc/manual/manual/index.html`.
#### `@docroot@` variable
The build checks for broken internal links.
This happens late in the process, so `nix build` is not suitable for iterating and it's recommended to use the `meson` command above instead.
`@docroot@` provides a base path for links that occur in reusable snippets or other documentation that doesn't have a base path of its own.
### `@\docroot\@` variable
If a broken link occurs in a snippet that was inserted into multiple generated files in different directories, use `@docroot@` to reference the `doc/manual/src` directory.
`@\docroot\@` provides a base path for links that occur in reusable snippets or other documentation that doesn't have a base path of its own.
If a broken link occurs in a snippet that was inserted into multiple generated files in different directories, use `@\docroot\@` to reference the `doc/manual/src` directory.
If the `@\docroot\@` literal appears in an error message from the `mdbook-linkcheck` tool, the `@\docroot\@` replacement needs to be applied to the generated source file that mentions it.
See existing `@\docroot\@` logic in `doc/manual/substitute.py`.
Regular markdown files used for the manual have a base path of their own and they can use relative paths instead of `@\docroot\@`.
If the `@docroot@` literal appears in an error message from the `mdbook-linkcheck` tool, the `@docroot@` replacement needs to be applied to the generated source file that mentions it.
See existing `@docroot@` logic in the [Makefile].
Regular markdown files used for the manual have a base path of their own and they can use relative paths instead of `@docroot@`.
## API documentation
@@ -387,7 +341,7 @@ You can build it yourself:
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}
## Add a release note
`doc/manual/rl-next` contains release notes entries for all unreleased changes.
@@ -456,15 +410,15 @@ The following properties are supported:
### Build process
Releases have a precomputed `rl-MAJOR.MINOR.md`, and no `rl-next.md`.
Development releases have a generated `rl-next.md`.
Set `buildUnreleasedNotes = true;` in `flake.nix` to build the release notes on the fly.
# Adding experimental or deprecated features, global settings, or builtins
## Adding experimental or deprecated features, global settings, or builtins
Experimental and deprecated features, global settings, and builtins are generally referenced both in the code and in the documentation.
To prevent duplication or divergence, they are defined in data files, and a script generates the necessary glue.
The data file format is similar to the release notes: it consists of a YAML metadata header, followed by the documentation in Markdown format.
## Experimental or deprecated features
### Experimental or deprecated features
Experimental and deprecated features support the following metadata properties:
* `name` (required): user-facing name of the feature, to be used in `nix.conf` options and on the command line.
@@ -474,7 +428,7 @@ Experimental and deprecated features support the following metadata properties:
Experimental feature data files should live in `lix/libutil/experimental-features`, and deprecated features in `lix/libutil/deprecated-features`.
They must be listed in the `experimental_feature_definitions` or `deprecated_feature_definitions` lists in `lix/libutil/meson.build` respectively to be considered by the build system.
## Global settings
### Global settings
Global settings support the following metadata properties:
* `name` (required): user-facing name of the setting, to be used as key in `nix.conf` and in the `--option` command line argument.
@@ -502,7 +456,7 @@ Settings are not collected in a single place in the source tree, so an appropria
Look for related setting definition files under second-level subdirectories of `lix` whose name includes `settings`.
Then add the new file there, and don't forget to register it in the appropriate `meson.build` file.
## Builtin functions
### Builtin functions
The following metadata properties are supported for builtin functions:
* `name` (required): the language-facing name (as a member of the `builtins` attribute set) of the function.
@@ -518,7 +472,7 @@ The following metadata properties are supported for builtin functions:
New builtin function definition files must be added to `lix/libexpr/builtins` and registered in the `builtin_definitions` list in `lix/libexpr/meson.build`.
## Builtin constants
### Builtin constants
The following metadata properties are supported for builtin constants:
* `name` (required): the language-facing name (as a member of the `builtins` attribute set) of the constant.
* `type` (required): the Nix language type of the constant; the C++ type is automatically derived.
@@ -54,6 +54,11 @@ The most current alternative to this section is to read `package.nix` and see wh
obtained from the its repository
<https://github.com/troglobit/editline>.
- The `libsodium` library for verifying cryptographic signatures
of contents fetched from binary caches.
It can be obtained from the official web site
<https://libsodium.org>.
- Recent versions of Bison and Flex to build the parser. (This is
because Nix needs GLR support in Bison and reentrancy support in
Flex.) For Bison, you need version 2.6, which can be obtained from
@@ -302,6 +302,8 @@ Derivations can declare some infrequently used optional attributes.
- `maxSize` defines the maximum size of the resulting [store object](../glossary.md#gloss-store-object).
- `maxClosureSize` defines the maximum size of the output's closure.
- `ignoreSelfRefs` controls whether self-references should be considered when
checking for allowed references/requisites.
Example:
-5
View File
@@ -90,11 +90,6 @@ def recursive_replace(data, book_root, search_path):
).replace(
'@docroot@',
("../" * len(path_to_chapter.parent.parts) or "./")[:-1]
).replace(
# this replacement is to avoid corrupting the
# hacking.md manual section on docroot
'@\\docroot\\@',
'@docroot@',
),
sub_items = [
recursive_replace(sub_item, book_root, search_path)
+31 -31
View File
@@ -62,37 +62,38 @@ let
++ autoLayered
++ extraPkgs;
users = {
users =
{
root = {
uid = 0;
shell = "${pkgs.bashInteractive}/bin/bash";
home = "/root";
gid = 0;
groups = [ "root" ];
description = "System administrator";
};
nobody = {
uid = 65534;
shell = "${pkgs.shadow}/bin/nologin";
home = "/var/empty";
gid = 65534;
groups = [ "nobody" ];
description = "Unprivileged account (don't use!)";
};
}
// lib.listToAttrs (
map (n: {
name = "nixbld${toString n}";
value = {
uid = 30000 + n;
gid = 30000;
groups = [ "nixbld" ];
description = "Nix build user ${toString n}";
root = {
uid = 0;
shell = "${pkgs.bashInteractive}/bin/bash";
home = "/root";
gid = 0;
groups = [ "root" ];
description = "System administrator";
};
}) (lib.lists.range 1 32)
);
nobody = {
uid = 65534;
shell = "${pkgs.shadow}/bin/nologin";
home = "/var/empty";
gid = 65534;
groups = [ "nobody" ];
description = "Unprivileged account (don't use!)";
};
}
// lib.listToAttrs (
map (n: {
name = "nixbld${toString n}";
value = {
uid = 30000 + n;
gid = 30000;
groups = [ "nixbld" ];
description = "Nix build user ${toString n}";
};
}) (lib.lists.range 1 32)
);
groups = {
root.gid = 0;
@@ -360,8 +361,7 @@ let
"org.opencontainers.image.version" = pkgs.nix.version;
"org.opencontainers.image.description" =
"Minimal Lix container image, with some batteries included.";
}
// lib.optionalAttrs (lixRevision != null) { "org.opencontainers.image.revision" = lixRevision; };
} // lib.optionalAttrs (lixRevision != null) { "org.opencontainers.image.revision" = lixRevision; };
};
meta = {
Generated
+3 -3
View File
@@ -108,11 +108,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1758391731,
"narHash": "sha256-UuwQoPWv13DVKMveeev+F0OC/N95AOmAz6SzCuGhxjQ=",
"lastModified": 1749522908,
"narHash": "sha256-eWANkhWXFL1MmaxzsZ9bhLCNT8OVs7CC+OXaSDGlA8A=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "3f00d36f15e16e0471d9ca1e8f88958941fa970a",
"rev": "e5cb99555c45a13dcc5f1317462238530b0066b7",
"type": "github"
},
"original": {
+10 -57
View File
@@ -175,13 +175,8 @@
{
nixStable = prev.nix;
# Nix 2.18 has been removed from Nixpkgs ≥ 25.05, so we need to reintroduce it ourselves for our tests.
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;
};
@@ -243,22 +238,6 @@
boehmgc-nix = final.nix.passthru.boehmgc-nix;
# And same thing for our build-release-notes package.
build-release-notes = final.nix.passthru.build-release-notes;
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;
});
};
in
{
@@ -273,30 +252,6 @@
# Binary package for various platforms.
build = forAllSystems (system: self.packages.${system}.nix);
# 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:
self.packages.${system}.nix-static.overrideAttrs {
dontBuild = true;
installPhase = ''
runHook preInstall
echo "configure-static complete. exiting with success"
mkdir -p "$out"
exit 0
'';
}
);
# 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;
clang = self.devShells.${system}.native-clangStdenvPackages;
@@ -443,16 +398,15 @@
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;
})
];
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" { nixVersions.latest = nix; }).attrpathsSuperset {
evalSystem = system;
})
];
}
);
};
@@ -501,7 +455,6 @@
# 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};
-10
View File
@@ -1,10 +0,0 @@
# This reproduces the Lix Approvers plus Lix groups to yield the status quo
alois1@gmx-topmail.de
jade@lix.systems
lunaphied@lunaphied.me
maximilian@mbosch.me
me@0upti.me
pennae@lix.systems
qyriad@qyriad.me
raito@lix.systems
rbt@sent.as
-64
View File
@@ -1,64 +0,0 @@
#!@python@
import argparse
import capnp
from pathlib import Path
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@'])
request = schema.CodeGeneratorRequest.read(sys.stdin)
subprocess.run([lang], input=request.as_builder().to_bytes()).check_returncode()
base_dir = os.getcwd()
os.chdir(old_cwd)
include = [ str(Path(p).resolve()) for p in os.environ['lix_capnp_include'].split(':') ]
if depfile := os.environ['lix_capnp_depfile']:
deps = ""
for input in request.requestedFiles:
deps += " ".join(f"{input.filename}.{o}" for o in outputs)
deps += ":"
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:
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='+')
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'] = os.getcwd()
if args.language == "c++":
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()
+191 -360
View File
@@ -1,19 +1,7 @@
#include "lix/libstore/path.hh"
#include "lix/libutil/async.hh"
#include "lix/libutil/error.hh"
#include "lix/libutil/file-descriptor.hh"
#include "lix/libutil/logging.hh"
#include "lix/libutil/rpc.hh"
#include "lix/libutil/types-rpc.hh"
#include <algorithm>
#include <capnp/rpc-twoparty.h>
#include <chrono>
#include <cstring>
#include <future>
#include <kj/time.h>
#include <set>
#include <memory>
#include <string>
#include <tuple>
#if __APPLE__
#include <sys/time.h>
@@ -29,27 +17,13 @@
#include "lix/libstore/derivations.hh"
#include "lix/libutil/strings.hh"
#include "lix/libstore/local-store.hh"
#include "lix/libstore/types-rpc.hh"
#include "lix/libcmd/legacy.hh"
#include "lix/libutil/experimental-features.hh"
#include "lix/libutil/hash.hh"
#include "build-remote.hh"
#include "lix/libstore/build/hook-instance.capnp.h"
namespace nix {
namespace {
struct Instance final : rpc::build_remote::HookInstance::Server
{
unsigned int maxBuildJobs;
Instance(unsigned int maxBuildJobs) : maxBuildJobs(maxBuildJobs) {}
kj::Promise<void> build(BuildContext context) override;
};
}
std::string escapeUri(std::string uri)
{
std::replace(uri.begin(), uri.end(), '/', '_');
@@ -79,245 +53,6 @@ static bool allSupportedLocally(Store & store, const std::set<std::string>& requ
return true;
}
static std::tuple<bool, Machine *, AutoCloseFD> selectBestMachine(
Machines & machines,
const std::string & neededSystem,
const std::set<std::string> & requiredFeatures
)
{
bool rightType = false;
Machine * bestMachine = nullptr;
AutoCloseFD bestSlotLock;
uint64_t bestLoad = 0;
for (auto & m : machines) {
debug("considering building on remote machine '%s'", m.storeUri);
if (m.enabled && m.systemSupported(neededSystem) && m.allSupported(requiredFeatures)
&& m.mandatoryMet(requiredFeatures))
{
rightType = true;
AutoCloseFD free;
uint64_t load = 0;
for (uint64_t slot = 0; slot < m.maxJobs; ++slot) {
auto slotLock = openSlotLock(m, slot);
if (tryLockFile(slotLock.get(), ltWrite)) {
if (!free) {
free = std::move(slotLock);
}
} else {
++load;
}
}
if (!free) {
continue;
}
bool best = false;
if (!bestSlotLock) {
best = true;
} else if (load / m.speedFactor < bestLoad / bestMachine->speedFactor) {
best = true;
} else if (load / m.speedFactor == bestLoad / bestMachine->speedFactor) {
if (m.speedFactor > bestMachine->speedFactor) {
best = true;
} else if (m.speedFactor == bestMachine->speedFactor) {
if (load < bestLoad) {
best = true;
}
}
}
if (best) {
bestLoad = load;
bestSlotLock = std::move(free);
bestMachine = &m;
}
}
}
return {rightType, bestMachine, std::move(bestSlotLock)};
}
static void printSelectionFailureMessage(
Verbosity level,
const std::string_view drvstr,
const Machines & machines,
const std::string & neededSystem,
const std::set<std::string> & requiredFeatures
)
{
std::string machinesFormatted;
for (auto & m : machines) {
machinesFormatted += HintFmt(
"\n([%s], %s, [%s], [%s])",
concatStringsSep<StringSet>(", ", m.systemTypes),
m.maxJobs,
concatStringsSep<StringSet>(", ", m.supportedFeatures),
concatStringsSep<StringSet>(", ", m.mandatoryFeatures)
)
.str();
}
printMsg(
level,
"Failed to find a machine for remote build!\n"
"derivation: %s\n"
"required (system, features): (%s, [%s])\n"
"%s available machines:\n"
"(systems, maxjobs, supportedFeatures, mandatoryFeatures)%s",
drvstr,
neededSystem,
concatStringsSep<StringSet>(", ", requiredFeatures),
machines.size(),
Uncolored(machinesFormatted)
);
}
namespace {
struct BuilderConnection
{
AutoCloseFD slotLock;
std::shared_ptr<Store> sshStore;
std::string storeUri;
Pipe logPipe;
// start the thread that reads ssh stderr and turns it into log items.
// this future *must* outlive sshStore, otherwise it will never finish
std::future<void> startLogThread(int intoFD)
{
if (!logPipe.readSide) {
return {};
}
logPipe.writeSide.close();
return std::async(
std::launch::async,
[](int from, int to) {
AsyncIoRoot aio;
auto reader = AIO().lowLevelProvider.wrapInputFd(from);
auto writer = AIO().lowLevelProvider.wrapOutputFd(to);
reader->pumpTo(*writer).wait(aio.kj.waitScope);
},
logPipe.readSide.get(),
intoFD
);
}
};
struct AcceptedBuild final : rpc::build_remote::HookInstance::AcceptedBuild::Server
{
ref<Store> store;
StorePath drvPath;
BuilderConnection builder;
rpc::build_remote::HookInstance::BuildLogger::Client buildLogger;
AcceptedBuild(
ref<Store> store,
StorePath drvPath,
BuilderConnection builder,
rpc::build_remote::HookInstance::BuildLogger::Client buildLogger
)
: store(store)
, drvPath(drvPath)
, builder(std::move(builder))
, buildLogger(std::move(buildLogger))
{
}
kj::Promise<void> run(RunContext context) override;
};
enum class BuildRejected { Temporarily, Permanently };
}
static kj::Promise<Result<std::variant<BuildRejected, BuilderConnection>>> connectToBuilder(
const ref<Store> & store,
const std::optional<StorePath> & drvPath,
Machines & machines,
const unsigned int maxBuildJobs,
const bool amWilling,
const std::string & neededSystem,
const std::set<std::string> & requiredFeatures
)
try {
AutoCloseFD bestSlotLock;
/* It would be possible to build locally after some builds clear out,
so don't show the warning now: */
bool couldBuildLocally = maxBuildJobs > 0
&& (neededSystem == settings.thisSystem
|| settings.extraPlatforms.get().count(neededSystem) > 0)
&& allSupportedLocally(*store, requiredFeatures);
/* It's possible to build this locally right now: */
bool canBuildLocally = amWilling && couldBuildLocally;
/* Error ignored here, will be caught later */
mkdir(currentLoad.c_str(), 0777);
while (true) {
bestSlotLock.reset();
AutoCloseFD lock = openLockFile(currentLoad + "/main-lock", true);
TRY_AWAIT(lockFileAsync(lock.get(), ltWrite));
auto [rightType, bestMachine, slotLock] =
selectBestMachine(machines, neededSystem, requiredFeatures);
bestSlotLock = std::move(slotLock);
if (!bestSlotLock) {
if (rightType && !canBuildLocally) {
co_return BuildRejected::Temporarily;
} else {
printSelectionFailureMessage(
couldBuildLocally ? lvlChatty : lvlWarn,
drvPath ? drvPath->to_string() : "<unknown>",
machines,
neededSystem,
requiredFeatures
);
co_return BuildRejected::Permanently;
}
}
#if __APPLE__
futimes(bestSlotLock.get(), nullptr);
#else
futimens(bestSlotLock.get(), nullptr);
#endif
lock.reset();
std::shared_ptr<Store> sshStore;
Pipe logPipe;
try {
Activity act(
*logger, lvlTalkative, actUnknown, fmt("connecting to '%s'", bestMachine->storeUri)
);
std::tie(sshStore, logPipe) = TRY_AWAIT(bestMachine->openStore());
TRY_AWAIT(sshStore->connect());
co_return BuilderConnection{
std::move(bestSlotLock), sshStore, bestMachine->storeUri, std::move(logPipe)
};
} catch (std::exception & e) { // NOLINT(lix-foreign-exceptions)
std::string msg = logPipe.readSide ? chomp(drainFD(logPipe.readSide.get(), false)) : "";
printError(
"cannot build on '%s': %s%s",
bestMachine->storeUri,
e.what(),
msg.empty() ? "" : ": " + msg
);
bestMachine->enabled = false;
}
}
} catch (...) {
co_return result::current_exception();
}
static int main_build_remote(AsyncIoRoot & aio, std::string programName, Strings argv)
{
{
@@ -338,7 +73,7 @@ static int main_build_remote(AsyncIoRoot & aio, std::string programName, Strings
FdSource source(STDIN_FILENO);
/* Read the parent's settings. */
while (readNum<unsigned>(source)) {
while (readInt(source)) {
auto name = readString(source);
auto value = readString(source);
settings.set(name, value);
@@ -349,23 +84,13 @@ static int main_build_remote(AsyncIoRoot & aio, std::string programName, Strings
initPlugins();
auto conn = aio.kj.lowLevelProvider->wrapUnixSocketFd(1);
capnp::TwoPartyServer srv(kj::heap<Instance>(maxBuildJobs));
srv.accept(*conn, 1).wait(aio.kj.waitScope);
return 0;
}
}
kj::Promise<void> Instance::build(BuildContext context)
{
try {
// FIXME this does not open a daemon connection for historical reasons.
// we may create a lot of build hook instances, and having each of them
// also create a daemon instance is inefficient and wasteful. in future
// versions of the build hook (where we don't need one hook process per
// build) we should change this to using a daemon connection, ideally a
// daemon connection provided by the parent via file descriptor passing
auto store = TRY_AWAIT(openStore(settings.storeUri, {}, AllowDaemon::Disallow));
auto store = aio.blockOn(openStore(settings.storeUri, {}, AllowDaemon::Disallow));
/* It would be more appropriate to use $XDG_RUNTIME_DIR, since
that gets cleared on reboot, but it wouldn't work on macOS. */
@@ -375,69 +100,181 @@ kj::Promise<void> Instance::build(BuildContext context)
else
currentLoad = settings.nixStateDir + currentLoadName;
std::shared_ptr<Store> sshStore;
AutoCloseFD bestSlotLock;
auto machines = getMachines();
debug("got %d remote builders", machines.size());
if (machines.empty()) {
context.getResults().initResult().initGood().setDeclinePermanently();
co_return;
std::cerr << "# decline-permanently\n";
return 0;
}
auto amWilling = context.getParams().getAmWilling();
auto neededSystem = rpc::to<std::string>(context.getParams().getNeededSystem());
auto drvPath = from(context.getParams().getDrvPath(), *store);
auto requiredFeatures =
rpc::to<std::set<std::string>>(context.getParams().getRequiredFeatures());
auto buildLogger = context.getParams().getBuildLogger();
std::optional<StorePath> drvPath;
std::string storeUri;
auto result = TRY_AWAIT(connectToBuilder(
store, drvPath, machines, maxBuildJobs, amWilling, neededSystem, requiredFeatures
));
while (true) {
if (auto immediateResponse = std::get_if<BuildRejected>(&result)) {
switch (*immediateResponse) {
case BuildRejected::Temporarily:
context.getResults().initResult().initGood().setPostpone();
co_return;
case BuildRejected::Permanently:
context.getResults().initResult().initGood().setDecline();
co_return;
try {
auto s = readString(source);
if (s != "try") return 0;
} catch (EndOfFile &) { return 0; }
auto amWilling = readInt(source);
auto neededSystem = readString(source);
drvPath = store->parseStorePath(readString(source));
auto requiredFeatures = readStrings<std::set<std::string>>(source);
/* It would be possible to build locally after some builds clear out,
so don't show the warning now: */
bool couldBuildLocally = maxBuildJobs > 0
&& ( neededSystem == settings.thisSystem
|| settings.extraPlatforms.get().count(neededSystem) > 0)
&& allSupportedLocally(*store, requiredFeatures);
/* It's possible to build this locally right now: */
bool canBuildLocally = amWilling && couldBuildLocally;
/* Error ignored here, will be caught later */
mkdir(currentLoad.c_str(), 0777);
while (true) {
bestSlotLock.reset();
AutoCloseFD lock = openLockFile(currentLoad + "/main-lock", true);
lockFile(lock.get(), ltWrite);
bool rightType = false;
Machine * bestMachine = nullptr;
uint64_t bestLoad = 0;
for (auto & m : machines) {
debug("considering building on remote machine '%s'", m.storeUri);
if (m.enabled &&
m.systemSupported(neededSystem) &&
m.allSupported(requiredFeatures) &&
m.mandatoryMet(requiredFeatures))
{
rightType = true;
AutoCloseFD free;
uint64_t load = 0;
for (uint64_t slot = 0; slot < m.maxJobs; ++slot) {
auto slotLock = openSlotLock(m, slot);
if (tryLockFile(slotLock.get(), ltWrite)) {
if (!free) {
free = std::move(slotLock);
}
} else {
++load;
}
}
if (!free) {
continue;
}
bool best = false;
if (!bestSlotLock) {
best = true;
} else if (load / m.speedFactor < bestLoad / bestMachine->speedFactor) {
best = true;
} else if (load / m.speedFactor == bestLoad / bestMachine->speedFactor) {
if (m.speedFactor > bestMachine->speedFactor) {
best = true;
} else if (m.speedFactor == bestMachine->speedFactor) {
if (load < bestLoad) {
best = true;
}
}
}
if (best) {
bestLoad = load;
bestSlotLock = std::move(free);
bestMachine = &m;
}
}
}
if (!bestSlotLock) {
if (rightType && !canBuildLocally)
std::cerr << "# postpone\n";
else
{
// add the template values.
std::string drvstr;
if (drvPath.has_value())
drvstr = drvPath->to_string();
else
drvstr = "<unknown>";
std::string machinesFormatted;
for (auto & m : machines) {
machinesFormatted += HintFmt(
"\n([%s], %s, [%s], [%s])",
concatStringsSep<StringSet>(", ", m.systemTypes),
m.maxJobs,
concatStringsSep<StringSet>(", ", m.supportedFeatures),
concatStringsSep<StringSet>(", ", m.mandatoryFeatures)
).str();
}
auto error = HintFmt(
"Failed to find a machine for remote build!\n"
"derivation: %s\n"
"required (system, features): (%s, [%s])\n"
"%s available machines:\n"
"(systems, maxjobs, supportedFeatures, mandatoryFeatures)%s",
drvstr,
neededSystem,
concatStringsSep<StringSet>(", ", requiredFeatures),
machines.size(),
Uncolored(machinesFormatted)
);
printMsg(couldBuildLocally ? lvlChatty : lvlWarn, error.str());
std::cerr << "# decline\n";
}
break;
}
#if __APPLE__
futimes(bestSlotLock.get(), nullptr);
#else
futimens(bestSlotLock.get(), nullptr);
#endif
lock.reset();
try {
Activity act(*logger, lvlTalkative, actUnknown, fmt("connecting to '%s'", bestMachine->storeUri));
sshStore = aio.blockOn(bestMachine->openStore());
aio.blockOn(sshStore->connect());
storeUri = bestMachine->storeUri;
} catch (std::exception & e) { // NOLINT(lix-foreign-exceptions)
auto msg = chomp(drainFD(5, false));
printError("cannot build on '%s': %s%s",
bestMachine->storeUri, e.what(),
msg.empty() ? "" : ": " + msg);
bestMachine->enabled = false;
continue;
}
goto connected;
}
}
auto builder = std::get_if<BuilderConnection>(&result);
assert(builder);
connected:
close(5);
auto ac = context.getResults().initResult().initGood().initAccept();
RPC_FILL(ac, setMachineName, builder->storeUri);
ac.setMachine(kj::heap<AcceptedBuild>(store, drvPath, std::move(*builder), buildLogger));
} catch (...) {
RPC_FILL(context.getResults(), initResult, std::current_exception());
}
}
assert(sshStore);
kj::Promise<void> AcceptedBuild::run(RunContext context)
{
try {
const int logFD = (co_await buildLogger.getFd()).orDefault(-1);
if (logFD < 0) {
throw Error("build-hook needs a logFD from the builder to build");
}
std::cerr << "# accept\n" << storeUri << "\n";
auto logThread = builder.startLogThread(logFD);
KJ_DEFER({
// drop any existing ssh connection so the log thread can exit
builder.sshStore = nullptr;
if (logThread.valid()) {
logThread.get();
}
});
auto & sshStore = builder.sshStore;
auto & storeUri = builder.storeUri;
auto inputs = rpc::to<std::set<StorePath>>(context.getParams().getInputs(), *store);
auto wantedOutputs = rpc::to<std::set<std::string>>(context.getParams().getWantedOutputs());
auto inputs = readStrings<PathSet>(source);
auto wantedOutputs = readStrings<StringSet>(source);
auto lockFileName = currentLoad + "/" + makeLockFilename(storeUri) + ".upload-lock";
@@ -446,24 +283,27 @@ kj::Promise<void> AcceptedBuild::run(RunContext context)
{
Activity act(*logger, lvlTalkative, actUnknown, fmt("waiting for the upload lock to '%s'", storeUri));
auto result = TRY_AWAIT(
AIO().timeoutAfter(15 * kj::MINUTES, lockFileAsync(uploadLock.get(), ltWrite))
);
if (!result) {
if (!unsafeLockFileSingleThreaded(uploadLock.get(), ltWrite, std::chrono::minutes(15)))
printError("somebody is hogging the upload lock for '%s', continuing...");
}
}
auto substitute = settings.buildersUseSubstitutes ? Substitute : NoSubstitute;
{
Activity act(*logger, lvlTalkative, actUnknown, fmt("copying dependencies to '%s'", storeUri));
TRY_AWAIT(copyPaths(*store, *sshStore, inputs, NoRepair, NoCheckSigs, substitute));
aio.blockOn(copyPaths(
*store,
*sshStore,
store->parseStorePathSet(inputs),
NoRepair,
NoCheckSigs,
substitute
));
}
uploadLock.reset();
auto drv = TRY_AWAIT(store->readDerivation(drvPath));
auto drv = aio.blockOn(store->readDerivation(*drvPath));
std::optional<BuildResult> optResult;
@@ -471,7 +311,7 @@ kj::Promise<void> AcceptedBuild::run(RunContext context)
// stores), we assume we are. This is necessary for backwards
// compat.
bool trustedOrLegacy = ({
std::optional trusted = TRY_AWAIT(sshStore->isTrustedClient());
std::optional trusted = aio.blockOn(sshStore->isTrustedClient());
!trusted || *trusted;
});
@@ -490,40 +330,33 @@ kj::Promise<void> AcceptedBuild::run(RunContext context)
//
// 2. Changing the `inputSrcs` set changes the associated
// output ids, which break CA derivations
if (!drv.inputDrvs.empty()) {
drv.inputSrcs = inputs;
}
optResult =
TRY_AWAIT(sshStore->buildDerivation(drvPath, (const BasicDerivation &) drv));
if (!drv.inputDrvs.empty())
drv.inputSrcs = store->parseStorePathSet(inputs);
optResult = aio.blockOn(sshStore->buildDerivation(*drvPath, (const BasicDerivation &) drv));
auto & result = *optResult;
if (!result.success())
throw Error("build of '%s' on '%s' failed: %s", store->printStorePath(*drvPath), storeUri, result.errorMsg);
} else {
TRY_AWAIT(copyClosure(
*store, *sshStore, StorePathSet{drvPath}, NoRepair, NoCheckSigs, substitute
aio.blockOn(copyClosure(
*store, *sshStore, StorePathSet{*drvPath}, NoRepair, NoCheckSigs, substitute
));
auto res = TRY_AWAIT(sshStore->buildPathsWithResults({DerivedPath::Built{
.drvPath = makeConstantStorePath(drvPath),
.outputs = OutputsSpec::All{},
}}));
auto res = aio.blockOn(sshStore->buildPathsWithResults({
DerivedPath::Built {
.drvPath = makeConstantStorePath(*drvPath),
.outputs = OutputsSpec::All {},
}
}));
// One path to build should produce exactly one build result
assert(res.size() == 1);
optResult = std::move(res[0]);
}
auto & result = *optResult;
if (!result.success()) {
throw Error(
"build of '%s' on '%s' failed: %s",
store->printStorePath(drvPath),
storeUri,
result.errorMsg
);
}
StorePathSet missingPaths;
auto outputPaths = drv.outputsAndPaths(*store);
for (auto & [outputName, outputPath] : outputPaths) {
if (!TRY_AWAIT(store->isValidPath(outputPath.second))) {
if (!aio.blockOn(store->isValidPath(outputPath.second)))
missingPaths.insert(outputPath.second);
}
}
if (!missingPaths.empty()) {
@@ -531,14 +364,12 @@ kj::Promise<void> AcceptedBuild::run(RunContext context)
if (auto localStore = store.try_cast_shared<LocalStore>())
for (auto & path : missingPaths)
localStore->locksHeld.insert(store->printStorePath(path)); /* FIXME: ugly */
TRY_AWAIT(
aio.blockOn(
copyPaths(*sshStore, *store, missingPaths, NoRepair, NoCheckSigs, NoSubstitute)
);
}
context.getResults().initResult().setGood();
} catch (...) {
RPC_FILL(context.getResults(), initResult, std::current_exception());
return 0;
}
}
+7 -9
View File
@@ -272,7 +272,7 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
}
bool add = false;
if (v.type() == nFunction) {
if (auto pattern = dynamic_cast<AttrsPattern *>(v.lambda().fun->pattern.get())) {
if (auto pattern = dynamic_cast<AttrsPattern *>(v.lambda.fun->pattern.get())) {
for (auto & i : pattern->formals) {
if (evaluator->symbols[i.name] == "inNixShell") {
add = true;
@@ -285,12 +285,12 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
};
for (auto & i : attrPaths) {
Value v(
findAlongAttrPath(
*state, i, takesNixShellAttr(vRoot) ? *autoArgsWithInNixShell : *autoArgs, vRoot
)
.first
);
Value & v(*findAlongAttrPath(
*state,
i,
takesNixShellAttr(vRoot) ? *autoArgsWithInNixShell : *autoArgs,
vRoot
).first);
state->forceValue(v, noPos);
getDerivations(
*state,
@@ -544,8 +544,6 @@ static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings a
logger->pause();
printMsg(lvlChatty, "running shell: %s", concatMapStringsSep(" ", args, shellEscape));
execvp(shell->c_str(), argPtrs.data());
throw SysError("executing shell '%s'", *shell);
+9 -25
View File
@@ -9,7 +9,6 @@
#include "lix/libstore/temporary-dir.hh"
#include "lix/libutil/async.hh"
#include "lix/libutil/regex.hh"
#include "lix/libutil/result.hh"
#include "lix/libutil/users.hh"
#include "nix-channel.hh"
@@ -66,18 +65,13 @@ static void addChannel(const std::string & url, const std::string & name)
static Path profile;
// Remove a channel.
static kj::Promise<Result<void>> removeChannel(const std::string & name)
try {
static void removeChannel(const std::string & name)
{
readChannels();
channels.erase(name);
writeChannels();
TRY_AWAIT(runProgram(
settings.nixBinDir + "/nix-env", true, {"--profile", profile, "--uninstall", name}
));
co_return result::success();
} catch (...) {
co_return result::current_exception();
runProgram(settings.nixBinDir + "/nix-env", true, { "--profile", profile, "--uninstall", name });
}
static Path nixDefExpr;
@@ -133,14 +127,8 @@ static void update(AsyncIoRoot & aio, const StringSet & channelNames)
bool unpacked = false;
if (std::regex_search(filename, regex::parse("\\.tar\\.(gz|bz2|xz)$"))) {
aio.blockOn(runProgram(
settings.nixBinDir + "/nix-build",
false,
{"--no-out-link",
"--expr",
"import " + unpackChannelPath + "{ name = \"" + cname + "\"; channelName = \""
+ name + "\"; src = builtins.storePath \"" + filename + "\"; }"}
));
runProgram(settings.nixBinDir + "/nix-build", false, { "--no-out-link", "--expr", "import " + unpackChannelPath +
"{ name = \"" + cname + "\"; channelName = \"" + name + "\"; src = builtins.storePath \"" + filename + "\"; }" });
unpacked = true;
}
@@ -170,7 +158,7 @@ static void update(AsyncIoRoot & aio, const StringSet & channelNames)
for (auto & expr : exprs)
envArgs.push_back(std::move(expr));
envArgs.push_back("--quiet");
aio.blockOn(runProgram(settings.nixBinDir + "/nix-env", false, envArgs));
runProgram(settings.nixBinDir + "/nix-env", false, envArgs);
// Make the channels appear in nix-env.
struct stat st;
@@ -256,7 +244,7 @@ static int main_nix_channel(AsyncIoRoot & aio, std::string programName, Strings
case cRemove:
if (args.size() != 1)
throw UsageError("'--remove' requires one argument");
aio.blockOn(removeChannel(args[0]));
removeChannel(args[0]);
break;
case cList:
if (!args.empty())
@@ -271,11 +259,7 @@ static int main_nix_channel(AsyncIoRoot & aio, std::string programName, Strings
case cListGenerations:
if (!args.empty())
throw UsageError("'--list-generations' expects no arguments");
std::cout << aio.blockOn(runProgram(
settings.nixBinDir + "/nix-env",
false,
{"--profile", profile, "--list-generations"}
)) << std::flush;
std::cout << runProgram(settings.nixBinDir + "/nix-env", false, {"--profile", profile, "--list-generations"}) << std::flush;
break;
case cRollback:
if (args.size() > 1)
@@ -287,7 +271,7 @@ static int main_nix_channel(AsyncIoRoot & aio, std::string programName, Strings
} else {
envArgs.push_back("--rollback");
}
aio.blockOn(runProgram(settings.nixBinDir + "/nix-env", false, envArgs));
runProgram(settings.nixBinDir + "/nix-env", false, envArgs);
break;
}
+1 -1
View File
@@ -103,7 +103,7 @@ static int main_nix_collect_garbage(AsyncIoRoot & aio, std::string programName,
if (dryRun) {
// Only print results for dry run; when !dryRun, paths will be printed as they're deleted.
for (auto & i : results.paths) {
printInfo("%s", Uncolored(i));
printInfo("%s", i);
}
}
+1 -1
View File
@@ -23,7 +23,7 @@ static int main_nix_copy_closure(AsyncIoRoot & aio, std::string programName, Str
printVersion("nix-copy-closure");
else if (*arg == "--gzip" || *arg == "--bzip2" || *arg == "--xz") {
if (*arg != "--gzip")
printTaggedWarning("'%1%' is not implemented, falling back to gzip", *arg);
warn("'%1%' is not implemented, falling back to gzip", *arg);
gzip = true;
} else if (*arg == "--from")
toMode = false;
+26 -36
View File
@@ -1,7 +1,6 @@
#include "lix/libcmd/cmd-profiles.hh"
#include "lix/libexpr/attr-path.hh"
#include "lix/libcmd/common-eval-args.hh"
#include "lix/libexpr/value.hh"
#include "lix/libstore/derivations.hh"
#include "lix/libutil/terminal.hh"
#include "lix/libexpr/eval.hh"
@@ -151,12 +150,11 @@ static void getAllExprs(Evaluator & state,
continue;
}
/* Load the expression on demand. */
Value vArg;
vArg.mkString(path2.canonical().abs());
auto vArg = state.mem.allocValue();
vArg->mkString(path2.canonical().abs());
if (seen.size() == maxAttrs)
throw Error("too many Nix expressions in directory '%1%'", path);
attrs.alloc(attrName
) = {NewValueAs::app, state.mem, state.builtins.get("import"), vArg};
attrs.alloc(attrName).mkApp(&state.builtins.get("import"), vArg);
}
else if (st.type == InputAccessor::tDirectory)
/* `path2' is a directory (with no default.nix in it);
@@ -183,7 +181,7 @@ static void loadSourceExpr(EvalState & state, const SourcePath & path_, Value &
directory). */
else if (st.type == InputAccessor::tDirectory) {
auto attrs = state.ctx.buildBindings(maxAttrs);
attrs.alloc("_combineChannels") = Value::EMPTY_LIST;
attrs.alloc("_combineChannels").mkList(0);
StringSet seen;
getAllExprs(state.ctx, path, seen, attrs);
v.mkAttrs(attrs);
@@ -200,7 +198,7 @@ static void loadDerivations(EvalState & state, const SourcePath & nixExprPath,
Value vRoot;
loadSourceExpr(state, nixExprPath, vRoot);
Value v(findAlongAttrPath(state, pathPrefix, autoArgs, vRoot).first);
Value & v(*findAlongAttrPath(state, pathPrefix, autoArgs, vRoot).first);
getDerivations(state, v, pathPrefix, autoArgs, elems, true);
@@ -319,9 +317,9 @@ std::vector<Match> pickNewestOnly(EvalState & state, std::vector<Match> matches)
matches.clear();
for (auto & [name, match] : newest) {
if (multiple.find(name) != multiple.end())
printTaggedWarning(
"there are multiple derivations named '%1%'; using the first one", name
);
warn(
"there are multiple derivations named '%1%'; using the first one",
name);
matches.push_back(match);
}
@@ -427,7 +425,7 @@ static void queryInstSources(EvalState & state,
Expr & eFun = state.ctx.parseExprFromString(i, CanonPath::fromCwd());
Value vFun, vTmp;
state.eval(eFun, vFun);
vTmp = {NewValueAs::app, state.ctx.mem, vFun, vArg};
vTmp.mkApp(&vFun, &vArg);
getDerivations(state, vTmp, "", *instSource.autoArgs, elems, true);
}
@@ -482,7 +480,7 @@ static void queryInstSources(EvalState & state,
Value vRoot;
loadSourceExpr(state, *instSource.nixExprPath, vRoot);
for (auto & i : args) {
Value v(findAlongAttrPath(state, i, *instSource.autoArgs, vRoot).first);
Value & v(*findAlongAttrPath(state, i, *instSource.autoArgs, vRoot).first);
getDerivations(state, v, "", *instSource.autoArgs, elems, true);
}
break;
@@ -517,8 +515,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;
v.mkString(value);
auto v = state.ctx.mem.allocValue();
v->mkString(value);
drv.setMeta(state, name, v);
}
@@ -688,12 +686,8 @@ static void upgradeDerivations(Globals & globals,
{
const char * action = compareVersions(drvName.version, bestVersion) <= 0
? "upgrading" : "downgrading";
printInfo(
"%1% '%2%' to '%3%'",
Uncolored(action),
i.queryName(*state),
bestElem->queryName(*state)
);
printInfo("%1% '%2%' to '%3%'",
action, i.queryName(*state), bestElem->queryName(*state));
newElems.push_back(*bestElem);
} else newElems.push_back(i);
@@ -851,7 +845,7 @@ static void uninstallDerivations(Globals & globals, Strings & selectors,
);
}
if (split == workingElems.end())
printTaggedWarning("selector '%s' matched no installed derivations", selector);
warn("selector '%s' matched no installed derivations", selector);
for (auto removedElem = split; removedElem != workingElems.end(); removedElem++) {
printInfo("uninstalling '%s'", removedElem->queryName(*state));
}
@@ -1273,43 +1267,39 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
else {
if (v->type() == nString) {
attrs2["type"] = "string";
attrs2["value"] = v->str();
attrs2["value"] = v->string.s;
xml.writeEmptyElement("meta", attrs2);
} else if (v->type() == nInt) {
attrs2["type"] = "int";
attrs2["value"] = fmt("%1%", v->integer());
attrs2["value"] = fmt("%1%", v->integer);
xml.writeEmptyElement("meta", attrs2);
} else if (v->type() == nFloat) {
attrs2["type"] = "float";
attrs2["value"] = fmt("%1%", v->fpoint());
attrs2["value"] = fmt("%1%", v->fpoint);
xml.writeEmptyElement("meta", attrs2);
} else if (v->type() == nBool) {
attrs2["type"] = "bool";
attrs2["value"] = v->boolean() ? "true" : "false";
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;
}
for (auto elem : v->listItems()) {
if (elem->type() != nString) continue;
XMLAttrs attrs3;
attrs3["value"] = elem.str();
attrs3["value"] = elem->string.s;
xml.writeEmptyElement("string", attrs3);
}
} else if (v->type() == nAttrs) {
attrs2["type"] = "strings";
XMLOpenElement m(xml, "meta", attrs2);
Bindings & attrs = *v->attrs();
Bindings & attrs = *v->attrs;
for (auto &i : attrs) {
const Attr & a(*attrs.get(i.name));
if (a.value.type() != nString) {
continue;
}
Attr & a(*attrs.find(i.name));
if(a.value->type() != nString) continue;
XMLAttrs attrs3;
attrs3["type"] = globals.state->symbols[i.name];
attrs3["value"] = a.value.str();
attrs3["value"] = a.value->string.s;
xml.writeEmptyElement("string", attrs3);
}
}
+1 -1
View File
@@ -38,7 +38,7 @@ void processExpr(EvalState & state, const Strings & attrPaths,
state.eval(e, vRoot);
for (auto & i : attrPaths) {
Value v(findAlongAttrPath(state, i, autoArgs, vRoot).first);
Value & v(*findAlongAttrPath(state, i, autoArgs, vRoot).first);
state.forceValue(v, noPos);
NixStringContext context;
+14 -47
View File
@@ -17,11 +17,8 @@
#include "graphml.hh"
#include "lix/libcmd/legacy.hh"
#include "lix/libstore/path-with-outputs.hh"
#include "lix/libutil/serialise.hh"
#include "nix-store.hh"
#include <cstdint>
#include <ctime>
#include <iostream>
#include <algorithm>
@@ -826,7 +823,7 @@ opVerify(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strin
else throw UsageError("unknown flag '%1%'", i);
if (aio.blockOn(store->verifyStore(checkContents, repair))) {
printTaggedWarning("not all store errors were fixed");
warn("not all store errors were fixed");
throw Exit(1);
}
}
@@ -899,11 +896,11 @@ opServe(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
FdSink out(STDOUT_FILENO);
/* Exchange the greeting. */
unsigned int magic = readNum<unsigned>(in);
unsigned int magic = readInt(in);
if (magic != SERVE_MAGIC_1) throw Error("protocol mismatch");
out << SERVE_MAGIC_2 << SERVE_PROTOCOL_VERSION;
out.flush();
ServeProto::Version clientVersion = readNum<unsigned>(in);
ServeProto::Version clientVersion = readInt(in);
ServeProto::ReadConn rconn {
.from = in,
@@ -921,12 +918,12 @@ opServe(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
verbosity = lvlError;
settings.keepLog.override(false);
settings.useSubstitutes.override(false);
settings.maxSilentTime.override(readNum<unsigned>(in));
settings.buildTimeout.override(readNum<unsigned>(in));
settings.maxSilentTime.override(readInt(in));
settings.buildTimeout.override(readInt(in));
if (GET_PROTOCOL_MINOR(clientVersion) >= 2)
settings.maxLogSize.override(readNum<unsigned long>(in));
if (GET_PROTOCOL_MINOR(clientVersion) >= 3) {
auto nrRepeats = readNum<unsigned>(in);
auto nrRepeats = readInt(in);
if (nrRepeats != 0) {
throw Error("client requested repeating builds, but this is not currently implemented");
}
@@ -936,19 +933,19 @@ opServe(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
// `nrRepeats` in fact is 0, so we can safely ignore this
// without doing something other than what the client
// asked for.
readNum<unsigned>(in);
readInt(in);
settings.runDiffHook.override(true);
}
if (GET_PROTOCOL_MINOR(clientVersion) >= 7) {
settings.keepFailed.override((bool) readNum<unsigned>(in));
settings.keepFailed.override((bool) readInt(in));
}
};
while (true) {
ServeProto::Command cmd;
try {
cmd = (ServeProto::Command) readNum<unsigned>(in);
cmd = (ServeProto::Command) readInt(in);
} catch (EndOfFile & e) {
break;
}
@@ -956,8 +953,8 @@ opServe(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
switch (cmd) {
case ServeProto::Command::QueryValidPaths: {
bool lock = readNum<unsigned>(in);
bool substitute = readNum<unsigned>(in);
bool lock = readInt(in);
bool substitute = readInt(in);
auto paths = ServeProto::Serialise<StorePathSet>::read(rconn);
if (lock && writeAllowed)
for (auto & path : paths)
@@ -1000,7 +997,7 @@ opServe(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
}
case ServeProto::Command::ExportPaths: {
readNum<unsigned>(in); // obsolete
readInt(in); // obsolete
aio.blockOn(store->exportPaths(
ServeProto::Serialise<StorePathSet>::read(rconn), out
));
@@ -1046,7 +1043,7 @@ opServe(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
}
case ServeProto::Command::QueryClosure: {
bool includeOutputs = readNum<unsigned>(in);
bool includeOutputs = readInt(in);
StorePathSet closure;
aio.blockOn(store->computeFSClosure(
ServeProto::Serialise<StorePathSet>::read(rconn),
@@ -1070,43 +1067,13 @@ opServe(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
if (deriver != "")
info.deriver = store->parseStorePath(deriver);
info.references = ServeProto::Serialise<StorePathSet>::read(rconn);
info.registrationTime = readNum<time_t>(in);
info.narSize = readNum<uint64_t>(in);
info.ultimate = readBool(in);
in >> info.registrationTime >> info.narSize >> info.ultimate;
info.sigs = readStrings<StringSet>(in);
info.ca = ContentAddress::parseOpt(readString(in));
if (info.narSize == 0)
throw Error("narInfo is too old and missing the narSize field");
struct SizedSource : Source
{
Source & orig;
size_t remain;
SizedSource(Source & orig, size_t size) : orig(orig), remain(size) {}
size_t read(char * data, size_t len) override
{
if (this->remain <= 0) {
throw EndOfFile("sized: unexpected end-of-file");
}
len = std::min(len, this->remain);
size_t n = this->orig.read(data, len);
this->remain -= n;
return n;
}
size_t drainAll()
{
std::vector<char> buf(8192);
size_t sum = 0;
while (this->remain > 0) {
size_t n = read(buf.data(), buf.size());
sum += n;
}
return sum;
}
};
SizedSource sizedSource(in, info.narSize);
AsyncSourceInputStream stream{sizedSource};
+13 -15
View File
@@ -1,5 +1,4 @@
#include "user-env.hh"
#include "lix/libexpr/value.hh"
#include "lix/libstore/derivations.hh"
#include "lix/libstore/store-api.hh"
#include "lix/libstore/path-with-outputs.hh"
@@ -33,8 +32,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
/* Construct the whole top level derivation. */
StorePathSet references;
auto manifest = state.ctx.mem.newList(elems.size());
Value vManifest{NewValueAs::list, manifest};
Value manifest = state.ctx.mem.newList(elems.size());
size_t n = 0;
for (auto & i : elems) {
/* Create a pseudo-derivation containing the name, system,
@@ -57,10 +55,9 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
// Copy each output meant for installation.
auto & vOutputs = attrs.alloc(state.ctx.s.outputs);
auto outputsList = state.ctx.mem.newList(outputs.size());
vOutputs = {NewValueAs::list, outputsList};
vOutputs = state.ctx.mem.newList(outputs.size());
for (const auto & [m, j] : enumerate(outputs)) {
outputsList->elems[m].mkString(j.first);
(vOutputs.listElems()[m] = state.ctx.mem.allocValue())->mkString(j.first);
auto outputAttrs = state.ctx.buildBindings(2);
outputAttrs.alloc(state.ctx.s.outPath).mkString(state.ctx.store->printStorePath(*j.second));
attrs.alloc(j.first).mkAttrs(outputAttrs);
@@ -78,12 +75,12 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
for (auto & j : metaNames) {
Value * v = i.queryMeta(state, j);
if (!v) continue;
meta.insert(state.ctx.symbols.create(j), *v);
meta.insert(state.ctx.symbols.create(j), v);
}
attrs.alloc(state.ctx.s.meta).mkAttrs(meta);
manifest->elems[n++].mkAttrs(attrs);
(manifest.listElems()[n++] = state.ctx.mem.allocValue())->mkAttrs(attrs);
if (drvPath) references.insert(*drvPath);
}
@@ -92,7 +89,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
the store; we need it for future modifications of the
environment. */
std::ostringstream str;
printAmbiguous(vManifest, state.ctx.symbols, str, nullptr, std::numeric_limits<int>::max());
printAmbiguous(manifest, state.ctx.symbols, str, nullptr, std::numeric_limits<int>::max());
auto manifestFile = state.aio.blockOn(state.ctx.store->addTextToStore("env-manifest.nix",
str.str(), references));
@@ -106,20 +103,21 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
builder with the manifest as argument. */
auto attrs = state.ctx.buildBindings(3);
state.ctx.paths.mkStorePathString(manifestFile, attrs.alloc("manifest"));
attrs.insert(state.ctx.symbols.create("derivations"), vManifest);
attrs.insert(state.ctx.symbols.create("derivations"), &manifest);
Value args;
args.mkAttrs(attrs);
Value topLevel{NewValueAs::app, state.ctx.mem, envBuilder, args};
Value topLevel;
topLevel.mkApp(&envBuilder, &args);
/* Evaluate it. */
debug("evaluating user environment builder");
state.forceValue(topLevel, noPos);
NixStringContext context;
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.s.outPath));
auto topLevelOut = state.coerceToStorePath(aOutPath.pos, aOutPath.value, context, "");
Attr & aDrvPath(*topLevel.attrs->find(state.ctx.s.drvPath));
auto topLevelDrv = state.coerceToStorePath(aDrvPath.pos, *aDrvPath.value, context, "");
Attr & aOutPath(*topLevel.attrs->find(state.ctx.s.outPath));
auto topLevelOut = state.coerceToStorePath(aOutPath.pos, *aOutPath.value, context, "");
/* Realise the resulting store expression. */
debug("building user environment");
+6 -8
View File
@@ -4,7 +4,6 @@
#include "lix/libstore/derivations.hh"
#include "lix/libstore/profiles.hh"
#include "lix/libcmd/repl.hh"
#include "lix/libutil/async.hh"
extern char * * environ __attribute__((weak));
@@ -40,15 +39,14 @@ StoreCommand::StoreCommand()
ref<Store> StoreCommand::getStore()
{
if (!_store) {
_store = createStore(aio());
}
if (!_store)
_store = createStore();
return *_store;
}
ref<Store> StoreCommand::createStore(AsyncIoRoot & in)
ref<Store> StoreCommand::createStore()
{
return in.blockOn(openStore());
return aio().blockOn(openStore());
}
void StoreCommand::run()
@@ -73,9 +71,9 @@ CopyCommand::CopyCommand()
});
}
ref<Store> CopyCommand::createStore(AsyncIoRoot & in)
ref<Store> CopyCommand::createStore()
{
return srcUri.empty() ? StoreCommand::createStore(in) : in.blockOn(openStore(srcUri));
return srcUri.empty() ? StoreCommand::createStore() : aio().blockOn(openStore(srcUri));
}
ref<Store> CopyCommand::getDstStore()
+2 -2
View File
@@ -39,7 +39,7 @@ struct StoreCommand : virtual Command
StoreCommand();
void run() override;
ref<Store> getStore();
virtual ref<Store> createStore(AsyncIoRoot & in);
virtual ref<Store> createStore();
/**
* Main entry point, with a `Store` provided
*/
@@ -59,7 +59,7 @@ struct CopyCommand : virtual StoreCommand
CopyCommand();
ref<Store> createStore(AsyncIoRoot & in) override;
ref<Store> createStore() override;
ref<Store> getDstStore();
};
+3 -3
View File
@@ -183,13 +183,13 @@ Bindings * MixEvalArgs::getAutoArgs(Evaluator & state)
{
auto res = state.buildBindings(autoArgs.size());
for (auto & i : autoArgs) {
Value v;
auto v = state.mem.allocValue();
if (i.second[0] == 'E')
state.evalLazily(
state.parseExprFromString(i.second.substr(1), CanonPath::fromCwd()), v
state.parseExprFromString(i.second.substr(1), CanonPath::fromCwd()), *v
);
else
v.mkString(((std::string_view) i.second).substr(1));
v->mkString(((std::string_view) i.second).substr(1));
res.insert(state.symbols.create(i.first), v);
}
return res.finish();
+9 -11
View File
@@ -12,10 +12,9 @@ namespace nix {
InstallableAttrPath::InstallableAttrPath(
ref<eval_cache::CachingEvaluator> state,
SourceExprCommand & cmd,
Value & v,
Value * v,
const std::string & attrPath,
ExtendedOutputsSpec extendedOutputsSpec
)
ExtendedOutputsSpec extendedOutputsSpec)
: InstallableValue(state)
, cmd(cmd)
, v(allocRootValue(v))
@@ -23,10 +22,10 @@ InstallableAttrPath::InstallableAttrPath(
, extendedOutputsSpec(std::move(extendedOutputsSpec))
{ }
std::pair<Value, PosIdx> InstallableAttrPath::toValue(EvalState & state)
std::pair<Value *, PosIdx> InstallableAttrPath::toValue(EvalState & state)
{
auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(*evaluator), *v);
state.forceValue(vRes, pos);
auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(*evaluator), **v);
state.forceValue(*vRes, pos);
return {vRes, pos};
}
@@ -35,7 +34,7 @@ DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths(EvalState & state)
auto [v, pos] = toValue(state);
if (std::optional derivedPathWithInfo = trySinglePathToDerivedPaths(
state, v, pos, fmt("while evaluating the attribute '%s'", attrPath)
state, *v, pos, fmt("while evaluating the attribute '%s'", attrPath)
))
{
return { *derivedPathWithInfo };
@@ -44,7 +43,7 @@ DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths(EvalState & state)
Bindings & autoArgs = *cmd.getAutoArgs(*evaluator);
DrvInfos drvInfos;
getDerivations(state, v, "", autoArgs, drvInfos, false);
getDerivations(state, *v, "", autoArgs, drvInfos, false);
// Backward compatibility hack: group results by drvPath. This
// helps keep .all output together.
@@ -93,10 +92,9 @@ DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths(EvalState & state)
InstallableAttrPath InstallableAttrPath::parse(
ref<eval_cache::CachingEvaluator> state,
SourceExprCommand & cmd,
Value & v,
Value * v,
std::string_view prefix,
ExtendedOutputsSpec extendedOutputsSpec
)
ExtendedOutputsSpec extendedOutputsSpec)
{
return {
state, cmd, v,
+5 -7
View File
@@ -20,14 +20,13 @@ class InstallableAttrPath : public InstallableValue
InstallableAttrPath(
ref<eval_cache::CachingEvaluator> state,
SourceExprCommand & cmd,
Value & v,
Value * v,
const std::string & attrPath,
ExtendedOutputsSpec extendedOutputsSpec
);
ExtendedOutputsSpec extendedOutputsSpec);
std::string what() const override { return attrPath; };
std::pair<Value, PosIdx> toValue(EvalState & state) override;
std::pair<Value *, PosIdx> toValue(EvalState & state) override;
DerivedPathsWithInfo toDerivedPaths(EvalState & state) override;
@@ -36,10 +35,9 @@ public:
static InstallableAttrPath parse(
ref<eval_cache::CachingEvaluator> state,
SourceExprCommand & cmd,
Value & v,
Value * v,
std::string_view prefix,
ExtendedOutputsSpec extendedOutputsSpec
);
ExtendedOutputsSpec extendedOutputsSpec);
};
}
+27 -19
View File
@@ -26,27 +26,35 @@ InstallableDerivedPath InstallableDerivedPath::parse(
std::string_view prefix,
ExtendedOutputsSpec extendedOutputsSpec)
{
auto derivedPath = std::visit(
overloaded{
// If the user did not use ^, we treat the output more
// liberally: we accept a symlink chain or an actual
// store path.
[&](const ExtendedOutputsSpec::Default &) -> DerivedPath {
return DerivedPath::Opaque{
.path = store->followLinksToStorePath(prefix),
auto derivedPath = std::visit(overloaded {
// If the user did not use ^, we treat the output more
// liberally: we accept a symlink chain or an actual
// store path.
[&](const ExtendedOutputsSpec::Default &) -> DerivedPath {
auto storePath = store->followLinksToStorePath(prefix);
// Remove this prior to stabilizing the new CLI.
if (storePath.isDerivation()) {
auto oldDerivedPath = DerivedPath::Built {
.drvPath = makeConstantStorePath(storePath),
.outputs = OutputsSpec::All { },
};
},
// If the user did use ^, we just do exactly what is written.
[&](const ExtendedOutputsSpec::Explicit & outputSpec) -> DerivedPath {
auto drv = DerivedPathOpaque::parse(*store, prefix);
return DerivedPath::Built{
.drvPath = std::move(drv),
.outputs = outputSpec,
};
},
warn(
"The interpretation of store paths arguments ending in `.drv` recently changed. If this command is now failing try again with '%s'",
oldDerivedPath.to_string(*store));
};
return DerivedPath::Opaque {
.path = std::move(storePath),
};
},
extendedOutputsSpec.raw
);
// If the user did use ^, we just do exactly what is written.
[&](const ExtendedOutputsSpec::Explicit & outputSpec) -> DerivedPath {
auto drv = DerivedPathOpaque::parse(*store, prefix);
return DerivedPath::Built {
.drvPath = std::move(drv),
.outputs = outputSpec,
};
},
}, extendedOutputsSpec.raw);
return InstallableDerivedPath {
store,
std::move(derivedPath),
+2 -2
View File
@@ -136,9 +136,9 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths(EvalState & state)
}};
}
std::pair<Value, PosIdx> InstallableFlake::toValue(EvalState & state)
std::pair<Value *, PosIdx> InstallableFlake::toValue(EvalState & state)
{
return {getCursor(state)->forceValue(state), noPos};
return {&getCursor(state)->forceValue(state), noPos};
}
std::vector<ref<eval_cache::AttrCursor>>
+1 -1
View File
@@ -55,7 +55,7 @@ struct InstallableFlake : InstallableValue
DerivedPathsWithInfo toDerivedPaths(EvalState & state) override;
std::pair<Value, PosIdx> toValue(EvalState & state) override;
std::pair<Value *, PosIdx> toValue(EvalState & state) override;
/**
* Get a cursor to every attrpath in getActualAttrPaths() that
+2 -3
View File
@@ -9,9 +9,8 @@ std::vector<ref<eval_cache::AttrCursor>>
InstallableValue::getCursors(EvalState & state)
{
auto evalCache =
std::make_shared<nix::eval_cache::EvalCache>(std::nullopt, [&](EvalState & state) {
return toValue(state).first;
});
std::make_shared<nix::eval_cache::EvalCache>(std::nullopt,
[&](EvalState & state) { return toValue(state).first; });
return {evalCache->getRoot()};
}
+1 -1
View File
@@ -77,7 +77,7 @@ struct InstallableValue : Installable
virtual ~InstallableValue() { }
virtual std::pair<Value, PosIdx> toValue(EvalState & state) = 0;
virtual std::pair<Value *, PosIdx> toValue(EvalState & state) = 0;
/**
* Get a cursor to each value this Installable could refer to.
+18 -16
View File
@@ -61,7 +61,7 @@ MixFlakeOptions::MixFlakeOptions()
.category = category,
.handler = {[&]() {
lockFlags.useRegistries = false;
printTaggedWarning("'--no-registries' is deprecated; use '--no-use-registries'");
warn("'--no-registries' is deprecated; use '--no-use-registries'");
}}
});
@@ -235,14 +235,15 @@ void SourceExprCommand::completeInstallable(EvalState & state, AddCompletions &
prefix_ = "";
}
auto [v1, pos] = findAlongAttrPath(state, prefix_, *autoArgs, root);
auto [v, pos] = findAlongAttrPath(state, prefix_, *autoArgs, root);
Value &v1(*v);
state.forceValue(v1, pos);
Value v2;
state.autoCallFunction(*autoArgs, v1, v2, pos);
if (v2.type() == nAttrs) {
for (auto & i : *v2.attrs()) {
std::string name{evaluator->symbols[i.name]};
for (auto & i : *v2.attrs) {
std::string name = evaluator->symbols[i.name];
if (name.find(searchWord) == 0) {
if (prefix_ == "")
completions.add(name);
@@ -343,7 +344,7 @@ void completeFlakeRefWithFragment(
}
}
} catch (Error & e) {
printTaggedWarning("%1%", Uncolored(e.msg()));
warn(e.msg());
}
}
@@ -411,12 +412,12 @@ 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, vFlake);
auto vFlake = state.ctx.mem.allocValue();
flake::callFlake(state, *lockedFlake, *vFlake);
state.forceAttrs(vFlake, noPos, "while parsing cached flake data");
state.forceAttrs(*vFlake, noPos, "while parsing cached flake data");
auto aOutputs = vFlake.attrs()->get(state.ctx.symbols.create("outputs"));
auto aOutputs = vFlake->attrs->get(state.ctx.symbols.create("outputs"));
assert(aOutputs);
return aOutputs->value;
@@ -449,24 +450,25 @@ Installables SourceExprCommand::parseInstallables(
throw UsageError("'--file' and '--expr' are exclusive");
auto evaluator = getEvaluator();
Value vFile;
auto vFile = evaluator->mem.allocValue();
if (file == "-") {
auto & e = evaluator->parseStdin();
state.eval(e, vFile);
state.eval(e, *vFile);
}
else if (file)
state.evalFile(state.aio.blockOn(lookupFileArg(*evaluator, *file)).unwrap(), vFile);
state.evalFile(state.aio.blockOn(lookupFileArg(*evaluator, *file)).unwrap(), *vFile);
else {
auto & e = evaluator->parseExprFromString(*expr, CanonPath::fromCwd());
state.eval(e, vFile);
state.eval(e, *vFile);
}
for (auto & s : ss) {
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(s);
result.push_back(make_ref<InstallableAttrPath>(InstallableAttrPath::parse(
evaluator, *this, vFile, std::move(prefix), std::move(extendedOutputsSpec)
)));
result.push_back(
make_ref<InstallableAttrPath>(
InstallableAttrPath::parse(
evaluator, *this, vFile, std::move(prefix), std::move(extendedOutputsSpec))));
}
} else {
+2 -3
View File
@@ -5,6 +5,5 @@ includedir=@includedir@
Name: Lix (libcmd)
Description: Lix Package Manager (libcmd)
Version: @PACKAGE_VERSION@
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
Requires: lix-base lix-util
Libs: -L${libdir} -llixcmd
+2 -17
View File
@@ -51,28 +51,13 @@ static void processLinks(struct lowdown_node * node)
std::string renderMarkdownToTerminal(std::string_view markdown, StandardOutputStream fileno)
{
int windowWidth = getWindowSize().second;
size_t lowdown_cols = std::max(windowWidth - 5, 60);
struct lowdown_opts opts{
struct lowdown_opts opts {
.type = LOWDOWN_TERM,
#ifdef LOWDOWN_SEPARATE_TERM_OPTS
.term =
{
.cols = lowdown_cols,
.width = 0,
.hmargin = 0,
.hpadding = 4,
.vmargin = 0,
.centre = 0,
},
// maxdepth needs to be part of the ifdefs to match declaration order
.maxdepth = 20,
#else
.maxdepth = 20,
.cols = lowdown_cols,
.cols = (size_t) std::max(windowWidth - 5, 60),
.hmargin = 0,
.vmargin = 0,
#endif /* LOWDOWN_SEPARATE_TERM_OPTS */
.feat = LOWDOWN_COMMONMARK | LOWDOWN_FENCED | LOWDOWN_DEFLIST | LOWDOWN_TABLES,
.oflags = LOWDOWN_TERM_NOLINK,
};
+5 -13
View File
@@ -43,16 +43,15 @@ libcmd = library(
dependencies : [
liblixutil,
liblixstore,
liblixfetchers,
liblixexpr,
liblixfetchers,
liblixmain,
liblix_doc,
boehm,
editline,
kj,
lowdown,
ncurses,
editline,
lowdown,
nlohmann_json,
liblix_doc,
kj,
],
# '../..' for self references like "lix/libcmd/*.hh"
include_directories : [ '../..' ],
@@ -73,11 +72,6 @@ custom_target(
liblixcmd = declare_dependency(
include_directories : include_directories('../..'),
dependencies : [
liblixutil,
liblixstore,
kj,
],
link_with : libcmd,
)
meson.override_dependency('lix-cmd', liblixcmd)
@@ -93,7 +87,5 @@ configure_file(
'libdir' : libdir,
'includedir' : includedir,
'PACKAGE_VERSION' : meson.project_version(),
'BOEHM_IF_FOUND' : boehm.found() ? 'bdw-gc' : '',
'LIBLIX_DOC_IF_STATIC' : is_static ? '-llix_doc' : '',
},
)
+2 -1
View File
@@ -244,7 +244,8 @@ void ReadlineLikeInteracter::writeHistory()
// them so the user isn't confused why their history is getting eaten.
std::string_view const errMsg(std::strerror(writeHistErr));
printTaggedWarning("ignoring error writing repl history to %s: %s", this->historyFile, errMsg);
warn("ignoring error writing repl history to %s: %s", this->historyFile, errMsg);
}
ReadlineLikeInteracter::~ReadlineLikeInteracter()
+65 -75
View File
@@ -5,7 +5,6 @@
#include <cstring>
#include <string_view>
#include "lix/libexpr/value.hh"
#include "lix/libutil/box_ptr.hh"
#include "lix/libcmd/repl-interacter.hh"
#include "lix/libcmd/repl.hh"
@@ -174,37 +173,35 @@ struct NixRepl
/**
* Get a list of each of the `repl-overlays` (parsed and evaluated).
*/
Value replOverlays();
Value * replOverlays();
/**
* Get the Nix function that composes the `repl-overlays` together.
*/
Value getReplOverlaysEvalFunction();
Value * getReplOverlaysEvalFunction();
/**
* Cached return value of `getReplOverlaysEvalFunction`.
*
* Note: This is `shared_ptr` to avoid garbage collection.
*/
std::shared_ptr<std::optional<Value>> replOverlaysEvalFunction =
std::allocate_shared<std::optional<Value>>(
TraceableAllocator<std::optional<Value>>(), std::nullopt
);
std::shared_ptr<Value *> replOverlaysEvalFunction =
std::allocate_shared<Value *>(TraceableAllocator<Value *>(), nullptr);
/**
* Get the `info` AttrSet that's passed as the first argument to each
* of the `repl-overlays`.
*/
Value replInitInfo();
Value * replInitInfo();
/**
* Get the current top-level bindings as an AttrSet.
*/
Value bindingsToAttrs();
Value * bindingsToAttrs();
/**
* Parse a file, evaluate its result, and force the resulting value.
*/
Value evalFile(SourcePath & path);
Value * evalFile(SourcePath & path);
void printValue(std::ostream & str,
Value & v,
@@ -293,7 +290,7 @@ ReplExitStatus NixRepl::mainLoop()
if (evaluator.debug && evaluator.debug->inDebugger) {
debuggerNotice = " debugger";
}
notice("Lix %1%%2%\nType :? for help.", Uncolored(nixVersion), debuggerNotice);
notice("Lix %1%%2%\nType :? for help.", nixVersion, debuggerNotice);
}
isFirstRepl = false;
@@ -309,7 +306,7 @@ ReplExitStatus NixRepl::mainLoop()
std::string input;
while (true) {
unsetUserInterruptRequest();
_isInterrupted = false;
// When continuing input from previous lines, don't print a prompt, just align to the same
// number of chars as the prompt.
@@ -340,14 +337,14 @@ ReplExitStatus NixRepl::mainLoop()
// input without clearing the input so far.
continue;
} else {
printMsg(lvlError, "%1%", Uncolored(e.msg()));
printMsg(lvlError, e.msg());
}
} catch (EvalError & e) {
printMsg(lvlError, "%1%", Uncolored(e.msg()));
printMsg(lvlError, e.msg());
} catch (Error & e) {
printMsg(lvlError, "%1%", Uncolored(e.msg()));
printMsg(lvlError, e.msg());
} catch (Interrupted & e) {
printMsg(lvlError, "%1%", Uncolored(e.msg()));
printMsg(lvlError, e.msg());
}
// We handled the current input fully, so we should clear it
@@ -453,7 +450,7 @@ StringSet NixRepl::completePrefix(const std::string &prefix)
e.eval(state, *env, v);
state.forceAttrs(v, noPos, "while evaluating an attrset for the purpose of completion (this error should not be displayed; file an issue?)");
for (auto & i : *v.attrs()) {
for (auto & i : *v.attrs) {
std::ostringstream output;
printAttributeName(output, evaluator.symbols[i.name]);
std::string name = output.str();
@@ -656,7 +653,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
auto path = state.coerceToPath(noPos, v, context, "while evaluating the filename to edit");
return {path, 0};
} else if (v.isLambda()) {
auto pos = evaluator.positions[v.lambda().fun->pos];
auto pos = evaluator.positions[v.lambda.fun->pos];
if (auto path = std::get_if<CheckedSourcePath>(&pos.origin))
return {*path, pos.line};
else
@@ -791,7 +788,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
Value v;
evalString(arg, v);
if (v.type() == nString) {
std::cout << v.str();
std::cout << v.string.s;
} else {
printValue(std::cout, v);
}
@@ -825,7 +822,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
logger->cout(trim(renderMarkdownToTerminal(markdown)));
} else if (v.isLambda()) {
auto pos = evaluator.positions[v.lambda().fun->pos];
auto pos = evaluator.positions[v.lambda.fun->pos];
if (auto path = std::get_if<CheckedSourcePath>(&pos.origin)) {
// Path and position have now been obtained, feed to nix-doc library to get data.
auto docComment = lambdaDocsForPos(*path, pos);
@@ -865,10 +862,10 @@ ProcessLineResult NixRepl::processLine(std::string line)
std::visit(overloaded {
[&](ExprReplBindings & b) {
for (auto & [name, e] : b.symbols) {
Value v;
e->eval(state, *env, v);
Value * v = state.ctx.mem.allocValue();
e->eval(state, *env, *v);
(void) e.release(); // NOLINT(bugprone-unused-return-value): leak because of thunk references
addVarToScope(name, v);
addVarToScope(name, *v);
}
},
[&](std::unique_ptr<Expr> & e) {
@@ -951,7 +948,7 @@ void NixRepl::loadFiles()
for (auto & [i, what] : getValues()) {
notice("Loading installable '%1%'...", Magenta(what));
addAttrsToScope(i);
addAttrsToScope(*i);
}
loadReplOverlays();
@@ -963,12 +960,12 @@ void NixRepl::loadReplOverlays()
return;
}
notice("Loading '%1%'...", "repl-overlays");
notice("Loading '%1%'...", Magenta("repl-overlays"));
auto replInitFilesFunction = getReplOverlaysEvalFunction();
Value newAttrs;
Value args[] = {replInitInfo(), bindingsToAttrs(), replOverlays()};
state.callFunction(replInitFilesFunction, args, newAttrs, noPos);
Value &newAttrs(*evaluator.mem.allocValue());
SmallValueVector<3> args = {replInitInfo(), bindingsToAttrs(), replOverlays()};
state.callFunction(*replInitFilesFunction, args.size(), args.data(), newAttrs, noPos);
// n.b. this does in fact load the stuff into the environment twice (once
// from the superset of the environment returned by repl-overlays and once
@@ -978,14 +975,14 @@ void NixRepl::loadReplOverlays()
addAttrsToScope(newAttrs);
}
Value NixRepl::getReplOverlaysEvalFunction()
Value * NixRepl::getReplOverlaysEvalFunction()
{
if (replOverlaysEvalFunction && *replOverlaysEvalFunction) {
return **replOverlaysEvalFunction;
return *replOverlaysEvalFunction;
}
auto evalReplInitFilesPath = CanonPath::root + "repl-overlays.nix";
*replOverlaysEvalFunction = Value{};
*replOverlaysEvalFunction = evaluator.mem.allocValue();
auto code =
#include "repl-overlays.nix.gen.hh"
;
@@ -997,14 +994,14 @@ Value NixRepl::getReplOverlaysEvalFunction()
state.eval(expr, **replOverlaysEvalFunction);
return **replOverlaysEvalFunction;
return *replOverlaysEvalFunction;
}
Value NixRepl::replOverlays()
Value * NixRepl::replOverlays()
{
Value replInits;
auto replInitElems = evaluator.mem.newList(evalSettings.replOverlays.get().size());
replInits = {NewValueAs::list, replInitElems};
Value * replInits(evaluator.mem.allocValue());
*replInits = evaluator.mem.newList(evalSettings.replOverlays.get().size());
Value ** replInitElems = replInits->listElems();
size_t i = 0;
for (auto path : evalSettings.replOverlays.get()) {
@@ -1020,32 +1017,27 @@ Value NixRepl::replOverlays()
auto replInit = evalFile(sourcePath);
evalSettings.pureEval.setDefault(prevPureEval);
if (!replInit.isLambda()) {
evaluator.errors
.make<TypeError>(
"Expected `repl-overlays` entry %s to be a lambda but found %s: %s",
path,
showType(replInit),
ValuePrinter(state, replInit, errorPrintOptions)
)
if (!replInit->isLambda()) {
evaluator.errors.make<TypeError>(
"Expected `repl-overlays` entry %s to be a lambda but found %s: %s",
path,
showType(*replInit),
ValuePrinter(state, *replInit, errorPrintOptions)
)
.debugThrow();
}
if (auto attrs = dynamic_cast<AttrsPattern *>(replInit->lambda.fun->pattern.get()); attrs && !attrs->ellipsis) {
evaluator.errors.make<TypeError>(
"Expected first argument of %1% to have %2% to allow future versions of Lix to add additional attributes to the argument",
"repl-overlays",
"..."
)
.atPos(replInit->lambda.fun->pos)
.debugThrow();
}
if (auto attrs = dynamic_cast<AttrsPattern *>(replInit.lambda().fun->pattern.get());
attrs && !attrs->ellipsis)
{
evaluator.errors
.make<TypeError>(
"Expected first argument of %1% to have %2% to allow future versions of Lix to "
"add additional attributes to the argument",
"repl-overlays",
"..."
)
.atPos(replInit.lambda().fun->pos)
.debugThrow();
}
replInitElems->elems[i] = replInit;
replInitElems[i] = replInit;
i++;
}
@@ -1053,16 +1045,16 @@ Value NixRepl::replOverlays()
return replInits;
}
Value NixRepl::replInitInfo()
Value * NixRepl::replInitInfo()
{
auto builder = evaluator.buildBindings(2);
Value currentSystem;
currentSystem.mkString(evalSettings.getCurrentSystem());
Value * currentSystem(evaluator.mem.allocValue());
currentSystem->mkString(evalSettings.getCurrentSystem());
builder.insert(evaluator.symbols.create("currentSystem"), currentSystem);
Value info;
info.mkAttrs(builder.finish());
Value * info(evaluator.mem.allocValue());
info->mkAttrs(builder.finish());
return info;
}
@@ -1097,9 +1089,7 @@ void NixRepl::addToScope(T && things, NameFn nameFn, ValueFn valueFn)
void NixRepl::addAttrsToScope(Value & attrs)
{
state.forceAttrs(attrs, noPos, "while evaluating an attribute set to be merged in the global scope");
addToScope(
*attrs.attrs(), [](const Attr & a) { return a.name; }, [](const Attr & a) { return a.value; }
);
addToScope(*attrs.attrs, [](Attr & a) { return a.name; }, [](Attr & a) { return a.value; });
}
void NixRepl::addValMapToScope(const ValMap & attrs)
@@ -1120,19 +1110,19 @@ void NixRepl::addVarToScope(const Symbol name, Value & v)
} else {
notice("Added %s.", evaluator.symbols[name]);
}
env->values[displ++] = v;
env->values[displ++] = &v;
varNames.emplace(evaluator.symbols[name]);
}
Value NixRepl::bindingsToAttrs()
Value * NixRepl::bindingsToAttrs()
{
auto builder = evaluator.buildBindings(staticEnv->vars.size());
for (auto & [symbol, displacement] : staticEnv->vars) {
builder.insert(symbol, env->values[displacement]);
}
Value attrs;
attrs.mkAttrs(builder.finish());
Value * attrs(evaluator.mem.allocValue());
attrs->mkAttrs(builder.finish());
return attrs;
}
@@ -1155,12 +1145,12 @@ void NixRepl::evalString(std::string s, Value & v)
state.forceValue(v, noPos);
}
Value NixRepl::evalFile(SourcePath & path)
Value * NixRepl::evalFile(SourcePath & path)
{
auto & expr = evaluator.parseExprFromFile(evaluator.paths.checkSourcePath(path), staticEnv);
Value result;
expr.eval(state, *env, result);
state.forceValue(result, noPos);
Value * result(evaluator.mem.allocValue());
expr.eval(state, *env, *result);
state.forceValue(*result, noPos);
return result;
}
+1 -1
View File
@@ -8,7 +8,7 @@ namespace nix {
struct AbstractNixRepl : NeverAsync
{
typedef std::vector<std::pair<Value, std::string>> AnnotatedValues;
typedef std::vector<std::pair<Value*,std::string>> AnnotatedValues;
static ReplExitStatus
run(const SearchPath & searchPath,
+24 -26
View File
@@ -69,12 +69,13 @@ std::string unparseAttrPath(std::vector<std::string> const & attrPath)
return ret.str();
}
std::pair<Value, PosIdx>
findAlongAttrPath(EvalState & state, const std::string & attrPath, Bindings & autoArgs, Value & vIn)
std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::string & attrPath,
Bindings & autoArgs, Value & vIn)
{
auto tokens = parseAttrPath(attrPath);
Value v = vIn;
Value * v = &vIn;
PosIdx pos = noPos;
for (auto [attrPathIdx, attr] : enumerate(tokens)) {
@@ -83,10 +84,10 @@ findAlongAttrPath(EvalState & state, const std::string & attrPath, Bindings & au
auto attrIndex = string2Int<unsigned int>(attr);
/* Evaluate the expression. */
Value vNew;
state.autoCallFunction(autoArgs, v, vNew, pos);
Value * vNew = state.ctx.mem.allocValue();
state.autoCallFunction(autoArgs, *v, *vNew, pos);
v = vNew;
state.forceValue(v, noPos);
state.forceValue(*v, noPos);
/* It should evaluate to either a set or an expression,
according to what is specified in the attrPath. */
@@ -95,7 +96,7 @@ findAlongAttrPath(EvalState & state, const std::string & attrPath, Bindings & au
if (attr.empty())
throw Error("empty attribute name in selection path '%1%'", attrPath);
if (v.type() != nAttrs) {
if (v->type() != nAttrs) {
auto pathPart =
std::vector<std::string>(tokens.begin(), tokens.begin() + attrPathIdx);
state.ctx.errors
@@ -104,18 +105,17 @@ findAlongAttrPath(EvalState & state, const std::string & attrPath, Bindings & au
"set but is %3%: %4%",
attrPath,
unparseAttrPath(pathPart),
showType(v),
ValuePrinter(state, v, errorPrintOptions)
showType(*v),
ValuePrinter(state, *v, errorPrintOptions)
)
.debugThrow();
}
auto a = v.attrs()->get(state.ctx.symbols.create(attr));
if (!a) {
Bindings::iterator a = v->attrs->find(state.ctx.symbols.create(attr));
if (a == v->attrs->end()) {
std::set<std::string> attrNames;
for (auto & attr : *v.attrs()) {
attrNames.emplace(state.ctx.symbols[attr.name]);
}
for (auto & attr : *v->attrs)
attrNames.insert(state.ctx.symbols[attr.name]);
auto suggestions = Suggestions::bestMatches(attrNames, attr);
auto pathPart =
@@ -127,33 +127,33 @@ findAlongAttrPath(EvalState & state, const std::string & attrPath, Bindings & au
attr,
attrPath,
unparseAttrPath(pathPart),
ValuePrinter(state, v, errorPrintOptions)
ValuePrinter(state, *v, errorPrintOptions)
);
}
v = a->value;
v = &*a->value;
pos = a->pos;
} else {
if (!v.isList()) {
if (!v->isList()) {
state.ctx.errors
.make<TypeError>(
"the expression selected by the selection path '%1%' should be a list but "
"is %2%: %3%",
attrPath,
showType(v),
ValuePrinter(state, v, errorPrintOptions)
showType(*v),
ValuePrinter(state, *v, errorPrintOptions)
)
.debugThrow();
}
if (*attrIndex >= v.listSize()) {
if (*attrIndex >= v->listSize()) {
throw AttrPathNotFound(
"list index %1% in selection path '%2%' is out of range for list %3%",
*attrIndex,
attrPath,
ValuePrinter(state, v, errorPrintOptions)
ValuePrinter(state, *v, errorPrintOptions)
);
}
v = v.listElems()[*attrIndex];
v = v->listElems()[*attrIndex];
pos = noPos;
}
@@ -165,7 +165,7 @@ findAlongAttrPath(EvalState & state, const std::string & attrPath, Bindings & au
std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what)
{
Value v2;
Value * v2;
try {
auto dummyArgs = state.ctx.mem.allocBindings(0);
v2 = findAlongAttrPath(state, "meta.position", *dummyArgs, v).first;
@@ -176,9 +176,7 @@ std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v
// FIXME: is it possible to extract the Pos object instead of doing this
// toString + parsing?
NixStringContext context;
auto path = state.coerceToPath(
noPos, v2, context, "while evaluating the 'meta.position' attribute of a derivation"
);
auto path = state.coerceToPath(noPos, *v2, context, "while evaluating the 'meta.position' attribute of a derivation");
auto fn = path.canonical().abs();
+5 -3
View File
@@ -10,9 +10,11 @@ namespace nix {
MakeError(AttrPathNotFound, Error);
MakeError(NoPositionInfo, Error);
std::pair<Value, PosIdx> findAlongAttrPath(
EvalState & state, const std::string & attrPath, Bindings & autoArgs, Value & vIn
);
std::pair<Value *, PosIdx> findAlongAttrPath(
EvalState & state,
const std::string & attrPath,
Bindings & autoArgs,
Value & vIn);
/**
* Heuristic to find the filename and lineno or a nix value.
+6 -4
View File
@@ -7,7 +7,8 @@
namespace nix {
Bindings Bindings::EMPTY{};
Bindings Bindings::EMPTY{0};
/* Allocate a new array of attributes for an attribute set with a specific
capacity. The space is implicitly reserved after the Bindings
@@ -20,14 +21,15 @@ Bindings * EvalMemory::allocBindings(size_t capacity)
throw Error("attribute set of size %d is too big", capacity);
stats.nrAttrsets++;
stats.nrAttrsInAttrsets += capacity;
return new (allocBytes(sizeof(Bindings) + sizeof(Attr) * capacity)) Bindings();
return new (gcAllocBytes(sizeof(Bindings) + sizeof(Attr) * capacity)) Bindings((Bindings::Size) capacity);
}
Value & BindingsBuilder::alloc(Symbol name, PosIdx pos)
{
bindings->push_back(Attr(name, {}, pos));
return (bindings->end() - 1)->value;
auto value = mem.allocValue();
bindings->push_back(Attr(name, value, pos));
return *value;
}
+21 -18
View File
@@ -23,8 +23,9 @@ struct Attr
way we keep Attr size at two words with no wasted space. */
Symbol name;
PosIdx pos;
mutable Value value;
Attr(Symbol name, Value value, PosIdx pos = noPos) : name(name), pos(pos), value(value) {}
Value * value;
Attr(Symbol name, Value * value, PosIdx pos = noPos)
: name(name), pos(pos), value(value) { };
Attr() { };
bool operator < (const Attr & a) const
{
@@ -52,10 +53,10 @@ public:
static Bindings EMPTY;
private:
Size size_ = 0;
Size size_, capacity_;
Attr attrs[0];
Bindings() = default;
Bindings(Size capacity) : size_(0), capacity_(capacity) { }
Bindings(const Bindings & bindings) = delete;
public:
@@ -67,12 +68,21 @@ public:
void push_back(const Attr & attr)
{
assert(size_ < capacity_);
attrs[size_++] = attr;
}
const Attr * get(Symbol name)
iterator find(Symbol name)
{
Attr key(name, {});
Attr key(name, 0);
iterator i = std::lower_bound(begin(), end(), key);
if (i != end() && i->name == name) return i;
return end();
}
Attr * get(Symbol name)
{
Attr key(name, 0);
iterator i = std::lower_bound(begin(), end(), key);
if (i != end() && i->name == name) return &*i;
return nullptr;
@@ -88,6 +98,8 @@ public:
void sort();
Size capacity() { return capacity_; }
/**
* Returns the attributes in lexicographically sorted order.
*/
@@ -114,28 +126,21 @@ public:
*/
class BindingsBuilder
{
public:
using Size = Bindings::Size;
private:
Bindings * bindings;
EvalMemory & mem;
SymbolTable & symbols;
Size capacity;
public:
// needed by std::back_inserter
using value_type = Attr;
BindingsBuilder(EvalMemory & mem, SymbolTable & symbols, Bindings * bindings, Size capacity)
BindingsBuilder(EvalMemory & mem, SymbolTable & symbols, Bindings * bindings)
: bindings(bindings)
, mem(mem)
, symbols(symbols)
, capacity(capacity)
{
}
{ }
void insert(Symbol name, Value value, PosIdx pos = noPos)
void insert(Symbol name, Value * value, PosIdx pos = noPos)
{
insert(Attr(name, value, pos));
}
@@ -147,7 +152,6 @@ public:
void push_back(const Attr & attr)
{
assert(bindings->size() < capacity);
bindings->push_back(attr);
}
@@ -155,7 +159,6 @@ public:
Value & alloc(std::string_view name, PosIdx pos = noPos);
[[nodiscard("must use created bindings")]]
Bindings * finish()
{
bindings->sort();
+20 -27
View File
@@ -341,7 +341,7 @@ EvalCache::EvalCache(
{
}
Value & EvalCache::getRootValue(EvalState & state)
Value * EvalCache::getRootValue(EvalState & state)
{
if (!value) {
debug("getting root value");
@@ -362,9 +362,8 @@ AttrCursor::AttrCursor(
std::optional<std::pair<AttrId, AttrValue>> && cachedValue)
: root(root), parent(parent), cachedValue(std::move(cachedValue))
{
if (value) {
_value = allocRootValue(*value);
}
if (value)
_value = allocRootValue(value);
}
AttrKey AttrCursor::getKey()
@@ -384,14 +383,14 @@ Value & AttrCursor::getValue(EvalState & state)
if (parent) {
auto & vParent = parent->first->getValue(state);
state.forceAttrs(vParent, noPos, "while searching for an attribute");
auto attr = vParent.attrs()->get(state.ctx.symbols.create(parent->second));
auto attr = vParent.attrs->get(state.ctx.symbols.create(parent->second));
if (!attr)
throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr(state));
_value = allocRootValue(attr->value);
} else
_value = allocRootValue(root->getRootValue(state));
}
return *_value;
return **_value;
}
std::vector<std::string> AttrCursor::getAttrPath(EvalState & state) const
@@ -438,17 +437,16 @@ Value & AttrCursor::forceValue(EvalState & state)
if (root->db && (!cachedValue || std::get_if<placeholder_t>(&cachedValue->second))) {
if (v.type() == nString)
cachedValue = {
root->db->setString(getKey(), v.str(), v.string().context), string_t{v.str(), {}}
};
cachedValue = {root->db->setString(getKey(), v.string.s, v.string.context),
string_t{v.string.s, {}}};
else if (v.type() == nPath) {
auto path = v.path().canonical().abs();
cachedValue = {root->db->setString(getKey(), path), string_t{path, {}}};
}
else if (v.type() == nBool)
cachedValue = {root->db->setBool(getKey(), v.boolean()), v.boolean()};
cachedValue = {root->db->setBool(getKey(), v.boolean), v.boolean};
else if (v.type() == nInt)
cachedValue = {root->db->setInt(getKey(), v.integer().value), int_t{v.integer()}};
cachedValue = {root->db->setInt(getKey(), v.integer.value), int_t{v.integer}};
else if (v.type() == nAttrs)
; // FIXME: do something?
else
@@ -501,7 +499,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(EvalState & state, const st
return nullptr;
//errors.make<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
auto attr = v.attrs()->get(state.ctx.symbols.create(name));
auto attr = v.attrs->get(state.ctx.symbols.create(name));
if (!attr) {
if (root->db) {
@@ -520,8 +518,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(EvalState & state, const st
}
return make_ref<AttrCursor>(
root, std::make_pair(shared_from_this(), name), &attr->value, std::move(cachedValue2)
);
root, std::make_pair(shared_from_this(), name), attr->value, std::move(cachedValue2));
}
ref<AttrCursor> AttrCursor::getAttr(EvalState & state, const std::string & name)
@@ -566,7 +563,7 @@ std::string AttrCursor::getString(EvalState & state)
state.ctx.errors.make<TypeError>("'%s' is not a string but %s", getAttrPathStr(state), v.type()).debugThrow();
}
return v.type() == nString ? std::string(v.str()) : v.path().to_string();
return v.type() == nString ? v.string.s : v.path().to_string();
}
string_t AttrCursor::getStringWithContext(EvalState & state)
@@ -608,7 +605,7 @@ string_t AttrCursor::getStringWithContext(EvalState & state)
if (v.type() == nString) {
NixStringContext context;
copyContext(v, context);
return {std::string(v.str()), std::move(context)};
return {v.string.s, std::move(context)};
} else if (v.type() == nPath) {
return {v.path().to_string(), {}};
} else {
@@ -635,7 +632,7 @@ bool AttrCursor::getBool(EvalState & state)
if (v.type() != nBool)
state.ctx.errors.make<TypeError>("'%s' is not a Boolean", getAttrPathStr(state)).debugThrow();
return v.boolean();
return v.boolean;
}
NixInt AttrCursor::getInt(EvalState & state)
@@ -657,7 +654,7 @@ NixInt AttrCursor::getInt(EvalState & state)
if (v.type() != nInt)
state.ctx.errors.make<TypeError>("'%s' is not an integer", getAttrPathStr(state)).debugThrow();
return v.integer();
return v.integer;
}
std::vector<std::string> AttrCursor::getListOfStrings(EvalState & state)
@@ -684,15 +681,11 @@ std::vector<std::string> AttrCursor::getListOfStrings(EvalState & state)
std::vector<std::string> res;
for (auto & elem : v.listItems()) {
res.push_back(std::string(
state.forceStringNoCtx(elem, noPos, "while evaluating an attribute for caching")
));
}
for (auto & elem : v.listItems())
res.push_back(std::string(state.forceStringNoCtx(*elem, noPos, "while evaluating an attribute for caching")));
if (root->db) {
if (root->db)
cachedValue = {root->db->setListOfStrings(getKey(), res), res};
}
return res;
}
@@ -717,8 +710,8 @@ std::vector<std::string> AttrCursor::getAttrs(EvalState & state)
state.ctx.errors.make<TypeError>("'%s' is not an attribute set", getAttrPathStr(state)).debugThrow();
fullattr_t attrs;
for (auto & attr : *getValue(state).attrs())
attrs.p.emplace_back(state.ctx.symbols[attr.name]);
for (auto & attr : *getValue(state).attrs)
attrs.p.push_back(state.ctx.symbols[attr.name]);
std::sort(attrs.p.begin(), attrs.p.end());
if (root->db)
+2 -2
View File
@@ -12,7 +12,7 @@ namespace nix::eval_cache {
struct AttrDb;
class AttrCursor;
typedef std::function<Value(EvalState &)> RootLoader;
typedef std::function<Value *(EvalState &)> RootLoader;
/**
* EvalState with caching support. Historically this was part of EvalState,
@@ -42,7 +42,7 @@ class EvalCache : public std::enable_shared_from_this<EvalCache>
RootLoader rootLoader;
RootValue value;
Value & getRootValue(EvalState & state);
Value * getRootValue(EvalState & state);
public:
+43 -94
View File
@@ -5,94 +5,59 @@
#include "lix/libexpr/eval.hh"
#include "lix/libexpr/eval-error.hh"
#include "lix/libexpr/gc-alloc.hh"
#include "value.hh"
#include <cstdint>
namespace nix {
inline Value::Value(app_t, EvalMemory & mem, Value & lhs, Value & rhs)
{
auto app = static_cast<Value::App *>(mem.allocBytes(sizeof(Value::App) + sizeof(Value *)));
app->_left = lhs;
app->_n = 1;
app->_args[0] = rhs;
raw = tag(tApp, app);
}
inline Value::Value(app_t, EvalMemory & mem, Value & lhs, std::span<Value> args)
{
auto app = static_cast<Value::App *>(mem.allocBytes(sizeof(Value::App) + args.size_bytes()));
app->_left = lhs;
app->_n = args.size();
std::copy(args.begin(), args.end(), app->_args);
raw = tag(tApp, app);
}
inline Value::Value(thunk_t, EvalMemory & mem, Env & env, Expr & expr)
{
auto thunk = mem.allocType<Thunk>();
*thunk = {._env = reinterpret_cast<uintptr_t>(&env), .expr = &expr};
raw = tag(tThunk, thunk);
}
inline Value::Value(lambda_t, EvalMemory & mem, Env & env, ExprLambda & lambda)
{
auto lp = mem.allocType<Lambda>();
new (lp) Lambda{env, lambda};
raw = tag(tAuxiliary, lp);
}
[[gnu::always_inline]]
void * EvalMemory::allocBytes(size_t size)
Value * EvalMemory::allocValue()
{
#if HAVE_BOEHMGC
/* We use the boehm batch allocator to speed up allocations of Values (of which there are many).
GC_malloc_many returns a linked list of objects of the given size, where the first word
of each object is also the pointer to the next object in the list. This also means that we
have to explicitly clear the first word of every object we take. */
// NOTE: we purposely do not allocate 0 byte blocks on caches; we never allocate
// zero bytes anyway, and it makes cache index calculation a little bit simpler.
const auto cacheIdx = (size - 1) / CACHE_INCREMENT;
if (cacheIdx < CACHES) {
const auto roundedSize = (cacheIdx + 1) * CACHE_INCREMENT;
auto & cache = gcCache[cacheIdx];
if (!cache) {
cache = GC_malloc_many(roundedSize);
if (!cache) {
throw std::bad_alloc();
}
}
/* GC_NEXT is a convenience macro for accessing the first word of an object.
Take the first list item, advance the list to the next item, and clear the next pointer.
*/
void * p = cache;
cache = GC_NEXT(p);
GC_NEXT(p) = nullptr;
return p;
if (!*valueAllocCache) {
*valueAllocCache = GC_malloc_many(sizeof(Value));
if (!*valueAllocCache) throw std::bad_alloc();
}
/* GC_NEXT is a convenience macro for accessing the first word of an object.
Take the first list item, advance the list to the next item, and clear the next pointer. */
void * p = *valueAllocCache;
*valueAllocCache = GC_NEXT(p);
GC_NEXT(p) = nullptr;
#else
void * p = gcAllocBytes(sizeof(Value));
#endif
return gcAllocBytes(size);
stats.nrValues++;
return static_cast<Value *>(p);
}
/// `gcAllocType`, but using allocation caches to amortize allocation overhead.
template<typename T>
[[gnu::always_inline]]
T * EvalMemory::allocType(size_t n)
{
return static_cast<T *>(allocBytes(checkedArrayAllocSize(sizeof(T), n)));
}
[[gnu::always_inline]]
Env & EvalMemory::allocEnv(size_t size)
{
static_assert(CACHES * CACHE_INCREMENT >= sizeof(Env) + sizeof(Value *));
stats.nrEnvs++;
stats.nrValuesInEnvs += size;
Env * env = static_cast<Env *>(allocBytes(sizeof(Env) + size * sizeof(Value *)));
Env * env;
#if HAVE_BOEHMGC
if (size == 1) {
/* see allocValue for explanations. */
if (!*env1AllocCache) {
*env1AllocCache = GC_malloc_many(sizeof(Env) + sizeof(Value *));
if (!*env1AllocCache) throw std::bad_alloc();
}
void * p = *env1AllocCache;
*env1AllocCache = GC_NEXT(p);
GC_NEXT(p) = nullptr;
env = static_cast<Env *>(p);
} else
#endif
env = static_cast<Env *>(gcAllocBytes(sizeof(Env) + size * sizeof(Value *)));
/* We assume that env->values has been cleared by the allocator; maybeThunk() and lookupVar fromWith expect this. */
@@ -104,38 +69,22 @@ Env & EvalMemory::allocEnv(size_t size)
void EvalState::forceValue(Value & v, const PosIdx pos)
{
if (v.isThunk()) {
auto & thunk = v.thunk();
if (thunk.resolved()) {
v = thunk.result();
} else {
const auto backup = thunk;
Env * env = thunk.env();
Expr & expr = *thunk.expr;
thunk = Value::blackHole;
try {
expr.eval(*this, *env, v);
thunk.resolve(v);
} catch (...) {
thunk = backup;
tryFixupBlackHolePos(v, pos);
throw;
}
}
} else if (v.isApp()) {
auto & app = v.app();
if (app.resolved()) {
v = app.result();
} else {
auto target = app.target();
if (!target.isPrimOp() || target.primOp()->arity <= app.totalArgs()) {
auto tmp = v.app().left();
callFunction(tmp, v.app().args(), v, pos);
app.resolve(v);
}
Env * env = v.thunk.env;
Expr & expr = *v.thunk.expr;
try {
v.mkBlackhole();
expr.eval(*this, *env, v);
} catch (...) {
v.mkThunk(env, expr);
tryFixupBlackHolePos(v, pos);
throw;
}
}
else if (v.isApp())
callFunction(*v.app.left, *v.app.right, v, pos);
}
[[gnu::always_inline]]
inline void EvalState::forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx)
{
+310 -388
View File
File diff suppressed because it is too large Load Diff
+68 -21
View File
@@ -36,7 +36,52 @@ namespace eval_cache {
class EvalCache;
}
std::ostream & operator<<(std::ostream & output, const PrimOp & primOp);
/**
* Function that implements a primop.
*/
using PrimOpImpl = void(EvalState & state, Value ** args, Value & v);
/**
* Info about a primitive operation, and its implementation
*/
struct PrimOp
{
/**
* Name of the primop. `__` prefix is treated specially.
*/
std::string name;
/**
* Names of the parameters of a primop, for primops that take a
* fixed number of arguments to be substituted for these parameters.
*/
std::vector<std::string> args;
/**
* Aritiy of the primop.
*
* If `args` is not empty, this field will be computed from that
* field instead, so it doesn't need to be manually set.
*/
size_t arity = 0;
/**
* Optional free-form documentation about the primop.
*/
const char * doc = nullptr;
/**
* Implementation of the primop.
*/
std::function<PrimOpImpl> fun;
/**
* Optional experimental for this to be gated on.
*/
std::optional<ExperimentalFeature> experimentalFeature;
};
std::ostream & operator<<(std::ostream & output, PrimOp & primOp);
/**
* Info about a constant
@@ -61,12 +106,12 @@ struct Constant
bool impureOnly = false;
};
using ValMap = GcMap<std::string, Value>;
using ValMap = GcMap<std::string, Value *>;
struct alignas(Value::Acb::TAG_ALIGN) Env
struct Env
{
Env * up;
Value values[0];
Value * values[0];
};
void printEnvBindings(const EvalState &es, const Expr & expr, const Env & env);
@@ -179,44 +224,43 @@ struct StaticSymbols
class EvalMemory
{
static constexpr size_t CACHES = 8;
static constexpr size_t CACHE_INCREMENT = sizeof(void *);
/**
* Allocation cache for GC'd Value objects.
*/
std::shared_ptr<void *> valueAllocCache;
/**
* Allocation caches for small values.
* Allocation cache for size-1 Env objects.
*/
void * gcCache[CACHES] = {};
std::shared_ptr<void *> env1AllocCache;
public:
struct Statistics
{
unsigned long nrEnvs = 0;
unsigned long nrValuesInEnvs = 0;
unsigned long nrValues = 0;
unsigned long nrAttrsets = 0;
unsigned long nrAttrsInAttrsets = 0;
unsigned long nrListElems = 0;
};
EvalMemory();
~EvalMemory();
EvalMemory(const EvalMemory &) = delete;
EvalMemory(EvalMemory &&) = delete;
EvalMemory & operator=(const EvalMemory &) = delete;
EvalMemory & operator=(EvalMemory &&) = delete;
inline void * allocBytes(size_t size);
template<typename T>
inline T * allocType(size_t n = 1);
inline Value * allocValue();
inline Env & allocEnv(size_t size);
Bindings * allocBindings(size_t capacity);
Value::List * newList(size_t length);
Value newList(size_t length);
BindingsBuilder buildBindings(SymbolTable & symbols, size_t capacity)
{
return BindingsBuilder(*this, symbols, allocBindings(capacity), capacity);
return BindingsBuilder(*this, symbols, allocBindings(capacity));
}
const Statistics getStats() const { return stats; }
@@ -263,9 +307,11 @@ private:
void createBaseEnv(const SearchPath & searchPath, const Path & storeDir);
void addConstant(const std::string & name, const Value & v, Constant info);
Value * addConstant(const std::string & name, const Value & v, Constant info);
void addPrimOp(PrimOpDetails && primOp);
void addConstant(const std::string & name, Value * v, Constant info);
Value * addPrimOp(PrimOp && primOp);
Value prepareNixPath(const SearchPath & searchPath);
@@ -793,11 +839,13 @@ public:
bool isFunctor(Value & fun);
void callFunction(Value & fun, std::span<Value> args, Value & vRes, const PosIdx pos);
// FIXME: use std::span
void callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos);
void callFunction(Value & fun, Value & arg, Value & vRes, const PosIdx pos)
{
callFunction(fun, {&arg, 1}, vRes, pos);
Value * args[] = {&arg};
callFunction(fun, 1, args, vRes, pos);
}
/**
@@ -837,8 +885,7 @@ public:
const SingleDerivedPath & p,
Value & v);
void
concatLists(Value & v, std::span<Value> lists, const PosIdx pos, std::string_view errorCtx);
void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos, std::string_view errorCtx);
private:
+4 -19
View File
@@ -41,24 +41,14 @@ static bool askForSetting(
auto reply = logger->ask(fmt("Do you want to allow configuration setting '%s' to be set to '" ANSI_RED "%s" ANSI_NORMAL "'?\nThis may allow the flake to gain root, see the nix.conf manual page (" ANSI_BOLD "y" ANSI_NORMAL "es/" ANSI_BOLD "n" ANSI_NORMAL "o/" ANSI_BOLD "N" ANSI_NORMAL "o to all) ", name, valueS)).value_or('n');
if (reply == 'N') {
printTaggedWarning("Rejecting all untrusted nix.conf entries");
printTaggedWarning(
"you can set '%s' to '%b' to automatically reject configuration options supplied by "
"flakes",
"accept-flake-config",
false
);
warn("Rejecting all untrusted nix.conf entries");
warn("you can set '%s' to '%b' to automatically reject configuration options supplied by flakes", "accept-flake-config", false);
negativeTrustOverride = true;
} else {
if (std::tolower(reply) == 'y') {
trusted = true;
} else {
printTaggedWarning(
"you can set '%s' to '%b' to automatically reject configuration options supplied "
"by flakes",
"accept-flake-config",
false
);
warn("you can set '%s' to '%b' to automatically reject configuration options supplied by flakes", "accept-flake-config", false);
}
if (std::tolower(logger->ask(fmt("do you want to permanently (in %s) mark this value as %s? (y/N) ", trustedListPath(), trusted ? "trusted": "untrusted" )).value_or('n')) == 'y') {
@@ -127,12 +117,7 @@ void ConfigFile::apply()
debug("accepting trusted flake configuration setting '%s'", name);
globalConfig.set(name, valueS);
} else {
printTaggedWarning(
"ignoring untrusted flake configuration setting '%s', pass '%s' to trust it (may "
"allow the flake to gain root, see the nix.conf manual page)",
name,
"--accept-flake-config"
);
warn("ignoring untrusted flake configuration setting '%s', pass '%s' to trust it (may allow the flake to gain root, see the nix.conf manual page)", name, "--accept-flake-config");
}
}
}
+131 -298
View File
@@ -94,67 +94,15 @@ static void expectType(EvalState & state, ValueType type,
showType(type), showType(value.type()), state.ctx.positions[pos]);
}
static std::pair<std::map<FlakeId, FlakeInput>, std::optional<fetchers::Attrs>> parseFlakeInputs(
EvalState & state,
Value & value,
const PosIdx pos,
const std::optional<Path> & baseDir,
InputPath lockRootPath,
unsigned depth,
bool allowSelf
);
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
EvalState & state, Value * value, const PosIdx pos,
const std::optional<Path> & baseDir, InputPath lockRootPath, unsigned depth);
static void parseFlakeInputAttr(EvalState & state, const Attr & attr, fetchers::Attrs & attrs)
static FlakeInput parseFlakeInput(EvalState & state,
const std::string & inputName, Value * value, const PosIdx pos,
const std::optional<Path> & baseDir, InputPath lockRootPath, unsigned depth)
{
// Allow selecting a subset of enum values
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
switch (attr.value.type()) {
case nString:
attrs.emplace(state.ctx.symbols[attr.name], std::string(attr.value.str()));
break;
case nBool:
attrs.emplace(state.ctx.symbols[attr.name], Explicit<bool>{attr.value.boolean()});
break;
case nInt: {
auto intValue = attr.value.integer().value;
if (intValue < 0) {
state.ctx.errors
.make<EvalError>(
"negative value given for flake input attribute %1%: %2%",
state.ctx.symbols[attr.name],
intValue
)
.debugThrow();
}
uint64_t asUnsigned = intValue;
attrs.emplace(state.ctx.symbols[attr.name], asUnsigned);
break;
}
default:
state.ctx.errors
.make<TypeError>(
"flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
state.ctx.symbols[attr.name],
showType(attr.value)
)
.debugThrow();
}
#pragma GCC diagnostic pop
}
static FlakeInput parseFlakeInput(
EvalState & state,
const std::string & inputName,
Value & value,
const PosIdx pos,
const std::optional<Path> & baseDir,
InputPath lockRootPath,
unsigned depth
)
{
expectType(state, nAttrs, value, pos);
expectType(state, nAttrs, *value, pos);
FlakeInput input;
@@ -166,28 +114,48 @@ static FlakeInput parseFlakeInput(
fetchers::Attrs attrs;
std::optional<std::string> url;
for (nix::Attr attr : *(value.attrs())) {
for (nix::Attr attr : *(value->attrs)) {
try {
if (attr.name == sUrl) {
expectType(state, nString, attr.value, attr.pos);
url = attr.value.str();
expectType(state, nString, *attr.value, attr.pos);
url = attr.value->string.s;
attrs.emplace("url", *url);
} else if (attr.name == sFlake) {
expectType(state, nBool, attr.value, attr.pos);
input.isFlake = attr.value.boolean();
expectType(state, nBool, *attr.value, attr.pos);
input.isFlake = attr.value->boolean;
} else if (attr.name == sInputs) {
input.overrides =
parseFlakeInputs(
state, attr.value, attr.pos, baseDir, lockRootPath, depth + 1, false
)
.first;
input.overrides = parseFlakeInputs(state, attr.value, attr.pos, baseDir, lockRootPath, depth + 1);
} else if (attr.name == sFollows) {
expectType(state, nString, attr.value, attr.pos);
auto follows(parseInputPath(attr.value.str()));
expectType(state, nString, *attr.value, attr.pos);
auto follows(parseInputPath(attr.value->string.s));
follows.insert(follows.begin(), lockRootPath.begin(), lockRootPath.end());
input.follows = follows;
} else {
parseFlakeInputAttr(state, attr, attrs);
// Allow selecting a subset of enum values
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
switch (attr.value->type()) {
case nString:
attrs.emplace(state.ctx.symbols[attr.name], attr.value->string.s);
break;
case nBool:
attrs.emplace(state.ctx.symbols[attr.name], Explicit<bool> { attr.value->boolean });
break;
case nInt: {
auto intValue = attr.value->integer.value;
if (intValue < 0) {
state.ctx.errors.make<EvalError>("negative value given for flake input attribute %1%: %2%", state.ctx.symbols[attr.name], intValue).debugThrow();
}
uint64_t asUnsigned = intValue;
attrs.emplace(state.ctx.symbols[attr.name], asUnsigned);
break;
}
default:
state.ctx.errors.make<TypeError>("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
state.ctx.symbols[attr.name], showType(*attr.value)).debugThrow();
}
#pragma GCC diagnostic pop
}
} catch (Error & e) {
e.addTrace(
@@ -222,72 +190,26 @@ static FlakeInput parseFlakeInput(
return input;
}
static std::pair<std::map<FlakeId, FlakeInput>, std::optional<fetchers::Attrs>> parseFlakeInputs(
EvalState & state,
Value & value,
const PosIdx pos,
const std::optional<Path> & baseDir,
InputPath lockRootPath,
unsigned depth,
bool allowSelf = true
)
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
EvalState & state, Value * value, const PosIdx pos,
const std::optional<Path> & baseDir, InputPath lockRootPath, unsigned depth)
{
std::map<FlakeId, FlakeInput> inputs;
expectType(state, nAttrs, value, pos);
expectType(state, nAttrs, *value, pos);
std::optional<fetchers::Attrs> selfAttrs = std::nullopt;
for (const nix::Attr & inputAttr : *value.attrs()) {
std::string inputName{state.ctx.symbols[inputAttr.name]};
if (inputName == "self") {
experimentalFeatureSettings.require(Xp::FlakeSelfAttrs);
if (!allowSelf) {
throw Error(
"'self' input attributes not allowed at %s", state.ctx.positions[inputAttr.pos]
);
}
expectType(state, nAttrs, inputAttr.value, inputAttr.pos);
selfAttrs = selfAttrs.value_or(fetchers::Attrs{});
for (auto & attr : *inputAttr.value.attrs()) {
parseFlakeInputAttr(state, attr, *selfAttrs);
}
} else {
inputs.emplace(
inputName,
parseFlakeInput(
state, inputName, inputAttr.value, inputAttr.pos, baseDir, lockRootPath, depth
)
);
}
for (nix::Attr & inputAttr : *(*value).attrs) {
inputs.emplace(state.ctx.symbols[inputAttr.name],
parseFlakeInput(state,
state.ctx.symbols[inputAttr.name],
inputAttr.value,
inputAttr.pos,
baseDir,
lockRootPath,
depth));
}
return {inputs, selfAttrs};
}
static std::optional<FlakeRef> applySelfAttrs(const FlakeRef & ref, const Flake & flake)
{
// silently failing here is ok; since the parser requires the feature, we'll
// crash much earlier if it wasn't enabled
if (!flake.selfAttrs.has_value() || !experimentalFeatureSettings.isEnabled(Xp::FlakeSelfAttrs))
{
return std::nullopt;
}
static std::set<std::string> allowedAttrs{"submodules"};
auto newRef(ref);
for (auto & attr : *flake.selfAttrs) {
if (!allowedAttrs.contains(attr.first)) {
throw Error("flake 'self' attribute '%s' is not supported", attr.first);
}
newRef.input.attrs.insert_or_assign(attr.first, attr.second);
}
if (newRef != ref) {
return newRef;
}
return std::nullopt;
return inputs;
}
static Flake getFlake(
@@ -334,55 +256,26 @@ static Flake getFlake(
Value vInfo;
state.eval(flakeExpr, vInfo);
if (auto description = vInfo.attrs()->get(state.ctx.s.description)) {
expectType(state, nString, description->value, description->pos);
flake.description = description->value.str();
if (auto description = vInfo.attrs->get(state.ctx.s.description)) {
expectType(state, nString, *description->value, description->pos);
flake.description = description->value->string.s;
}
auto sInputs = state.ctx.symbols.create("inputs");
if (auto inputs = vInfo.attrs()->get(sInputs)) {
auto [flakeInputs, selfAttrs] =
parseFlakeInputs(state, inputs->value, inputs->pos, flakeDir, lockRootPath, 0, true);
flake.inputs = std::move(flakeInputs);
flake.selfAttrs = std::move(selfAttrs);
}
if (auto inputs = vInfo.attrs->get(sInputs))
flake.inputs = parseFlakeInputs(state, inputs->value, inputs->pos, flakeDir, lockRootPath, 0);
auto newLockedRef = applySelfAttrs(lockedRef, flake);
if (newLockedRef.has_value()) {
debug("refetching input '%s' due to self attribute", *newLockedRef);
// FIXME: need to remove attrs that are invalidated by the changed input
// attrs, such as 'narHash'.
newLockedRef->input.attrs.erase("narHash");
auto [sourceInfo2, resolvedRef2, lockedRef2] =
state.aio.blockOn(fetchOrSubstituteTree(state.ctx, *newLockedRef, false, flakeCache));
if (auto outputs = vInfo.attrs->get(state.ctx.s.outputs)) {
expectType(state, nFunction, *outputs->value, outputs->pos);
lockedRef = lockedRef2;
flake.lockedRef = lockedRef;
sourceInfo = sourceInfo2;
flake.sourceInfo = std::make_shared<fetchers::Tree>(std::move(sourceInfo));
resolvedRef = resolvedRef2;
flake.resolvedRef = resolvedRef;
}
if (auto outputs = vInfo.attrs()->get(state.ctx.s.outputs)) {
expectType(state, nFunction, outputs->value, outputs->pos);
if (outputs->value.isLambda()) {
if (auto pattern =
dynamic_cast<AttrsPattern *>(outputs->value.lambda().fun->pattern.get());
pattern)
{
if (outputs->value->isLambda()) {
if (auto pattern = dynamic_cast<AttrsPattern *>(outputs->value->lambda.fun->pattern.get()); pattern) {
for (auto & formal : pattern->formals) {
if (formal.name != state.ctx.s.self)
flake.inputs.emplace(
state.ctx.symbols[formal.name],
FlakeInput{
.ref = parseFlakeRef(std::string(state.ctx.symbols[formal.name]))
}
);
flake.inputs.emplace(state.ctx.symbols[formal.name], FlakeInput {
.ref = parseFlakeRef(state.ctx.symbols[formal.name])
});
}
}
}
@@ -392,71 +285,46 @@ static Flake getFlake(
auto sNixConfig = state.ctx.symbols.create("nixConfig");
if (auto nixConfig = vInfo.attrs()->get(sNixConfig)) {
expectType(state, nAttrs, nixConfig->value, nixConfig->pos);
if (auto nixConfig = vInfo.attrs->get(sNixConfig)) {
expectType(state, nAttrs, *nixConfig->value, nixConfig->pos);
for (auto & setting : *nixConfig->value.attrs()) {
forceTrivialValue(state, setting.value, setting.pos);
if (setting.value.type() == nString) {
for (auto & setting : *nixConfig->value->attrs) {
forceTrivialValue(state, *setting.value, setting.pos);
if (setting.value->type() == nString)
flake.config.settings.emplace(
state.ctx.symbols[setting.name],
std::string(state.forceStringNoCtx(setting.value, setting.pos, ""))
);
} else if (setting.value.type() == nPath) {
std::string(state.forceStringNoCtx(*setting.value, setting.pos, "")));
else if (setting.value->type() == nPath) {
NixStringContext emptyContext = {};
flake.config.settings.emplace(
state.ctx.symbols[setting.name],
state
.coerceToString(
setting.pos,
setting.value,
emptyContext,
"",
StringCoercionMode::Strict,
true,
true
)
.toOwned()
);
} else if (setting.value.type() == nInt) {
state.coerceToString(setting.pos, *setting.value, emptyContext, "", StringCoercionMode::Strict, true, true) .toOwned());
}
else if (setting.value->type() == nInt)
flake.config.settings.emplace(
state.ctx.symbols[setting.name],
state.forceInt(setting.value, setting.pos, "").value
);
} else if (setting.value.type() == nBool) {
state.forceInt(*setting.value, setting.pos, "").value);
else if (setting.value->type() == nBool)
flake.config.settings.emplace(
state.ctx.symbols[setting.name],
Explicit<bool>{state.forceBool(setting.value, setting.pos, "")}
);
} else if (setting.value.type() == nList) {
Explicit<bool> { state.forceBool(*setting.value, setting.pos, "") });
else if (setting.value->type() == nList) {
std::vector<std::string> ss;
for (auto & elem : setting.value.listItems()) {
if (elem.type() != nString) {
state.ctx.errors
.make<TypeError>(
"list element in flake configuration setting '%s' is %s while a "
"string is expected",
state.ctx.symbols[setting.name],
showType(setting.value)
)
.debugThrow();
}
ss.emplace_back(state.forceStringNoCtx(elem, setting.pos, ""));
for (auto elem : setting.value->listItems()) {
if (elem->type() != nString)
state.ctx.errors.make<TypeError>("list element in flake configuration setting '%s' is %s while a string is expected",
state.ctx.symbols[setting.name], showType(*setting.value)).debugThrow();
ss.emplace_back(state.forceStringNoCtx(*elem, setting.pos, ""));
}
flake.config.settings.emplace(state.ctx.symbols[setting.name], ss);
} else {
state.ctx.errors
.make<TypeError>(
"flake configuration setting '%s' is %s",
state.ctx.symbols[setting.name],
showType(setting.value)
)
.debugThrow();
}
else
state.ctx.errors.make<TypeError>("flake configuration setting '%s' is %s",
state.ctx.symbols[setting.name], showType(*setting.value)).debugThrow();
}
}
for (auto & attr : *vInfo.attrs()) {
for (auto & attr : *vInfo.attrs) {
if (attr.name != state.ctx.s.description &&
attr.name != sInputs &&
attr.name != state.ctx.s.outputs &&
@@ -589,11 +457,9 @@ LockedFlake lockFlake(
auto follow = inputPath2.back();
inputPath2.pop_back();
if (inputPath2 == inputPathPrefix && !flakeInputs.count(follow))
printTaggedWarning(
warn(
"input '%s' has an override for a non-existent input '%s'",
printInputPath(inputPathPrefix),
follow
);
printInputPath(inputPathPrefix), follow);
}
/* Go over the flake inputs, resolve/fetch them if
@@ -804,17 +670,12 @@ LockedFlake lockFlake(
for (auto & i : lockFlags.inputOverrides)
if (!overridesUsed.count(i.first))
printTaggedWarning(
"the flag '--override-input %s %s' does not match any input",
printInputPath(i.first),
i.second
);
warn("the flag '--override-input %s %s' does not match any input",
printInputPath(i.first), i.second);
for (auto & i : lockFlags.inputUpdates)
if (!updatesUsed.count(i))
printTaggedWarning(
"'%s' does not match any input of this flake", printInputPath(i)
);
warn("'%s' does not match any input of this flake", printInputPath(i));
/* Check 'follows' inputs. */
newLockFile.check();
@@ -832,12 +693,7 @@ LockedFlake lockFlake(
if (sourcePath || lockFlags.outputLockFilePath) {
if (auto unlockedInput = newLockFile.isUnlocked()) {
if (fetchSettings.warnDirty)
printTaggedWarning(
"will not write lock file of flake '%s' because it has an unlocked "
"input ('%s')",
topRef,
*unlockedInput
);
warn("will not write lock file of flake '%s' because it has an unlocked input ('%s')", topRef, *unlockedInput);
} else {
if (!lockFlags.updateLockFile)
throw Error("flake '%s' requires lock file changes but they're not allowed due to '--no-update-lock-file'", topRef);
@@ -857,19 +713,11 @@ LockedFlake lockFlake(
auto s = chomp(diff);
if (lockFileExists) {
if (s.empty())
printTaggedWarning(
"updating lock file '%s'", outputLockFilePath
);
warn("updating lock file '%s'", outputLockFilePath);
else
printTaggedWarning(
"updating lock file '%s':\n%s",
outputLockFilePath,
Uncolored(s)
);
warn("updating lock file '%s':\n%s", outputLockFilePath, Uncolored(s));
} else
printTaggedWarning(
"creating lock file '%s':\n%s", outputLockFilePath, Uncolored(s)
);
warn("creating lock file '%s':\n%s", outputLockFilePath, Uncolored(s));
std::optional<std::string> commitMessage = std::nullopt;
@@ -887,13 +735,9 @@ LockedFlake lockFlake(
commitMessage = cm;
}
state.aio.blockOn(topRef.input.putFile(
CanonPath(
(topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock"
),
newLockFileS,
commitMessage
));
topRef.input.putFile(
CanonPath((topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock"),
newLockFileS, commitMessage);
}
/* Rewriting the lockfile changed the top-level
@@ -906,10 +750,7 @@ LockedFlake lockFlake(
if (lockFlags.commitLockFile &&
flake.lockedRef.input.getRev() &&
prevLockedRef.input.getRev() != flake.lockedRef.input.getRev())
printTaggedWarning(
"committed new revision '%s'",
flake.lockedRef.input.getRev()->gitRev()
);
warn("committed new revision '%s'", flake.lockedRef.input.getRev()->gitRev());
/* Make sure that we picked up the change,
i.e. the tree should usually be dirty
@@ -922,9 +763,7 @@ LockedFlake lockFlake(
} else
throw Error("cannot write modified lock file of flake '%s' (use '--no-write-lock-file' to ignore)", topRef);
} else {
printTaggedWarning(
"not writing modified lock file of flake '%s':\n%s", topRef, chomp(diff)
);
warn("not writing modified lock file of flake '%s':\n%s", topRef, chomp(diff));
flake.forceDirty = true;
}
}
@@ -941,39 +780,34 @@ void callFlake(EvalState & state,
const LockedFlake & lockedFlake,
Value & vRes)
{
Value vLocks;
Value vRootSrc;
Value vRootSubdir;
Value vTmp1;
Value vTmp2;
auto vLocks = state.ctx.mem.allocValue();
auto vRootSrc = state.ctx.mem.allocValue();
auto vRootSubdir = state.ctx.mem.allocValue();
auto vTmp1 = state.ctx.mem.allocValue();
auto vTmp2 = state.ctx.mem.allocValue();
vLocks.mkString(lockedFlake.lockFile.to_string());
vLocks->mkString(lockedFlake.lockFile.to_string());
emitTreeAttrs(
state.ctx,
*lockedFlake.flake.sourceInfo,
lockedFlake.flake.lockedRef.input,
vRootSrc,
*vRootSrc,
false,
lockedFlake.flake.forceDirty
);
lockedFlake.flake.forceDirty);
vRootSubdir.mkString(lockedFlake.flake.lockedRef.subdir);
vRootSubdir->mkString(lockedFlake.flake.lockedRef.subdir);
if (!state.ctx.caches.vCallFlake) {
state.ctx.caches.vCallFlake = allocRootValue({});
state.eval(
state.ctx.parseExprFromString(
#include "call-flake.nix.gen.hh"
, CanonPath::root
),
*state.ctx.caches.vCallFlake
);
state.ctx.caches.vCallFlake = allocRootValue(state.ctx.mem.allocValue());
state.eval(state.ctx.parseExprFromString(
#include "call-flake.nix.gen.hh"
, CanonPath::root), **state.ctx.caches.vCallFlake);
}
state.callFunction(*state.ctx.caches.vCallFlake, vLocks, vTmp1, noPos);
state.callFunction(vTmp1, vRootSrc, vTmp2, noPos);
state.callFunction(vTmp2, vRootSubdir, vRes, noPos);
state.callFunction(**state.ctx.caches.vCallFlake, *vLocks, *vTmp1, noPos);
state.callFunction(*vTmp1, *vRootSrc, *vTmp2, noPos);
state.callFunction(*vTmp2, *vRootSubdir, vRes, noPos);
}
void prim_getFlake(EvalState & state, Value * * args, Value & v)
@@ -1023,10 +857,10 @@ void prim_flakeRefToString(
state.forceAttrs(*args[0], noPos,
"while evaluating the argument passed to builtins.flakeRefToString");
fetchers::Attrs attrs;
for (const auto & attr : *args[0]->attrs()) {
auto t = attr.value.type();
for (const auto & attr : *args[0]->attrs) {
auto t = attr.value->type();
if (t == nInt) {
auto intValue = attr.value.integer().value;
auto intValue = attr.value->integer.value;
if (intValue < 0) {
state.ctx.errors.make<EvalError>("negative value given for flake ref attr %1%: %2%", state.ctx.symbols[attr.name], intValue).debugThrow();
@@ -1035,18 +869,17 @@ void prim_flakeRefToString(
attrs.emplace(state.ctx.symbols[attr.name], asUnsigned);
} else if (t == nBool) {
attrs.emplace(state.ctx.symbols[attr.name], Explicit<bool>{attr.value.boolean()});
attrs.emplace(state.ctx.symbols[attr.name],
Explicit<bool> { attr.value->boolean });
} else if (t == nString) {
attrs.emplace(state.ctx.symbols[attr.name], std::string(attr.value.str()));
attrs.emplace(state.ctx.symbols[attr.name],
std::string(attr.value->str()));
} else {
state.ctx.errors
.make<EvalError>(
"flake reference attribute sets may only contain integers, Booleans, "
"and strings, but attribute '%s' is %s",
state.ctx.symbols[attr.name],
showType(attr.value)
)
.debugThrow();
state.ctx.errors.make<EvalError>(
"flake reference attribute sets may only contain integers, Booleans, "
"and strings, but attribute '%s' is %s",
state.ctx.symbols[attr.name],
showType(*attr.value)).debugThrow();
}
}
auto flakeRef = FlakeRef::fromAttrs(attrs);
-14
View File
@@ -72,39 +72,25 @@ struct Flake
* The original flake specification (by the user)
*/
FlakeRef originalRef;
/**
* registry references and caching resolved to the specific underlying flake
*/
FlakeRef resolvedRef;
/**
* the specific local store result of invoking the fetcher
*/
FlakeRef lockedRef;
/**
* pretend that 'lockedRef' is dirty
*/
bool forceDirty = false;
std::optional<std::string> description;
std::shared_ptr<const fetchers::Tree> sourceInfo;
FlakeInputs inputs;
/**
* Attributes to be retroactively applied to the `self` input
* (such as `submodules = true`).
*/
std::optional<fetchers::Attrs> selfAttrs;
/**
* 'nixConfig' attribute
*/
ConfigFile config;
~Flake();
};
+2 -2
View File
@@ -6,13 +6,13 @@ namespace nix {
FunctionCallTrace::FunctionCallTrace(const Pos & pos) : pos(pos) {
auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration);
printMsg(lvlInfo, "function-trace entered %1% at %2%", Uncolored(pos), Uncolored(ns.count()));
printMsg(lvlInfo, "function-trace entered %1% at %2%", pos, ns.count());
}
FunctionCallTrace::~FunctionCallTrace() {
auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration);
printMsg(lvlInfo, "function-trace exited %1% at %2%", Uncolored(pos), Uncolored(ns.count()));
printMsg(lvlInfo, "function-trace exited %1% at %2%", pos, ns.count());
}
}
+15 -19
View File
@@ -95,24 +95,6 @@ inline void * gcAllocBytes(size_t n)
return ptr;
}
[[gnu::always_inline]]
inline size_t checkedArrayAllocSize(size_t size, size_t howMany)
{
// NOTE: size_t * size_t, which can definitely overflow.
// Unsigned integer overflow is definitely a bug, but isn't undefined
// behavior, so we can just check if we overflowed after the fact.
// However, people can and do request zero sized allocations, so we need
// to check that neither of our multiplicands were zero before complaining
// about it.
auto checkedSz = checked::Checked<size_t>(howMany) * size;
if (checkedSz.overflowed()) {
// Congrats, you done did an overflow.
throw std::bad_alloc();
}
return checkedSz.valueWrapping();
}
/// Typed, safe wrapper around calloc() (transparently GC-enabled). Allocates
/// enough for the requested count of the specified type. Also checks for
/// nullptr (and throws @ref std::bad_alloc), and casts the void pointer to
@@ -121,7 +103,21 @@ template<typename T>
[[gnu::always_inline]]
inline T * gcAllocType(size_t howMany = 1)
{
return static_cast<T *>(gcAllocBytes(checkedArrayAllocSize(sizeof(T), howMany)));
// NOTE: size_t * size_t, which can definitely overflow.
// Unsigned integer overflow is definitely a bug, but isn't undefined
// behavior, so we can just check if we overflowed after the fact.
// However, people can and do request zero sized allocations, so we need
// to check that neither of our multiplicands were zero before complaining
// about it.
// NOLINTNEXTLINE(bugprone-sizeof-expression): yeah we only seem to alloc pointers with this. the calculation *is* correct though!
auto checkedSz = checked::Checked<size_t>(howMany) * sizeof(T);
size_t sz = checkedSz.valueWrapping();
if (checkedSz.overflowed()) {
// Congrats, you done did an overflow.
throw std::bad_alloc();
}
return static_cast<T *>(gcAllocBytes(sz));
}
/// GC-transparently allocates a buffer for a C-string of @ref size *bytes*,
+2 -2
View File
@@ -17,8 +17,8 @@ using SmallVector = boost::container::small_vector<T, nItems, TraceableAllocator
/**
* A vector of value pointers. See `SmallVector`.
*/
template<size_t nItems>
using SmallValueVector = SmallVector<Value, nItems>;
template <size_t nItems>
using SmallValueVector = SmallVector<Value *, nItems>;
/**
* A vector of values that must not be referenced after the vector is destroyed.
+85 -112
View File
@@ -64,13 +64,9 @@ try {
std::string DrvInfo::queryName(EvalState & state)
{
if (name == "" && attrs) {
auto i = attrs->get(state.ctx.s.name);
if (!i) {
state.ctx.errors.make<TypeError>("derivation name missing").debugThrow();
}
name = state.forceStringNoCtx(
i->value, noPos, "while evaluating the 'name' attribute of a derivation"
);
auto i = attrs->find(state.ctx.s.name);
if (i == attrs->end()) state.ctx.errors.make<TypeError>("derivation name missing").debugThrow();
name = state.forceStringNoCtx(*i->value, noPos, "while evaluating the 'name' attribute of a derivation");
}
return name;
}
@@ -79,12 +75,8 @@ std::string DrvInfo::queryName(EvalState & state)
std::string DrvInfo::querySystem(EvalState & state)
{
if (system == "" && attrs) {
auto i = attrs->get(state.ctx.s.system);
system = !i
? "unknown"
: state.forceStringNoCtx(
i->value, i->pos, "while evaluating the 'system' attribute of a derivation"
);
auto i = attrs->find(state.ctx.s.system);
system = i == attrs->end() ? "unknown" : state.forceStringNoCtx(*i->value, i->pos, "while evaluating the 'system' attribute of a derivation");
}
return system;
}
@@ -93,18 +85,12 @@ std::string DrvInfo::querySystem(EvalState & state)
std::optional<StorePath> DrvInfo::queryDrvPath(EvalState & state)
{
if (!drvPath && attrs) {
auto i = attrs->get(state.ctx.s.drvPath);
Bindings::iterator i = attrs->find(state.ctx.s.drvPath);
NixStringContext context;
if (!i) {
if (i == attrs->end())
drvPath = {std::nullopt};
} else {
drvPath = {state.coerceToStorePath(
i->pos,
i->value,
context,
"while evaluating the 'drvPath' attribute of a derivation"
)};
}
else
drvPath = {state.coerceToStorePath(i->pos, *i->value, context, "while evaluating the 'drvPath' attribute of a derivation")};
}
return drvPath.value_or(std::nullopt);
}
@@ -121,13 +107,10 @@ StorePath DrvInfo::requireDrvPath(EvalState & state)
StorePath DrvInfo::queryOutPath(EvalState & state)
{
if (!outPath && attrs) {
auto i = attrs->get(state.ctx.s.outPath);
Bindings::iterator i = attrs->find(state.ctx.s.outPath);
NixStringContext context;
if (i) {
outPath = state.coerceToStorePath(
i->pos, i->value, context, "while evaluating the output path of a derivation"
);
}
if (i != attrs->end())
outPath = state.coerceToStorePath(i->pos, *i->value, context, "while evaluating the output path of a derivation");
}
if (!outPath)
throw UnimplementedError("CA derivations are not yet supported");
@@ -150,7 +133,7 @@ void DrvInfo::fillOutputs(EvalState & state, bool withPaths)
return;
}
const Attr * outputs = this->attrs->get(state.ctx.s.outputs);
Attr * outputs = this->attrs->get(state.ctx.s.outputs);
if (outputs == nullptr) {
fillDefault();
return;
@@ -158,21 +141,27 @@ void DrvInfo::fillOutputs(EvalState & state, bool withPaths)
// NOTE(Qyriad): I don't think there is any codepath that can cause this to error.
state.forceList(
outputs->value, outputs->pos, "while evaluating the 'outputs' attribute of a derivation"
*outputs->value,
outputs->pos,
"while evaluating the 'outputs' attribute of a derivation"
);
for (auto && [idx, elem] : enumerate(outputs->value.listItems())) {
for (auto [idx, elem] : enumerate(outputs->value->listItems())) {
// NOTE(Qyriad): This error should be *extremely* rare in practice.
// It is impossible to construct with `stdenv.mkDerivation`,
// `builtins.derivation`, or even `derivationStrict`. As far as we can tell,
// it is only possible by overriding a derivation attrset already created by
// one of those with `//` to introduce the failing `outputs` entry.
auto errMsg = fmt("while evaluating output %d of a derivation", idx);
std::string_view outputName = state.forceStringNoCtx(elem, outputs->pos, errMsg);
std::string_view outputName = state.forceStringNoCtx(
*elem,
outputs->pos,
errMsg
);
if (withPaths) {
// Find the attr with this output's name...
const Attr * out = this->attrs->get(state.ctx.symbols.create(outputName));
Attr * out = this->attrs->get(state.ctx.symbols.create(outputName));
if (out == nullptr) {
// FIXME: throw error?
continue;
@@ -180,10 +169,10 @@ void DrvInfo::fillOutputs(EvalState & state, bool withPaths)
// Meanwhile we couldn't figure out any circumstances
// that cause this to error.
state.forceAttrs(out->value, outputs->pos, errMsg);
state.forceAttrs(*out->value, outputs->pos, errMsg);
// ...and evaluate its `outPath` attribute.
const Attr * outPath = out->value.attrs()->get(state.ctx.s.outPath);
Attr * outPath = out->value->attrs->get(state.ctx.s.outPath);
if (outPath == nullptr) {
continue;
// FIXME: throw error?
@@ -192,7 +181,12 @@ void DrvInfo::fillOutputs(EvalState & state, bool withPaths)
NixStringContext context;
// And idk what could possibly cause this one to error
// that wouldn't error before here.
auto storePath = state.coerceToStorePath(outPath->pos, outPath->value, context, errMsg);
auto storePath = state.coerceToStorePath(
outPath->pos,
*outPath->value,
context,
errMsg
);
this->outputs.emplace(outputName, storePath);
} else {
this->outputs.emplace(outputName, std::nullopt);
@@ -222,9 +216,9 @@ DrvInfo::Outputs DrvInfo::queryOutputs(EvalState & state, bool withPaths, bool o
// output by its attribute, e.g. `pkgs.lix.dev`, which (lol?) sets the magic
// attribute `outputSpecified = true`, and changes the `outputName` attr to the
// explicitly selected-into output.
if (const Attr * outSpecAttr = attrs->get(state.ctx.s.outputSpecified)) {
if (Attr * outSpecAttr = attrs->get(state.ctx.s.outputSpecified)) {
bool outputSpecified = state.forceBool(
outSpecAttr->value,
*outSpecAttr->value,
outSpecAttr->pos,
"while evaluating the 'outputSpecified' attribute of a derivation"
);
@@ -244,17 +238,14 @@ DrvInfo::Outputs DrvInfo::queryOutputs(EvalState & state, bool withPaths, bool o
/* ^ this shows during `nix-env -i` right under the bad derivation */
if (!outTI->isList()) throw Error(errMsg + "expected a list but got %s", Uncolored(showType(outTI->type())));
Outputs result;
for (auto & elem : outTI->listItems()) {
if (elem.type() != nString) {
for (auto elem : outTI->listItems()) {
if (elem->type() != nString)
throw Error(
errMsg + "element is %s where a string was expected",
Uncolored(showType(elem.type()))
Uncolored(showType(elem->type()))
);
}
auto out = outputs.find(std::string(elem.str()));
if (out == outputs.end()) {
throw Error(errMsg + "output '%s' does not exist", elem.str());
}
auto out = outputs.find(elem->string.s);
if (out == outputs.end()) throw Error(errMsg + "output '%s' does not exist", elem->string.s);
result.insert(*out);
}
return result;
@@ -264,11 +255,8 @@ DrvInfo::Outputs DrvInfo::queryOutputs(EvalState & state, bool withPaths, bool o
std::string DrvInfo::queryOutputName(EvalState & state)
{
if (outputName == "" && attrs) {
auto i = attrs->get(state.ctx.s.outputName);
outputName = i ? state.forceStringNoCtx(
i->value, noPos, "while evaluating the output name of a derivation"
)
: "";
Bindings::iterator i = attrs->find(state.ctx.s.outputName);
outputName = i != attrs->end() ? state.forceStringNoCtx(*i->value, noPos, "while evaluating the output name of a derivation") : "";
}
return outputName;
}
@@ -278,12 +266,10 @@ Bindings * DrvInfo::getMeta(EvalState & state)
{
if (meta) return meta;
if (!attrs) return 0;
auto a = attrs->get(state.ctx.s.meta);
if (!a) {
return 0;
}
state.forceAttrs(a->value, a->pos, "while evaluating the 'meta' attribute of a derivation");
meta = a->value.attrs();
Bindings::iterator a = attrs->find(state.ctx.s.meta);
if (a == attrs->end()) return 0;
state.forceAttrs(*a->value, a->pos, "while evaluating the 'meta' attribute of a derivation");
meta = a->value->attrs;
return meta;
}
@@ -302,23 +288,15 @@ bool DrvInfo::checkMeta(EvalState & state, Value & v)
{
state.forceValue(v, noPos);
if (v.type() == nList) {
for (auto & elem : v.listItems()) {
if (!checkMeta(state, elem)) {
return false;
}
}
for (auto elem : v.listItems())
if (!checkMeta(state, *elem)) return false;
return true;
}
else if (v.type() == nAttrs) {
auto i = v.attrs()->get(state.ctx.s.outPath);
if (i) {
return false;
}
for (auto & i : *v.attrs()) {
if (!checkMeta(state, i.value)) {
return false;
}
}
Bindings::iterator i = v.attrs->find(state.ctx.s.outPath);
if (i != v.attrs->end()) return false;
for (auto & i : *v.attrs)
if (!checkMeta(state, *i.value)) return false;
return true;
}
else return v.type() == nInt || v.type() == nBool || v.type() == nString ||
@@ -329,11 +307,9 @@ bool DrvInfo::checkMeta(EvalState & state, Value & v)
Value * DrvInfo::queryMeta(EvalState & state, const std::string & name)
{
if (!getMeta(state)) return 0;
auto a = meta->get(state.ctx.symbols.create(name));
if (!a || !checkMeta(state, a->value)) {
return 0;
}
return &a->value;
Bindings::iterator a = meta->find(state.ctx.symbols.create(name));
if (a == meta->end() || !checkMeta(state, *a->value)) return 0;
return a->value;
}
@@ -341,7 +317,7 @@ std::string DrvInfo::queryMetaString(EvalState & state, const std::string & name
{
Value * v = queryMeta(state, name);
if (!v || v->type() != nString) return "";
return std::string(v->str());
return v->string.s;
}
@@ -349,15 +325,12 @@ NixInt DrvInfo::queryMetaInt(EvalState & state, const std::string & name, NixInt
{
Value * v = queryMeta(state, name);
if (!v) return def;
if (v->type() == nInt) {
return v->integer();
}
if (v->type() == nInt) return v->integer;
if (v->type() == nString) {
/* Backwards compatibility with before we had support for
integer meta fields. */
if (auto n = string2Int<NixInt::Inner>(v->str())) {
if (auto n = string2Int<NixInt::Inner>(v->string.s))
return NixInt{*n};
}
}
return def;
}
@@ -366,23 +339,18 @@ bool DrvInfo::queryMetaBool(EvalState & state, const std::string & name, bool de
{
Value * v = queryMeta(state, name);
if (!v) return def;
if (v->type() == nBool) {
return v->boolean();
}
if (v->type() == nBool) return v->boolean;
if (v->type() == nString) {
/* Backwards compatibility with before we had support for
Boolean meta fields. */
if (v->str() == "true") {
return true;
}
if (v->str() == "false") {
return false;
}
if (strcmp(v->string.s, "true") == 0) return true;
if (strcmp(v->string.s, "false") == 0) return false;
}
return def;
}
void DrvInfo::setMeta(EvalState & state, const std::string & name, Value & v)
void DrvInfo::setMeta(EvalState & state, const std::string & name, Value * v)
{
getMeta(state);
auto attrs = state.ctx.buildBindings(1 + (meta ? meta->size() : 0));
@@ -391,7 +359,7 @@ void DrvInfo::setMeta(EvalState & state, const std::string & name, Value & v)
for (auto i : *meta)
if (i.name != sym)
attrs.insert(i);
attrs.insert(sym, v);
if (v) attrs.insert(sym, v);
meta = attrs.finish();
}
@@ -410,7 +378,7 @@ static bool getDerivation(EvalState & state, Value & v,
state.forceValue(v, noPos);
if (!state.isDerivation(v)) return true;
DrvInfo drv(attrPath, v.attrs());
DrvInfo drv(attrPath, v.attrs);
drv.queryName(state);
@@ -434,9 +402,10 @@ std::optional<DrvInfo> getDerivation(EvalState & state, Value & v,
return std::move(drvs.front());
}
static std::string addToPath(std::string_view s1, std::string_view s2)
static std::string addToPath(const std::string & s1, const std::string & s2)
{
return s1.empty() ? std::string(s2) : fmt("%s.%s", s1, s2);
return s1.empty() ? s2 : s1 + "." + s2;
}
@@ -463,13 +432,19 @@ static void getDerivations(EvalState & state, Value & vIn, PosIdx pos,
if (v.type() == nList) {
// NOTE we can't really deduplicate here because small lists don't have stable addresses
// and can cause spurious duplicate detections due to v being on the stack.
for (auto && [n, elem] : enumerate(v.listItems())) {
for (auto [n, elem] : enumerate(v.listItems())) {
std::string joinedAttrPath = addToPath(pathPrefix, fmt("%d", n));
bool shouldRecurse =
getDerivation(state, elem, joinedAttrPath, drvs, ignoreAssertionFailures);
bool shouldRecurse = getDerivation(state, *elem, joinedAttrPath, drvs, ignoreAssertionFailures);
if (shouldRecurse) {
getDerivations(
state, elem, pos, joinedAttrPath, autoArgs, drvs, done, ignoreAssertionFailures
state,
*elem,
pos,
joinedAttrPath,
autoArgs,
drvs,
done,
ignoreAssertionFailures
);
}
}
@@ -484,7 +459,7 @@ static void getDerivations(EvalState & state, Value & vIn, PosIdx pos,
/* Dont consider sets we've already seen, e.g. y in
`rec { x.d = derivation {...}; y = x; }`. */
auto const &[_, didInsert] = done.insert(v.attrs());
auto const &[_, didInsert] = done.insert(v.attrs);
if (!didInsert) {
return;
}
@@ -492,14 +467,14 @@ static void getDerivations(EvalState & state, Value & vIn, PosIdx pos,
// FIXME: what the fuck???
/* !!! undocumented hackery to support combining channels in
nix-env.cc. */
bool combineChannels = v.attrs()->get(state.ctx.symbols.create("_combineChannels"));
bool combineChannels = v.attrs->find(state.ctx.symbols.create("_combineChannels")) != v.attrs->end();
/* Consider the attributes in sorted order to get more
deterministic behaviour in nix-env operations (e.g. when
there are names clashes between derivations, the derivation
bound to the attribute with the "lower" name should take
precedence). */
for (auto & attr : v.attrs()->lexicographicOrder(state.ctx.symbols)) {
for (auto & attr : v.attrs->lexicographicOrder(state.ctx.symbols)) {
debug("evaluating attribute '%1%'", state.ctx.symbols[attr->name]);
// FIXME: only consider attrs with identifier-like names?? Why???
if (!std::regex_match(std::string(state.ctx.symbols[attr->name]), attrRegex)) {
@@ -509,7 +484,7 @@ static void getDerivations(EvalState & state, Value & vIn, PosIdx pos,
if (combineChannels) {
getDerivations(
state,
attr->value,
*attr->value,
attr->pos,
joinedAttrPath,
autoArgs,
@@ -517,19 +492,17 @@ static void getDerivations(EvalState & state, Value & vIn, PosIdx pos,
done,
ignoreAssertionFailures
);
} else if (getDerivation(state, attr->value, joinedAttrPath, drvs, ignoreAssertionFailures))
{
} else if (getDerivation(state, *attr->value, joinedAttrPath, drvs, ignoreAssertionFailures)) {
/* If the value of this attribute is itself a set,
should we recurse into it? => Only if it has a
`recurseForDerivations = true' attribute. */
if (attr->value.type() == nAttrs) {
const Attr * recurseForDrvs =
attr->value.attrs()->get(state.ctx.s.recurseForDerivations);
if (attr->value->type() == nAttrs) {
Attr * recurseForDrvs = attr->value->attrs->get(state.ctx.s.recurseForDerivations);
if (recurseForDrvs == nullptr) {
continue;
}
bool shouldRecurse = state.forceBool(
recurseForDrvs->value,
*recurseForDrvs->value,
attr->pos,
fmt("while evaluating the '%s' attribute", Magenta("recurseForDerivations"))
);
@@ -539,7 +512,7 @@ static void getDerivations(EvalState & state, Value & vIn, PosIdx pos,
getDerivations(
state,
attr->value,
*attr->value,
attr->pos,
joinedAttrPath,
autoArgs,
+1 -1
View File
@@ -73,7 +73,7 @@ public:
std::string queryMetaString(EvalState & state, const std::string & name);
NixInt queryMetaInt(EvalState & state, const std::string & name, NixInt def);
bool queryMetaBool(EvalState & state, const std::string & name, bool def);
void setMeta(EvalState & state, const std::string & name, Value & v);
void setMeta(EvalState & state, const std::string & name, Value * v);
/*
MetaInfo queryMetaInfo(EvalState & state) const;
+28 -39
View File
@@ -1,5 +1,4 @@
#include "lix/libexpr/json-to-value.hh"
#include "gc-alloc.hh"
#include "lix/libexpr/value.hh"
#include "lix/libexpr/eval.hh"
#include "lix/libutil/json.hh"
@@ -8,6 +7,11 @@
namespace nix {
/*
* Used for `JSONObjectState`
*/
using ValueMap = GcMap<Symbol, Value *>;
// for more information, refer to
// https://github.com/nlohmann/json/blob/master/include/nlohmann/detail/input/json_sax.hpp
class JSONSax : nlohmann::json_sax<JSON> {
@@ -21,14 +25,13 @@ class JSONSax : nlohmann::json_sax<JSON> {
assert(false && "tried to close toplevel json parser state");
}
explicit JSONState(std::unique_ptr<JSONState> && p) : parent(std::move(p)) {}
JSONState() = default;
explicit JSONState(Value * v) : v(allocRootValue(v)) {}
JSONState(JSONState & p) = delete;
Value & value()
Value & value(EvalState & state)
{
if (!v) {
v = allocRootValue({});
}
return *v;
if (!v)
v = allocRootValue(state.ctx.mem.allocValue());
return **v;
}
virtual ~JSONState() {}
virtual void add() {}
@@ -36,41 +39,35 @@ class JSONSax : nlohmann::json_sax<JSON> {
class JSONObjectState : public JSONState {
using JSONState::JSONState;
GcMap<Symbol, Value> attrs;
Symbol _key;
ValueMap attrs;
std::unique_ptr<JSONState> resolve(EvalState & state) override
{
auto attrs2 = state.ctx.buildBindings(attrs.size());
for (auto & i : attrs)
attrs2.insert(i.first, i.second);
parent->value().mkAttrs(attrs2.alreadySorted());
parent->value(state).mkAttrs(attrs2.alreadySorted());
return std::move(parent);
}
void add() override
{
attrs.insert_or_assign(_key, value());
v = nullptr;
}
void add() override { v = nullptr; }
public:
void key(string_t & name, EvalState & state)
{
_key = state.ctx.symbols.create(name);
attrs.insert_or_assign(state.ctx.symbols.create(name), &value(state));
}
};
class JSONListState : public JSONState {
GcVector<Value> values;
ValueVector values;
std::unique_ptr<JSONState> resolve(EvalState & state) override
{
auto list = state.ctx.mem.newList(values.size());
parent->value() = {NewValueAs::list, list};
Value & v = parent->value(state);
v = state.ctx.mem.newList(values.size());
for (size_t n = 0; n < values.size(); ++n) {
list->elems[n] = values[n];
v.listElems()[n] = values[n];
}
return std::move(parent);
}
void add() override
{
void add() override {
values.push_back(*v);
v = nullptr;
}
@@ -85,30 +82,25 @@ class JSONSax : nlohmann::json_sax<JSON> {
std::unique_ptr<JSONState> rs;
public:
JSONSax(EvalState & state) : state(state), rs(new JSONState()) {};
Value result()
{
return rs->value();
}
JSONSax(EvalState & state, Value & v) : state(state), rs(new JSONState(&v)) {};
bool null() override
{
rs->value().mkNull();
rs->value(state).mkNull();
rs->add();
return true;
}
bool boolean(bool val) override
{
rs->value().mkBool(val);
rs->value(state).mkBool(val);
rs->add();
return true;
}
bool number_integer(number_integer_t val) override
{
rs->value().mkInt(val);
rs->value(state).mkInt(val);
rs->add();
return true;
}
@@ -116,26 +108,24 @@ public:
bool number_unsigned(number_unsigned_t val_) override
{
if (val_ > std::numeric_limits<NixInt::Inner>::max()) {
// Parse as a float for consistency with signed integers
// and interoperability with JSONs single numeric type.
return number_float(static_cast<number_float_t>(val_), "");
throw Error("unsigned json number %1% outside of Nix integer range", val_);
}
NixInt::Inner val = val_;
rs->value().mkInt(val);
rs->value(state).mkInt(val);
rs->add();
return true;
}
bool number_float(number_float_t val, const string_t & s) override
{
rs->value().mkFloat(val);
rs->value(state).mkFloat(val);
rs->add();
return true;
}
bool string(string_t & val) override
{
rs->value().mkString(val);
rs->value(state).mkString(val);
rs->add();
return true;
}
@@ -186,11 +176,10 @@ public:
void parseJSON(EvalState & state, const std::string_view & s_, Value & v)
{
JSONSax parser(state);
JSONSax parser(state, v);
bool res = JSON::sax_parse(s_, &parser);
if (!res)
throw JSONParseError("Invalid JSON Value");
v = parser.result();
}
}
+1 -2
View File
@@ -5,6 +5,5 @@ includedir=@includedir@
Name: Lix libexpr
Description: Lix Package Manager (libexpr)
Version: @PACKAGE_VERSION@
# dependencies on boost is omitted since it is optional (only required by some headers)
Requires: lix-base lix-util lix-store lix-fetchers @BOEHM_IF_FOUND@
Requires: lix-base lix-util lix-fetchers lix-store bdw-gc
Libs: -L${libdir} -llixexpr
+27 -48
View File
@@ -270,59 +270,32 @@ libexpr_headers = files(
# keep-sorted end
)
dependencies = [
liblixutil,
liblixstore,
liblixfetchers,
boehm,
boost,
kj,
nlohmann_json,
toml11,
]
libexpr_temp = library(
is_static ? 'lixexpr_temp' : 'lixexpr',
libexpr = library(
'lixexpr',
libexpr_sources,
libexpr_settings_header,
libexpr_generated_headers,
register_builtins_header,
register_builtin_constants_header,
dependencies : dependencies,
dependencies : [
liblixutil,
liblixstore,
liblixfetchers,
boehm,
boost,
toml11,
nlohmann_json,
kj,
],
# for shared.hh
include_directories : [
'../libmain',
],
cpp_pch : cpp_pch,
install : not is_static,
install : true,
# FIXME(Qyriad): is this right?
install_rpath : libdir,
)
# FIXME: remove when https://git.lix.systems/lix-project/lix/issues/359 is fixed.
# FIXME: replace by prelink when https://github.com/mesonbuild/meson/pull/14846 is widely available.
if is_static
libexpr_prelink = custom_target(
'lixexpr-prelink',
output : 'lixexpr-prelink.o',
input : libexpr_temp,
command : [
cxx.cmd_array(),
'-r',
'-o',
'@OUTPUT@',
is_darwin ? '-Wl,-force_load' : '-Wl,--whole-archive',
'@INPUT@',
],
)
libexpr = library(
'lixexpr',
[libexpr_prelink],
dependencies : dependencies,
install : true,
)
else
libexpr = libexpr_temp
endif
install_headers(
libexpr_headers,
@@ -333,16 +306,23 @@ install_headers(
liblixexpr = declare_dependency(
include_directories : include_directories('../..'),
sources : libexpr_settings_header,
dependencies : [
liblixutil,
liblixfetchers,
boehm,
boost,
],
# Parallels the requirement to link with boehm of the pkg-config but for internal targets.
dependencies : [boehm],
link_with : libexpr,
)
meson.override_dependency('lix-expr', liblixexpr)
# FIXME: remove when https://git.lix.systems/lix-project/lix/issues/359 is fixed.
if is_static
liblixexpr_mstatic = declare_dependency(
include_directories : include_directories('../..'),
sources : libexpr_settings_header,
dependencies : [boehm],
link_whole : libexpr,
)
else
liblixexpr_mstatic = liblixexpr
endif
meson.override_dependency('lix-expr', liblixexpr_mstatic)
# 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
@@ -355,6 +335,5 @@ configure_file(
'libdir' : libdir,
'includedir' : includedir,
'PACKAGE_VERSION' : meson.project_version(),
'BOEHM_IF_FOUND' : boehm.found() ? 'bdw-gc' : '',
},
)
+9 -85
View File
@@ -9,8 +9,7 @@
namespace nix {
ExprBlackHole eBlackHole;
Value::Thunk Value::blackHole{{0}, &eBlackHole};
Expr *eBlackHoleAddr = &eBlackHole;
// FIXME: remove, because *symbols* are abstract and do not have a single
// textual representation; see printIdentifier()
@@ -48,15 +47,15 @@ JSON ExprLiteral::toJSON(const SymbolTable & symbols) const
switch (v.type()) {
case nInt:
valueType = "Int";
value = v.integer().value;
value = v.integer.value;
break;
case nFloat:
valueType = "Float";
value = v.fpoint();
value = v.fpoint;
break;
case nString:
valueType = "String";
value = v.str();
value = v.string.s;
break;
case nPath:
valueType = "Path";
@@ -123,10 +122,10 @@ void ExprAttrs::addBindingsToJSON(JSON & out, const SymbolTable & symbols) const
for (auto & i : sorted) {
switch (i->second.kind) {
case AttrDef::Kind::Plain:
out["attrs"][std::string(symbols[i->first])] = i->second.e->toJSON(symbols);
out["attrs"][symbols[i->first]] = i->second.e->toJSON(symbols);
break;
case AttrDef::Kind::Inherited:
out["inherit"][std::string(symbols[i->first])] = i->second.e->toJSON(symbols);
out["inherit"][symbols[i->first]] = i->second.e->toJSON(symbols);
break;
case AttrDef::Kind::InheritedFrom: {
auto & select = i->second.e->cast<ExprSelect>();
@@ -198,9 +197,9 @@ void AttrsPattern::addBindingsToJSON(JSON & out, const SymbolTable & symbols) co
// context. always use lexicographic ordering to avoid this.
for (const Formal & i : lexicographicOrder(symbols)) {
if (i.def)
out["formals"][std::string(symbols[i.name])] = i.def->toJSON(symbols);
out["formals"][symbols[i.name]] = i.def->toJSON(symbols);
else
out["formals"][std::string(symbols[i.name])] = nullptr;
out["formals"][symbols[i.name]] = nullptr;
}
out["formalsEllipsis"] = ellipsis;
}
@@ -321,74 +320,6 @@ JSON printAttrPathToJson(const SymbolTable & symbols, const AttrPath & attrPath)
/* Computing levels/displacements for variables. */
namespace {
// This is a one-pass static analyzer for
// various topics.
struct StaticAnalyzer : ExprVisitor
{
std::set<Symbol> staticallyUsedVariables;
bool usedDynamicVariables = false;
StaticAnalyzer() {}
using ExprVisitor::visit;
void visit(ExprDebugFrame & e, std::unique_ptr<Expr> & ptr) override
{
visit(e.inner);
}
void visit(ExprLiteral & e, std::unique_ptr<Expr> & ptr) override {}
void visit(ExprVar & e, std::unique_ptr<Expr> & ptr) override
{
staticallyUsedVariables.insert(e.name);
}
void visit(ExprInheritFrom & e, std::unique_ptr<Expr> & ptr) override {}
void visit(ExprSelect & e, std::unique_ptr<Expr> & ptr) override
{
if (e.isDynamic()) {
usedDynamicVariables = true;
}
visit(e.def);
visit(e.e);
}
void visit(ExprOpHasAttr & e, std::unique_ptr<Expr> & ptr) override
{
if (e.isDynamic()) {
usedDynamicVariables = true;
}
visit(e.e);
}
void visit(ExprSet & e, std::unique_ptr<Expr> & ptr) override
{
// TODO: oh bro, we need to analyze dynamic attributes for their value expressions.
}
void visit(ExprList & e, std::unique_ptr<Expr> & ptr) override {}
void visit(ExprLambda & e, std::unique_ptr<Expr> & ptr) override {}
void visit(ExprCall & e, std::unique_ptr<Expr> & ptr) override {}
void visit(ExprLet & e, std::unique_ptr<Expr> & ptr) override {}
void visit(ExprWith & e, std::unique_ptr<Expr> & ptr) override {}
void visit(ExprIf & e, std::unique_ptr<Expr> & ptr) override {}
void visit(ExprAssert & e, std::unique_ptr<Expr> & ptr) override {}
void visit(ExprOpNot & e, std::unique_ptr<Expr> & ptr) override {}
#define BINOP(type) \
/* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
void visit(type & e, std::unique_ptr<Expr> & ptr) override \
{ \
visit(e.e1); \
visit(e.e2); \
}
BINOP(ExprOpEq)
BINOP(ExprOpNEq)
BINOP(ExprOpAnd)
BINOP(ExprOpOr)
BINOP(ExprOpImpl)
BINOP(ExprOpUpdate)
BINOP(ExprOpConcatLists)
#undef BINOP
void visit(ExprConcatStrings & e, std::unique_ptr<Expr> & ptr) override {}
void visit(ExprPos & e, std::unique_ptr<Expr> & ptr) override {}
void visit(ExprBlackHole & e, std::unique_ptr<Expr> & ptr) override {}
};
struct VarBinder : ExprVisitor
{
Evaluator & es;
@@ -658,13 +589,6 @@ void VarBinder::visit(ExprLambda & e, std::unique_ptr<Expr> & ptr)
{
withEnv(e.pattern->buildEnv(env.get()), [&] {
e.pattern->accept(*this);
/* TODO: If statically, e.body makes only use of some parameters and not the whole scope.
* We shouldn't have to keep around all the environment data which might contain trapped
* pointers. Analyze `e.body` and return its statically known set of used variables.
* */
DirectCallAnalyzer analyzer{es, env};
analyzer.visit(e.body);
e.shortcut = analyzer.shortcut;
visit(e.body);
});
}
@@ -841,7 +765,7 @@ Pos PosTable::operator[](PosIdx p) const
size_t SymbolTable::totalSize() const
{
size_t n = 0;
dump([&](const std::string_view s) { n += s.size(); });
dump([&] (const std::string & s) { n += s.size(); });
return n;
}
+14 -70
View File
@@ -127,7 +127,7 @@ public:
virtual JSON toJSON(const SymbolTable & symbols) const;
virtual void accept(ExprVisitor & ev, std::unique_ptr<Expr> & ptr) = 0;
virtual void eval(EvalState & state, Env & env, Value & v);
virtual Value maybeThunk(EvalState & state, Env & env);
virtual Value * maybeThunk(EvalState & state, Env & env);
virtual void setName(Symbol name);
PosIdx getPos() const { return pos; }
@@ -175,51 +175,26 @@ protected:
Value v;
ExprLiteral(const PosIdx pos) : Expr(pos) {};
public:
Value maybeThunk(EvalState & state, Env & env) override;
ExprLiteral(const PosIdx pos, NewValueAs::integer_t, NixInt n) : Expr(pos) { v.mkInt(n); };
ExprLiteral(const PosIdx pos, NewValueAs::integer_t, NixInt::Inner n) : Expr(pos) { v.mkInt(n); };
ExprLiteral(const PosIdx pos, NewValueAs::floating_t, NixFloat nf) : Expr(pos) { v.mkFloat(nf); };
Value * maybeThunk(EvalState & state, Env & env) override;
JSON toJSON(const SymbolTable & symbols) const override;
void eval(EvalState & state, Env & env, Value & v) override;
void accept(ExprVisitor & ev, std::unique_ptr<Expr> & ptr) override { ev.visit(*this, ptr); }
};
struct ExprInt : ExprLiteral
{
Value::Int i;
ExprInt(const PosIdx pos, NixInt n) : ExprLiteral(pos), i{{Value::Acb::tInt}, n}
{
v = Value::isTaggableInteger(n) ? Value{NewValueAs::integer, n} : Value(i);
}
ExprInt(const PosIdx pos, NixInt::Inner n) : ExprInt(pos, NixInt(n)) {}
};
struct ExprFloat : ExprLiteral
{
Value::Float f;
ExprFloat(const PosIdx pos, NewValueAs::floating_t, double f)
: ExprLiteral(pos)
, f{{Value::Acb::tFloat}, f}
{
v = Value(this->f);
}
};
struct ExprString : ExprLiteral
{
std::string s;
Value::String strcb{.content = s.c_str(), .context = nullptr};
ExprString(const PosIdx pos, std::string && s) : ExprLiteral(pos), s(std::move(s))
{
v = {NewValueAs::string, &strcb};
}
ExprString(const PosIdx pos, std::string &&s) : ExprLiteral(pos), s(std::move(s)) { v.mkString(this->s.data()); };
};
struct ExprPath : ExprLiteral
{
std::string s;
Value::String strcb{.content = s.c_str(), .context = Value::String::path};
ExprPath(const PosIdx pos, std::string s) : ExprLiteral(pos), s(std::move(s))
{
v = {NewValueAs::path, &strcb};
}
ExprPath(const PosIdx pos, std::string s) : ExprLiteral(pos), s(std::move(s)) { v.mkPath(this->s.c_str()); };
};
typedef uint32_t Level;
@@ -253,7 +228,7 @@ struct ExprVar : Expr
ExprVar(Symbol name) : name(name), needsRoot(false) { };
ExprVar(const PosIdx & pos, Symbol name, bool needsRoot = false) : Expr(pos), name(name), needsRoot(needsRoot) { };
Value maybeThunk(EvalState & state, Env & env) override;
Value * maybeThunk(EvalState & state, Env & env) override;
JSON toJSON(const SymbolTable & symbols) const override;
void eval(EvalState & state, Env & env, Value & v) override;
void accept(ExprVisitor & ev, std::unique_ptr<Expr> & ptr) override { ev.visit(*this, ptr); }
@@ -292,17 +267,6 @@ struct ExprSelect : Expr
/** The path of attributes being selected. e.g. `bar.baz` in `foo.bar.baz.` */
AttrPath attrPath;
bool isDynamic() const
{
for (auto & name : attrPath) {
if (name.expr) {
return true;
}
}
return false;
}
ExprSelect(const PosIdx & pos, std::unique_ptr<Expr> e, AttrPath attrPath, std::unique_ptr<Expr> def) : Expr(pos), e(std::move(e)), def(std::move(def)), attrPath(std::move(attrPath)) { };
ExprSelect(const PosIdx & pos, std::unique_ptr<Expr> e, const PosIdx namePos, Symbol name) : Expr(pos), e(std::move(e)) { attrPath.push_back(AttrName(namePos, name)); };
JSON toJSON(const SymbolTable & symbols) const override;
@@ -314,16 +278,6 @@ struct ExprOpHasAttr : Expr
{
std::unique_ptr<Expr> e;
AttrPath attrPath;
bool isDynamic() const
{
for (auto & name : attrPath) {
if (name.expr) {
return true;
}
}
return false;
}
ExprOpHasAttr(const PosIdx & pos, std::unique_ptr<Expr> e, AttrPath attrPath) : Expr(pos), e(std::move(e)), attrPath(std::move(attrPath)) { };
JSON toJSON(const SymbolTable & symbols) const override;
void eval(EvalState & state, Env & env, Value & v) override;
@@ -416,7 +370,7 @@ struct ExprList : Expr
JSON toJSON(const SymbolTable & symbols) const override;
void eval(EvalState & state, Env & env, Value & v) override;
void accept(ExprVisitor & ev, std::unique_ptr<Expr> & ptr) override { ev.visit(*this, ptr); }
Value maybeThunk(EvalState & state, Env & env) override;
Value * maybeThunk(EvalState & state, Env & env) override;
};
struct Pattern {
@@ -430,8 +384,7 @@ struct Pattern {
virtual std::shared_ptr<const StaticEnv> buildEnv(const StaticEnv * up) = 0;
virtual void accept(ExprVisitor & ev) = 0;
virtual Env &
match(ExprLambda & lambda, EvalState & state, Env & up, Value & arg, const PosIdx pos) = 0;
virtual Env & match(ExprLambda & lambda, EvalState & state, Env & up, Value * arg, const PosIdx pos) = 0;
virtual void addBindingsToJSON(JSON & out, const SymbolTable & symbols) const = 0;
};
@@ -446,8 +399,7 @@ struct SimplePattern : Pattern
virtual std::shared_ptr<const StaticEnv> buildEnv(const StaticEnv * up) override;
virtual void accept(ExprVisitor & ev) override;
virtual Env &
match(ExprLambda & lambda, EvalState & state, Env & up, Value & arg, const PosIdx pos) override;
virtual Env & match(ExprLambda & lambda, EvalState & state, Env & up, Value * arg, const PosIdx pos) override;
virtual void addBindingsToJSON(JSON & out, const SymbolTable & symbols) const override;
};
@@ -468,8 +420,7 @@ struct AttrsPattern : Pattern
virtual std::shared_ptr<const StaticEnv> buildEnv(const StaticEnv * up) override;
virtual void accept(ExprVisitor & ev) override;
virtual Env &
match(ExprLambda & lambda, EvalState & state, Env & up, Value & arg, const PosIdx pos) override;
virtual Env & match(ExprLambda & lambda, EvalState & state, Env & up, Value * arg, const PosIdx pos) override;
virtual void addBindingsToJSON(JSON & out, const SymbolTable & symbols) const override;
@@ -500,13 +451,6 @@ struct ExprLambda : Expr
Symbol name;
std::unique_ptr<Pattern> pattern;
std::unique_ptr<Expr> body;
// This is a shortcut variant which
// exhausts the body further lambda constructions
// to transform x1: x2: …: xn: b
// into { x1, …, xn }: b
// This can be used when you know that you are
// passing all the arguments at once.
std::unique_ptr<ExprLambda> shortcut;
ExprLambda(PosIdx pos, std::unique_ptr<Pattern> pattern, std::unique_ptr<Expr> body)
: Expr(pos), pattern(std::move(pattern)), body(std::move(body))
{
@@ -517,7 +461,7 @@ struct ExprLambda : Expr
/** Returns the name of the lambda,
* or "anonymous lambda" if it doesn't have one.
*/
inline std::string_view getName(SymbolTable const & symbols) const
inline std::string getName(SymbolTable const & symbols) const
{
if (this->name) {
return symbols[this->name];
+3 -3
View File
@@ -148,7 +148,7 @@ struct ExprState
std::unique_ptr<Expr> negate(PosIdx pos, State & state)
{
std::vector<std::unique_ptr<Expr>> args(2);
args[0] = std::make_unique<ExprInt>(pos, 0);
args[0] = std::make_unique<ExprLiteral>(pos, NewValueAs::integer, 0);
args[1] = popExprOnly();
return std::make_unique<ExprCall>(pos, state.mkInternalVar(pos, state.s.sub), std::move(args));
}
@@ -507,7 +507,7 @@ template<> struct BuildAST<grammar::v1::expr::int_> {
.pos = ps.positions[ps.at(in)],
});
}
s.emplaceExpr<ExprInt>(ps.at(in), v);
s.emplaceExpr<ExprLiteral>(ps.at(in), NewValueAs::integer, v);
}
};
@@ -542,7 +542,7 @@ template<> struct BuildAST<grammar::v1::expr::float_> {
});
}
}();
s.emplaceExpr<ExprFloat>(ps.at(in), NewValueAs::floating, v);
s.emplaceExpr<ExprLiteral>(ps.at(in), NewValueAs::floating, v);
}
};
+379 -654
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -33,7 +33,7 @@ struct RegisterPrimOp
* will get called during EvalState initialization, so there
* may be primops not yet added and builtins is not yet sorted.
*/
RegisterPrimOp(PrimOpDetails && primOp);
RegisterPrimOp(PrimOp && primOp);
};
/* These primops are disabled without enableNativeCode, but plugins
+17 -32
View File
@@ -3,7 +3,6 @@
#include "lix/libstore/derivations.hh"
#include "lix/libstore/store-api.hh"
#include "lix/libutil/types.hh"
#include "value.hh"
namespace nix {
@@ -148,10 +147,9 @@ void prim_getContext(EvalState & state, Value * * args, Value & v)
infoAttrs.alloc(sAllOutputs).mkBool(true);
if (!info.second.outputs.empty()) {
auto & outputsVal = infoAttrs.alloc(state.ctx.s.outputs);
auto content = state.ctx.mem.newList(info.second.outputs.size());
outputsVal = {NewValueAs::list, content};
outputsVal = state.ctx.mem.newList(info.second.outputs.size());
for (const auto & [i, output] : enumerate(info.second.outputs))
content->elems[i].mkString(output);
(outputsVal.listElems()[i] = state.ctx.mem.allocValue())->mkString(output);
}
attrs.alloc(state.ctx.store->printStorePath(info.first)).mkAttrs(infoAttrs);
}
@@ -173,7 +171,7 @@ static void prim_appendContext(EvalState & state, Value * * args, Value & v)
state.forceAttrs(*args[1], noPos, "while evaluating the second argument passed to builtins.appendContext");
auto sAllOutputs = state.ctx.symbols.create("allOutputs");
for (auto & i : *args[1]->attrs()) {
for (auto & i : *args[1]->attrs) {
const auto & name = state.ctx.symbols[i.name];
if (!state.ctx.store->isStorePath(name))
state.ctx.errors.make<EvalError>(
@@ -183,27 +181,18 @@ static void prim_appendContext(EvalState & state, Value * * args, Value & v)
auto namePath = state.ctx.store->parseStorePath(name);
if (!settings.readOnlyMode)
state.aio.blockOn(state.ctx.store->ensurePath(namePath));
state.forceAttrs(i.value, i.pos, "while evaluating the value of a string context");
auto a = i.value.attrs()->get(state.ctx.s.path);
if (a) {
if (state.forceBool(
a->value, a->pos, "while evaluating the `path` attribute of a string context"
))
{
context.emplace(NixStringContextElem::Opaque{
state.forceAttrs(*i.value, i.pos, "while evaluating the value of a string context");
auto iter = i.value->attrs->find(state.ctx.s.path);
if (iter != i.value->attrs->end()) {
if (state.forceBool(*iter->value, iter->pos, "while evaluating the `path` attribute of a string context"))
context.emplace(NixStringContextElem::Opaque {
.path = namePath,
});
}
}
a = i.value.attrs()->get(sAllOutputs);
if (a) {
if (state.forceBool(
a->value,
a->pos,
"while evaluating the `allOutputs` attribute of a string context"
))
{
iter = i.value->attrs->find(sAllOutputs);
if (iter != i.value->attrs->end()) {
if (state.forceBool(*iter->value, iter->pos, "while evaluating the `allOutputs` attribute of a string context")) {
if (!isDerivation(name)) {
state.ctx.errors.make<EvalError>(
"tried to add all-outputs context of %s, which is not a derivation, to a string",
@@ -216,21 +205,17 @@ static void prim_appendContext(EvalState & state, Value * * args, Value & v)
}
}
a = i.value.attrs()->get(state.ctx.s.outputs);
if (a) {
state.forceList(
a->value, a->pos, "while evaluating the `outputs` attribute of a string context"
);
if (a->value.listSize() && !isDerivation(name)) {
iter = i.value->attrs->find(state.ctx.s.outputs);
if (iter != i.value->attrs->end()) {
state.forceList(*iter->value, iter->pos, "while evaluating the `outputs` attribute of a string context");
if (iter->value->listSize() && !isDerivation(name)) {
state.ctx.errors.make<EvalError>(
"tried to add derivation output context of %s, which is not a derivation, to a string",
name
).atPos(i.pos).debugThrow();
}
for (auto & elem : a->value.listItems()) {
auto outputName = state.forceStringNoCtx(
elem, a->pos, "while evaluating an output name within a string context"
);
for (auto elem : iter->value->listItems()) {
auto outputName = state.forceStringNoCtx(*elem, iter->pos, "while evaluating an output name within a string context");
context.emplace(NixStringContextElem::Built {
.drvPath = makeConstantStorePath(namePath),
.output = std::string { outputName },
+8 -7
View File
@@ -118,7 +118,7 @@ void prim_fetchClosure(EvalState & state, Value * * args, Value & v)
std::optional<StorePathOrGap> toPath;
std::optional<bool> inputAddressedMaybe;
for (auto & attr : *args[0]->attrs()) {
for (auto & attr : *args[0]->attrs) {
const auto & attrName = state.ctx.symbols[attr.name];
auto attrHint = [&]() -> std::string {
return "while evaluating the '" + attrName + "' attribute passed to builtins.fetchClosure";
@@ -126,26 +126,27 @@ void prim_fetchClosure(EvalState & state, Value * * args, Value & v)
if (attrName == "fromPath") {
NixStringContext context;
fromPath = state.coerceToStorePath(attr.pos, attr.value, context, attrHint());
fromPath = state.coerceToStorePath(attr.pos, *attr.value, context, attrHint());
}
else if (attrName == "toPath") {
state.forceValue(attr.value, attr.pos);
bool isEmptyString = attr.value.type() == nString && attr.value.str().empty();
state.forceValue(*attr.value, attr.pos);
bool isEmptyString = attr.value->type() == nString && attr.value->string.s == std::string("");
if (isEmptyString) {
toPath = StorePathOrGap {};
}
else {
NixStringContext context;
toPath = state.coerceToStorePath(attr.pos, attr.value, context, attrHint());
toPath = state.coerceToStorePath(attr.pos, *attr.value, context, attrHint());
}
}
else if (attrName == "fromStore")
fromStoreUrl = state.forceStringNoCtx(attr.value, attr.pos, attrHint());
fromStoreUrl = state.forceStringNoCtx(*attr.value, attr.pos,
attrHint());
else if (attrName == "inputAddressed")
inputAddressedMaybe = state.forceBool(attr.value, attr.pos, attrHint());
inputAddressedMaybe = state.forceBool(*attr.value, attr.pos, attrHint());
else
throw Error({
+10 -33
View File
@@ -17,48 +17,25 @@ static void prim_fetchMercurial(EvalState & state, Value * * args, Value & v)
if (args[0]->type() == nAttrs) {
for (auto & attr : *args[0]->attrs()) {
for (auto & attr : *args[0]->attrs) {
std::string_view n(state.ctx.symbols[attr.name]);
if (n == "url")
url = state
.coerceToString(
attr.pos,
attr.value,
context,
"while evaluating the `url` attribute passed to "
"builtins.fetchMercurial",
StringCoercionMode::Strict,
false
)
.toOwned();
url = state.coerceToString(attr.pos, *attr.value, context,
"while evaluating the `url` attribute passed to builtins.fetchMercurial",
StringCoercionMode::Strict, false).toOwned();
else if (n == "rev") {
// Ugly: unlike fetchGit, here the "rev" attribute can
// be both a revision or a branch/tag name.
auto value = state.forceStringNoCtx(
attr.value,
attr.pos,
"while evaluating the `rev` attribute passed to builtins.fetchMercurial"
);
if (std::regex_match(value.begin(), value.end(), revRegex)) {
auto value = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `rev` attribute passed to builtins.fetchMercurial");
if (std::regex_match(value.begin(), value.end(), revRegex))
rev = Hash::parseAny(value, HashType::SHA1);
} else
else
ref = value;
}
else if (n == "name")
name = state.forceStringNoCtx(
attr.value,
attr.pos,
"while evaluating the `name` attribute passed to builtins.fetchMercurial"
);
else {
state.ctx.errors
.make<EvalError>(
"unsupported argument '%s' to 'fetchMercurial'",
state.ctx.symbols[attr.name]
)
.atPos(attr.pos)
.debugThrow();
}
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.fetchMercurial");
else
state.ctx.errors.make<EvalError>("unsupported argument '%s' to 'fetchMercurial'", state.ctx.symbols[attr.name]).atPos(attr.pos).debugThrow();
}
if (url.empty())
+26 -57
View File
@@ -122,44 +122,35 @@ static void fetchTree(
fetchers::Attrs attrs;
if (auto aType = args[0]->attrs()->get(state.ctx.s.type)) {
if (auto aType = args[0]->attrs->get(state.ctx.s.type)) {
if (type)
state.ctx.errors.make<EvalError>(
"unexpected attribute 'type'"
).atPos(pos).debugThrow();
type = state.forceStringNoCtx(
aType->value,
aType->pos,
"while evaluating the `type` attribute passed to builtins.fetchTree"
);
} else if (!type) {
state.ctx.errors.make<EvalError>("attribute 'type' is missing in call to 'fetchTree'")
.atPos(pos)
.debugThrow();
}
type = state.forceStringNoCtx(*aType->value, aType->pos, "while evaluating the `type` attribute passed to builtins.fetchTree");
} else if (!type)
state.ctx.errors.make<EvalError>(
"attribute 'type' is missing in call to 'fetchTree'"
).atPos(pos).debugThrow();
attrs.emplace("type", type.value());
for (auto & attr : *args[0]->attrs()) {
for (auto & attr : *args[0]->attrs) {
if (attr.name == state.ctx.s.type) continue;
state.forceValue(attr.value, attr.pos);
if (attr.value.type() == nPath || attr.value.type() == nString) {
auto s =
state
.coerceToString(
attr.pos, attr.value, context, "", StringCoercionMode::Strict, false
)
.toOwned();
state.forceValue(*attr.value, attr.pos);
if (attr.value->type() == nPath || attr.value->type() == nString) {
auto s = state.coerceToString(attr.pos, *attr.value, context, "", StringCoercionMode::Strict, false).toOwned();
attrs.emplace(state.ctx.symbols[attr.name],
state.ctx.symbols[attr.name] == "url"
? type == "git"
? fixURIForGit(s, state)
: fixURI(s, state)
: s);
} else if (attr.value.type() == nBool) {
attrs.emplace(state.ctx.symbols[attr.name], Explicit<bool>{attr.value.boolean()});
} else if (attr.value.type() == nInt) {
auto intValue = attr.value.integer().value;
}
else if (attr.value->type() == nBool)
attrs.emplace(state.ctx.symbols[attr.name], Explicit<bool>{attr.value->boolean});
else if (attr.value->type() == nInt) {
auto intValue = attr.value->integer.value;
if (intValue < 0) {
state.ctx.errors.make<EvalError>("negative value given for fetchTree attr %1%: %2%", state.ctx.symbols[attr.name], intValue).atPos(pos).debugThrow();
@@ -167,16 +158,9 @@ static void fetchTree(
unsigned long asUnsigned = intValue;
attrs.emplace(state.ctx.symbols[attr.name], asUnsigned);
} else {
state.ctx.errors
.make<TypeError>(
"fetchTree argument '%s' is %s while a string, Boolean or integer is "
"expected",
state.ctx.symbols[attr.name],
showType(attr.value)
)
.debugThrow();
}
} else
state.ctx.errors.make<TypeError>("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
state.ctx.symbols[attr.name], showType(*attr.value)).debugThrow();
}
if (!params.allowNameArgument)
@@ -237,32 +221,17 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
if (args[0]->type() == nAttrs) {
for (auto & attr : *args[0]->attrs()) {
for (auto & attr : *args[0]->attrs) {
std::string_view n(state.ctx.symbols[attr.name]);
if (n == "url")
url = state.forceStringNoCtx(
attr.value, attr.pos, "while evaluating the url we should fetch"
);
else if (n == "sha256") {
expectedHash = newHashAllowEmpty(
state.forceStringNoCtx(
attr.value,
attr.pos,
"while evaluating the sha256 of the content we should fetch"
),
HashType::SHA256
);
} else if (n == "name")
name = state.forceStringNoCtx(
attr.value,
attr.pos,
"while evaluating the name of the content we should fetch"
);
else {
url = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the url we should fetch");
else if (n == "sha256")
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the sha256 of the content we should fetch"), HashType::SHA256);
else if (n == "name")
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the name of the content we should fetch");
else
state.ctx.errors.make<EvalError>("unsupported argument '%s' to '%s'", n, who)
.atPos(pos)
.debugThrow();
}
.atPos(pos).debugThrow();
}
if (!url)
+65 -56
View File
@@ -1,78 +1,87 @@
#include "lix/libexpr/eval.hh"
#include "lix/libexpr/extra-primops.hh"
#include "value.hh"
#include <sstream>
#include <toml.hpp>
namespace nix {
void prim_fromTOML(EvalState & state, Value ** args, Value & val)
void prim_fromTOML(EvalState & state, Value * * args, Value & val)
{
auto toml = state.forceStringNoCtx(
*args[0], noPos, "while evaluating the argument passed to builtins.fromTOML"
);
auto toml = state.forceStringNoCtx(*args[0], noPos, "while evaluating the argument passed to builtins.fromTOML");
std::istringstream tomlStream(std::string{toml});
auto visit = [&](this const auto & self, Value & v, toml::value t) -> void {
switch (t.type()) {
case toml::value_t::table: {
auto table = toml::get<toml::table>(t);
auto attrs = state.ctx.buildBindings(table.size());
std::function<void(Value &, toml::value)> visit;
for (auto & elem : table) {
self(attrs.alloc(elem.first), elem.second);
}
visit = [&](Value & v, toml::value t) {
v.mkAttrs(attrs);
} break;
case toml::value_t::array: {
auto array = toml::get<std::vector<toml::value>>(t);
switch(t.type())
{
case toml::value_t::table:
{
auto table = toml::get<toml::table>(t);
size_t size = 0;
for (auto & i : table) { (void) i; size++; }
auto attrs = state.ctx.buildBindings(size);
for(auto & elem : table)
visit(attrs.alloc(elem.first), elem.second);
v.mkAttrs(attrs);
}
break;;
case toml::value_t::array:
{
auto array = toml::get<std::vector<toml::value>>(t);
size_t size = array.size();
v = state.ctx.mem.newList(size);
for (size_t i = 0; i < size; ++i)
visit(*(v.listElems()[i] = state.ctx.mem.allocValue()), array[i]);
}
break;;
case toml::value_t::boolean:
v.mkBool(toml::get<bool>(t));
break;;
case toml::value_t::integer:
v.mkInt(toml::get<int64_t>(t));
break;;
case toml::value_t::floating:
v.mkFloat(toml::get<NixFloat>(t));
break;;
case toml::value_t::string:
v.mkString(toml::get<std::string>(t));
break;;
case toml::value_t::local_datetime:
case toml::value_t::offset_datetime:
case toml::value_t::local_date:
case toml::value_t::local_time:
{
if (experimentalFeatureSettings.isEnabled(Xp::ParseTomlTimestamps)) {
auto attrs = state.ctx.buildBindings(2);
attrs.alloc("_type").mkString("timestamp");
std::ostringstream s;
s << t;
attrs.alloc("value").mkString(s.str());
v.mkAttrs(attrs);
} else {
// NOLINTNEXTLINE(lix-foreign-exceptions)
throw std::runtime_error("Dates and times are not supported");
}
}
break;;
case toml::value_t::empty:
v.mkNull();
break;;
size_t size = array.size();
auto list = state.ctx.mem.newList(size);
v = {NewValueAs::list, list};
for (size_t i = 0; i < size; ++i) {
self(list->elems[i], array[i]);
}
} break;
case toml::value_t::boolean:
v.mkBool(toml::get<bool>(t));
break;
case toml::value_t::integer:
v.mkInt(toml::get<int64_t>(t));
break;
case toml::value_t::floating:
v.mkFloat(toml::get<NixFloat>(t));
break;
case toml::value_t::string:
v.mkString(toml::get<std::string>(t));
break;
case toml::value_t::local_datetime:
case toml::value_t::offset_datetime:
case toml::value_t::local_date:
case toml::value_t::local_time:
// NOLINTNEXTLINE(lix-foreign-exceptions)
throw std::runtime_error("Dates and times are not supported");
break;
case toml::value_t::empty:
v.mkNull();
break;
}
};
try {
visit(
val,
toml::parse(
tomlStream,
"fromTOML", /* the "filename" */
toml::spec::v(
1, 0, 0
) // Be explicit that we are parsing TOML 1.0.0 without extensions
)
);
visit(val, toml::parse(tomlStream, "fromTOML" /* the "filename" */));
} catch (std::exception & e) { // NOLINT(lix-foreign-exceptions) // TODO: toml::syntax_error
state.ctx.errors.make<EvalError>("while parsing TOML: %s", e.what()).debugThrow();
}
+18 -20
View File
@@ -9,12 +9,11 @@ namespace nix {
// See: https://github.com/NixOS/nix/issues/9730
void printAmbiguous(
const Value & v,
const SymbolTable & symbols,
std::ostream & str,
std::set<const void *> * seen,
int depth
)
Value &v,
const SymbolTable &symbols,
std::ostream &str,
std::set<const void *> *seen,
int depth)
{
checkInterrupt();
@@ -22,19 +21,15 @@ void printAmbiguous(
str << "«too deep»";
return;
}
if (v.isInvalid()) {
str << "<INVALID>";
return;
}
switch (v.type()) {
case nInt:
str << v.integer();
str << v.integer;
break;
case nBool:
printLiteralBool(str, v.boolean());
printLiteralBool(str, v.boolean);
break;
case nString:
escapeString(str, v.str());
escapeString(str, v.string.s);
break;
case nPath:
str << v.path().to_string(); // !!! escaping?
@@ -43,13 +38,13 @@ void printAmbiguous(
str << "null";
break;
case nAttrs: {
if (seen && !v.attrs()->empty() && !seen->insert(v.attrs()).second)
if (seen && !v.attrs->empty() && !seen->insert(v.attrs).second)
str << "«repeated»";
else {
str << "{ ";
for (auto & i : v.attrs()->lexicographicOrder(symbols)) {
for (auto & i : v.attrs->lexicographicOrder(symbols)) {
str << symbols[i->name] << " = ";
printAmbiguous(i->value, symbols, str, seen, depth - 1);
printAmbiguous(*i->value, symbols, str, seen, depth - 1);
str << "; ";
}
str << "}";
@@ -61,8 +56,11 @@ void printAmbiguous(
str << "«repeated»";
else {
str << "[ ";
for (auto & v2 : v.listItems()) {
printAmbiguous(v2, symbols, str, seen, depth - 1);
for (auto v2 : v.listItems()) {
if (v2)
printAmbiguous(*v2, symbols, str, seen, depth - 1);
else
str << "(nullptr)";
str << " ";
}
str << "]";
@@ -91,10 +89,10 @@ void printAmbiguous(
}
break;
case nExternal:
str << *v.external();
str << *v.external;
break;
case nFloat:
str << v.fpoint();
str << v.fpoint;
break;
default:
printError("Lix evaluator internal error: printAmbiguous: invalid value type");
+6 -6
View File
@@ -17,10 +17,10 @@ namespace nix {
* See: https://github.com/NixOS/nix/issues/9730
*/
void printAmbiguous(
const Value & v,
const SymbolTable & symbols,
std::ostream & str,
std::set<const void *> * seen,
int depth
);
Value &v,
const SymbolTable &symbols,
std::ostream &str,
std::set<const void *> *seen,
int depth);
}

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