Compare commits
76
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2dbb0f5bf | ||
|
|
b67ee8e801 | ||
|
|
b1469316cf | ||
|
|
ac2abb6aa4 | ||
|
|
0eb56266a0 | ||
|
|
1410c6ac7d | ||
|
|
4746f2e4d5 | ||
|
|
dcb715e773 | ||
|
|
61b44f783c | ||
|
|
94cbf73aa2 | ||
|
|
fbe811e94e | ||
|
|
53dc27f752 | ||
|
|
ced467fe49 | ||
|
|
d76581dbcb | ||
|
|
b5971baa4f | ||
|
|
02aefad372 | ||
|
|
ba71ad6236 | ||
|
|
24348f9bca | ||
|
|
5b0bc2e5b4 | ||
|
|
1fa9c4d55f | ||
|
|
9f87a43076 | ||
|
|
84912edd66 | ||
|
|
a9ac3d0173 | ||
|
|
1df3d8c79d | ||
|
|
fc22163c57 | ||
|
|
8a27e3d657 | ||
|
|
1cc3989c8e | ||
|
|
75c0314204 | ||
|
|
9bfef6a06c | ||
|
|
b7c2f17e91 | ||
|
|
b6d5670bcf | ||
|
|
176b834464 | ||
|
|
e29a1ccf0a | ||
|
|
ad52cbde2f | ||
|
|
699d3a63a6 | ||
|
|
96a39dc464 | ||
|
|
c8dc916356 | ||
|
|
1a4393d0aa | ||
|
|
7ac20fc47c | ||
|
|
e101400359 | ||
|
|
54fdb1edd8 | ||
|
|
dc6d5962a5 | ||
|
|
927facd35d | ||
|
|
ba5b1cd1cc | ||
|
|
a6201a64e5 | ||
|
|
65c0ede1e9 | ||
|
|
18e56efd9c | ||
|
|
f3a7bbe5f8 | ||
|
|
1d7368585e | ||
|
|
016d019340 | ||
|
|
f6ad1bfefb | ||
|
|
ff16735ca5 | ||
|
|
85d1465b93 | ||
|
|
5a0ab5af09 | ||
|
|
9d40ddb627 | ||
|
|
38b358ce27 | ||
|
|
24edb364b2 | ||
|
|
7e8c005d44 | ||
|
|
f85c84db37 | ||
|
|
469cb4218d | ||
|
|
959f6cb084 | ||
|
|
c773df3b58 | ||
|
|
8ceda6db13 | ||
|
|
58b113d623 | ||
|
|
0df9344b28 | ||
|
|
c085f5160a | ||
|
|
77daadb029 | ||
|
|
3f02ca5c35 | ||
|
|
1a4cb13411 | ||
|
|
e9f0354f7a | ||
|
|
fbd6a014ec | ||
|
|
2387104452 | ||
|
|
d84f13b73f | ||
|
|
37a570bd40 | ||
|
|
e62b7236e8 | ||
|
|
33eaaf02fd |
+3
-4
@@ -4,10 +4,9 @@ AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: BlockIndent
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: DontAlign
|
||||
AlignTrailingComments: false
|
||||
AllowShortBlocksOnASingleLine: Empty
|
||||
AllowShortBlocksOnASingleLine: Always
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortIfStatementsOnASingleLine: WithoutElse
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BinPackArguments: false
|
||||
@@ -36,7 +35,7 @@ BreakAfterAttributes: Always
|
||||
BreakBeforeBinaryOperators: NonAssignment
|
||||
BreakBeforeBraces: Custom
|
||||
BreakConstructorInitializers: BeforeComma
|
||||
ColumnLimit: 110
|
||||
ColumnLimit: 100
|
||||
EmptyLineAfterAccessModifier: Leave
|
||||
EmptyLineBeforeAccessModifier: Leave
|
||||
FixNamespaceComments: false
|
||||
|
||||
+7
-4
@@ -8,16 +8,18 @@ Checks:
|
||||
- -bugprone-narrowing-conversions
|
||||
# kind of nonsense
|
||||
- -bugprone-easily-swappable-parameters
|
||||
# too many warnings for now
|
||||
- -bugprone-implicit-widening-of-multiplication-result
|
||||
# Lix's exception handling is Questionable
|
||||
- -bugprone-empty-catch
|
||||
# many warnings
|
||||
- -bugprone-unchecked-optional-access
|
||||
# many warnings, seems like a questionable lint
|
||||
- -bugprone-branch-clone
|
||||
# extremely noisy before clang 19: https://github.com/llvm/llvm-project/issues/93959
|
||||
- -bugprone-multi-level-implicit-pointer-conversion
|
||||
# we don't compile out our asserts
|
||||
- -bugprone-assert-side-effect
|
||||
# FIXME(jade): figure out if this warning is any good
|
||||
- -bugprone-exception-escape
|
||||
# all thrown exceptions must derive from std::exception
|
||||
- hicpp-exception-baseclass
|
||||
# capturing async lambdas are dangerous
|
||||
@@ -25,6 +27,9 @@ Checks:
|
||||
# crimes must be appropriately declared as crimes
|
||||
- cppcoreguidelines-pro-type-cstyle-cast
|
||||
- lix-*
|
||||
# This can not yet be applied to Lix itself since we need to do source
|
||||
# reorganization so that lix/ include paths work.
|
||||
- -lix-fixincludes
|
||||
# This lint is included as an example, but the lib function it replaces is
|
||||
# already gone.
|
||||
- -lix-hasprefixsuffix
|
||||
@@ -33,5 +38,3 @@ Checks:
|
||||
CheckOptions:
|
||||
bugprone-reserved-identifier.AllowedIdentifiers: '__asan_default_options'
|
||||
bugprone-unused-return-value.AllowCastToVoid: true
|
||||
|
||||
ExtraArgs: ["-Werror=unnecessary-virtual-specifier"]
|
||||
|
||||
@@ -33,7 +33,3 @@ max_line_length = 0
|
||||
[meson.build]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.json]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
@@ -39,8 +39,3 @@ buildtime.bin
|
||||
|
||||
# Python compiled files from the code generators and test suite
|
||||
*.pyc
|
||||
|
||||
**/.idea
|
||||
|
||||
# Yeah, I've got no clue.
|
||||
/subprojects/.wraplock
|
||||
|
||||
@@ -1,5 +1,2 @@
|
||||
Fiona Behrens <me@kloenk.dev>
|
||||
Fiona Behrens <me@kloenk.dev> <me@kloenk.de>
|
||||
rootile <lix@rootile.de>
|
||||
rootile <lix@rootile.de> <commentator2.0@crystal-cavern.systems>
|
||||
rootile <lix@rootile.de> <lix@crystal-cavern.systems>
|
||||
|
||||
Generated
+3
-7
@@ -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"
|
||||
@@ -39,10 +39,6 @@ dependencies = [
|
||||
"rowan",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lixutil-rs"
|
||||
version = "0.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
@@ -51,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",
|
||||
]
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["lix/lix-doc", "lix/libutil"]
|
||||
members = ["lix/lix-doc"]
|
||||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
*
|
||||
+36
-157
@@ -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)
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
*
|
||||
@@ -1,19 +0,0 @@
|
||||
# Darwin: don't link liblix* into plugins (host process provides them at runtime).
|
||||
# Explicitly link curl so it binds to Nix-store libcurl, not /usr/lib/libcurl.
|
||||
if is_darwin
|
||||
plugin_deps = [
|
||||
liblix.partial_dependency(includes : true, compile_args : true),
|
||||
curl,
|
||||
]
|
||||
else
|
||||
plugin_deps = [liblix, curl]
|
||||
endif
|
||||
|
||||
plugin_mtls_store = shared_module(
|
||||
'plugin_mtls_store',
|
||||
'plugin_mtls_store.cc',
|
||||
dependencies : plugin_deps,
|
||||
install : false,
|
||||
build_by_default : true,
|
||||
link_args : is_darwin ? shared_module_link_args : strict_shared_module_link_args,
|
||||
)
|
||||
@@ -1,14 +0,0 @@
|
||||
R"(
|
||||
|
||||
**Store URL format**: `https+mtls://...`
|
||||
|
||||
This store allows a binary cache to be accessed via HTTPS with mutual TLS (client certificate authentication).
|
||||
|
||||
Both parameters are required:
|
||||
|
||||
- `tls-certificate`, a path to the TLS client certificate
|
||||
- `tls-private-key`, a path to the TLS private key backing the client certificate
|
||||
|
||||
If you don't need mTLS, use `https://` instead.
|
||||
|
||||
)"
|
||||
@@ -1,102 +0,0 @@
|
||||
#include "lix/libstore/store-api.hh"
|
||||
#include "lix/libutil/config.hh"
|
||||
#include "lix/libstore/http-binary-cache-store.hh"
|
||||
#include <stdlib.h>
|
||||
#include <curl/curl.h>
|
||||
|
||||
namespace nix {
|
||||
struct mTLSBinaryCacheStoreConfig : HttpBinaryCacheStoreConfig
|
||||
{
|
||||
using HttpBinaryCacheStoreConfig::HttpBinaryCacheStoreConfig;
|
||||
|
||||
const std::string name() override
|
||||
{
|
||||
return "mTLS HTTP Binary Cache Store";
|
||||
}
|
||||
|
||||
std::string doc() override
|
||||
{
|
||||
return
|
||||
#include "mtls-http-binary-cache-store.md"
|
||||
;
|
||||
}
|
||||
|
||||
PathsSetting<nix::Path> tlsCertificate{
|
||||
this,
|
||||
"",
|
||||
"tls-certificate",
|
||||
"Path of the TLS client certificate in PEM format as expected by CURLOPT_SSLCERT"
|
||||
};
|
||||
|
||||
PathsSetting<nix::Path> tlsKey{
|
||||
this,
|
||||
"",
|
||||
"tls-private-key",
|
||||
"Path of the TLS client certificate private key in PEM format as expected by CURLOPT_SSLKEY"
|
||||
};
|
||||
};
|
||||
|
||||
struct mTLSBinaryCacheStoreImpl : public HttpBinaryCacheStore
|
||||
{
|
||||
struct Keyring
|
||||
{
|
||||
nix::Path tlsCertificate;
|
||||
nix::Path tlsKey;
|
||||
};
|
||||
|
||||
mTLSBinaryCacheStoreConfig config_;
|
||||
std::shared_ptr<Keyring> keyring;
|
||||
|
||||
mTLSBinaryCacheStoreConfig & config() override
|
||||
{
|
||||
return config_;
|
||||
}
|
||||
const mTLSBinaryCacheStoreConfig & config() const override
|
||||
{
|
||||
return config_;
|
||||
}
|
||||
|
||||
mTLSBinaryCacheStoreImpl(
|
||||
const std::string & uriScheme, const Path & _cacheUri, mTLSBinaryCacheStoreConfig config
|
||||
)
|
||||
: Store(config)
|
||||
, HttpBinaryCacheStore("https", _cacheUri, config)
|
||||
, config_(std::move(config))
|
||||
, keyring(std::make_shared<Keyring>(config_.tlsCertificate.get(), config_.tlsKey.get()))
|
||||
{
|
||||
}
|
||||
|
||||
FileTransferOptions makeOptions(Headers && headers = {}) override
|
||||
{
|
||||
auto options = HttpBinaryCacheStore::makeOptions(std::move(headers));
|
||||
auto baseExtraSetup = std::move(options.extraSetup);
|
||||
auto keyring = this->keyring;
|
||||
|
||||
options.extraSetup = [keyring, baseExtraSetup{std::move(baseExtraSetup)}](CURL * req) {
|
||||
if (baseExtraSetup) {
|
||||
baseExtraSetup(req);
|
||||
}
|
||||
|
||||
const bool haveCert = !keyring->tlsCertificate.empty();
|
||||
const bool haveKey = !keyring->tlsKey.empty();
|
||||
if (!(haveCert && haveKey)) {
|
||||
throw Error("https+mtls requires both tls-certificate and tls-private-key");
|
||||
}
|
||||
curl_easy_setopt(req, CURLOPT_SSLCERT, keyring->tlsCertificate.c_str());
|
||||
curl_easy_setopt(req, CURLOPT_SSLKEY, keyring->tlsKey.c_str());
|
||||
};
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
static std::set<std::string> uriSchemes()
|
||||
{
|
||||
return {"https+mtls"};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
extern "C" void nix_plugin_entry()
|
||||
{
|
||||
nix::StoreImplementations::add<nix::mTLSBinaryCacheStoreImpl, nix::mTLSBinaryCacheStoreConfig>();
|
||||
}
|
||||
+9
-15
@@ -1,15 +1,9 @@
|
||||
let
|
||||
lockFile = builtins.fromJSON (builtins.readFile ./flake.lock);
|
||||
flake-compat-node = lockFile.nodes.${lockFile.nodes.root.inputs.flake-compat};
|
||||
flake-compat = builtins.fetchTarball {
|
||||
inherit (flake-compat-node.locked) url;
|
||||
sha256 = flake-compat-node.locked.narHash;
|
||||
};
|
||||
|
||||
flake = (
|
||||
import flake-compat {
|
||||
src = ./.;
|
||||
}
|
||||
);
|
||||
in
|
||||
flake.defaultNix
|
||||
(import (
|
||||
let
|
||||
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
|
||||
in
|
||||
fetchTarball {
|
||||
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
|
||||
sha256 = lock.nodes.flake-compat.locked.narHash;
|
||||
}
|
||||
) { src = ./.; }).defaultNix
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
*
|
||||
@@ -24,8 +24,8 @@ def map_contents_recursively(transformer):
|
||||
def process_command:
|
||||
.[0] as $context |
|
||||
.[1] as $body |
|
||||
# XXX FUTURE: drop sections once mdBook is at 0.5.0 or above in nixpkgs
|
||||
$body | (.items? // .sections) |= map(map_contents_recursively(if $context.renderer == "html" then transform_anchors_html else transform_anchors_strip end))
|
||||
;
|
||||
$body + {
|
||||
sections: $body.sections | map(map_contents_recursively(if $context.renderer == "html" then transform_anchors_html else transform_anchors_strip end)),
|
||||
};
|
||||
|
||||
process_command
|
||||
|
||||
@@ -22,21 +22,20 @@ fold.level = 30
|
||||
# not want to disable the links preprocessor entirely though because that requires
|
||||
# disabling *all* built-in preprocessors and selectively reenabling those we want.
|
||||
[preprocessor.substitute]
|
||||
command = "python3 substitute.py"
|
||||
command = "python3 doc/manual/substitute.py"
|
||||
before = ["anchors", "links"]
|
||||
|
||||
[preprocessor.anchors]
|
||||
renderers = ["html"]
|
||||
command = "jq --from-file anchors.jq"
|
||||
command = "jq --from-file doc/manual/anchors.jq"
|
||||
|
||||
[output.markdown]
|
||||
|
||||
# XXX FUTURE: may be reenabled once mdBook 0.5.0 or above and matching mdbook-linkchecker are in nixpkgs
|
||||
#[output.linkcheck]
|
||||
[output.linkcheck]
|
||||
# no Internet during the build (in the sandbox)
|
||||
#follow-web-links = false
|
||||
follow-web-links = false
|
||||
|
||||
# mdbook-linkcheck does not understand [foo]{#bar} style links, resulting in
|
||||
# excessive "Potential incomplete link" warnings. No other kind of warning was
|
||||
# produced at the time of writing.
|
||||
#warning-policy = "ignore"
|
||||
warning-policy = "ignore"
|
||||
|
||||
@@ -66,9 +66,6 @@ delan:
|
||||
forgejo: delan
|
||||
github: delan
|
||||
|
||||
delroth:
|
||||
github: delroth
|
||||
|
||||
detroyejr:
|
||||
display_name: Jonathan De Troye
|
||||
github: detroyejr
|
||||
@@ -80,20 +77,10 @@ edolstra:
|
||||
display_name: Eelco Dolstra
|
||||
github: edolstra
|
||||
|
||||
emilazy:
|
||||
display_name: Emily
|
||||
forgejo: emilazy
|
||||
github: emilazy
|
||||
|
||||
ericson:
|
||||
display_name: John Ericson
|
||||
github: ericson2314
|
||||
|
||||
getchoo:
|
||||
display_name: Seth Flynn
|
||||
forgejo: getchoo
|
||||
github: getchoo
|
||||
|
||||
gilice:
|
||||
forgejo: gilice
|
||||
|
||||
@@ -102,9 +89,6 @@ goldstein:
|
||||
forgejo: goldstein
|
||||
github: GoldsteinE
|
||||
|
||||
gustavderdrache:
|
||||
github: gustavderdrache
|
||||
|
||||
horrors:
|
||||
display_name: eldritch horrors
|
||||
forgejo: pennae
|
||||
@@ -117,9 +101,6 @@ ian-h-chamberlain:
|
||||
forgejo: ian-h-chamberlain
|
||||
github: ian-h-chamberlain
|
||||
|
||||
infinisil:
|
||||
github: infinisil
|
||||
|
||||
isabelroses:
|
||||
forgejo: isabelroses
|
||||
github: isabelroses
|
||||
@@ -131,16 +112,6 @@ jade:
|
||||
just1602:
|
||||
forgejo: just1602
|
||||
|
||||
k900:
|
||||
display_name: K900
|
||||
forgejo: K900
|
||||
github: K900
|
||||
|
||||
kasimeka:
|
||||
display_name: ورد
|
||||
forgejo: janw4ld
|
||||
github: kasimeka
|
||||
|
||||
kfears:
|
||||
display_name: KFears
|
||||
forgejo: kfearsoff
|
||||
@@ -179,33 +150,14 @@ ma27:
|
||||
matthewbauer:
|
||||
github: matthewbauer
|
||||
|
||||
mic92:
|
||||
github: mic92
|
||||
|
||||
midnightveil:
|
||||
display_name: julia
|
||||
forgejo: midnightveil
|
||||
github: midnightveil
|
||||
|
||||
milibopp:
|
||||
display_name: Emilia Bopp
|
||||
forgejo: milibopp
|
||||
github: milibopp
|
||||
|
||||
nan-git:
|
||||
display_name: NaN-git
|
||||
github: NaN-git
|
||||
|
||||
ncfavier:
|
||||
github: ncfavier
|
||||
|
||||
nkk0:
|
||||
github: nkk0
|
||||
|
||||
not-my-profile:
|
||||
display_name: Martin Fischer
|
||||
github: not-my-profile
|
||||
|
||||
p-e-meunier:
|
||||
display_name: Pierre-Etienne Meunier
|
||||
github: P-E-Meunier
|
||||
@@ -248,19 +200,12 @@ roberth:
|
||||
display_name: Robert Hensing
|
||||
github: roberth
|
||||
|
||||
rootile:
|
||||
display_name: rootile (Rutile)
|
||||
forgejo: rootile
|
||||
|
||||
sandydoo:
|
||||
github: sandydoo
|
||||
|
||||
seppel3210:
|
||||
github: Seppel3210
|
||||
|
||||
stevalkr:
|
||||
github: stevalkr
|
||||
|
||||
teofilc:
|
||||
forgejo: teofilc
|
||||
github: TeofilC
|
||||
@@ -287,14 +232,6 @@ vigress8:
|
||||
forgejo: vigress8
|
||||
github: vigress8
|
||||
|
||||
vlaci:
|
||||
github: vlaci
|
||||
|
||||
vlinkz:
|
||||
display_name: Victor Fuentes
|
||||
forgejo: vlinkz
|
||||
github: vlinkz
|
||||
|
||||
winter:
|
||||
forgejo: winter
|
||||
github: winterqt
|
||||
@@ -302,21 +239,11 @@ winter:
|
||||
xanderio:
|
||||
github: xanderio
|
||||
|
||||
xokdvium:
|
||||
github: xokdvium
|
||||
|
||||
xyenon:
|
||||
forgejo: xyenon
|
||||
github: xyenon
|
||||
|
||||
yorickvp:
|
||||
github: yorickvp
|
||||
|
||||
yshui:
|
||||
github: yshui
|
||||
|
||||
ysndr:
|
||||
github: ysndr
|
||||
|
||||
zimbatm:
|
||||
github: zimbatm
|
||||
|
||||
@@ -69,7 +69,7 @@ let
|
||||
let
|
||||
result = squash ''
|
||||
- ${
|
||||
if inlineHTML then ''<span id="conf-${name}">[`${name}`](#conf-${name})</span>'' else "`${name}`"
|
||||
if inlineHTML then ''<span id="conf-${name}">[`${name}`](#conf-${name})</span>'' else ''`${name}`''
|
||||
}
|
||||
|
||||
${indent " " body}
|
||||
@@ -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 =
|
||||
|
||||
@@ -41,13 +41,9 @@ manual = custom_target(
|
||||
'-euo', 'pipefail',
|
||||
'-c',
|
||||
'''
|
||||
@0@ @INPUT0@ @3@ > @DEPFILE@
|
||||
|
||||
# Needs to be in lix/doc/manual for e.g. substitute.py
|
||||
pushd @3@
|
||||
@1@ build . -d @2@
|
||||
popd
|
||||
|
||||
@0@ @INPUT0@ @CURRENT_SOURCE_DIR@ > @DEPFILE@
|
||||
cd @SOURCE_ROOT@
|
||||
@1@ build doc/manual -d @2@ | { grep -Fv "because fragment resolution isn't implemented" || :; }
|
||||
rm -rf @2@/manual
|
||||
mv @2@/html @2@/manual
|
||||
find @2@/manual -iname meson.build -delete
|
||||
@@ -55,7 +51,6 @@ manual = custom_target(
|
||||
python.full_path(),
|
||||
mdbook.full_path(),
|
||||
meson.current_build_dir(),
|
||||
meson.current_source_dir()
|
||||
),
|
||||
],
|
||||
input : [
|
||||
@@ -86,7 +81,7 @@ manual = custom_target(
|
||||
depfile : 'manual.d',
|
||||
env : {
|
||||
'RUST_LOG': 'info',
|
||||
'MANUAL_SUBSTITUTE_SEARCH': meson.current_build_dir() / 'src',
|
||||
'MDBOOK_SUBSTITUTE_SEARCH': meson.current_build_dir() / 'src',
|
||||
},
|
||||
)
|
||||
manual_md = manual[1]
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
synopsis: "nix-eval-jobs support `--apply` flag"
|
||||
cls: [5748]
|
||||
category: "Features"
|
||||
credits: [isabelroses,mic92,ysndr]
|
||||
issues: [fj#1214]
|
||||
---
|
||||
|
||||
`nix-eval-jobs` now supports the `--apply` flag. With this you can apply the
|
||||
provided function to the each derivation, the result of this function will then
|
||||
be serialized as a JSON value and stored inside `"extraValue"` key of the json
|
||||
line output.
|
||||
@@ -20,6 +20,7 @@
|
||||
- [Basic Package Management](package-management/basic-package-mgmt.md)
|
||||
- [Profiles](package-management/profiles.md)
|
||||
- [Garbage Collection](package-management/garbage-collection.md)
|
||||
- [Garbage Collector Roots](package-management/garbage-collector-roots.md)
|
||||
- [Sharing Packages Between Machines](package-management/sharing-packages.md)
|
||||
- [Serving a Nix store via HTTP](package-management/binary-cache-substituter.md)
|
||||
- [Copying Closures via SSH](package-management/copy-closure.md)
|
||||
@@ -39,9 +40,6 @@
|
||||
- [Tuning Cores and Jobs](advanced-topics/cores-vs-jobs.md)
|
||||
- [Verifying Build Reproducibility](advanced-topics/diff-hook.md)
|
||||
- [Using the `post-build-hook`](advanced-topics/post-build-hook.md)
|
||||
- [Pasta](advanced-topics/pasta.md)
|
||||
- [Known Issues](known-issues/known-issues.md)
|
||||
- [Limitations around non-isolated builds](known-issues/non-isolated-build-limits.md)
|
||||
- [Command Reference](command-ref/command-ref.md)
|
||||
- [Common Options](command-ref/opt-common.md)
|
||||
- [Common Environment Variables](command-ref/env-common.md)
|
||||
@@ -200,8 +198,6 @@
|
||||
- [Release Notes](release-notes/release-notes.md)
|
||||
- [Upcoming release](release-notes/rl-next.md)
|
||||
<!-- RELENG-AUTO-INSERTION-MARKER (see releng/release_notes.py) -->
|
||||
- [Lix 2.95 (2026-03-13)](release-notes/rl-2.95.md)
|
||||
- [Lix 2.94 (2025-11-17)](release-notes/rl-2.94.md)
|
||||
- [Lix 2.93 (2025-05-09)](release-notes/rl-2.93.md)
|
||||
- [Lix 2.92 (2025-01-18)](release-notes/rl-2.92.md)
|
||||
- [Lix 2.91 (2024-08-12)](release-notes/rl-2.91.md)
|
||||
|
||||
@@ -41,17 +41,106 @@ contains Nix.
|
||||
> If you are building via the Lix daemon (default on Linux and macOS), it is the Lix daemon user account (that is, `root`) that should have SSH access to a user (not necessarily `root`) on the remote machine.
|
||||
>
|
||||
> Furthermore, `root` needs to have the public host keys for the remote system in its `.ssh/known_hosts`.
|
||||
> To add them to `known_hosts` for root, do `ssh-keyscan HOST | sudo tee -a ~root/.ssh/known_hosts`.
|
||||
> To add them to `known_hosts` for root, do `ssh-keyscan USER@HOST | sudo tee -a ~root/.ssh/known_hosts`.
|
||||
>
|
||||
> If you can’t or don’t want to configure `root` to be able to access the remote machine, you can use a private Nix store instead by passing e.g. `--store ~/my-nix` when running a Nix command from the local machine.
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
The list of remote machines can be specified on the command line or in
|
||||
the Lix configuration file. The former is convenient for testing.
|
||||
Additionally, there are two supported formats to configure remote builders:
|
||||
The legacy, "space"-separated format and starting with Lix 2.95.0, a TOML.
|
||||
the Lix configuration file. The former is convenient for testing. For
|
||||
example, the following command allows you to build a derivation for
|
||||
`x86_64-darwin` on a Linux machine:
|
||||
|
||||
```console
|
||||
$ uname
|
||||
Linux
|
||||
|
||||
$ nix build --impure \
|
||||
--expr '(with import <nixpkgs> { system = "x86_64-darwin"; }; runCommand "foo" {} "uname > $out")' \
|
||||
--builders 'ssh://mac x86_64-darwin'
|
||||
[1/0/1 built, 0.0 MiB DL] building foo on ssh://mac
|
||||
|
||||
$ cat ./result
|
||||
Darwin
|
||||
```
|
||||
|
||||
It is possible to specify multiple builders separated by a semicolon or
|
||||
a newline, e.g.
|
||||
|
||||
```console
|
||||
--builders 'ssh://mac x86_64-darwin ; ssh://beastie x86_64-freebsd'
|
||||
```
|
||||
|
||||
Each machine specification consists of the following elements, separated
|
||||
by spaces. Only the first element is required. To leave a field at its
|
||||
default, set it to `-`.
|
||||
|
||||
1. The URI of the remote store in the format
|
||||
`ssh://[username@]hostname[?port=<port>]`, e.g. `ssh://nix@mac` or `ssh://mac`.
|
||||
If the ssh server is not listening on port 22 (e.g. port 1337 in this case)
|
||||
the URI would be `ssh://nix@mac?port=1337`
|
||||
For backward compatibility, `ssh://` may be omitted. The hostname
|
||||
may be an alias defined in your `~/.ssh/config`.
|
||||
|
||||
2. A comma-separated list of Nix platform type identifiers, such as
|
||||
`x86_64-darwin`. It is possible for a machine to support multiple
|
||||
platform types, e.g., `i686-linux,x86_64-linux`. If omitted, this
|
||||
defaults to the local platform type.
|
||||
|
||||
3. The SSH identity file to be used to log in to the remote machine. If
|
||||
omitted, SSH will use its regular identities.
|
||||
|
||||
4. The maximum number of builds that Lix will execute in parallel on
|
||||
the machine. Typically this should be equal to the number of CPU
|
||||
cores. For instance, the machine `itchy` in the example will execute
|
||||
up to 8 builds in parallel.
|
||||
|
||||
5. The “speed factor”, indicating the relative speed of the machine. If
|
||||
there are multiple machines of the right type, Lix will prefer the
|
||||
fastest, taking load into account.
|
||||
|
||||
6. A comma-separated list of *supported features*. If a derivation has
|
||||
the `requiredSystemFeatures` attribute, then Lix will only perform
|
||||
the derivation on a machine that has the specified features. For
|
||||
instance, the attribute
|
||||
|
||||
```nix
|
||||
requiredSystemFeatures = [ "kvm" ];
|
||||
```
|
||||
|
||||
will cause the build to be performed on a machine that has the `kvm`
|
||||
feature.
|
||||
|
||||
7. A comma-separated list of *mandatory features*. A machine will only
|
||||
be used to build a derivation if all of the machine’s mandatory
|
||||
features appear in the derivation’s `requiredSystemFeatures`
|
||||
attribute.
|
||||
|
||||
8. The (base64-encoded) public host key of the remote machine. If omitted, SSH
|
||||
will use its regular known-hosts file. Specifically, the field is calculated
|
||||
via `base64 -w0 /etc/ssh/ssh_host_ed25519_key.pub`.
|
||||
|
||||
For example, the machine specification
|
||||
|
||||
nix@scratchy.labs.cs.uu.nl i686-linux /home/nix/.ssh/id_scratchy_auto 8 1 kvm
|
||||
nix@itchy.labs.cs.uu.nl i686-linux /home/nix/.ssh/id_scratchy_auto 8 2
|
||||
nix@poochie.labs.cs.uu.nl i686-linux /home/nix/.ssh/id_scratchy_auto 1 2 kvm benchmark
|
||||
|
||||
specifies several machines that can perform `i686-linux` builds.
|
||||
However, `poochie` will only do builds that have the attribute
|
||||
|
||||
```nix
|
||||
requiredSystemFeatures = [ "benchmark" ];
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```nix
|
||||
requiredSystemFeatures = [ "benchmark" "kvm" ];
|
||||
```
|
||||
|
||||
`itchy` cannot do builds that require `kvm`, but `scratchy` does support
|
||||
such builds. For regular builds, `itchy` will be preferred over
|
||||
`scratchy` because it has a higher speed factor.
|
||||
|
||||
Remote builders can also be configured in `nix.conf`, e.g.
|
||||
|
||||
@@ -70,180 +159,3 @@ option `builders-use-substitutes` in your local `nix.conf`.
|
||||
|
||||
To build only on remote builders and disable building on the local
|
||||
machine, you can use the option `--max-jobs 0`.
|
||||
|
||||
---
|
||||
|
||||
Each machine specification consists of the following attributes.
|
||||
How those are combined within the configuration file differs for the formats, and will be explained further down.
|
||||
|
||||
1. `uri` (**required**)
|
||||
The URI of the remote store in the format
|
||||
`ssh[-ng]://[username@]hostname[?port=<port>]`, e.g. `ssh://nix@mac` or `ssh://mac`.
|
||||
If the ssh server is not listening on port 22 (e.g. port 1337 in this case)
|
||||
the URI would be `ssh[-ng]://nix@mac?port=1337`. The hostname
|
||||
may be an alias defined in your `~/.ssh/config`.
|
||||
|
||||
2. `system-types` (**optional**)
|
||||
A list of Nix platform type identifiers, such as
|
||||
`x86_64-darwin`. It is possible for a machine to support multiple
|
||||
platform types, e.g., `i686-linux` and `x86_64-linux`.
|
||||
|
||||
Defaults to the local platform type
|
||||
|
||||
3. `ssh-key` (**optional**)
|
||||
The SSH identity file to be used to log in to the remote machine.
|
||||
|
||||
Defaults to SSHs regular identities.
|
||||
|
||||
4. `jobs` (**optional**)
|
||||
The maximum number of builds that Lix will execute in parallel on
|
||||
the machine. Typically, this should be equal to the number of CPU
|
||||
cores divided by the cores within the target machines configuration, i.e. `jobs * cores ~= cpu cores`
|
||||
|
||||
Defaults to 1; must be a positive integer.
|
||||
|
||||
5. `speed-factor`
|
||||
The “speed factor”, indicating the relative speed of the machine. If
|
||||
there are multiple machines of the right type, Lix will prefer the
|
||||
fastest, taking load into account.
|
||||
|
||||
Defaults to 1; must be a positive float.
|
||||
|
||||
6. `supported-features` (**optional**)
|
||||
A list of *supported features*. If a derivation has
|
||||
the `requiredSystemFeatures` attribute, then Lix will only schedule
|
||||
the derivation on a machine that has the specified features. For
|
||||
example, the attribute
|
||||
|
||||
```nix
|
||||
requiredSystemFeatures = [ "kvm" ];
|
||||
```
|
||||
|
||||
will cause the build to be performed on a machine that has the `kvm`
|
||||
feature.
|
||||
|
||||
Defaults to an empty list.
|
||||
|
||||
7. `mandatory-features` (**optional**)
|
||||
A list of *mandatory features*. A machine will only
|
||||
be used to build a derivation if all the machine’s mandatory
|
||||
features appear in the derivation’s `requiredSystemFeatures`
|
||||
attribute.
|
||||
|
||||
Defaults to an empty list.
|
||||
|
||||
8. `ssh-public-host-key` (**optional**)
|
||||
The public host key of the remote machine.
|
||||
|
||||
Defaults to basic ssh behavior (checking contents of the known-hosts file)
|
||||
|
||||
|
||||
### Using a TOML configuration
|
||||
|
||||
Each machine is configured as an attribute within the map called `machines`.
|
||||
The attributes name is the machines name.
|
||||
Attributes can be in any order.
|
||||
|
||||
For example:
|
||||
|
||||
```toml
|
||||
version = 1
|
||||
|
||||
[machines.andesite]
|
||||
uri = "ssh://lix@andesite.lix.systems" # toml also allows for comments
|
||||
system-types = ["i686-linux"]
|
||||
jobs = 8
|
||||
speed-factor = 1.0
|
||||
supported-features = ["kvm"]
|
||||
ssh-key = "/home/deepslate/.ssh/id_ed25519"
|
||||
|
||||
[machines.diorite]
|
||||
uri = "ssh://lix@diorite.lix.systems"
|
||||
system-types = ["i686-linux"]
|
||||
jobs = 8
|
||||
speed-factor = 2.0
|
||||
ssh-key = "/home/deepslate/.ssh/id_ed25519"
|
||||
|
||||
[machines.granite]
|
||||
uri = "ssh://lix@granite.lix.systems"
|
||||
system-types = ["i686-linux"]
|
||||
jobs = 1
|
||||
speed-factor = 2.0
|
||||
supported-features = ["kvm", "benchmark"]
|
||||
ssh-key = "/home/deepslate/.ssh/id_ed25519"
|
||||
|
||||
[machines.legacy]
|
||||
uri = "ssh://nix@nix-15-11.nixos.org"
|
||||
enable = false
|
||||
|
||||
```
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> If the version tag is omitted (e.g. in the CLI), it defaults to the latest version.
|
||||
> It is strongly recommended to always provide a version tag for configuration within files to avoid breakage.
|
||||
|
||||
For testing purposes, one can also define a builder ad hoc on the CLI as follows:
|
||||
`--builders 'machines.andesite = {uri = "ssh://lix@andesite.lix.systems", jobs = 8}'`
|
||||
|
||||
|
||||
#### Special handling of fields
|
||||
- `enable` (**optional**)
|
||||
If set to false, the declared machine will not be loaded.
|
||||
This allows one to statically disable machines.
|
||||
|
||||
Defaults to true
|
||||
|
||||
### Using the legacy format
|
||||
> **Warning**
|
||||
>
|
||||
> This format is frozen and new features / configuration options will not be backported to this format.
|
||||
|
||||
It is possible to specify multiple builders separated by a semicolon or
|
||||
a newline, e.g.
|
||||
|
||||
```console
|
||||
--builders 'ssh://mac x86_64-darwin ; ssh://beastie x86_64-freebsd'
|
||||
```
|
||||
|
||||
Every machine specification consists of the elements listed in the section above, seperated by any amount of spaces or tabs.
|
||||
The Attributes need to be provided **in order** and without names.
|
||||
To leave a field at its default, set it to `-`.
|
||||
Lists are colon seperated, without additional spaces.
|
||||
|
||||
```
|
||||
lix@andesite.lix.systems i686-linux /home/deepslate/.ssh/id_ed25519 8 1 kvm
|
||||
lix@diorite.lix.systems i686-linux /home/deepslate/.ssh/id_ed25519 8 2
|
||||
lix@granite.lix.systems i686-linux /home/deepslate/.ssh/id_ed25519 1 2 kvm benchmark
|
||||
```
|
||||
|
||||
#### Special handling of fields
|
||||
- `uri`: Due to backward compatibility, the `ssh://` may be omitted for the store-uri.
|
||||
- `ssh-public-host-key`: The key must be provided encoded in base64. Specifically calculated via `base64 -w0 /etc/ssh/ssh_host_ed25519_key.pub`
|
||||
|
||||
|
||||
### Format detection
|
||||
|
||||
At first, the given configuration is being parsed syntactically as a toml.
|
||||
If parsing fails and the given configuration contains a `"` the error is presented to the user, as those characters are necessary for TOML, but disallowed for the legacy format.
|
||||
Otherwise, parsing is retried using the legacy format.
|
||||
If non-syntactic errors are detected within the toml, the exception will always be shown to the user directly.
|
||||
|
||||
|
||||
## Builder selection
|
||||
The configuration(s) above specify several machines that can perform `i686-linux` builds.
|
||||
However, `granite` will only do builds that have the attribute
|
||||
|
||||
```nix
|
||||
requiredSystemFeatures = [ "benchmark" ];
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```nix
|
||||
requiredSystemFeatures = [ "benchmark" "kvm" ];
|
||||
```
|
||||
|
||||
`diorite` cannot do builds that require `kvm`, but `andesite` does support
|
||||
such builds. For regular builds, `diorite` will be preferred over
|
||||
`andesite` because it has a higher speed factor.
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
# [Pasta](https://passt.top/passt/about/): a network sandbox for fixed-output derivations
|
||||
|
||||
## Introduction
|
||||
|
||||
This section only applies to **Linux systems** as Pasta is a Linux-only measure.
|
||||
|
||||
Since [CVE-2025-46416](https://lix.systems/blog/2025-06-24-lix-cves/), the Lix project decided to adopt [Pasta](https://passt.top/passt/about/) for all fixed-output derivations, protecting against various attack vectors such as UNIX abstract domain sockets or more manipulation at the network layer from a malicious fixed-output derivation code.
|
||||
|
||||
Pasta acts as a translation layer between a layer-2 network interface and layer-4 sockets (TCP, UDP, ICMP/ICMPv6 echo) on the host. It requires no special privileges and can serve as a alternative to [SLiRP](https://en.wikipedia.org/wiki/Slirp) which was used [by Guix to mitigate the same problem](https://codeberg.org/guix/guix/commit/fb42611b8f27960304db5a1c0d33b8371dcde2a8).
|
||||
|
||||
## How to disable Pasta?
|
||||
|
||||
It's sufficient to pass `pasta-path = ""` in your `/etc/nix/nix.conf` or on the command line `--pasta-path ""` of a Lix invocation.
|
||||
|
||||
## Known issues surrounding Pasta
|
||||
|
||||
- Only the first DNS server in `/etc/resolv.conf` is considered: failover is not possible.
|
||||
- [Reduced feature set compared to the Linux kernel](https://passt.top/passt/about/#features)
|
||||
- [Performance overhead in multi-gigabits contexts and IMIX MTUs](https://passt.top/passt/about/#performance_1)
|
||||
@@ -58,7 +58,7 @@ $ nix-build flake:nixpkgs -A firefox
|
||||
$ nix-build flake:github:NixOS/nixpkgs/release-23.11 -A firefox
|
||||
```
|
||||
|
||||
Finally, for legacy reasons, if a path starts with `channel:`, the rest of the argument is interpreted as the name of a *nixpkgs* channel tarball to fetch from `https://channels.nixos.org/$CHANNEL_NAME/nixexprs.tar.xz`.
|
||||
Finally, for legacy reasons, if a path starts with `channel:`, the rest of the argument is interpreted as the name of a *nixpkgs* channel tarball to fetch from `https://nixos.org/channels/$CHANNEL_NAME/nixexprs.tar.xz`.
|
||||
This is a **hard coded URL** pattern and is *not* related to the subscribed channels managed by the [nix-channel](./nix-channel.md) command.
|
||||
|
||||
> **Note**: any of the special syntaxes may always be disambiguated by prefixing the path.
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
Channels are a mechanism for referencing remote Nix expressions and conveniently retrieving their latest version.
|
||||
|
||||
The moving parts of channels are:
|
||||
- The official channels listed at <https://channels.nixos.org>
|
||||
- The official channels listed at <https://nixos.org/channels>
|
||||
- The user-specific list of [subscribed channels](#subscribed-channels)
|
||||
- The [downloaded channel contents](#channels)
|
||||
- The [Nix expression search path](@docroot@/command-ref/conf-file.md#conf-nix-path), set with the [`-I` option](#opt-I) or the [`NIX_PATH` environment variable](#env-NIX_PATH)
|
||||
@@ -77,9 +77,9 @@ This command has the following operations:
|
||||
Subscribe to the Nixpkgs channel and run `hello` from the GNU Hello package:
|
||||
|
||||
```console
|
||||
$ nix-channel --add https://channels.nixos.org/nixpkgs-unstable
|
||||
$ nix-channel --add https://nixos.org/channels/nixpkgs-unstable
|
||||
$ nix-channel --list
|
||||
nixpkgs https://channels.nixos.org/nixpkgs
|
||||
nixpkgs https://nixos.org/channels/nixpkgs
|
||||
$ nix-channel --update
|
||||
$ nix-shell -p hello --run hello
|
||||
hello
|
||||
|
||||
@@ -148,8 +148,8 @@ To copy the store path with symbolic name `gcc` from another profile:
|
||||
$ nix-env --install --from-profile /nix/var/nix/profiles/foo gcc
|
||||
```
|
||||
|
||||
To install a specific [store derivation](@docroot@/glossary.md#gloss-store-derivation)
|
||||
(typically created by `nix-instantiate`):
|
||||
To install a specific [store derivation] (typically created by
|
||||
`nix-instantiate`):
|
||||
|
||||
```console
|
||||
$ nix-env --install /nix/store/fibjb1bfbpm5mrsxc4mh2d8n37sxh91i-gcc-3.4.3.drv
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# Synopsis
|
||||
|
||||
`nix-instantiate`
|
||||
[`--parse` | `--eval` [`--strict`] [`--raw`] [`--json`] [`--xml`] ]
|
||||
[`--parse` | `--eval` [`--strict`] [`--json`] [`--xml`] ]
|
||||
[`--read-write-mode`]
|
||||
[`--arg` *name* *value*]
|
||||
[{`--attr`| `-A`} *attrPath*]
|
||||
@@ -107,27 +107,15 @@ 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`\
|
||||
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.
|
||||
|
||||
- `--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
|
||||
|
||||
@@ -15,6 +15,7 @@ Each of *paths* is processed as follows:
|
||||
1. If it is not [valid], substitute the store derivation file itself.
|
||||
2. Realise its [output paths]:
|
||||
- Try to fetch from [substituters] the [store objects] associated with the output paths in the store derivation's [closure].
|
||||
- With [content-addressed derivations] (experimental): Determine the output paths to realise by querying content-addressed realisation entries in the [Nix database].
|
||||
- For any store paths that cannot be substituted, produce the required store objects. This involves first realising all outputs of the derivation's dependencies and then running the derivation's [`builder`](@docroot@/language/derivations.md#attr-builder) executable. <!-- TODO: Link to build process page #8888 -->
|
||||
- Otherwise, and if the path is not already valid: Try to fetch the associated [store objects] in the path's [closure] from [substituters].
|
||||
|
||||
@@ -27,6 +28,7 @@ If no substitutes are available and no store derivation is given, realisation fa
|
||||
[store objects]: @docroot@/glossary.md#gloss-store-object
|
||||
[closure]: @docroot@/glossary.md#gloss-closure
|
||||
[substituters]: @docroot@/command-ref/conf-file.md#conf-substituters
|
||||
[content-addressed derivations]: @docroot@/contributing/experimental-features.md#xp-feature-ca-derivations
|
||||
[Nix database]: @docroot@/glossary.md#gloss-nix-database
|
||||
|
||||
The resulting paths are printed on standard output.
|
||||
|
||||
@@ -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,7 +19,7 @@ This description is not normative, but a feature removal may roughly happen like
|
||||
1. Add a warning when the feature is being used.
|
||||
2. Disable the feature by default, putting it behind a deprecated feature flag.
|
||||
- If disabling the feature started out as an opt-in experimental feature, turn that experimental flag into a no-op or remove it entirely.
|
||||
For example, `--extra-experimental-features no-url-literals` becomes `--extra-deprecated-features url-literals`.
|
||||
For example, `--extra-experimental-features=no-url-literals` becomes `--extra-deprecated-features=url-literals`.
|
||||
3. Decide on a time frame for how long that feature will still be supported for backwards compatibility, and clearly communicate that in the error messages.
|
||||
- Sometimes, automatic migration to alternatives is possible, and such should be provided if possible
|
||||
- At least one NixOS release cycle should be the minimum
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -51,7 +39,7 @@ $ nix-shell -A native-clangStdenvPackages
|
||||
|
||||
### Building from the development shell
|
||||
|
||||
Run a clean build and test with `just clean setup build install test`.
|
||||
Run a clean build and test with `just clean build install test`.
|
||||
|
||||
You can also run the unit tests and integration tests separately:
|
||||
|
||||
@@ -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,59 +129,7 @@ To inspect the canonical source of truth on what the state of the buildsystem co
|
||||
$ meson introspect
|
||||
```
|
||||
|
||||
#### LLD
|
||||
|
||||
The development shell on Linux uses LLD by default for faster link times.
|
||||
This is set using `mesonFlags`, so to override it, you can simplify re-specify the linker to Meson:
|
||||
|
||||
```bash
|
||||
$ just setup-custom -Dc_link_args=-fuse-ld=ld -Dcpp_link_args=-fuse-ld=ld
|
||||
```
|
||||
|
||||
While using LLD, you may find it helpful to use ThinLTO for even further improvements to link times for incremental builds:
|
||||
|
||||
```bash
|
||||
$ just setup-custom -Db_lto=true -Db_lto_mode=thin -Db_thinlto_cache=true
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
## Interacting with the CI, Buildkite
|
||||
|
||||
We use Buildkite for our CI, usually you will not have to interact directly with it other than reviewing any errors it produces, which are linked from Gerrit.
|
||||
|
||||
However in certain cases a CI run will fail due to transient issues not related to your code and you will need to rerun it by hand.
|
||||
You can log in to the CI via [SSO](https://buildkite.com/sso/lix-project). On your job you can then hit the "Retry failed" button to rerun it, normally you will not have a repeat of the transient issue.
|
||||
If the build still fails on CI issues or all builds are failing this should be reported via [Zulip on #T-infra](https://zulip.lix.systems/#narrow/channel/7-T-infra) or [Matrix on #dev](https://matrix.to/#/%23dev%3Alix.systems?via=lix.systems).
|
||||
|
||||
## 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:
|
||||
|
||||
@@ -350,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
|
||||
@@ -365,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
|
||||
|
||||
@@ -410,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.
|
||||
|
||||
@@ -479,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.
|
||||
@@ -497,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.
|
||||
@@ -525,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.
|
||||
@@ -541,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.
|
||||
|
||||
@@ -62,12 +62,6 @@ For `installcheck` specifically, first run `just install` before running the tes
|
||||
|
||||
Finer-grained filtering within a test suite is also possible using the [--gtest_filter](https://google.github.io/googletest/advanced.html#running-a-subset-of-the-tests) command-line option to a test suite executable, or the `GTEST_FILTER` environment variable.
|
||||
|
||||
### Inspecting failures
|
||||
|
||||
The test suite emits logs in `build/meson-logs/`; the full textual failure logs are in `build/meson-logs/testlog.txt`.
|
||||
|
||||
If you want a much nicer experience of viewing the logs in a structured manner, use `xunit-viewer --results build/meson-logs/testlog.junit.xml --server` to view them in a web browser.
|
||||
|
||||
### Unit test support libraries
|
||||
|
||||
There are headers and code which are not just used to test the library in question, but also downstream libraries.
|
||||
@@ -383,10 +377,7 @@ I grepped `lix/` for `get[eE]nv\("` to find the mentions in Lix code.
|
||||
Overrides compile-time configuration of various locations used by Lix. See `lix/libstore/globals.cc`.
|
||||
|
||||
**Expected value**: a directory
|
||||
- `LIX_DAEMON_SOCKET_DIR` (optional) - Overrides the daemon socket directory from `$NIX_STATE_DIR/daemon-socket`.
|
||||
|
||||
**Expected value**: a directory
|
||||
- `NIX_DAEMON_SOCKET_PATH` (optional) - Overrides the daemon socket path from `$NIX_STATE_DIR/daemon-socket/socket`. Ignored if `LIX_DAEMON_SOCKET_DIR` is set.
|
||||
- `NIX_DAEMON_SOCKET_PATH` (optional) - Overrides the daemon socket path from `$NIX_STATE_DIR/daemon-socket/socket`.
|
||||
|
||||
**Expected value**: path to a socket
|
||||
- `NIX_LOG_FD` (output) - An FD number for logs in `internal-json` format to be sent to.
|
||||
@@ -404,6 +395,7 @@ I grepped `lix/` for `get[eE]nv\("` to find the mentions in Lix code.
|
||||
|
||||
**Expected value**: the path to an executable shell
|
||||
- `PRINT_PATH` - Undocumented. Used by `nix-prefetch-url` as an alternative form of `--print-path`. Why???
|
||||
- `_NIX_IN_TEST` - If present with any value, makes `fetchClosure` accept file URLs in addition to HTTP ones. Why is this not `_NIX_FORCE_HTTP`??
|
||||
|
||||
Not used anywhere else.
|
||||
- `NIX_ALLOW_EVAL` - Used by eval-cache tests to block evaluation if set to `0`.
|
||||
@@ -457,6 +449,9 @@ I grepped `lix/` for `get[eE]nv\("` to find the mentions in Lix code.
|
||||
- `NIX_CLIENT_PACKAGE` - Runs the test suite against an alternate Nix client with the current daemon.
|
||||
|
||||
**Expected value**: something like `/nix/store/...-nix-2.18.2`
|
||||
- `NIX_TESTS_CA_BY_DEFAULT` - Pass `__contentAddressed`, `outputHashMode` and `outputHashAlgo` to builds of some input-addressed derivations in the test suite.
|
||||
|
||||
**Expected value**: 1
|
||||
- `TEST_DATA` - Not an environment variable! This is used in repl characterization tests to refer to `tests/functional/repl_characterization/data`.
|
||||
More specifically, that path is replaced with the string `$TEST_DATA` in output for reproducibility.
|
||||
- `TEST_HOME` (output) - Set to the temporary directory that is set as `$HOME` inside the tests, underneath `$TEST_ROOT`.
|
||||
|
||||
@@ -41,6 +41,12 @@
|
||||
|
||||
[realise]: #gloss-realise
|
||||
|
||||
- [content-addressed derivation]{#gloss-content-addressed-derivation}
|
||||
|
||||
A derivation which has the
|
||||
[`__contentAddressed`](./language/advanced-attributes.md#adv-attr-__contentAddressed)
|
||||
attribute set to `true`.
|
||||
|
||||
- [fixed-output derivation]{#gloss-fixed-output-derivation}
|
||||
|
||||
A derivation which includes the
|
||||
@@ -89,7 +95,7 @@
|
||||
|
||||
[store path]: #gloss-store-path
|
||||
|
||||
- [file system object]{#gloss-file-system-object}
|
||||
- [file system object]{#gloss-store-object}
|
||||
|
||||
The Nix data model for representing simplified file system data.
|
||||
|
||||
@@ -108,13 +114,14 @@
|
||||
- [input-addressed store object]{#gloss-input-addressed-store-object}
|
||||
|
||||
A store object produced by building a
|
||||
non-[content-addressed](#gloss-content-addressed-derivation),
|
||||
non-[fixed-output](#gloss-fixed-output-derivation)
|
||||
derivation.
|
||||
|
||||
- [output-addressed store object]{#gloss-output-addressed-store-object}
|
||||
|
||||
A [store object] whose [store path] is determined by its contents.
|
||||
This includes derivations and the outputs of [fixed-output derivations](#gloss-fixed-output-derivation).
|
||||
This includes derivations, the outputs of [content-addressed derivations](#gloss-content-addressed-derivation), and the outputs of [fixed-output derivations](#gloss-fixed-output-derivation).
|
||||
|
||||
- [substitute]{#gloss-substitute}
|
||||
|
||||
|
||||
@@ -60,10 +60,3 @@ Then:
|
||||
```console
|
||||
$ docker run -ti lix
|
||||
```
|
||||
|
||||
# Known issues
|
||||
|
||||
Lix in Docker is very sensitive to **functional** DNS resolution if you are running with [Pasta protections](../advanced-topics/pasta.md) which are enabled by default since Lix 2.93.0 on most distributions.
|
||||
If you notice failure to download things, double check whether your **first** DNS entry in `/etc/resolv.conf` is functional.
|
||||
|
||||
Lix with [Pasta protections](../advanced-topics/pasta.md) does not support failing over the next entries.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
This section lists known issues around Lix.
|
||||
@@ -1,21 +0,0 @@
|
||||
# Limitations of non-isolated builds
|
||||
|
||||
## What are non-isolated builds?
|
||||
|
||||
In Lix, only builds done on Linux with `sandbox = true` and a functioning
|
||||
`pasta-path` are isolated from the rest of the system, all other builds are
|
||||
considered non-isolated to some degree.
|
||||
|
||||
For example, running Lix with [Pasta](@docroot@/advanced-topics/pasta.md)
|
||||
disabled makes the host network visible to fixed-output derivations, reducing
|
||||
isolation somewhat.
|
||||
|
||||
## Clean termination of non-isolated builds
|
||||
|
||||
Non-isolated builds may not terminate cleanly in all cases due to limitations in Lix's process management.
|
||||
|
||||
This occurs when a build keeps the build log file descriptor open past the end of the actual build. A common cause of this are background tasks that aren't properly terminated before the main build process exits, for example: HTTP servers run as part of a test suite.
|
||||
|
||||
See [issue #1018](https://git.lix.systems/lix-project/lix/issues/1018) for an example.
|
||||
|
||||
The only solution is to manually terminate leftover processes in your derivation, including during failure scenarios.
|
||||
@@ -209,8 +209,15 @@ Derivations can declare some infrequently used optional attributes.
|
||||
|
||||
- [`__contentAddressed`]{#adv-attr-__contentAddressed}
|
||||
> **Warning**
|
||||
> This attribute is part of a removed [experimental feature](@docroot@/contributing/experimental-features.md).
|
||||
> Setting this flag *will* cause eval errors.
|
||||
> This attribute is part of an [experimental feature](@docroot@/contributing/experimental-features.md).
|
||||
>
|
||||
> To use this attribute, you must enable the
|
||||
> [`ca-derivations`](@docroot@/contributing/experimental-features.md#xp-feature-ca-derivations) experimental feature.
|
||||
> For example, in [nix.conf](../command-ref/conf-file.md) you could add:
|
||||
>
|
||||
> ```
|
||||
> extra-experimental-features = ca-derivations
|
||||
> ```
|
||||
|
||||
If this attribute is set to `true`, then the derivation
|
||||
outputs will be stored in a content-addressed location rather than the
|
||||
@@ -302,6 +309,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,7 +5,7 @@
|
||||
FIXME(Lix): This section does not document the most common modern practices in terms of avoiding channels, pinning, declarative software installation (see flakey-profile or home-manager or NixOS), or using flakes, etc.
|
||||
It is, however, likely correct at a technical level.
|
||||
|
||||
For more information on modern practices, see the [resources](https://wiki.lix.systems/books/lix-users/page/nix-resources) page on the Lix site.
|
||||
For more information on modern practices, see the [resources](https://lix.systems/resources) page on the Lix site.
|
||||
|
||||
</div>
|
||||
|
||||
@@ -41,7 +41,7 @@ install Lix. If this is not the case for some reason, you can add it
|
||||
as follows:
|
||||
|
||||
```console
|
||||
$ nix-channel --add https://channels.nixos.org/nixpkgs-unstable
|
||||
$ nix-channel --add https://nixos.org/channels/nixpkgs-unstable
|
||||
$ nix-channel --update
|
||||
```
|
||||
|
||||
@@ -49,7 +49,7 @@ $ nix-channel --update
|
||||
>
|
||||
> On NixOS, you’re automatically subscribed to a NixOS channel
|
||||
> corresponding to your NixOS major release (e.g.
|
||||
> <https://channels.nixos.org/nixos-21.11>). A NixOS channel is identical
|
||||
> <http://nixos.org/channels/nixos-21.11>). A NixOS channel is identical
|
||||
> to the Nixpkgs channel, except that it contains only Linux binaries
|
||||
> and is updated only if a set of regression tests succeed.
|
||||
|
||||
|
||||
@@ -71,62 +71,3 @@ $ nix-collect-garbage -d
|
||||
```
|
||||
|
||||
is a quick and easy way to clean up your system.
|
||||
|
||||
## Garbage Collector Roots
|
||||
|
||||
### Explicit roots
|
||||
|
||||
All store paths to which there are symlinks in the directory
|
||||
`prefix/nix/var/nix/gcroots` will be used as roots by the garbage
|
||||
collector. For instance, the following command makes the path
|
||||
`/nix/store/d718ef...-foo` a root of the collector:
|
||||
|
||||
```console
|
||||
$ ln -s /nix/store/d718ef...-foo /nix/var/nix/gcroots/bar
|
||||
```
|
||||
|
||||
That is, after this command, the garbage collector will not remove
|
||||
`/nix/store/d718ef...-foo` or any of its dependencies.
|
||||
|
||||
Subdirectories of `prefix/nix/var/nix/gcroots` are also searched for
|
||||
symlinks.
|
||||
|
||||
Symlinks may also point to paths outside the nix store. If the
|
||||
destination of the symlink is itself a symlink to a store path, it
|
||||
is also considered a root. This style of GC root is called an
|
||||
"indirect root", and is created by tools like `nix-build` to avoid
|
||||
garbage-collecting paths that are being used on-the-fly rather than
|
||||
installed in profiles.
|
||||
|
||||
|
||||
### In-use roots
|
||||
|
||||
Lix will also perform a best-effort detection of paths that are in use
|
||||
by running processes when scanning for garbage collection roots, to
|
||||
avoid removing paths that are still needed by running processes.
|
||||
|
||||
Exact details vary between platforms, but the following will generally
|
||||
be taken into account:
|
||||
|
||||
- Executables in the store that are currently running;
|
||||
- Other files in the store that are mapped into a process's address space (e.g. shared libraries);
|
||||
- Files in the store to which processes have open handles;
|
||||
- Store paths found in processes' environment variables.
|
||||
|
||||
Note that this detection is susceptible to missing paths that may still be in use for multiple reasons:
|
||||
|
||||
- Time-of-check-to-time-of-use (TOCTTOU): new processes may appear
|
||||
after Lix has enumerated the currently running processes, and will
|
||||
not be taken into account;
|
||||
- Access privileges: if the garbage collection is not running as the
|
||||
root user (this is typically the case for single-user
|
||||
installations), it will not be able to scan processes belonging to
|
||||
other users;
|
||||
- Other types of references: store paths may be stored in parts of the
|
||||
filesystem (e.g. databases) or process memory (e.g. environment
|
||||
variables changed since the start of the process) that Lix does not
|
||||
scan.
|
||||
|
||||
For this reason, it is recommended to create explicit roots whenever
|
||||
using store paths that aren't obtained from some existing explicit GC
|
||||
root.
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Garbage Collector Roots
|
||||
|
||||
The roots of the garbage collector are all store paths to which there
|
||||
are symlinks in the directory `prefix/nix/var/nix/gcroots`. For
|
||||
instance, the following command makes the path
|
||||
`/nix/store/d718ef...-foo` a root of the collector:
|
||||
|
||||
```console
|
||||
$ ln -s /nix/store/d718ef...-foo /nix/var/nix/gcroots/bar
|
||||
```
|
||||
|
||||
That is, after this command, the garbage collector will not remove
|
||||
`/nix/store/d718ef...-foo` or any of its dependencies.
|
||||
|
||||
Subdirectories of `prefix/nix/var/nix/gcroots` are also searched for
|
||||
symlinks. Symlinks to non-store paths are followed and searched for
|
||||
roots, but symlinks to non-store paths *inside* the paths reached in
|
||||
that way are not followed to prevent infinite recursion.
|
||||
@@ -2,8 +2,18 @@
|
||||
|
||||
For historical reasons, [derivations](@docroot@/glossary.md#gloss-store-derivation) are stored on-disk in [ATerm](https://homepages.cwi.nl/~daybuild/daily-books/technology/aterm-guide/aterm-guide.html) format.
|
||||
|
||||
Derivations are serialised in the following format:
|
||||
Derivations are serialised in one of the following formats:
|
||||
|
||||
```
|
||||
Derive(...)
|
||||
```
|
||||
- ```
|
||||
Derive(...)
|
||||
```
|
||||
|
||||
For all stable derivations.
|
||||
|
||||
- ```
|
||||
DrvWithVersion(<version-string>, ...)
|
||||
```
|
||||
|
||||
The only `version-string`s that are in use today are for [experimental features](@docroot@/contributing/experimental-features.md):
|
||||
|
||||
- `"xp-dyn-drv"` for the [`dynamic-derivations`](@docroot@/contributing/experimental-features.md#xp-feature-dynamic-derivations) experimental feature.
|
||||
|
||||
@@ -1,4 +1,242 @@
|
||||
# Lix 2.93 "Bici Bici" (2025-05-09)
|
||||
# Lix 2.93.4 (2026-05-04)
|
||||
## Fixes
|
||||
|
||||
- `build-dir` no longer defaults to `temp-dir` [cl/3453](https://gerrit.lix.systems/c/lix/+/3453)
|
||||
|
||||
The directory in which temporary build directories are created no longer defaults
|
||||
to the value of the `temp-dir` setting to avoid builders making their directories
|
||||
world-accessible. This behavior has been used to escape the build sandbox and can
|
||||
cause build impurities even when not used maliciously. We now default to `builds`
|
||||
in `NIX_STATE_DIR` (which is `/nix/var/nix/b` in the default configuration).
|
||||
|
||||
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
- Fix develop shells for derivations with escape codes [fj#991](https://git.lix.systems/lix-project/lix/issues/991) [cl/4154](https://gerrit.lix.systems/c/lix/+/4154) [cl/4155](https://gerrit.lix.systems/c/lix/+/4155)
|
||||
|
||||
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.
|
||||
|
||||
Many thanks to [Qyriad](https://git.lix.systems/Qyriad) for this.
|
||||
|
||||
- Fix nix develop for derivations that rejects dependencies with structured attrs [fj#997](https://git.lix.systems/lix-project/lix/issues/997) [cl/4182](https://gerrit.lix.systems/c/lix/+/4182) [cl/4214](https://gerrit.lix.systems/c/lix/+/4214)
|
||||
|
||||
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.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
|
||||
|
||||
- `nix-shell` default shell directory is not `/tmp` anymore for `$NIX_BUILD_TOP` [fj#940](https://git.lix.systems/lix-project/lix/issues/940)
|
||||
|
||||
Previously, Lix `nix-shell`s could exit non-zero status when `stdenv`'s `dumpVars` phase failed to write to `$NIX_BUILD_TOP/env-vars`, despite `dumpVars` being intended as a debugging aid.
|
||||
|
||||
This happens when `TMPDIR` is not set and defaults therefore to `/tmp`, resulting in a `/tmp/env-vars` global file that every `nix-shell` wants to write.
|
||||
|
||||
We fix this issue by reusing a pre-created, unique, and writable location, as the build top directory, avoiding shell exiting from write failures silently.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
|
||||
|
||||
- Fix unsigned overflow leading to out-of-band write in the NAR parser [cl/5537](https://gerrit.lix.systems/c/lix/+/5537)
|
||||
|
||||
The NAR parser contained an unsigned integer overflow that could be used by an
|
||||
attacker to write arbitrary data to an unknown memory location and possibly
|
||||
achieve code execution. A successful attack on the system-wide Lix daemon
|
||||
could lead to privilege escalation to root. Any process that involves NAR
|
||||
serialization could trigger this issue, including (but not limited to)
|
||||
|
||||
- local user interaction, whether the users are trusted or untrusted
|
||||
- malicious substituters sending malformed NARs
|
||||
- remote builders sending malformed build results
|
||||
- remote daemons sending malformed inputs when requesting remote builds
|
||||
|
||||
Successful attacks using this bug require ASLR weakening of some sort, whether
|
||||
by architecture constraints (e.g. on 32 bit systems, where little randomization
|
||||
is possible) or system configuration (e.g. low ASLR entropy when loading
|
||||
libraries), and millions of attempts. Local attacks can be mounted in less than
|
||||
an hour. Remote builds typically require a fresh SSH connection for each build
|
||||
and are thus less susceptible. Only one attempt can be made by substituters for
|
||||
every build using substituters, they are thus not a likely vector for attacks.
|
||||
|
||||
At the time of writing, MITRE has not assigned this a CVE yet.
|
||||
|
||||
Many thanks to [eldritch horrors](https://git.lix.systems/pennae), [Raito Bezarius](https://git.lix.systems/raito), [edef](https://github.com/edef1c), and [sandydoo](https://github.com/sandydoo) for this.
|
||||
|
||||
|
||||
|
||||
|
||||
# Lix 2.93.3 (2025-07-22)
|
||||
## Improvements
|
||||
|
||||
- `--keep-failed` chowns the build directory to the user that request the build [cl/3678](https://gerrit.lix.systems/c/lix/+/3678)
|
||||
|
||||
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.
|
||||
|
||||
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
|
||||
|
||||
|
||||
# Lix 2.93.2 (2025-06-30)
|
||||
## Fixes
|
||||
|
||||
- Revert CVE-2025-52992 failed mitigation [fj#883](https://git.lix.systems/lix-project/lix/issues/883) [fj#887](https://git.lix.systems/lix-project/lix/issues/887) [cl/3444](https://gerrit.lix.systems/c/lix/+/3444) [cl/3528](https://gerrit.lix.systems/c/lix/+/3528)
|
||||
|
||||
Following the initial mitigation of **CVE-2025-52992** in `cl/3444`, we
|
||||
received reports of **unexpected deletion of in-use store paths**.
|
||||
|
||||
Upon investigation, we found that the patch did **not correctly cancel all
|
||||
automatic deleters**, resulting in potentially critical path loss during normal
|
||||
operation.
|
||||
|
||||
Given the severity and time-sensitive nature of the situation ([see incident
|
||||
report](https://lix.systems/blog/2025-06-27-lix-critical-bug/)), we evaluated
|
||||
possible options to repair the behavior safely. However, we concluded that a
|
||||
rushed fix would either
|
||||
|
||||
* **Overdelete**, i.e. breaking running systems, or,
|
||||
* **Underdelete**, effectively **reopening CVE-2025-52992** while leaving
|
||||
orphaned paths behind.
|
||||
|
||||
As **CVE-2025-52992 has no known exploit vector**, and correctness is critical
|
||||
in the Lix project, we have **fully reverted the previous mitigations**.
|
||||
|
||||
The affected patches (`cl/3444`) have been rolled back for the time being.
|
||||
|
||||
Moving forward, the Lix team will rework this code path in a **long-term,
|
||||
correctness-first fix** on the main branch. We will explore backporting it to
|
||||
stable channels once its safety is assured.
|
||||
|
||||
We are deeply sorry for the stability incident and the Lix team remain
|
||||
available for assisting you in recovering your systems.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) and [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
- Fallback to safe temp dir when build-dir is unwritable [fj#876](https://git.lix.systems/lix-project/lix/issues/876) [cl/3501](https://gerrit.lix.systems/c/lix/+/3501)
|
||||
|
||||
Non-daemon builds started failing with a permission error after introducing the `build-dir` option:
|
||||
|
||||
```
|
||||
$ nix build --store ~/scratch nixpkgs#hello --rebuild
|
||||
error: creating directory '/nix/var/nix/builds/nix-build-hello-2.12.2.drv-0': Permission denied
|
||||
```
|
||||
|
||||
This happens because:
|
||||
|
||||
1. These builds are not run via the daemon, which owns `/nix/var/nix/builds`.
|
||||
2. The user lacks permissions for that path.
|
||||
|
||||
We considered making `build-dir` a store-level option and defaulting it to `<chroot-root>/nix/var/nix/builds` for chroot stores, but opted instead for a fallback: if the default fails, Nix now creates a safe build directory under `/tmp`.
|
||||
|
||||
To avoid CVE-2025-52991, the fallback uses an extra path component between `/tmp` and the build dir.
|
||||
|
||||
**Note**: this fallback clutters `/tmp` with build directories that are not cleaned up. To prevent this, explicitly set `build-dir` to a path managed by Lix, even for local workloads.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) and [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
|
||||
|
||||
|
||||
# Lix 2.93.1 (2025-06-23)
|
||||
## Breaking Changes
|
||||
|
||||
- Fixed output derivations can be run using `pasta` network isolation [fj#285](https://git.lix.systems/lix-project/lix/issues/285) [cl/3442](https://gerrit.lix.systems/c/lix/+/3442)
|
||||
|
||||
Fixed output derivations traditionally run in the host network namespace.
|
||||
On Linux this allows such derivations to communicate with other sandboxes
|
||||
or the host using the abstract Unix domains socket namespace; this hasn't
|
||||
been unproblematic in the past and has been used in two distinct exploits
|
||||
to break out of the sandbox. For this reason fixed output derivations can
|
||||
now run in a network namespace (provided by [`pasta`]), restricted to TCP
|
||||
and UDP communication with the rest of the world. When enabled this could
|
||||
be a breaking change and we classify it as such, even though we don't yet
|
||||
enable or require such isolation by default. We may enforce this in later
|
||||
releases of Lix once we have sufficient confidence that breakage is rare.
|
||||
|
||||
[`pasta`]: https://passt.top/
|
||||
|
||||
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) and [puck](https://git.lix.systems/puck) for this.
|
||||
|
||||
|
||||
## Fixes
|
||||
|
||||
- Always clean up scratch paths after derivations failed to build [cl/3444](https://gerrit.lix.systems/c/lix/+/3444)
|
||||
|
||||
Previously, scratch paths created during builds were not always cleaned up if
|
||||
the derivation failed, potentially leaving behind unnecessary temporary files
|
||||
or directories in the Nix store.
|
||||
|
||||
This fix ensures that such paths are consistently removed after a failed build,
|
||||
improving Nix store hygiene, hardening Lix against mis-reuse of failed builds
|
||||
scratch paths.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) and [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
- `build-dir` no longer defaults to `temp-dir` [cl/3443](https://gerrit.lix.systems/c/lix/+/3443)
|
||||
|
||||
The directory in which temporary build directories are created no longer defaults
|
||||
to the value of the `temp-dir` setting to avoid builders making their directories
|
||||
world-accessible. This behavior has been used to escape the build sandbox and can
|
||||
cause build impurities even when not used maliciously. We now default to `builds`
|
||||
in `NIX_STATE_DIR` (which is `/nix/var/nix/builds` in the default configuration).
|
||||
|
||||
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
- Remove reliance on Bash for remote stores via SSH [fj#830](https://git.lix.systems/lix-project/lix/issues/830) [fj#805](https://git.lix.systems/lix-project/lix/issues/805) [fj#304](https://git.lix.systems/lix-project/lix/issues/304) [cl/3159](https://gerrit.lix.systems/c/lix/+/3159)
|
||||
|
||||
The pre-flight `echo started` handshake -- added years ago to catch race conditions -- has been removed.
|
||||
|
||||
After removal of connection sharing in Lix 2.93, it required a Bash-compatible shell and a standard `echo`, so it failed on:
|
||||
|
||||
* builders protected by `ForceCommand` wrappers (e.g. `nix-remote-build`),
|
||||
* BusyBox / initrd images with no Bash,
|
||||
* hosts using non-POSIX shells such as Nushell.
|
||||
|
||||
The race the probe once addressed was tied to SSH connection-sharing -- since connection-sharing code has already been removed, the probe is now pointless.
|
||||
|
||||
Real connection or protocol errors are now left to SSH/Nix to report directly.
|
||||
|
||||
This is technically a breaking change if you had scripts that relied on the literal "started" which needs to be updated to rely on other signals, e.g., exit codes.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
|
||||
|
||||
|
||||
## Miscellany
|
||||
|
||||
- Deprecation of CA derivations, dynamic derivations, and impure derivations [fj#815](https://git.lix.systems/lix-project/lix/issues/815)
|
||||
|
||||
Content-addressed derivations are now deprecated and slated for removal in Lix 2.94.
|
||||
We're doing this because the CA derivation system has been a known cause of problems
|
||||
and inconsistencies, is unmaintained, habitually makes improving the store code very
|
||||
difficult (or blocks such improvements outright), and is beset by a number of design
|
||||
flaws that in our opinion cannot be fixed without a full reimplementation from zero.
|
||||
Dynamic derivations and impure derivations are built on the CA derivation framework,
|
||||
and owing to this they too are deprecated and slated for removal in another release.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Lix 2.93.0 (2025-05-09)
|
||||
|
||||
@@ -1,955 +0,0 @@
|
||||
# Lix 2.94 "Açaí na tigela" (2025-11-17)
|
||||
|
||||
|
||||
# Lix 2.94.0 (2025-11-17)
|
||||
## Breaking Changes
|
||||
|
||||
- Remove support for daemon protocols before 2.18 [fj#510](https://git.lix.systems/lix-project/lix/issues/510) [cl/3249](https://gerrit.lix.systems/c/lix/+/3249)
|
||||
|
||||
Support for daemon wire protocols belonging to Nix 2.17 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.
|
||||
Additionally Lix will not be able to connect to an old daemon locally, and
|
||||
remote build connections to old daemons is likewise limited to `ssh` urls.
|
||||
|
||||
We have decided to take this step because the old protocols are very badly
|
||||
tested (if at all), maintenance overhead is high, and a number of problems
|
||||
with their design makes it infeasible to remain backwards compatible while
|
||||
we move Lix to a more modern RPC mechanism with better versioning support.
|
||||
|
||||
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
- Remove impure derivations and dynamic derivations [fj#815](https://git.lix.systems/lix-project/lix/issues/815) [cl/3210](https://gerrit.lix.systems/c/lix/+/3210)
|
||||
|
||||
The `impure-derivations` and `dynamic-derivations` experimental feature have
|
||||
been removed.
|
||||
|
||||
New impure or dynamic derivations cannot be created from this point forward, and
|
||||
any such pre-existing store derivations canot be read or built any more.
|
||||
Derivation outputs created by building such a derivation are still valid
|
||||
until garbage collected; existing store derivations can only be garbage
|
||||
collected.
|
||||
|
||||
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
- First argument to `--arg`/`--argstr` must be a valid Nix identifier [fj#496](https://git.lix.systems/lix-project/lix/issues/496)
|
||||
|
||||
The first argument to `--arg`/`--argstr` must be a valid Nix identifier, i.e.
|
||||
`nix-build --arg config.allowUnfree true` is now rejected.
|
||||
|
||||
This is because that invocation is a false friend since it doesn't set
|
||||
`{ config = { allowUnfree = true; }; }`, but `{ "config.allowUnfree" = true; }`.
|
||||
|
||||
The idea is to change the behavior to the latter in the long-term. For that,
|
||||
non-identifiers started giving a warning since 2.92 and are now rejected to give people
|
||||
who depend on that a chance to notice and potentially weigh in on the discussion.
|
||||
|
||||
Many thanks to [ma27](https://git.lix.systems/ma27) for this.
|
||||
|
||||
- New cgroup delegation model [fj#537](https://git.lix.systems/lix-project/lix/issues/537) [fj#77](https://git.lix.systems/lix-project/lix/issues/77) [cl/3230](https://gerrit.lix.systems/c/lix/+/3230)
|
||||
|
||||
Builds using cgroups (i.e. `use-cgroups = true` and the experimental feature
|
||||
`cgroups`) now always delegate a cgroup tree to the sandbox.
|
||||
|
||||
Compared to the original C++ Nix project, our delegation includes the
|
||||
`subtree_control` file as well, which means that the sandbox can disable
|
||||
certain controllers in its own cgroup tree.
|
||||
|
||||
This is a breaking change because this requires the Nix daemon to run with an
|
||||
already delegated cgroup tree by the service manager.
|
||||
|
||||
## How to setup the cgroup tree with systemd?
|
||||
|
||||
systemd offers knobs to perform the required setup using:
|
||||
|
||||
```
|
||||
[Service]
|
||||
Delegate=yes
|
||||
DelegateSubtree=supervisor
|
||||
```
|
||||
|
||||
These directives are now included in our systemd packaging.
|
||||
|
||||
## What about using Nix as root without connecting to the daemon?
|
||||
|
||||
Builds run as `root` without connecting to the daemon relying on the cgroup
|
||||
feature are now broken, i.e.
|
||||
|
||||
```console
|
||||
# nix-build --use-cgroups --sandbox ... # will not work
|
||||
```
|
||||
|
||||
Consider doing instead:
|
||||
|
||||
```console
|
||||
# systemd-run --same-dir --wait -p Delegate=yes -p DelegateSubgroup=supervisor nix-build --use-cgroups ...
|
||||
```
|
||||
|
||||
If you need to disable cgroups temporarily, remember that you can do
|
||||
`NIX_CONF='include /etc/nix/nix.conf\nuse-cgroups = false' nix-build ...` or
|
||||
`nix-build --no-use-cgroups ...`.
|
||||
|
||||
## What about other service managers than systemd?
|
||||
|
||||
systemd has a [documentation](https://systemd.io/CGROUP_DELEGATION/) on how to
|
||||
handle cgroup delegation from service management perspective.
|
||||
|
||||
If your service manager adheres to systemd semantics, e.g. writing an extended
|
||||
attribute `user.delegate=1` on the delegated cgroup tree directory and moving
|
||||
the `nix-daemon` process inside a cgroup tree to respect the inner process
|
||||
rule, then, the feature will work as well.
|
||||
|
||||
## Why is the cgroup feature still experimental?
|
||||
|
||||
While the cgroup feature unlocks many use cases, its behavior and integration (e.g. user experience), especially at scale on build farms or in multi-tenant environments, are not yet fully matured. There’s also potential for deeper systemd integration (e.g. using slices and scopes) that has not been fully explored.
|
||||
|
||||
To avoid locking in an unstable interface, we’re keeping the experimental flag until we have validated the feature across a broader range of scenarios, including but not limited to:
|
||||
|
||||
* Nix as root
|
||||
* Hydra-style build farms
|
||||
* Forgejo CI runners
|
||||
* Shared remote builders
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito), [eldritch horrors](https://git.lix.systems/pennae), and [lheckemann](https://git.lix.systems/lheckemann) for this.
|
||||
|
||||
- Enable high compress ratio zstd compression by default for binary caches uploads [fj#945](https://git.lix.systems/lix-project/lix/issues/945) [cl/4503](https://gerrit.lix.systems/c/lix/+/4503)
|
||||
|
||||
The default compression method for binary cache uploads has been switched from
|
||||
[`xz`](https://github.com/tukaani-project/xz) to
|
||||
[`zstd`](https://github.com/facebook/zstd) to address performance and usability
|
||||
issues related to modern hardware and high-speed connections.
|
||||
|
||||
## Why?
|
||||
|
||||
`xz` offers compression ratios but is single-threaded in our implementation and
|
||||
very slow (~10-20 Mbps in our test), preventing full utilization of 100Mbps+
|
||||
connections and significantly slowing decompression for end users.
|
||||
|
||||
Lix is a "compress once, decompress many" application: build farms can afford
|
||||
to spend more time compressing to achieve a faster download transfer for the
|
||||
end user. More importantly, it matters that all end users spend the least
|
||||
amount of time decompressing.
|
||||
|
||||
## What about compression ratios?
|
||||
|
||||
`zstd` cannot achieve the same peaks as `xz`, nonetheless, `zstd` compression
|
||||
level has been increased to level 12 by default to balance compression ratio
|
||||
and performance.
|
||||
|
||||
## Synthetic test case data
|
||||
|
||||
* **xz** (default compression level) on a 4.4GB file: ~632MB (77s)
|
||||
* **zstd** (level 12) on the same file: ~775MB (18s), 18% larger but 50% faster
|
||||
* **zstd** (level 14): ~773MB (37s)
|
||||
* **zstd** (level 16): ~735MB (66s)
|
||||
|
||||
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) and [Raito Bezarius](https://git.lix.systems/raito) for this.
|
||||
|
||||
- Repl debugger uses `--ignore-try` by default [lix#666](https://git.lix.systems/lix-project/lix/issues/666) [cl/3488](https://gerrit.lix.systems/c/lix/+/3488)
|
||||
|
||||
Previously, using the debugger meant that exceptions thrown in `builtins.tryEval` would trigger the debugger.
|
||||
|
||||
However, this caught nixpkgs initialization code, which is unhelpful in the majority of cases, so we changed the default.
|
||||
|
||||
To get the old behaviour, use `--no-ignore-try`.
|
||||
|
||||
```
|
||||
$ nix repl --debugger --expr 'with import <nixpkgs> {}; pkgs.hello'
|
||||
Lix 2.94.0-dev-pre20250625-9a59106
|
||||
Type :? for help.
|
||||
error: file 'nixpkgs-overlays' was not found in the Nix search path (add it using $NIX_PATH or -I)
|
||||
|
||||
This exception occurred in a 'tryEval' call. Use --ignore-try to skip these.
|
||||
|
||||
Added 13 variables.
|
||||
nix-repl>
|
||||
```
|
||||
|
||||
Many thanks to [jade](https://git.lix.systems/jade) for this.
|
||||
|
||||
- Strings may now contain NUL bytes [cl/3968](https://gerrit.lix.systems/c/lix/+/3968)
|
||||
|
||||
Lix now allows strings to contain NUL bytes instead of silently truncating the
|
||||
string before the first such byte. Notably NUL-bearing strings were allowed as
|
||||
attribute names—even though the corresponding strings were not representable!—
|
||||
leading to very surprising and incorrect behavior in corner cases, for example
|
||||
|
||||
```
|
||||
nix-repl> builtins.fromJSON ''{"a": 1, "a\u0000b": 2}''
|
||||
{
|
||||
a = 1;
|
||||
"ab" = 2;
|
||||
}
|
||||
|
||||
nix-repl> builtins.attrNames (builtins.fromJSON ''{"a": 1, "a\u0000b": 2}'')
|
||||
[
|
||||
"a"
|
||||
"a"
|
||||
]
|
||||
```
|
||||
|
||||
rather than the more correct but still with the terminal eating NUL on display
|
||||
|
||||
```
|
||||
nix-repl> builtins.fromJSON ''{"a": 1, "a\u0000b": 2}''
|
||||
{
|
||||
a = 1;
|
||||
"ab" = 2;
|
||||
}
|
||||
|
||||
nix-repl> builtins.attrNames (builtins.fromJSON ''{"a": 1, "a\u0000b": 2}'')
|
||||
[
|
||||
"a"
|
||||
"ab"
|
||||
]
|
||||
```
|
||||
|
||||
We consider this a breaking change since eval results *will* change if strings
|
||||
with embedded NUL bytes were used, but we also consider the old behavior to be
|
||||
not intentional (seeing how inconsistent it was) but merely fallout from a old
|
||||
and misguided implementation decision to be worked around, not actually fixed.
|
||||
|
||||
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
- Fixed output derivations can be run using `pasta` network isolation [fj#285](https://git.lix.systems/lix-project/lix/issues/285) [cl/3452](https://gerrit.lix.systems/c/lix/+/3452)
|
||||
|
||||
Fixed output derivations traditionally run in the host network namespace.
|
||||
On Linux this allows such derivations to communicate with other sandboxes
|
||||
or the host using the abstract Unix domains socket namespace; this hasn't
|
||||
been unproblematic in the past and has been used in two distinct exploits
|
||||
to break out of the sandbox. For this reason fixed output derivations can
|
||||
now run in a network namespace (provided by [`pasta`]), restricted to TCP
|
||||
and UDP communication with the rest of the world. When enabled this could
|
||||
be a breaking change and we classify it as such, even though we don't yet
|
||||
enable or require such isolation by default. We may enforce this in later
|
||||
releases of Lix once we have sufficient confidence that breakage is rare.
|
||||
|
||||
[`pasta`]: https://passt.top/
|
||||
|
||||
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) and [puck](https://git.lix.systems/puck) for this.
|
||||
|
||||
- Function equality semantics are more consistent, but still bad [cl/4556](https://gerrit.lix.systems/c/lix/+/4556) [cl/4244](https://gerrit.lix.systems/c/lix/+/4244)
|
||||
|
||||
Lix has inherited a historic misfeature from CppNix in the form of pointer
|
||||
equality checks built into the `==` operator. These checks were originally
|
||||
meant to optimize comparison for large sets, but they have the unfortunate
|
||||
side effect of producing unexpected results when sets containing functions
|
||||
are compared. **Lix 2.93 and earlier** behave as shown in the repl session
|
||||
|
||||
```
|
||||
Lix 2.93.3
|
||||
Type :? for help.
|
||||
nix-repl> f = x: x
|
||||
Added f.
|
||||
|
||||
nix-repl> f == f
|
||||
false
|
||||
|
||||
nix-repl> let s.f = f; in s.f == s.f
|
||||
false
|
||||
|
||||
nix-repl> # however!
|
||||
{ inherit f; } == { inherit f; }
|
||||
true
|
||||
|
||||
nix-repl> [ f ] == [ f ]
|
||||
true
|
||||
|
||||
nix-repl> # and, in another twist:
|
||||
[ f ] == map f [ f ]
|
||||
false
|
||||
```
|
||||
|
||||
Nixpkgs relies on sets containing functions being comparable, so we cannot
|
||||
simply deprecate this behavior. Due to changes to the object model used by
|
||||
Lix ***all* comparisons above now evaluate to `true`**. This is considered
|
||||
a breaking change because eval results may differ, but we also consider it
|
||||
minor because the optimization is unsound (c.f. `let l = [NaN]; in l == l`
|
||||
evaluates to `true` even though floating point `NaN` is incomparable). Lix
|
||||
intends to remove this optimization altogether in the future, but until we
|
||||
can do that we instead make it slightly less broken to allow other, *real*
|
||||
optimizations. Function equality comparison remains **undefined behavior**
|
||||
and should not be relied upon in Nixlang code that intends to be portable.
|
||||
|
||||
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
- `nix eval --write-to` has been removed [fj#974](https://git.lix.systems/lix-project/lix/issues/974) [fj#227](https://git.lix.systems/lix-project/lix/issues/227) [cl/4045](https://gerrit.lix.systems/c/lix/+/4045)
|
||||
|
||||
`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.
|
||||
|
||||
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
- Remove the `parse-toml-timestamps` experimental feature
|
||||
|
||||
The `parse-toml-timestamps` experimental feature has been removed.
|
||||
|
||||
This feature used in‐band signalling to mark timestamps, making it
|
||||
impossible to unambiguously parse TOML documents. It also exposed
|
||||
implementation‐defined 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.
|
||||
|
||||
Many thanks to [Emily](https://git.lix.systems/emilazy) for this.
|
||||
|
||||
- Reject overflowing TOML integer literals [cl/3916](https://gerrit.lix.systems/c/lix/+/3916)
|
||||
|
||||
The toml11 library used by Lix was updated. The new
|
||||
version aligns with the [TOML v1.0.0 specification’s
|
||||
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.
|
||||
|
||||
Many thanks to [Emily](https://git.lix.systems/emilazy) for this.
|
||||
|
||||
- uid-range depends on cgroups [cl/3230](https://gerrit.lix.systems/c/lix/+/3230)
|
||||
|
||||
`uid-range` builds now depends on `cgroups`, an experimental feature.
|
||||
|
||||
`uid-range` builds already depended upon `auto-allocate-uids`, another experimental feature.
|
||||
|
||||
The rationale for doing so is that `uid-range` provides a sandbox with many
|
||||
UIDs, this is useful for re-mapping them into a nested namespace, e.g. a
|
||||
container.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) and [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- Add `inputs.self.submodules` flake attribute [fj#942](https://git.lix.systems/lix-project/lix/issues/942) [cl/3839](https://gerrit.lix.systems/c/lix/+/3839)
|
||||
|
||||
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.
|
||||
|
||||
Many thanks to [Eelco Dolstra](https://github.com/edolstra) and [ورد](https://git.lix.systems/janw4ld) for this.
|
||||
|
||||
- Lix supports HTTP/3 behind `--http3` [fj#1033](https://git.lix.systems/lix-project/lix/issues/1033)
|
||||
|
||||
Lix now supports HTTP/3 for file transfers when the linked curl version
|
||||
supports it.
|
||||
|
||||
By default, HTTP/3 is disabled notably due to performance issues reported in
|
||||
mid-2024. [More details
|
||||
here](https://daniel.haxx.se/blog/2024/06/10/http-3-in-curl-mid-2024/).
|
||||
|
||||
As of 2025-11-14, [NixOS official cache](https://cache.nixos.org) supports
|
||||
HTTP/3 via Fastly. [More info
|
||||
here](https://github.com/NixOS/infra/commit/157fa70e46afbd6338a32407be461fce05c57bf8).
|
||||
|
||||
To enable HTTP/3:
|
||||
|
||||
* Use `--http3` for individual transfers.
|
||||
* Add `http3 = true` in your Nix configuration for permanent activation.
|
||||
|
||||
To disable it, use `--no-http3`.
|
||||
|
||||
**Note**:
|
||||
|
||||
* `--no-http2 --http3` will still enable both HTTP/2 and HTTP/3.
|
||||
* `--http2 --http3` will prioritize HTTP/3 and fall back to HTTP/2 (and then
|
||||
HTTP/1.1).
|
||||
|
||||
These are current CLI limitations. In the future, we plan to replace `--httpX`
|
||||
options with `--max-http-version [1,2,3]` for easier version selection in Lix
|
||||
transfers.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) and [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
- Add hyperlinks in attr set printing [cl/3790](https://gerrit.lix.systems/c/lix/+/3790)
|
||||
|
||||
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"; }
|
||||
```
|
||||
|
||||
Many thanks to [jade](https://git.lix.systems/jade) for this.
|
||||
|
||||
- Experimental integer coercion in interpolated strings [cl/3198](https://gerrit.lix.systems/c/lix/+/3198)
|
||||
|
||||
Ever tried interpolating a port number in Lix and ended up with something like this?
|
||||
|
||||
```nix
|
||||
"http://${config.network.host}:${builtins.toString config.network.port}/"
|
||||
```
|
||||
|
||||
You're not alone. Thousands of Lix users suffer every day from excessive `builtins.toString` syndrome. It’s 2025, and we still have to cast integers to use them in strings.
|
||||
|
||||
To address this, Lix introduces the **`coerce-integers`** experimental feature. When enabled, interpolated integers within `"${...}"` are automatically coerced to strings. This allows writing:
|
||||
|
||||
```nix
|
||||
"http://${config.network.host}:${config.network.port}/"
|
||||
```
|
||||
|
||||
without additional conversion.
|
||||
|
||||
To enable the feature, you need to add `coerce-integers` to your set of experimental features.
|
||||
|
||||
### Stabilization criteria
|
||||
|
||||
The `coerce-integers` feature is experimental and limited strictly to string interpolation (`"${...}"`). Before stabilization, the following must hold:
|
||||
|
||||
1. **Interpolation-only**
|
||||
Coercion must not occur outside interpolation. Expressions like `"" + 42` must continue to fail.
|
||||
|
||||
2. **Expectation that no explicit cast are being observed**
|
||||
Cases observing explicit coercion (e.g., via `tryEval` gadget or similar) are expected not to be load-bearing in actual production code.
|
||||
|
||||
### Timeline for stabilization
|
||||
|
||||
If the feature proves safe and is widely adopted across typical usage (e.g., actual configurations in the wild turning on the flag, non-trivial out-of-tree projects using it), the experimental flag will be removed **after six months of active use or two Lix releases**, whichever is longer.
|
||||
|
||||
This avoids locking the feature in experimental status indefinitely, as happened with Flakes, while allowing time for validation and ecosystem integration.
|
||||
|
||||
### What about coercing floats or more?
|
||||
|
||||
Coercion beyond integers -- such as for floats or other types -- is **not planned**, even under an experimental flag. Questions like "what is the canonical string representation of a float?" involve subtle and context-dependent trade-offs. Without a robust and principled mechanism to define and audit such behavior, introducing broader coercion risks setting unintended and hard-to-reverse precedents. The scope of `coerce-integers` is intentionally narrow and will remain so.
|
||||
|
||||
In terms of outlook, a proposal like https://git.lix.systems/lix-project/lix/issues/835 could pave the way for a better solution.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito), [delroth](https://github.com/delroth), [eldritch horrors](https://git.lix.systems/pennae), and [winter](https://git.lix.systems/winter) for this.
|
||||
|
||||
- nix-eval-jobs: support `--no-instantiate` flag [fj#987](https://git.lix.systems/lix-project/lix/issues/987)
|
||||
|
||||
`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.
|
||||
|
||||
Many thanks to [mic92](https://github.com/mic92) and [ma27](https://git.lix.systems/ma27) for this.
|
||||
|
||||
|
||||
## Improvements
|
||||
|
||||
- Assess current profile generations pointers in `nix doctor` [cl/3108](https://gerrit.lix.systems/c/lix/+/3108)
|
||||
|
||||
Added a new check to `nix doctor` that verifies whether the current generation of
|
||||
a Nix profile can be resolved. This helps users diagnose issues with broken or
|
||||
misconfigured profile symlinks.
|
||||
|
||||
This helps determining if you have broken symlinks or misconfigured packaging.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
|
||||
|
||||
- Improved susbtituter query speed
|
||||
|
||||
The code used to query substituters for derivations has been rewritten slightly
|
||||
to take advantage of our asynchronous runtime. Such queries run for every build
|
||||
that could download from substituters and processes every derivation that isn't
|
||||
yet present on the local system. Previously Lix would use `http-connections` to
|
||||
limit query concurrency, even for modern caches that support HTTP/2 and have no
|
||||
limit on how many queries can be run concurrently on one single connection. Lix
|
||||
no longer does this, resulting in approximately 60% reduction in query time for
|
||||
medium-sized closures (e.g. NixOS system closures) during testing, although the
|
||||
exact number depends greatly on local network latency and generally improves as
|
||||
latency increases. Unlike previously setting `http-connections` to `1` or other
|
||||
low values no longer brings a massive penalty in query performance if the cache
|
||||
in use by the querying system supports HTTP/2 (as e.g. `cache.nixos.org` does).
|
||||
|
||||
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
- Hitting Control-C twice always terminates Lix [cl/3574](https://gerrit.lix.systems/c/lix/+/3574)
|
||||
|
||||
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 ❯
|
||||
```
|
||||
|
||||
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
- `--keep-failed` chowns the build directory to the user that request the build
|
||||
|
||||
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.
|
||||
|
||||
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
- Better debuggability on fixed-output hash mismatches
|
||||
|
||||
Fixed-output derivation hash mismatch error messages will now include the path that was
|
||||
produced unexpectedly, and this path will be registered as valid even if `--check`
|
||||
(`nix-store`, `nix-build`) or `--rebuild` (`nix build`) was passed. This makes comparing
|
||||
the expected path with the obtained path easier, and is useful for debugging when
|
||||
upstreams modify previously-published releases or when changes in fixed-output
|
||||
derivations' dependencies affect their output unexpectedly.
|
||||
|
||||
Many thanks to [lheckemann](https://git.lix.systems/lheckemann) for this.
|
||||
|
||||
- Add --raw flag to `nix-instantiate --eval` for unescaped output [gh#12119](https://github.com/NixOS/nix/pull/12119) [cl/2886](https://gerrit.lix.systems/c/lix/+/2886)
|
||||
|
||||
The `nix-instantiate --eval` command now supports a `--raw` flag. When used,
|
||||
the result must be coercible to a string (as with `${...}`) and is printed
|
||||
verbatim, without quotes or escaping.
|
||||
|
||||
Many thanks to [Martin Fischer](https://github.com/not-my-profile), [infinisil](https://github.com/infinisil), and [Raito Bezarius](https://git.lix.systems/raito) for this.
|
||||
|
||||
- Allow `nix store ls` to read nar listings from binary cache stores. [cl/3225](https://gerrit.lix.systems/c/lix/+/3225)
|
||||
|
||||
The `nix store ls` command now supports reading `.ls` nar listings from binary cache stores.
|
||||
If a listing is detected for the store path being queried, the nar is no longer downloaded.
|
||||
These nar listings are available in binary cache stores where the `write-nar-listing` option is
|
||||
enabled, such as cache.nixos.org.
|
||||
|
||||
Many thanks to [Victor Fuentes](https://git.lix.systems/vlinkz) for this.
|
||||
|
||||
- show tree with references that lead to an output cycle [fj#551](https://git.lix.systems/lix-project/lix/issues/551)
|
||||
|
||||
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.
|
||||
|
||||
Many thanks to [ma27](https://git.lix.systems/ma27) for this.
|
||||
|
||||
- Lix now enables parallel marking in boehm-gc [fj#983](https://git.lix.systems/lix-project/lix/issues/983) [cl/3880](https://gerrit.lix.systems/c/lix/+/3880)
|
||||
|
||||
This brings a fairly modest performance improvement (~38% for `nixpkgs search hello`) to evaluation, especially in scenarios that necessitate larger heap sizes.
|
||||
|
||||
Many thanks to [Eelco Dolstra](https://github.com/edolstra) and [Seth Flynn](https://git.lix.systems/getchoo) for this.
|
||||
|
||||
- `disallowedRequisites` now reports chains of disallowed requisites [fj#334](https://git.lix.systems/lix-project/lix/issues/334) [fj#626](https://git.lix.systems/lix-project/lix/issues/626) [gh#10877](https://github.com/NixOS/nix/issues/10877)
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
Many thanks to [ma27](https://git.lix.systems/ma27) and [Robert Hensing](https://github.com/roberth) for this.
|
||||
|
||||
- Stack traces now summarize involved derivations at the bottom [cl/4493](https://gerrit.lix.systems/c/lix/+/4493)
|
||||
|
||||
When evaluation errors and a stack trace is printed,
|
||||
|
||||
For example, if I add Nheko to a NixOS `environment.systemPackages` without adding `olm-3.2.16` `nixpkgs.config.permittedInsecurePackages`, then without `--show-trace`, I previously got this:
|
||||
|
||||
```
|
||||
error:
|
||||
… while calling the 'head' builtin
|
||||
at /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/lib/attrsets.nix:1701:13:
|
||||
1700| if length values == 1 || pred here (elemAt values 1) (head values) then
|
||||
1701| head values
|
||||
| ^
|
||||
1702| else
|
||||
|
||||
… while evaluating the attribute 'value'
|
||||
at /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/lib/modules.nix:1118:7:
|
||||
1117| // {
|
||||
1118| value = addErrorContext "while evaluating the option `${showOption loc}':" value;
|
||||
| ^
|
||||
1119| inherit (res.defsFinal') highestPrio;
|
||||
|
||||
(stack trace truncated; use '--show-trace' to show the full trace)
|
||||
|
||||
error: Package ‘olm-3.2.16’ in /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/pkgs/by-name/ol/olm/package.nix:37 is marked as insecure, refusing to evaluate.
|
||||
|
||||
< -snip the whole explanation about olm's CVEs- >
|
||||
```
|
||||
|
||||
This doesn't tell me anything about where `olm-3.2.16` came from.
|
||||
With `--show-trace`, there's 1 155 lines to sift through, but does contain lines like "while evaluating derivation 'nheko-0.12.1'".
|
||||
|
||||
With this change, those lines are summarized and collected at the bottom, regardless of `--show-trace`:
|
||||
|
||||
```
|
||||
error:
|
||||
… while calling the 'head' builtin
|
||||
at /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/lib/attrsets.nix:1701:13:
|
||||
1700| if length values == 1 || pred here (elemAt values 1) (head values) then
|
||||
1701| head values
|
||||
| ^
|
||||
1702| else
|
||||
|
||||
… while evaluating the attribute 'value'
|
||||
at /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/lib/modules.nix:1118:7:
|
||||
1117| // {
|
||||
1118| value = addErrorContext "while evaluating the option `${showOption loc}':" value;
|
||||
| ^
|
||||
1119| inherit (res.defsFinal') highestPrio;
|
||||
|
||||
(stack trace truncated; use '--show-trace' to show the full trace)
|
||||
|
||||
error: Package ‘olm-3.2.16’ in /nix/store/9v6qa656sq3xc58vkxslqy646p0ajj61-source/pkgs/by-name/ol/olm/package.nix:37 is marked as insecure, refusing to evaluate.
|
||||
|
||||
|
||||
< -snip the whole explanation about olm's CVEs- >
|
||||
|
||||
|
||||
note: trace involved the following derivations:
|
||||
derivation 'etc'
|
||||
derivation 'dbus-1'
|
||||
derivation 'system-path'
|
||||
derivation 'nheko-0.12.1'
|
||||
derivation 'mtxclient-0.10.1'
|
||||
```
|
||||
|
||||
Now we finally know that olm was evaluated because of Nheko, without sifting through *thousands* of lines of error message.
|
||||
|
||||
Many thanks to [Qyriad](https://git.lix.systems/Qyriad) for this.
|
||||
|
||||
- Symbols reuses once-allocated Value to reduce garbage collected allocations [cl/3308](https://gerrit.lix.systems/c/lix/+/3308) [cl/3300](https://gerrit.lix.systems/c/lix/+/3300) [cl/3314](https://gerrit.lix.systems/c/lix/+/3314) [cl/3310](https://gerrit.lix.systems/c/lix/+/3310) [cl/3312](https://gerrit.lix.systems/c/lix/+/3312) [cl/3313](https://gerrit.lix.systems/c/lix/+/3313)
|
||||
|
||||
In the Lix evaluator, **symbols** represent immutable strings, like those used
|
||||
for attribute names.
|
||||
|
||||
In evaluator design, such strings are typically [**interned**](https://en.wikipedia.org/wiki/String_interning), stored uniquely
|
||||
to save memory, and Lix inherits this approach from the original C++ codebase.
|
||||
|
||||
However, some builtins, like `builtins.attrNames`, must return a `Value` type
|
||||
that can represent any Nix value (strings, integers, lists, etc.).
|
||||
|
||||
Before this change, these builtins would create lists of `Value` objects by
|
||||
allocating them through the garbage collector, copying the symbol’s string
|
||||
content each time.
|
||||
|
||||
This allocation is unnecessary if the interned symbols themselves also hold a
|
||||
`Value` representation allocated outside the garbage collector, since these
|
||||
live for the full duration of evaluation.
|
||||
|
||||
As a result, this reduces the number of allocations, leading to:
|
||||
|
||||
* A significant drop in maximum [resident set memory](https://en.wikipedia.org/wiki/Resident_set_size) (RSS), with some large-scale
|
||||
tests showing up to 11% (about 500 MiB) savings in large colmena deployments.
|
||||
* 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.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito), [eldritch horrors](https://git.lix.systems/pennae), [Tom Hubrecht](https://git.lix.systems/tom-hubrecht), [xokdvium](https://github.com/xokdvium), and [NaN-git](https://github.com/NaN-git) for this.
|
||||
|
||||
|
||||
## Fixes
|
||||
|
||||
- `build-dir` no longer defaults to `temp-dir` [cl/3453](https://gerrit.lix.systems/c/lix/+/3453)
|
||||
|
||||
The directory in which temporary build directories are created no longer defaults
|
||||
to the value of the `temp-dir` setting to avoid builders making their directories
|
||||
world-accessible. This behavior has been used to escape the build sandbox and can
|
||||
cause build impurities even when not used maliciously. We now default to `builds`
|
||||
in `NIX_STATE_DIR` (which is `/nix/var/nix/b` in the default configuration).
|
||||
|
||||
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
- Global certificate authorities are copied inside the builder's environment [gh#12698](https://github.com/NixOS/nix/issues/12698) [fj#885](https://git.lix.systems/lix-project/lix/issues/885) [cl/3765](https://gerrit.lix.systems/c/lix/+/3765)
|
||||
|
||||
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 500 KB 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.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) and [Emily](https://git.lix.systems/emilazy) for this.
|
||||
|
||||
- libstore: exponential backoff for downloads [lix#932](https://git.lix.systems/lix-project/lix/issues/932) [cl/3856](https://gerrit.lix.systems/c/lix/+/3856)
|
||||
|
||||
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.
|
||||
|
||||
Many thanks to [ma27](https://git.lix.systems/ma27) for this.
|
||||
|
||||
- Fix develop shells for derivations with escape codes [fj#991](https://git.lix.systems/lix-project/lix/issues/991) [cl/4154](https://gerrit.lix.systems/c/lix/+/4154) [cl/4155](https://gerrit.lix.systems/c/lix/+/4155)
|
||||
|
||||
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.
|
||||
|
||||
Many thanks to [Qyriad](https://git.lix.systems/Qyriad) for this.
|
||||
|
||||
- nix-store --delete: always remove obsolete hardlinks [cl/3188](https://gerrit.lix.systems/c/lix/+/3188)
|
||||
|
||||
Deleting specific paths using `nix-store --delete` or `nix store
|
||||
delete` previously did not delete hard links created by `nix-store
|
||||
--optimise` even if they became obsolete, unless _all_ of the given
|
||||
paths were deleted successfully. Now, hard links are always cleaned
|
||||
up, even if some of the given paths could not be deleted.
|
||||
|
||||
Many thanks to [lheckemann](https://git.lix.systems/lheckemann) for this.
|
||||
|
||||
- Report GC statistics correctly [cl/3188](https://gerrit.lix.systems/c/lix/+/3188)
|
||||
|
||||
Deleting specific paths using `nix-store --delete` or `nix store delete` previously did
|
||||
not report statistics correctly when some of the paths could not be deleted, even if
|
||||
others were deleted:
|
||||
|
||||
```
|
||||
$ nix store delete /nix/store/9bwryidal9q3g91cjm6xschfn4ikd82q-hello-2.12.1 --delete-closure -v
|
||||
finding garbage collector roots...
|
||||
deleting '/nix/store/9bwryidal9q3g91cjm6xschfn4ikd82q-hello-2.12.1'
|
||||
0 store paths deleted, 0.00 MiB freed
|
||||
error: Cannot delete some of the given paths because they are still alive. Paths not deleted:
|
||||
k9bxzr1l92r5y6mihrkbpbr3fmc8qszx-libidn2-2.3.8
|
||||
mbx9ii53lzjlrsnlrfmzpwm33ynljwdn-libunistring-1.3
|
||||
rf8hcy6bldxdqc0g6q1dcka1vh47x69s-xgcc-14.2.1.20250322-libgcc
|
||||
vbrdc5wgzn0w1zdp10xd2favkjn5fk7y-glibc-2.40-66
|
||||
To find out why, use nix-store --query --roots and nix-store --query --referrers.
|
||||
```
|
||||
|
||||
Many thanks to [lheckemann](https://git.lix.systems/lheckemann) for this.
|
||||
|
||||
- Fallback to safe temp dir when build-dir is unwritable [fj#876](https://git.lix.systems/lix-project/lix/issues/876) [cl/3501](https://gerrit.lix.systems/c/lix/+/3501)
|
||||
|
||||
Non-daemon builds started failing with a permission error after introducing the `build-dir` option:
|
||||
|
||||
```
|
||||
$ nix build --store ~/scratch nixpkgs#hello --rebuild
|
||||
error: creating directory '/nix/var/nix/builds/nix-build-hello-2.12.2.drv-0': Permission denied
|
||||
```
|
||||
|
||||
This happens because:
|
||||
|
||||
1. These builds are not run via the daemon, which owns `/nix/var/nix/builds`.
|
||||
2. The user lacks permissions for that path.
|
||||
|
||||
We considered making `build-dir` a store-level option and defaulting it to `<chroot-root>/nix/var/nix/builds` for chroot stores, but opted instead for a fallback: if the default fails, Nix now creates a safe build directory under `/tmp`.
|
||||
|
||||
To avoid CVE-2025-52991, the fallback uses an extra path component between `/tmp` and the build dir.
|
||||
|
||||
**Note**: this fallback clutters `/tmp` with build directories that are not cleaned up. To prevent this, explicitly set `build-dir` to a path managed by Lix, even for local workloads.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) and [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
- Parse overflowing JSON number literals as floating‐point [cl/3919](https://gerrit.lix.systems/c/lix/+/3919)
|
||||
|
||||
Previously, `builtins.fromJSON "-9223372036854775809"` would
|
||||
return a floating‐point 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 JSON’s model of a single numeric type,
|
||||
and JSON number literals that do not fit in a Nix‐language integer
|
||||
will be parsed as floating‐point numbers.
|
||||
|
||||
Many thanks to [Emily](https://git.lix.systems/emilazy) for this.
|
||||
|
||||
- Fix handling of OSC codes in terminal output [fj#160](https://git.lix.systems/lix-project/lix/issues/160) [cl/3143](https://gerrit.lix.systems/c/lix/+/3143)
|
||||
|
||||
OSC codes in terminal output are now handled correctly, where OSC 8 (hyperlink) is preserved any
|
||||
time color codes are allowed and all other OSC codes are stripped out. This applies not only to
|
||||
output from build commands but also to rendered documentation in the REPL.
|
||||
|
||||
Many thanks to [lilyball](https://git.lix.systems/lilyball) for this.
|
||||
|
||||
- Fix nix develop for derivations that rejects dependencies with structured attrs [fj#997](https://git.lix.systems/lix-project/lix/issues/997) [cl/4182](https://gerrit.lix.systems/c/lix/+/4182)
|
||||
|
||||
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.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
|
||||
|
||||
- nix-eval-jobs: retain NIX_PATH [cl/3859](https://gerrit.lix.systems/c/lix/+/3859)
|
||||
|
||||
`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).
|
||||
|
||||
Many thanks to [ma27](https://git.lix.systems/ma27) and [mic92](https://github.com/mic92) for this.
|
||||
|
||||
- Remove reliance on Bash for remote stores via SSH [fj#830](https://git.lix.systems/lix-project/lix/issues/830) [fj#805](https://git.lix.systems/lix-project/lix/issues/805) [fj#304](https://git.lix.systems/lix-project/lix/issues/304) [cl/3159](https://gerrit.lix.systems/c/lix/+/3159)
|
||||
|
||||
The pre-flight `echo started` handshake -- added years ago to catch race conditions -- has been removed.
|
||||
|
||||
After removal of connection sharing in Lix 2.93, it required a Bash-compatible shell and a standard `echo`, so it failed on:
|
||||
|
||||
* builders protected by `ForceCommand` wrappers (e.g. `nix-remote-build`),
|
||||
* BusyBox / initrd images with no Bash,
|
||||
* hosts using non-POSIX shells such as Nushell.
|
||||
|
||||
The race the probe once addressed was tied to SSH connection-sharing -- since connection-sharing code has already been removed, the probe is now pointless.
|
||||
|
||||
Real connection or protocol errors are now left to SSH/Nix to report directly.
|
||||
|
||||
This is technically a breaking change if you had scripts that relied on the literal "started" which needs to be updated to rely on other signals, e.g., exit codes.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
|
||||
|
||||
- repl-overlays now work in the debugger for flakes [fj#777](https://git.lix.systems/lix-project/lix/issues/777) [cl/3398](https://gerrit.lix.systems/c/lix/+/3398)
|
||||
|
||||
Due to a bug, it was previously not possible to use the debugger on flakes with repl-overlays, or with pure evaluation in general:
|
||||
|
||||
```
|
||||
$ nix repl --pure-eval
|
||||
Lix 2.94.0-dev-pre20250617-87d99da
|
||||
Type :? for help.
|
||||
Loading 'repl-overlays'...
|
||||
error: access to absolute path '/Users/jade/.config/nix/repl.nix' is forbidden in pure eval mode (use '--impure' to override)
|
||||
```
|
||||
|
||||
This is now fixed.
|
||||
The contents of the repl-overlays file itself (i.e. most typically the top level lambda in it) will be evaluated in impure mode.
|
||||
It may be necessary to use `builtins.seq` to force the impure operations to happen first if one wants to do impure operations inside a repl-overlays file in pure evaluation mode.
|
||||
|
||||
Many thanks to [jade](https://git.lix.systems/jade) for this.
|
||||
|
||||
- `nix-shell` default shell directory is not `/tmp` anymore for `$NIX_BUILD_TOP` [fj#940](https://git.lix.systems/lix-project/lix/issues/940)
|
||||
|
||||
Previously, Lix `nix-shell`s could exit non-zero status when `stdenv`'s `dumpVars` phase failed to write to `$NIX_BUILD_TOP/env-vars`, despite `dumpVars` being intended as a debugging aid.
|
||||
|
||||
This happens when `TMPDIR` is not set and defaults therefore to `/tmp`, resulting in a `/tmp/env-vars` global file that every `nix-shell` wants to write.
|
||||
|
||||
We fix this issue by reusing a pre-created, unique, and writable location, as the build top directory, avoiding shell exiting from write failures silently.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
|
||||
|
||||
- libstore/binary-cache-store: don't cache narinfo on nix copy, remove negative entry [cl/3789](https://gerrit.lix.systems/c/lix/+/3789)
|
||||
|
||||
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.
|
||||
|
||||
Many thanks to [ma27](https://git.lix.systems/ma27) for this.
|
||||
|
||||
- Lix libraries can now be linked statically [fj#789](https://git.lix.systems/lix-project/lix/issues/789) [cl/3775](https://gerrit.lix.systems/c/lix/+/3775) [cl/3778](https://gerrit.lix.systems/c/lix/+/3778)
|
||||
|
||||
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.
|
||||
|
||||
Many thanks to [alois31](https://git.lix.systems/alois31) for this.
|
||||
|
||||
- add description to zsh completions [fj#910](https://git.lix.systems/lix-project/lix/issues/910) [cl/3632](https://gerrit.lix.systems/c/lix/+/3632)
|
||||
|
||||
Emit descriptions when completing args in zsh completions. This uses the descriptions we already
|
||||
provided in NIX\_GET\_COMPLETIONS.
|
||||
|
||||
Many thanks to [matthewbauer](https://github.com/matthewbauer) for this.
|
||||
|
||||
|
||||
## Miscellany
|
||||
|
||||
- Deprecation of CA derivations, dynamic derivations, and impure derivations [fj#815](https://git.lix.systems/lix-project/lix/issues/815)
|
||||
|
||||
Content-addressed derivations are now deprecated and slated for removal in Lix 2.94.
|
||||
We're doing this because the CA derivation system has been a known cause of problems
|
||||
and inconsistencies, is unmaintained, habitually makes improving the store code very
|
||||
difficult (or blocks such improvements outright), and is beset by a number of design
|
||||
flaws that in our opinion cannot be fixed without a full reimplementation from zero.
|
||||
Dynamic derivations and impure derivations are built on the CA derivation framework,
|
||||
and owing to this they too are deprecated and slated for removal in another release.
|
||||
@@ -1,607 +0,0 @@
|
||||
# Lix 2.95 "Kakigōri" (2026-03-13)
|
||||
# Lix 2.95.3 (2026-05-08)
|
||||
## Fixes
|
||||
|
||||
- Fix upgrade-nix breaking its own access to the daemon [lix#1189](https://git.lix.systems/lix-project/lix/issues/1189) [lix#1207](https://git.lix.systems/lix-project/lix/issues/1207) [cl/5504](https://gerrit.lix.systems/c/lix/+/5504) [cl/5567](https://gerrit.lix.systems/c/lix/+/5567)
|
||||
|
||||
`nix upgrade-nix`, and the helper script `misc/upgrade-lix.sh` now pass `--store local` to all Nix commands, so the upgrade process can make changes to the daemon without breaking further steps in the upgrade.
|
||||
|
||||
Many thanks to [Qyriad](https://git.lix.systems/Qyriad) for this.
|
||||
|
||||
|
||||
|
||||
|
||||
# Lix 2.95.2 (2026-05-04)
|
||||
## Fixes
|
||||
|
||||
- Fix unsigned overflow leading to out-of-band write in the NAR parser [cl/5550](https://gerrit.lix.systems/c/lix/+/5550)
|
||||
|
||||
The NAR parser contained an unsigned integer overflow that could be used by an
|
||||
attacker to write arbitrary data to an unknown memory location and possibly
|
||||
achieve code execution. A successful attack on the system-wide Lix daemon
|
||||
could lead to privilege escalation to root. Any process that involves NAR
|
||||
serialization could trigger this issue, including (but not limited to)
|
||||
|
||||
- local user interaction, whether the users are trusted or untrusted
|
||||
- malicious substituters sending malformed NARs
|
||||
- remote builders sending malformed build results
|
||||
- remote daemons sending malformed inputs when requesting remote builds
|
||||
|
||||
Successful attacks using this bug require ASLR weakening of some sort, whether
|
||||
by architecture constraints (e.g. on 32 bit systems, where little randomization
|
||||
is possible) or system configuration (e.g. low ASLR entropy when loading
|
||||
libraries), and millions of attempts. Local attacks can be mounted in less than
|
||||
an hour. Remote builds typically require a fresh SSH connection for each build
|
||||
and are thus less susceptible. Only one attempt can be made by substituters for
|
||||
every build using substituters, they are thus not a likely vector for attacks.
|
||||
|
||||
At the time of writing, MITRE has not assigned this a CVE yet.
|
||||
|
||||
Many thanks to [eldritch horrors](https://git.lix.systems/pennae), [Raito Bezarius](https://git.lix.systems/raito), [edef](https://github.com/edef1c), and [sandydoo](https://github.com/sandydoo) for this.
|
||||
|
||||
|
||||
|
||||
|
||||
# Lix 2.95.1 (2026-03-19)
|
||||
## Fixes
|
||||
|
||||
- fix static builds [cl/5385](https://gerrit.lix.systems/c/lix/+/5385)
|
||||
|
||||
Static builds using musl were broken in 2.95.0 and should work again now.
|
||||
|
||||
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
- flake config warnings are now printed to stderr [lix#1155](https://git.lix.systems/lix-project/lix/issues/1155) [cl/5379](https://gerrit.lix.systems/c/lix/+/5379)
|
||||
|
||||
The settings listed in a flake-config confirmation prompt are now printed to stderr rather than stdout, which allows `nix print-dev-env` to emit valid bash again even in the presence of untrusted settings.
|
||||
|
||||
Many thanks to [lheckemann](https://git.lix.systems/lheckemann) for this.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Lix 2.95.0 (2026-03-13)
|
||||
## Breaking Changes
|
||||
|
||||
- Deprecate shadowing internal files through the Nix search path [lix#998](https://git.lix.systems/lix-project/lix/issues/998) [cl/4632](https://gerrit.lix.systems/c/lix/+/4632)
|
||||
|
||||
As Lix uses the path `<nix/fetchurl.nix>` for bootstrapping purposes, the ability to shadow it by adding `nix=/some/path` (or `/other/path` that contains a `nix` directory) to the search path is not desirable.
|
||||
|
||||
To alleviate potential issues, Lix now emits a warning when the Nix search path contains potential shadows for internal files, which will be changed to an error in a future release.
|
||||
|
||||
The warning can be disabled by enabling the deprecated feature `nix-path-shadow`.
|
||||
|
||||
Many thanks to [Tom Hubrecht](https://git.lix.systems/tom-hubrecht) for this.
|
||||
|
||||
- More deprecated features [cl/2092](https://gerrit.lix.systems/c/lix/+/2092) [cl/2310](https://gerrit.lix.systems/c/lix/+/2310) [cl/2311](https://gerrit.lix.systems/c/lix/+/2311) [cl/4638](https://gerrit.lix.systems/c/lix/+/4638) [cl/4652](https://gerrit.lix.systems/c/lix/+/4652) [cl/4764](https://gerrit.lix.systems/c/lix/+/4764)
|
||||
|
||||
This release cycle features a new batch of deprecated (anti-)features.
|
||||
You can opt in into the old behavior with `--extra-deprecated-features` or any equivalent configuration option.
|
||||
|
||||
- `broken-string-indentation` indented strings (those starting with `''`) might produce unintended results due to how the whitespace stripping is done. Those cases will now warn the user.
|
||||
- `broken-string-escape` "escaped" characters without a properly defined escape sequence evaluate to "themselves". This is in most cases unintended behaviour, both for writing regexes, and using legacy or uncommon escape sequences like `\f`. The user will now be warned, if those are present.
|
||||
- `floating-without-zero` so far, one was able to declare a float using something like `.123`. This can cause confusion about accessing attributes. Floating point numbers must now always include the leading zero, i.e. `0.123`
|
||||
- `rec-set-merges` Attribute sets like `{ foo = {}; foo.bar = 42;}` implicitly merge at parse time, however if one of them is marked as recursive but not the others then the recursive attribute may get lost (order-dependent). Therefore, merging attrs with mixed-`rec` is now forbidden.
|
||||
- `rec-set-dynamic-attrs` Dynamic attributes have weird semantics in the presence of recursive attrsets (they evaluate *after* the rest of the set). This is now forbidden.
|
||||
- `or-as-identifier` `or` as an identifier has always been weird since the `or` (almost-)keyword has been introduced. We are deprecating the backcompat hacks from the early days of Nix in favor of making `or` a full and proper keyword.
|
||||
- `tokens-no-whitespace` Function applications without space around the arguments like `0a`, `0.00.0` or `foo"1"2` are now forbidden. The same applies to list elements. The primary reason for this deprecation is to remove foot guns around surprising tokenization rules regarding number literals, but this will also free up some syntax for other purposes (e.g. `r""` strings) for reuse at some point in the future.
|
||||
- `shadow-internal-symbols` has been expanded to also forbid shadowing `null`, `true` and `false`.
|
||||
- `ancient-let` deprecation has been turned into a full parser error instead of a warning.
|
||||
- `rec-set-overrides` deprecation has been turned into a full parser error instead of a warning.
|
||||
|
||||
Many thanks to [piegames](https://git.lix.systems/piegames), [rootile (Rutile)](https://git.lix.systems/rootile), and [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
- Move `/root/.cache/nix` to `/var/cache/nix` by default [lix#634](https://git.lix.systems/lix-project/lix/issues/634) [cl/4671](https://gerrit.lix.systems/c/lix/+/4671)
|
||||
|
||||
By default, Lix attempts to locate a cache directory for its operations (such
|
||||
as the narinfo cache) by checking the value of `$XDG_CACHE_DIR`.
|
||||
|
||||
However, since the Nix daemon is a system service, using `$XDG_CACHE_DIR` is
|
||||
not typical in this context.
|
||||
|
||||
To address this, systemd provides a better solution. Specifically, when
|
||||
`CacheDirectory=` is set in the `[Service]` section of a systemd unit, it
|
||||
automatically sets the `$CACHE_DIRECTORY` environment variable and systemd will
|
||||
manage that cache directory for us.
|
||||
|
||||
Now, our systemd unit includes `CacheDirectory=nix`, which sets the
|
||||
`$CACHE_DIRECTORY` and takes precedence over `$XDG_CACHE_DIR`.
|
||||
|
||||
If the daemon is run under user units, systemd will automatically set
|
||||
`$XDG_CACHE_DIR`.
|
||||
|
||||
If neither of these variables is set, Lix falls back to its default behavior.
|
||||
By default, Lix will try to find a cache directory for its various operations
|
||||
(e.g. narinfo cache) by looking into `$XDG_CACHE_DIR`.
|
||||
|
||||
In summary, what was stored in `/root/.cache/nix` is now moved to
|
||||
`/var/cache/nix/nix`.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
|
||||
|
||||
- Remove `fetch-closure` experimental feature [lix#1010](https://git.lix.systems/lix-project/lix/issues/1010) [cl/4595](https://gerrit.lix.systems/c/lix/+/4595)
|
||||
|
||||
The `fetch-closure` experimental feature has been removed.
|
||||
|
||||
Outside of allowing the user to import closure from binary cache,
|
||||
`fetchClosure` also allowed you to do the following:
|
||||
|
||||
* rewrite non-CA path to CA
|
||||
* reject non-CA paths at fetching time
|
||||
* reject CA paths at fetching time
|
||||
|
||||
Some people are using those mechanism to prevent users from having to build any
|
||||
package and force going via the declared cache or as a way to use ancient/old
|
||||
software without paying the evaluation cost of a second nixpkgs.
|
||||
|
||||
Both use cases are somewhat of an antipattern in Nix semantics. If the user
|
||||
cannot fetch a program directly via the substituter mechanism and fall back to
|
||||
local build, this is a feature *and* a misconfiguration. If the user cannot build
|
||||
certain derivations because they are too expensive, the build directives should
|
||||
pass `-j0` or similar.
|
||||
|
||||
As for the second usecase, there's a different way to do it that also allows to
|
||||
have a way to reproduce the paths that are hardcoded in that file, perform
|
||||
`import (fetchurl "https://my-cache/${hashparts storepath}.drv")` rather, i.e.
|
||||
an IFD to a possibly well known name. The backend can generate them on the fly
|
||||
or once, and possess stable names.
|
||||
|
||||
Finally, as for the non-CA → CA features, Lix removed ca-derivations.
|
||||
fetchClosure offers ca-derivations-like features which suffers from similar
|
||||
shortcomings albeit lessened. It only follows that we should deprecate
|
||||
and remove these capabilities.
|
||||
|
||||
Many thanks to [just1602](https://git.lix.systems/just1602) for this.
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
- `nix store add-path` now supports references [cl/5205](https://gerrit.lix.systems/c/lix/+/5205)
|
||||
|
||||
Lix supports two categories of hashes in store paths: input-addressed and output-addressed.
|
||||
|
||||
Currently, in Nix language, there is no way to produce output-addressed paths with references, as fixed-output derivations forbid references.
|
||||
However, the Nix store actually *supports* references in output-addressed paths.
|
||||
This is very useful for importing build products created outside of Lix that reference dependency store paths since such build products have no associated derivation so don't make any sense to input-address.
|
||||
Previously, output-addressed paths with references could only be created by writing a custom client to the rather-baroque Nix daemon protocol; now it's available in the CLI.
|
||||
|
||||
Using `nix store add-path --references-list-json REFS_LIST_FILE SOME_PATH` with a JSON list of string store paths, you can now create such paths with the Lix CLI.
|
||||
They may be consumed from Nix language using something like `builtins.storePath` or the following which also works in pure evaluation mode:
|
||||
|
||||
```nix
|
||||
# Hack from https://git.lix.systems/lix-project/lix/issues/402#issuecomment-5889
|
||||
path:
|
||||
builtins.appendContext path {
|
||||
${path} = {
|
||||
path = true;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Many thanks to [jade](https://git.lix.systems/jade) for this.
|
||||
|
||||
- Add `builtins.warn` for emitting warnings from Nix code [cl/2248](https://gerrit.lix.systems/c/lix/+/2248)
|
||||
|
||||
Lix now has a builtin function for emitting warnings.
|
||||
Like `builtins.trace`, it takes two arguments: the message to emit, and the expression to return.
|
||||
_Unlike_ `builtins.trace`, `builtins.warn` requires the first argument — the message — to be a string.
|
||||
In the future we may extend `builtins.warn` to accept a more structured API.
|
||||
|
||||
To go along with this, we also have two new config settings:
|
||||
- [`debugger-on-warn`](@docroot@/command-ref/conf-file.md#conf-debugger-on-warn), which, when used with `--debugger`, makes `builtins.warn` also function like [`builtins.break`](@docroot@/language/builtins.md#builtins-break).
|
||||
- [`abort-on-warn`](@docroot@/command-ref/conf-file.md#conf-abort-on-warn), which aborts evaluation entirely after the warning is emitted.
|
||||
|
||||
Many thanks to [Emilia Bopp](https://git.lix.systems/milibopp) and [Qyriad](https://git.lix.systems/Qyriad) for this.
|
||||
|
||||
- `keep-env-derivations` is now supported for nix3 CLI (`nix profile`) [lix#1095](https://git.lix.systems/lix-project/lix/issues/1095) [cl/5332](https://gerrit.lix.systems/c/lix/+/5332)
|
||||
|
||||
The `keep-env-derivations` feature is now available for `nix profile`. This allows users to prevent the garbage collection of derivations used to install a profile, even when `keep-derivations = false` (set to `true` by default).
|
||||
|
||||
Previously, `nix-env` supported this feature, but `nix profile` **never** did. This caused issues when garbage collection removed the associated `.drv` files, which are required, for example, by vulnerability management tools (e.g. [vulnix](https://github.com/nix-community/vulnix)) for proper operation.
|
||||
|
||||
This issue has now been resolved.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
|
||||
|
||||
- Make `log-format` a setting [cl/4686](https://gerrit.lix.systems/c/lix/+/4686)
|
||||
|
||||
The [`--log-format` CLI option](@docroot@/command-ref/opt-common.md#opt-log-format) can now be set in [`nix.conf`](@docroot@/command-ref/conf-file.md#conf-log-format)!
|
||||
For example, you can now persistently enable the `multiline-with-logs` log format [added in Lix 2.91](@docroot@/release-notes/rl-2.91.md) by adding the following to your `nix.conf`:
|
||||
|
||||
```conf
|
||||
log-format = multiline-with-logs
|
||||
```
|
||||
|
||||
Or the equivalent in a NixOS configuration:
|
||||
```nix
|
||||
{
|
||||
nix.settings.log-format = "multiline-with-logs";
|
||||
}
|
||||
```
|
||||
|
||||
Many thanks to [Qyriad](https://git.lix.systems/Qyriad) for this.
|
||||
|
||||
- Allow remote builders to be configured using TOML [cl/4533](https://gerrit.lix.systems/c/lix/+/4533)
|
||||
|
||||
Lix now supports configuring remote builders using a TOML file instead of the old, very cursed and incomprehensible format.
|
||||
This comes with not only a human-understandable file, but also with better messages and error reports on misconfiguration.
|
||||
|
||||
A more detailed Documentation can be found on the [distributed-builds](@docroot@/advanced-topics/distributed-builds.md) documentation page.
|
||||
|
||||
Many thanks to [rootile (Rutile)](https://git.lix.systems/rootile) and [Qyriad](https://git.lix.systems/Qyriad) for this.
|
||||
|
||||
- Emit warnings when encountering IFD with `warn-import-from-derivation` [nix#13279](https://github.com/NixOS/nix/pull/13279) [cl/3879](https://gerrit.lix.systems/c/lix/+/3879)
|
||||
|
||||
Instead of only being able to toggle the use of [Import from
|
||||
Derivation](https://nix.dev/manual/nix/stable/language/import-from-derivation) with
|
||||
`allow-import-from-derivation`, Lix is now able to warn users whenever IFD is encountered with
|
||||
`warn-import-from-derivation`.
|
||||
|
||||
Many thanks to [Seth Flynn](https://git.lix.systems/getchoo), [gustavderdrache](https://github.com/gustavderdrache), and [Eelco Dolstra](https://github.com/edolstra) for this.
|
||||
|
||||
|
||||
## Improvements
|
||||
|
||||
- Collect Flakes untrusted settings into one prompt [lix#682](https://git.lix.systems/lix-project/lix/issues/682) [cl/2921](https://gerrit.lix.systems/c/lix/+/2921)
|
||||
|
||||
When working with Flakes containing untrusted settings, a prompt is shown for each setting, asking whether to vet or approve it. This looks like:
|
||||
|
||||
```
|
||||
❯ nix flake lock
|
||||
warning: ignoring untrusted flake configuration setting 'allow-dirty', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
|
||||
warning: ignoring untrusted flake configuration setting 'sandbox', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
|
||||
The following settings require your decision:
|
||||
- allow-dirty = false
|
||||
- sandbox = false
|
||||
Do you want to allow configuration settings to be applied?
|
||||
This may allow the flake to gain root, see the nix.conf manual page (yes for now/Allow always/no/No to all)
|
||||
```
|
||||
|
||||
In Flakes with a large number of settings to approve or reject, this process can become tedious as each option must be handled individually.
|
||||
|
||||
To address this, all untrusted settings are now consolidated into a single prompt: allowing for bulk acceptance permanently or not, rejection, or detailed review. For example:
|
||||
|
||||
### Scrutiny scenario
|
||||
|
||||
```console
|
||||
❯ nix flake lock
|
||||
warning: ignoring untrusted flake configuration setting 'allow-dirty', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
|
||||
warning: ignoring untrusted flake configuration setting 'sandbox', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
|
||||
The following settings require your decision:
|
||||
- allow-dirty = false
|
||||
- sandbox = false
|
||||
Do you want to allow configuration settings to be applied?
|
||||
This may allow the flake to gain root, see the nix.conf manual page (yes for now/Allow always/no/No to all) n
|
||||
warning: you can set 'accept-flake-config' to 'false' to automatically reject configuration options supplied by flakes
|
||||
Do you want to allow setting 'allow-dirty = false'? (yes for now/Allow always/no for now) y
|
||||
Do you want to allow setting 'sandbox = false'? (yes for now/Allow always/no for now) n
|
||||
```
|
||||
|
||||
### Reject everything scenario
|
||||
|
||||
```console
|
||||
❯ nix flake lock
|
||||
warning: ignoring untrusted flake configuration setting 'allow-dirty', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
|
||||
warning: ignoring untrusted flake configuration setting 'sandbox', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
|
||||
The following settings require your decision:
|
||||
- allow-dirty = false
|
||||
- sandbox = false
|
||||
Do you want to allow configuration settings to be applied?
|
||||
This may allow the flake to gain root, see the nix.conf manual page (yes for now/Allow always/no/No to all) N
|
||||
Rejecting all untrusted nix.conf entries
|
||||
warning: you can set 'accept-flake-config' to 'false' to automatically reject configuration options supplied by flakes
|
||||
```
|
||||
|
||||
### Accept everything scenario
|
||||
|
||||
```console
|
||||
❯ nix flake lock
|
||||
warning: ignoring untrusted flake configuration setting 'allow-dirty', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
|
||||
warning: ignoring untrusted flake configuration setting 'sandbox', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
|
||||
The following settings require your decision:
|
||||
- allow-dirty = false
|
||||
- sandbox = false
|
||||
Do you want to allow configuration settings to be applied?
|
||||
This may allow the flake to gain root, see the nix.conf manual page (yes for now/Allow always/no/No to all) y
|
||||
```
|
||||
|
||||
### Accept everything PERMANENTLY scenario
|
||||
|
||||
Note that accepting everything permanently will authorize these options for any
|
||||
further operations.
|
||||
|
||||
The file containing this trust information is usually located in
|
||||
`~/.local/share/nix/trusted-settings.json` and can be edited manually to revoke
|
||||
this permission until Lix provides a first-class command for this manipulation.
|
||||
|
||||
```console
|
||||
❯ nix flake lock
|
||||
warning: ignoring untrusted flake configuration setting 'allow-dirty', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
|
||||
warning: ignoring untrusted flake configuration setting 'sandbox', pass '--accept-flake-config' to trust it (may allow the flake to gain root, see the nix.conf manual page)
|
||||
The following settings require your decision:
|
||||
- allow-dirty = false
|
||||
- sandbox = false
|
||||
Do you want to allow configuration settings to be applied?
|
||||
This may allow the flake to gain root, see the nix.conf manual page (yes for now/Allow always/no/No to all) A
|
||||
```
|
||||
|
||||
Many thanks to [isabelroses](https://git.lix.systems/isabelroses), [Raito Bezarius](https://git.lix.systems/raito), and [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
- `--check` or `--rebuild` is clearer about a missing path [lix#485](https://git.lix.systems/lix-project/lix/issues/485)
|
||||
|
||||
Previously, when running Lix with --check or --rebuild, failures often surfaced
|
||||
as an unhelpful error:
|
||||
|
||||
> "some outputs of '...' are not valid, so checking is not possible"
|
||||
|
||||
This message could mean two different things:
|
||||
|
||||
- The requested output paths don't exist at all, or,
|
||||
- Some outputs exist but are not known to Lix
|
||||
|
||||
Lix cannot reliably distinguish these cases, so it treated them the same.
|
||||
|
||||
We've updated the error messages to clarify what Lix can determine: whether any
|
||||
valid outputs (> 0) are present or whether no outputs are available.
|
||||
|
||||
When no valid outputs can be found, Lix will now suggest building the derivation
|
||||
normally (without --check or --rebuild) before trying again.
|
||||
|
||||
When some valid outputs are present, Lix now reports which ones are valid,
|
||||
shows the full list of known outputs, and also suggests building the derivation
|
||||
normally.
|
||||
|
||||
In the future, Lix may automate this recovery step when it knows how to rebuild
|
||||
the paths, but implementing that safely requires more extensive changes to the
|
||||
codebase.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
|
||||
|
||||
- `nix develop` no longer ignores the env variable `SSL_CERT_FILE` [cl/5042](https://gerrit.lix.systems/c/lix/+/5042)
|
||||
|
||||
Running `nix develop` and `nix print-dev-env` on shells that define the environment variable `SSL_CERT_FILE` now works correctly by exporting that variable inside the built shell.
|
||||
|
||||
Many thanks to [Tom Hubrecht](https://git.lix.systems/tom-hubrecht) for this.
|
||||
|
||||
- Linux sandbox launch overhead greatly reduced [cl/5030](https://gerrit.lix.systems/c/lix/+/5030) [cl/5073](https://gerrit.lix.systems/c/lix/+/5073) [cl/5074](https://gerrit.lix.systems/c/lix/+/5074)
|
||||
|
||||
Sandboxed builds are now much cheaper to launch on Linux, with constant management
|
||||
overhead. This will mostly be noticeable when building derivation trees containing
|
||||
many small derivations like nixpkgs' `writeFile` or `runCommand` with scripts that
|
||||
exit quickly. In synthetic tests we have seen build times of 3000 small runCommand
|
||||
drop from 80 seconds to 14 seconds, which is the most optimistic case in practice.
|
||||
|
||||
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
- mTLS store connections via a plugin [cl/3754](https://gerrit.lix.systems/c/lix/+/3754) [cl/3696](https://gerrit.lix.systems/c/lix/+/3696) [cl/3697](https://gerrit.lix.systems/c/lix/+/3697) [cl/3698](https://gerrit.lix.systems/c/lix/+/3698)
|
||||
|
||||
To support use cases requiring mutual TLS (mTLS) authentication when connecting
|
||||
to remote Nix stores, e.g. private stores, we have introduced a **contributed**
|
||||
mTLS plugin extending the Lix store interface.
|
||||
|
||||
This design follows an extensibility model which was brought up [by a proposal
|
||||
of making Kerberos authentication possible in Lix
|
||||
directly](https://gerrit.lix.systems/c/lix/+/3637).
|
||||
|
||||
This mTLS plugin serves as a concrete example of how store connection
|
||||
mechanisms can be modularized through external plugins, without extending Lix
|
||||
core. This idea can be generalized to integrate automatic certificate renewal
|
||||
or advanced integrations with secrets engine or posture checks.
|
||||
|
||||
It enables custom TLS client certificates to be used for authenticating against
|
||||
a remote store that enforces mTLS.
|
||||
|
||||
To use the plugin, configure Lix manually by setting in your `nix.conf`:
|
||||
|
||||
```
|
||||
plugin-files = /a/path/to/libplugin_mtls_store.so
|
||||
```
|
||||
|
||||
Currently, this must be done explicitly. In the future, Nixpkgs will provide a
|
||||
mechanism to reference an up-to-date and curated set of plugins automatically.
|
||||
|
||||
Making plugins easily consumable outside of Nixpkgs (e.g., from external plugin
|
||||
registries or binary distributions) remains an open question and will require
|
||||
further design.
|
||||
|
||||
Contributed plugins come with significantly reduced **stability** and
|
||||
**maintenance** guarantees compared to the Lix core. We encourage users who
|
||||
depend on a given plugin to take on maintenance responsibilities and apply for
|
||||
ownership within the Lix mono-repository. These plugins are subject to removal
|
||||
at any time.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito), [eldritch horrors](https://git.lix.systems/pennae), [mic92](https://github.com/mic92), [vlaci](https://github.com/vlaci), and [nkk0](https://github.com/nkk0) for this.
|
||||
|
||||
- Add an indication of nix-shell nesting depth [lix#826](https://git.lix.systems/lix-project/lix/issues/826) [cl/4657](https://gerrit.lix.systems/c/lix/+/4657)
|
||||
|
||||
When in a nix shell (either via a `nix-shell` or a `nix develop` invocation), a variable `NIX_SHELL_LEVEL` is exported to indicate the nesting depth of nix shells.
|
||||
|
||||
Many thanks to [Tom Hubrecht](https://git.lix.systems/tom-hubrecht) for this.
|
||||
|
||||
- `nix store delete` can now unlink a GC root before deleting its closure [cl/4660](https://gerrit.lix.systems/c/lix/+/4660)
|
||||
|
||||
Ever build something, and then you want to delete it and whatever dependencies it downloaded?
|
||||
Before you had to resolve the `result` symlink and copy it, then delete it, *then* `nix store delete --delete-closure --skip-live` on the path you copied.
|
||||
Now you can just pass `--unlink` and the `result` symlink itself.
|
||||
|
||||
Many thanks to [Qyriad](https://git.lix.systems/Qyriad) for this.
|
||||
|
||||
- `nix path-info` no longer lies to the user about fetching paths [lix#323](https://git.lix.systems/lix-project/lix/issues/323) [cl/4866](https://gerrit.lix.systems/c/lix/+/4866)
|
||||
|
||||
When running `nix path-info` with an installable that is not present in the store, Lix no longer
|
||||
tells the user which paths are missing and that they will be fetched, as the documentation clearly
|
||||
states that this command does not fetch missing paths.
|
||||
|
||||
Many thanks to [Tom Hubrecht](https://git.lix.systems/tom-hubrecht) for this.
|
||||
|
||||
- Derivations can now be printed in detail in `nix repl` [cl/3842](https://gerrit.lix.systems/c/lix/+/3842)
|
||||
|
||||
Traditionally derivations printed in the REPL would only print a formatted object
|
||||
representing the path of the derivation file it refers to. This makes inspecting
|
||||
the enhanced derivation attribute sets encountered from `mkDerivation` or similar
|
||||
wrappers more difficult. Even the `:p`/`:print` command would not elaborate attribute sets
|
||||
tagged as a derivation.
|
||||
|
||||
With this change you can now use `:p`/`:print` to directly inspect a derivation
|
||||
by providing one as the top-level object. Derivation attribute sets will only be
|
||||
printed two levels deep and internal derivation attrsets will remain in unexpanded
|
||||
path form as before. `drvAttrs` will also be elided as these attributes are already
|
||||
present in the top-level attribute set of the derivation. These heuristics provide
|
||||
a balance between readability and functionality. When the `:p`/`:print` is omitted,
|
||||
a bare derivation is printed in the path format as before.
|
||||
|
||||
Many thanks to [Lunaphied](https://git.lix.systems/Lunaphied) for this.
|
||||
|
||||
- Reject `__json` in structured attributes derivations [lix#380](https://git.lix.systems/lix-project/lix/issues/380) [cl/5286](https://gerrit.lix.systems/c/lix/+/5286)
|
||||
|
||||
In structured attributes derivations, `__json` is used internally to store the
|
||||
JSON representation of the `env` attribute field that users can set.
|
||||
|
||||
Unfortunately, a user can set `__json` *and* enable structured attributes,
|
||||
resulting in a broken derivation from a semantic point of view.
|
||||
|
||||
As no user can benefit from setting `__json` *and* enable structured attributes,
|
||||
we disallow that possibility and throw an error from now on.
|
||||
|
||||
This is not seen as a breaking change because there's no user code that can
|
||||
benefit from this behavior, hence, it's an improvement to user experience.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
|
||||
|
||||
- Shells support `$NIX_LOG_FD` now [lix#336](https://git.lix.systems/lix-project/lix/issues/336) [cl/4694](https://gerrit.lix.systems/c/lix/+/4694) [cl/4695](https://gerrit.lix.systems/c/lix/+/4695)
|
||||
|
||||
Lix's "debugging" shells (`nix3-develop` and `nix-shell`) now set the
|
||||
`$NIX_LOG_FD` environment variable.
|
||||
|
||||
This means that [hook logging in
|
||||
stdenv](https://github.com/NixOS/nixpkgs/pull/310387) appears while debugging
|
||||
derivations via `nix3-develop` or `nix-shell`.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
|
||||
|
||||
- Supplementary groups are now supported for daemon authentication [lix#968](https://git.lix.systems/lix-project/lix/issues/968) [cl/5021](https://gerrit.lix.systems/c/lix/+/5021)
|
||||
|
||||
macOS, FreeBSD and Linux now support receiving supplementary groups during UNIX domain authentication to a Lix daemon.
|
||||
|
||||
This change is particularly beneficial for systemd units with `DynamicUser=true` that need to connect to a Lix daemon, using a `SupplementaryGroups=` allocated by systemd in the context of the process. This is desirable if you wish to harden Lix clients.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito), [Tom Hubrecht](https://git.lix.systems/tom-hubrecht), [alois31](https://git.lix.systems/alois31), and [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
|
||||
## Fixes
|
||||
|
||||
- Nix shells' `$NIX_BUILD_TOP` are shorter [lix#1044](https://git.lix.systems/lix-project/lix/issues/1044) [cl/4663](https://gerrit.lix.systems/c/lix/+/4663)
|
||||
|
||||
Following the changes in 2.94.0 to shorten build directory paths, aimed at [resolving UNIX domain socket length issues](https://gerrit.lix.systems/c/lix/+/4168/13) and [improving nix-shell](https://git.lix.systems/lix-project/lix/issues/940), we inadvertently introduced an excessively long path for the `$NIX_BUILD_TOP` environment variable used by Nix shells (their effective temporary `/build` directory).
|
||||
|
||||
To fix this, we replaced the `build-top-$HASH` directory name with simply `build-top`, reducing these paths by at least 30 characters.
|
||||
|
||||
We also added a test to ensure that Nix shells do not introduce more than 50 extra characters relative to their base directory (e.g., `/tmp` when `$TMPDIR` is not set).
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) for this.
|
||||
|
||||
- Fix resolving of symlinks in flake paths [lix#106](https://git.lix.systems/lix-project/lix/issues/106) [lix#12286](https://git.lix.systems/lix-project/lix/pulls/12286) [cl/4783](https://gerrit.lix.systems/c/lix/+/4783)
|
||||
|
||||
Flake paths are now canonicalized to resolve symlinks. This ensures that when a flake is accessed via a symlink, paths are resolved relative to the target directory, not the symlink's location.
|
||||
|
||||
Many thanks to [stevalkr](https://github.com/stevalkr) and [xyenon](https://git.lix.systems/xyenon) for this.
|
||||
|
||||
- The REPL no longer considers failed loads for `:reload` [lix#50](https://git.lix.systems/lix-project/lix/issues/50) [cl/4864](https://gerrit.lix.systems/c/lix/+/4864) [cl/4865](https://gerrit.lix.systems/c/lix/+/4865) [cl/4700](https://gerrit.lix.systems/c/lix/+/4700) [cl/4889](https://gerrit.lix.systems/c/lix/+/4889)
|
||||
|
||||
The [REPL](@docroot@/command-ref/new-cli/nix3-repl.md) allows "loading" files, flakes, and expressions into the environment, with the commands `:load`/`:l`, `:load-flake`/`:lf`, and `:add`/`:a` respectively.
|
||||
The results of those stay in the environment as-is even if their sources change, until the `:reload` command is used.
|
||||
However `:reload` would re-perform *all* instances of `:l`/`:lf`/`:a`, meaning you would get things like this:
|
||||
|
||||
```nix
|
||||
nix-repl> :l /tmp/texting.nix
|
||||
error: getting status of '/tmp/texting.nix': No such file or directory
|
||||
# oops, typo.
|
||||
nix-repl> :l /tmp/testing.nix
|
||||
|
||||
# Do some stuff…
|
||||
|
||||
nix-repl> :reload
|
||||
error: getting status of '/tmp/texting.nix': No such file or directory
|
||||
```
|
||||
|
||||
This is pretty silly, but also *incredibly* annoying, as it would stop there and *not* reload the correct files anymore.
|
||||
This effectively meant typoing any of the load commands would make `:reload` useless for the rest of the entire `nix repl` session!
|
||||
|
||||
This has been fixed, so now only *successful* loads count towards `:reload`.
|
||||
|
||||
Many thanks to [Raito Bezarius](https://git.lix.systems/raito) and [Qyriad](https://git.lix.systems/Qyriad) for this.
|
||||
|
||||
- Consistently use commit hash as rev when locking git inputs [cl/4762](https://gerrit.lix.systems/c/lix/+/4762)
|
||||
|
||||
Lix will now use commit hashes instead of tag object hashes in the `rev` field
|
||||
when fetching git inputs by tag in `flake.lock` and `builtins.fetchTree` output.
|
||||
Note that this means that Lix may change some `flake.lock` files on re-locking. Old `flake.lock` files still remain valid.
|
||||
|
||||
Many thanks to [goldstein](https://git.lix.systems/goldstein) for this.
|
||||
|
||||
|
||||
## Development
|
||||
|
||||
- Functional lang migration [lix#856](https://git.lix.systems/lix-project/lix/issues/856) [cl/3213](https://gerrit.lix.systems/c/lix/+/3213)
|
||||
|
||||
We have done it! The functional/lang framework has now been fully migrated to functional2/lang.
|
||||
This means: no more `just clean` and `just install` mess and whatever because one removed a test.
|
||||
The lang test suite is also getting a face lift, with an improved folder structure and restructuring of many tests.
|
||||
|
||||
Only the first CL of the chain is provided but there's way more changes associated to this project.
|
||||
|
||||
Many thanks to [piegames](https://git.lix.systems/piegames) and [rootile (Rutile)](https://git.lix.systems/rootile) for this.
|
||||
|
||||
|
||||
## Miscellany
|
||||
|
||||
- Warn instead of erroring when the final destination of a transfer changes in-flight [lix#1004](https://git.lix.systems/lix-project/lix/issues/1004) [cl/4641](https://gerrit.lix.systems/c/lix/+/4641)
|
||||
|
||||
Lix will now emit a warning during downloads where the final destination changes suddently mid-transfer instead of throwing an error.
|
||||
This transfer behavior has been known to happen very rarely while fetching from some CDNs.
|
||||
|
||||
Many thanks to [Tom Hubrecht](https://git.lix.systems/tom-hubrecht) for this.
|
||||
|
||||
- `impersonate-linux-26` setting removed [cl/5047](https://gerrit.lix.systems/c/lix/+/5047)
|
||||
|
||||
Linux 3.0 was released 15 years ago. The `impersonate-linux-26` setting was added
|
||||
14 years ago with no mention of it being necessary to build anything, only saying
|
||||
that it improves determinism—which isn't accurate since impersonating Linux 2.6.x
|
||||
still allows the version string to change, and the final component of the version
|
||||
does still change with each Linux release. Since this setting should be no longer
|
||||
necessary in modern systems and workarounds for building old code exist (by using
|
||||
e.g. `setarch --uname-2.6` to wrap builds) we are removing this setting from Lix.
|
||||
|
||||
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
- Default to showing build logs in the new-style (nix3) CLI [cl/4674](https://gerrit.lix.systems/c/lix/+/4674)
|
||||
|
||||
Lix will now show logs by default, in addition to the progress bar, when invoked through the new-style "nix3" CLI (`nix build`, etc)
|
||||
|
||||
Many thanks to [K900](https://git.lix.systems/K900) for this.
|
||||
|
||||
- Lix daemons are now fully socket-activated on systemd setups [lix#1030](https://git.lix.systems/lix-project/lix/issues/1030)
|
||||
|
||||
When launched by systemd, Lix no longer uses a persistent daemon process and uses systemd socket
|
||||
activation instead. This is necessary to support the `cgroups` and `auto-allocate-uids` features
|
||||
and may improve observability of daemon behavior with common systemd-based monitoring solutions.
|
||||
|
||||
The old behavior with a single persistent daemon is still available, but disabled by default. It
|
||||
is not possible to enable both a persistent daemon and socket activation, starting one stops the
|
||||
other automatically. Existing installations should not require any changes when they're updated.
|
||||
|
||||
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
|
||||
- Plugin interfaces have changed (again) [lix#359](https://git.lix.systems/lix-project/lix/issues/359) [cl/4933](https://gerrit.lix.systems/c/lix/+/4933) [cl/4934](https://gerrit.lix.systems/c/lix/+/4934)
|
||||
|
||||
The `RegisterPrimOp` class used to register builtins has been removed. Plugins
|
||||
must now call `PluginPrimOps::add` from their `nix_plugin_entry` with the same
|
||||
parameters previously passed to `RegisterRrimOp` to register any new builtins.
|
||||
|
||||
The `GlobalConfig::Register` helper class has also been removed. Adding config
|
||||
options to the system is now done with `GlobalConfig::registerGlobalConfig`; a
|
||||
plugin can add config values by calling this function from `nix_plugin_entry`.
|
||||
|
||||
Many thanks to [eldritch horrors](https://git.lix.systems/pennae) for this.
|
||||
@@ -70,15 +70,10 @@ def do_include(content: str, relative_md_path: Path, source_root: Path, search_p
|
||||
|
||||
def recursive_replace(data, book_root, search_path):
|
||||
match data:
|
||||
# XXX FUTURE: drop sections once mdBook is at 0.5.0 or above in nixpkgs
|
||||
case {'sections': sections}:
|
||||
return data | dict(
|
||||
sections = [recursive_replace(section, book_root, search_path) for section in sections],
|
||||
)
|
||||
case {'items': items}:
|
||||
return data | dict(
|
||||
items = [recursive_replace(item, book_root, search_path) for item in items],
|
||||
)
|
||||
case {'Chapter': chapter}:
|
||||
path_to_chapter = Path(chapter['path'])
|
||||
chapter_content = chapter['content']
|
||||
@@ -95,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)
|
||||
@@ -124,10 +114,10 @@ def main():
|
||||
context, book = json.load(sys.stdin)
|
||||
|
||||
# book_root is the directory where book contents leave (ie, src/)
|
||||
book_root = Path(context['root']) / context['config']['book'].get('src', 'src')
|
||||
book_root = Path(context['root']) / context['config']['book']['src']
|
||||
|
||||
# includes pointing into @generated@ will look here
|
||||
search_path = Path(os.environ['MANUAL_SUBSTITUTE_SEARCH'])
|
||||
search_path = Path(os.environ['MDBOOK_SUBSTITUTE_SEARCH'])
|
||||
|
||||
# Find @var@ in all parts of our recursive book structure.
|
||||
replaced_content = recursive_replace(book, book_root, search_path)
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@
|
||||
tag ? "latest",
|
||||
bundleNixpkgs ? true,
|
||||
channelName ? "nixpkgs",
|
||||
channelURL ? "https://channels.nixos.org/nixpkgs-unstable",
|
||||
channelURL ? "https://nixos.org/channels/nixpkgs-unstable",
|
||||
extraPkgs ? [ ],
|
||||
maxLayers ? 100,
|
||||
nixConf ? { },
|
||||
@@ -381,7 +381,7 @@ image
|
||||
pkgs.buildPackages.runCommand "docker-image-tarball-${pkgs.nix.version}"
|
||||
{
|
||||
nativeBuildInputs = [ pkgs.buildPackages.bubblewrap ];
|
||||
meta.description = "Docker image tarball with Lix for ${pkgs.stdenv.hostPlatform.system}";
|
||||
meta.description = "Docker image tarball with Lix for ${pkgs.system}";
|
||||
}
|
||||
''
|
||||
mkdir -p $out/nix-support
|
||||
|
||||
Generated
+19
-17
@@ -3,15 +3,17 @@
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1751685974,
|
||||
"narHash": "sha256-NKw96t+BgHIYzHUjkTK95FqYRVKB8DHpVhefWSz/kTw=",
|
||||
"rev": "549f2762aebeff29a2e5ece7a7dc0f955281a1d1",
|
||||
"type": "tarball",
|
||||
"url": "https://git.lix.systems/api/v1/repos/lix-project/flake-compat/archive/549f2762aebeff29a2e5ece7a7dc0f955281a1d1.tar.gz"
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
"url": "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz"
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"lowdown-src": {
|
||||
@@ -33,11 +35,11 @@
|
||||
"nix2container": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1767195068,
|
||||
"narHash": "sha256-+OMnL79ZjqM/PCz2hoQ12MnXNoSSfBGnsYBOZnA9XbI=",
|
||||
"lastModified": 1724996935,
|
||||
"narHash": "sha256-njRK9vvZ1JJsP8oV2OgkBrpJhgQezI03S7gzskCcHos=",
|
||||
"owner": "nlewo",
|
||||
"repo": "nix2container",
|
||||
"rev": "bb6801be998ba857a62c002cb77ece66b0a57298",
|
||||
"rev": "fa6bb0a1159f55d071ba99331355955ae30b3401",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -106,16 +108,16 @@
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1773082486,
|
||||
"narHash": "sha256-TKUDrM0nKUo5s/b8jhjXa2prcu5KU5Cck3HBTRLDjfo=",
|
||||
"lastModified": 1757198069,
|
||||
"narHash": "sha256-m3VUcOD4rTs8J7S+3dOjWMrAjw6RcITC3XYQ98zhEFs=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "7f8b8875bdb38a70c7b5ceb9ba6a6a8d69859e16",
|
||||
"rev": "0747026fc57ecb9c28901c7f7a2b5dc40e8af43c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-25.11-small",
|
||||
"ref": "nixos-25.05-small",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -123,11 +125,11 @@
|
||||
"pre-commit-hooks": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1769939035,
|
||||
"narHash": "sha256-Fok2AmefgVA0+eprw2NDwqKkPGEI5wvR+twiZagBvrg=",
|
||||
"lastModified": 1733318908,
|
||||
"narHash": "sha256-SVQVsbafSM1dJ4fpgyBqLZ+Lft+jcQuMtEL3lQWx2Sk=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "a8ca480175326551d6c4121498316261cbb5b260",
|
||||
"rev": "6f4e2a2112050951a314d2733a994fbab94864c6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
description = "Lix: A modern, delicious implementation of the Nix package manager";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11-small";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05-small";
|
||||
nixpkgs-regression.url = "github:NixOS/nixpkgs/215d4d0fd80ca5163643b03a33fde804a29cc1e2";
|
||||
|
||||
# Required because Nix 2.18 is not in Nixpkgs ≥ 25.05 anymore.
|
||||
@@ -24,7 +24,7 @@
|
||||
flake = false;
|
||||
};
|
||||
flake-compat = {
|
||||
url = "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz";
|
||||
url = "github:edolstra/flake-compat";
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
@@ -88,7 +88,7 @@
|
||||
else
|
||||
"pre${
|
||||
builtins.substring 0 8 (self.lastModifiedDate or self.lastModified or "19700101")
|
||||
}-dev_${self.shortRev or "dirty"}";
|
||||
}_${self.shortRev or "dirty"}";
|
||||
|
||||
linux32BitSystems = [ "i686-linux" ];
|
||||
linux64BitSystems = [
|
||||
@@ -100,7 +100,6 @@
|
||||
"x86_64-darwin"
|
||||
"aarch64-darwin"
|
||||
];
|
||||
nonDarwinSystems = linuxSystems;
|
||||
systems = linuxSystems ++ darwinSystems;
|
||||
|
||||
# If you add something here, please update the list in doc/manual/src/contributing/hacking.md.
|
||||
@@ -177,12 +176,13 @@
|
||||
nixStable = prev.nix;
|
||||
|
||||
nixVersions = prev.nixVersions // {
|
||||
nix_2_3 = prev.nixVersions.nix_2_3.overrideAttrs (old: {
|
||||
meta = old.meta // {
|
||||
knownVulnerabilities = [ ];
|
||||
};
|
||||
});
|
||||
# Nix 2.18 has been removed from Nixpkgs ≥ 25.05, so we need to reintroduce it ourselves for our tests.
|
||||
nix_2_18 =
|
||||
nix_2_18.outputs.packages.${currentStdenv.hostPlatform.system}.default.overrideAttrs
|
||||
(_: {
|
||||
pname = "nix";
|
||||
});
|
||||
nix_2_18 = nix_2_18.outputs.packages.${currentStdenv.hostPlatform.system}.default;
|
||||
};
|
||||
|
||||
# Forward from the previous stage as we don’t want it to pick the lowdown override
|
||||
@@ -227,7 +227,6 @@
|
||||
lix-clang-tidy = final.callPackage ./subprojects/lix-clang-tidy { };
|
||||
|
||||
nix-eval-jobs = final.callPackage ./subprojects/nix-eval-jobs {
|
||||
stdenv = currentStdenv;
|
||||
srcDir = ./subprojects/nix-eval-jobs;
|
||||
};
|
||||
|
||||
@@ -254,28 +253,23 @@
|
||||
lowdown_3_0 =
|
||||
assert lib.versionOlder prev.lowdown.version "3.0.0";
|
||||
prev.lowdown.overrideAttrs (
|
||||
finalAttrs: _prevAttrs: {
|
||||
version = "3.0.0";
|
||||
|
||||
finalAttrs: prevAttrs: {
|
||||
version = "3.0.1";
|
||||
src = final.fetchurl {
|
||||
url = "https://kristaps.bsd.lv/lowdown/snapshots/lowdown-${finalAttrs.version}.tar.gz";
|
||||
sha512 = "94e97234d598382c3c3dc27f9bfdb3a3a2fcf7dbb6a8df3c85ee09f27f792449034a41d49d9cfd3d8450d2de01b8562c20c3d120e65c81af4d7d6c9454119e93";
|
||||
sha512 = "fe68e1b7ff23f3992398356d7aa9a330dfd7b72e22bea9a91eeef74182b209ecea0c9f3e2b2216e1a07b2358da2b746238ec9cbbdeebdd3551cef14dd2d79f46";
|
||||
};
|
||||
|
||||
# no longer compiles with GNU make
|
||||
nativeBuildInputs = prevAttrs.nativeBuildInputs ++ [ final.bmake ];
|
||||
# dylib fixups on darwin are no longer necessary
|
||||
postInstall = "";
|
||||
# doesn't work on darwin due to disallowed nested sandboxes
|
||||
doInstallCheck = prevAttrs.doInstallCheck && !(final.stdenv.hostPlatform.isDarwin);
|
||||
doCheck = prevAttrs.doCheck && !(final.stdenv.hostPlatform.isDarwin);
|
||||
}
|
||||
);
|
||||
|
||||
capnproto = prev.capnproto.overrideAttrs (old: {
|
||||
patches =
|
||||
old.patches or [ ]
|
||||
++ [
|
||||
# backport of https://github.com/capnproto/capnproto/pull/1810
|
||||
./misc/capnproto-promise-nodiscard.patch
|
||||
]
|
||||
++ lib.optionals (lib.versionOlder old.version "1.2.0") [
|
||||
# backport of https://github.com/capnproto/capnproto/pull/2296
|
||||
./misc/capnproto-monotonic-clocks-are-a-lie.patch
|
||||
];
|
||||
});
|
||||
};
|
||||
in
|
||||
{
|
||||
@@ -299,22 +293,6 @@
|
||||
}
|
||||
);
|
||||
|
||||
# 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
|
||||
'';
|
||||
}
|
||||
);
|
||||
|
||||
devShell = forAllSystems (system: {
|
||||
default = self.devShells.${system}.default;
|
||||
clang = self.devShells.${system}.native-clangStdenvPackages;
|
||||
@@ -389,10 +367,7 @@
|
||||
;
|
||||
}
|
||||
// {
|
||||
# the n-e-j test suite is unusably slow in darwin ci. disbled until anywho fixes this.
|
||||
nix-eval-jobs = (lib.genAttrs nonDarwinSystems) (
|
||||
system: self.packages.${system}.nix-eval-jobs.tests.nix-eval-jobs
|
||||
);
|
||||
nix-eval-jobs = forAllSystems (system: self.packages.${system}.nix-eval-jobs.tests.nix-eval-jobs);
|
||||
|
||||
# This is x86_64-linux only, just because we have significantly
|
||||
# cheaper x86_64-linux compute in CI.
|
||||
@@ -431,30 +406,49 @@
|
||||
'';
|
||||
|
||||
# clang-tidy run against the Lix codebase using the Lix clang-tidy plugin
|
||||
clang-tidy = forAllSystems (
|
||||
system:
|
||||
clang-tidy =
|
||||
let
|
||||
pkgs = nixpkgsFor.${system}.native;
|
||||
nixpkgs = nixpkgsFor.x86_64-linux.native;
|
||||
inherit (nixpkgs) pkgs;
|
||||
in
|
||||
pkgs.callPackage ./package.nix {
|
||||
# Required since we don't support gcc stdenv
|
||||
stdenv = pkgs.clangStdenv;
|
||||
versionSuffix = "";
|
||||
lintInsteadOfBuild = true;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
# Make sure that nix-env still produces the exact same result
|
||||
# on a particular version of Nixpkgs.
|
||||
evalNixpkgs = nixpkgsFor.x86_64-linux.native.callPackage ./tests/nixpkgs/eval.nix {
|
||||
inherit nixpkgs-regression;
|
||||
};
|
||||
evalNixpkgs =
|
||||
with nixpkgsFor.x86_64-linux.native;
|
||||
runCommand "eval-nixos" { buildInputs = [ nix ]; } ''
|
||||
type -p nix-env
|
||||
# Note: we're filtering out nixos-install-tools because https://github.com/NixOS/nixpkgs/pull/153594#issuecomment-1020530593.
|
||||
time nix-env --store dummy:// -f ${nixpkgs-regression} -qaP --drv-path | sort | grep -v nixos-install-tools > packages
|
||||
[[ $(sha1sum < packages | cut -c1-40) = 402242fca90874112b34718b8199d844e8b03d12 ]]
|
||||
mkdir $out
|
||||
'';
|
||||
|
||||
nixpkgsLibTests = forAllSystems (
|
||||
system:
|
||||
nixpkgsFor.${system}.native.callPackage ./tests/nixpkgs/lib.nix {
|
||||
inherit nixpkgs system;
|
||||
let
|
||||
inherit (self.packages.${system}) nix;
|
||||
pkgs = nixpkgsFor.${system}.native;
|
||||
testWithNix = import (nixpkgs + "/lib/tests/test-with-nix.nix") { inherit pkgs lib nix; };
|
||||
in
|
||||
pkgs.symlinkJoin {
|
||||
name = "nixpkgs-lib-tests";
|
||||
paths = [
|
||||
testWithNix
|
||||
]
|
||||
# NOTE: nixpkgs 25.05 is being ... *creative*, and requires this dance to override
|
||||
# the evaluator used for the test. it will break again in the future, don't worry.
|
||||
++ lib.optionals pkgs.stdenv.isLinux [
|
||||
((pkgs.callPackage "${nixpkgs}/ci/eval" { inherit nix; }).attrpathsSuperset {
|
||||
evalSystem = system;
|
||||
})
|
||||
];
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -527,14 +521,11 @@
|
||||
}
|
||||
// (
|
||||
lib.optionalAttrs (builtins.elem system linux64BitSystems) {
|
||||
# python doesn't work in static builds as of 2025-06-27
|
||||
nix-static = nixpkgsFor.${system}.static.nix.overrideAttrs (_: {
|
||||
doCheck = false;
|
||||
});
|
||||
nix-static = nixpkgsFor.${system}.static.nix;
|
||||
dockerImage =
|
||||
let
|
||||
pkgs = nixpkgsFor.${system}.native;
|
||||
nix2container' = import nix2container { inherit pkgs; };
|
||||
nix2container' = import nix2container { inherit pkgs system; };
|
||||
in
|
||||
import ./docker.nix {
|
||||
inherit pkgs;
|
||||
@@ -566,11 +557,8 @@
|
||||
inherit stdenv versionSuffix;
|
||||
busybox-sandbox-shell = pkgs.busybox-sandbox-shell or pkgs.default-busybox-sandbox;
|
||||
internalApiDocs = false;
|
||||
includeSanitizerLibs = true;
|
||||
# Use LLD in the dev shell by default for faster link times.
|
||||
useLld = stdenv.hostPlatform.isLinux;
|
||||
};
|
||||
pre-commit = self.hydraJobs.pre-commit.${pkgs.stdenv.hostPlatform.system} or { };
|
||||
pre-commit = self.hydraJobs.pre-commit.${pkgs.system} or { };
|
||||
in
|
||||
pkgs.callPackage nix.mkDevShell {
|
||||
pre-commit-checks = pre-commit;
|
||||
|
||||
@@ -33,7 +33,7 @@ install: (install-custom)
|
||||
|
||||
# Run tests (usually requires `install`) with extra options
|
||||
test *OPTIONS:
|
||||
meson test -C build --print-errorlogs --max-lines 10000 {{ OPTIONS }}
|
||||
meson test -C build --print-errorlogs {{ OPTIONS }}
|
||||
|
||||
# Run unit tests only
|
||||
test-unit *OPTIONS: (test "--suite" "check")
|
||||
@@ -41,10 +41,6 @@ test-unit *OPTIONS: (test "--suite" "check")
|
||||
# Run integration tests only
|
||||
test-integration *OPTIONS: install (test "--suite" "installcheck")
|
||||
|
||||
# Run functional2 tests using pytest directly, allowing for additional arguments to be passed to pytest e.g. for more granular test selection
|
||||
test-functional2 *OPTIONS:
|
||||
cd tests/functional2 && python -m pytest -v {{ OPTIONS }}
|
||||
|
||||
alias clang-tidy := lint
|
||||
|
||||
# Lint with `clang-tidy`
|
||||
|
||||
-10
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
# noqa: N999 # consistency with rest of the codebase
|
||||
@@ -1,113 +1,93 @@
|
||||
import dataclasses
|
||||
from enum import Enum
|
||||
from textwrap import dedent, indent
|
||||
from typing import NamedTuple
|
||||
from typing import List, NamedTuple
|
||||
|
||||
from common import cxx_literal, generate_file, load_data
|
||||
|
||||
from common import cxx_literal, generate_file, load_data, get_argument_parser
|
||||
KNOWN_KEYS = set([
|
||||
'name',
|
||||
'type',
|
||||
'constructorArgs',
|
||||
'implementation',
|
||||
'impure',
|
||||
'renameInGlobalScope',
|
||||
])
|
||||
|
||||
IMPURE_NOTE = """
|
||||
> **Note**
|
||||
class BuiltinConstant(NamedTuple):
|
||||
name: str
|
||||
type: str
|
||||
implementation: str
|
||||
impure: bool
|
||||
rename_in_global_scope: bool
|
||||
documentation: str
|
||||
|
||||
def parse(datum):
|
||||
unknown_keys = set(datum.keys()) - KNOWN_KEYS
|
||||
if unknown_keys:
|
||||
raise Exception('unknown keys', unknown_keys)
|
||||
return BuiltinConstant(
|
||||
name = datum['name'],
|
||||
type = datum['type'],
|
||||
implementation = ('{' + ', '.join([f'NewValueAs::{datum["type"]}', *datum['constructorArgs']]) + '}') if 'constructorArgs' in datum else datum['implementation'],
|
||||
impure = datum.get('impure', False),
|
||||
rename_in_global_scope = datum.get('renameInGlobalScope', True),
|
||||
documentation = datum.content,
|
||||
)
|
||||
|
||||
VALUE_TYPES = {
|
||||
'attrs': 'nAttrs',
|
||||
'boolean': 'nBool',
|
||||
'integer': 'nInt',
|
||||
'list': 'nList',
|
||||
'null': 'nNull',
|
||||
'string': 'nString',
|
||||
}
|
||||
|
||||
HUMAN_TYPES = {
|
||||
'attrs': 'set',
|
||||
'boolean': 'Boolean',
|
||||
'integer': 'integer',
|
||||
'list': 'list',
|
||||
'null': 'null',
|
||||
'string': 'string',
|
||||
}
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('--header', help='Path of the header to generate')
|
||||
ap.add_argument('--docs', help='Path of the documentation file to generate')
|
||||
ap.add_argument('defs', help='Builtin definition files', nargs='+')
|
||||
args = ap.parse_args()
|
||||
|
||||
builtin_constants = load_data(args.defs, BuiltinConstant.parse)
|
||||
|
||||
generate_file(args.header, builtin_constants, lambda constant:
|
||||
# `builtins` is magic and must come first
|
||||
'' if constant.name == 'builtins' else constant.name,
|
||||
lambda constant:
|
||||
f'''{'if (!evalSettings.pureEval) ' if constant.impure else ''}{{
|
||||
addConstant({cxx_literal(('__' if constant.rename_in_global_scope else '') + constant.name)}, {constant.implementation}, {{
|
||||
.type = {VALUE_TYPES[constant.type]},
|
||||
.doc = {cxx_literal(constant.documentation)},
|
||||
.impureOnly = {cxx_literal(constant.impure)},
|
||||
}});
|
||||
}}
|
||||
''')
|
||||
generate_file(args.docs, builtin_constants, lambda constant: constant.name, lambda constant:
|
||||
f'''<dt id="builtins-{constant.name}">
|
||||
<a href="#builtins-{constant.name}"><code>{constant.name}</code></a> ({HUMAN_TYPES[constant.type]})
|
||||
</dt>
|
||||
<dd>
|
||||
|
||||
{constant.documentation}
|
||||
|
||||
''' + ('''> **Note**
|
||||
>
|
||||
> Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval).
|
||||
|
||||
"""
|
||||
''' if constant.impure else '') + '''</dd>
|
||||
|
||||
''')
|
||||
|
||||
class TypeName(NamedTuple):
|
||||
human: str
|
||||
code: str
|
||||
|
||||
|
||||
class BuiltinType(TypeName, Enum):
|
||||
attrs = TypeName("set", "nAttrs")
|
||||
boolean = TypeName("boolean", "nBool")
|
||||
integer = TypeName("integer", "nInt")
|
||||
list = TypeName("list", "nList")
|
||||
null = TypeName("null", "nNull")
|
||||
string = TypeName("string", "nString")
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, t_name: str) -> "BuiltinType":
|
||||
for t in cls:
|
||||
if t_name == t.name:
|
||||
return t
|
||||
msg = f"Invalid builtin type: {t_name}"
|
||||
raise ValueError(msg)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class BuiltinConstant:
|
||||
name: str
|
||||
documentation: str
|
||||
|
||||
# Fields with different name in the Post than in here
|
||||
# our fields
|
||||
type: BuiltinType = dataclasses.field(init=False)
|
||||
|
||||
# Post fields
|
||||
type_str: dataclasses.InitVar[str]
|
||||
constructor_args: dataclasses.InitVar[list[str] | None] = None
|
||||
|
||||
implementation: str = ""
|
||||
impure: bool = False
|
||||
rename_in_global_scope: bool = True
|
||||
|
||||
def __post_init__(self, type_str: str, constructor_args: list[str] | None):
|
||||
self.type = BuiltinType.from_string(type_str)
|
||||
if constructor_args is not None:
|
||||
args = [f"NewValueAs::{type_str}"] + constructor_args
|
||||
self.implementation = f"{{{','.join(args)}}}"
|
||||
|
||||
@property
|
||||
def code(self) -> str:
|
||||
cond = "if (!evalSettings.pureEval) " if self.impure else ""
|
||||
return dedent(f"""
|
||||
{cond} {{
|
||||
addConstant(
|
||||
{cxx_literal(("__" if self.rename_in_global_scope else "") + self.name)},
|
||||
{self.implementation},
|
||||
{{
|
||||
.type = {self.type.code},
|
||||
.doc = {cxx_literal(self.documentation)},
|
||||
.impureOnly = {cxx_literal(self.impure)},
|
||||
}}
|
||||
);
|
||||
}}
|
||||
""")
|
||||
|
||||
@property
|
||||
def docs(self) -> str:
|
||||
indentation = " " * 3
|
||||
return dedent(f"""
|
||||
<dt id="builtins-{self.name}">
|
||||
<a href="#builtins-{self.name}"><code>{self.name}</code></a> ({self.type.human})
|
||||
</dt>
|
||||
<dd>
|
||||
|
||||
{indent(self.documentation, indentation)}
|
||||
{indent(IMPURE_NOTE, indentation) if self.impure else ""}
|
||||
|
||||
</dd>
|
||||
|
||||
""")
|
||||
|
||||
|
||||
def main():
|
||||
args = get_argument_parser().parse_args()
|
||||
|
||||
builtin_constants = load_data(args.defs, BuiltinConstant)
|
||||
|
||||
generate_file(
|
||||
args.header,
|
||||
builtin_constants,
|
||||
lambda constant:
|
||||
# `builtins` is magic and must come first
|
||||
"" if constant.name == "builtins" else constant.name,
|
||||
lambda b: b.code,
|
||||
)
|
||||
generate_file(args.docs, builtin_constants, lambda constant: constant.name, lambda b: b.docs)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -1,90 +1,82 @@
|
||||
import dataclasses
|
||||
from textwrap import dedent, indent
|
||||
from typing import List, NamedTuple, Optional
|
||||
|
||||
from common import (
|
||||
cxx_literal,
|
||||
generate_file,
|
||||
load_data,
|
||||
get_argument_parser,
|
||||
get_experimental_features,
|
||||
)
|
||||
from build_experimental_features import ExperimentalFeature
|
||||
from common import cxx_literal, generate_file, load_data
|
||||
|
||||
KNOWN_KEYS = set([
|
||||
'name',
|
||||
'implementation',
|
||||
'renameInGlobalScope',
|
||||
'args',
|
||||
'experimentalFeature',
|
||||
])
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Builtin:
|
||||
class Builtin(NamedTuple):
|
||||
name: str
|
||||
implementation: str
|
||||
rename_in_global_scope: bool
|
||||
args: List[str]
|
||||
experimental_feature: Optional[str]
|
||||
documentation: str
|
||||
args: list[str]
|
||||
experimental_feature: str | None = None
|
||||
implementation: str = ""
|
||||
rename_in_global_scope: bool = True
|
||||
|
||||
def __post_init__(self):
|
||||
self.implementation = self.implementation or f"prim_{self.name}"
|
||||
|
||||
def generate_code(self, experimental_features: dict[str, str]) -> str:
|
||||
xf = experimental_features[self.experimental_feature]
|
||||
cond = (
|
||||
f"if (experimentalFeatureSettings.isEnabled({xf})) "
|
||||
if self.experimental_feature
|
||||
else ""
|
||||
def parse(datum):
|
||||
unknown_keys = set(datum.keys()) - KNOWN_KEYS
|
||||
if unknown_keys:
|
||||
raise Exception('unknown keys', unknown_keys)
|
||||
return Builtin(
|
||||
name = datum['name'],
|
||||
implementation = datum['implementation'] if 'implementation' in datum else f'prim_{datum["name"]}',
|
||||
rename_in_global_scope = datum.get('renameInGlobalScope', True),
|
||||
args = datum['args'],
|
||||
experimental_feature = datum.get('experimentalFeature', None),
|
||||
documentation = datum.content,
|
||||
)
|
||||
return dedent(f"""
|
||||
{cond}{{
|
||||
addPrimOp({{
|
||||
.name = {cxx_literal(("__" if self.rename_in_global_scope else "") + self.name)},
|
||||
.args = {cxx_literal(self.args)},
|
||||
.arity = {len(self.args)},
|
||||
.doc = {cxx_literal(self.documentation)},
|
||||
.fun = {self.implementation},
|
||||
.experimentalFeature = {xf},
|
||||
}});
|
||||
}}
|
||||
""")
|
||||
|
||||
@property
|
||||
def docs(self) -> str:
|
||||
return dedent(f"""
|
||||
<dt id="builtins-{self.name}">
|
||||
<a href="#builtins-{self.name}"><code>{self.name} {
|
||||
" ".join([f"<var>{arg}</var>" for arg in self.args])
|
||||
}</code></a>
|
||||
</dt>
|
||||
<dd>
|
||||
|
||||
{indent(self.documentation, " " * 3)}
|
||||
|
||||
{
|
||||
f"This function is only available if the [{self.experimental_feature}](@docroot@/contributing/experimental-features.md#xp-feature-{self.experimental_feature}) experimental feature is enabled."
|
||||
if self.experimental_feature is not None
|
||||
else ""
|
||||
}
|
||||
</dd>
|
||||
|
||||
""")
|
||||
|
||||
|
||||
def main():
|
||||
ap = get_argument_parser()
|
||||
ap.add_argument(
|
||||
"--experimental-features", help="Directory containing the experimental feature definitions"
|
||||
)
|
||||
import argparse
|
||||
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('--header', help='Path of the header to generate')
|
||||
ap.add_argument('--docs', help='Path of the documentation file to generate')
|
||||
ap.add_argument('--experimental-features', help='Directory containing the experimental feature definitions')
|
||||
ap.add_argument('defs', help='Builtin definition files', nargs='+')
|
||||
args = ap.parse_args()
|
||||
|
||||
builtins = load_data(args.defs, Builtin)
|
||||
builtins = load_data(args.defs, Builtin.parse)
|
||||
|
||||
experimental_features = get_experimental_features(
|
||||
args.experimental_features, [b.experimental_feature for (_, b) in builtins]
|
||||
)
|
||||
experimental_feature_names = set([builtin.experimental_feature for (_, builtin) in builtins])
|
||||
experimental_feature_names.discard(None)
|
||||
experimental_feature_files = [f'{args.experimental_features}/{name}.md' for name in experimental_feature_names]
|
||||
experimental_features = load_data(experimental_feature_files, ExperimentalFeature.parse)
|
||||
experimental_features = dict(map(lambda path_and_feature:
|
||||
(path_and_feature[1].name, f'Xp::{path_and_feature[1].internal_name}'), experimental_features))
|
||||
experimental_features[None] = 'std::nullopt'
|
||||
|
||||
generate_file(
|
||||
args.header,
|
||||
builtins,
|
||||
lambda builtin: builtin.name,
|
||||
lambda b: b.generate_code(experimental_features),
|
||||
)
|
||||
generate_file(args.docs, builtins, lambda builtin: builtin.name, lambda b: b.docs)
|
||||
generate_file(args.header, builtins, lambda builtin: builtin.name, lambda builtin:
|
||||
f'''{'' if builtin.experimental_feature is None else f'if (experimentalFeatureSettings.isEnabled({experimental_features[builtin.experimental_feature]})) '}{{
|
||||
addPrimOp({{
|
||||
.name = {cxx_literal(('__' if builtin.rename_in_global_scope else '') + builtin.name)},
|
||||
.args = {cxx_literal(builtin.args)},
|
||||
.arity = {len(builtin.args)},
|
||||
.doc = {cxx_literal(builtin.documentation)},
|
||||
.fun = {builtin.implementation},
|
||||
.experimentalFeature = {experimental_features[builtin.experimental_feature]},
|
||||
}});
|
||||
}}
|
||||
''')
|
||||
generate_file(args.docs, builtins, lambda builtin: builtin.name, lambda builtin:
|
||||
f'''<dt id="builtins-{builtin.name}">
|
||||
<a href="#builtins-{builtin.name}"><code>{builtin.name} {' '.join([f'<var>{arg}</var>' for arg in builtin.args])}</code></a>
|
||||
</dt>
|
||||
<dd>
|
||||
|
||||
{builtin.documentation}
|
||||
|
||||
if __name__ == "__main__":
|
||||
''' + (f'''This function is only available if the [{builtin.experimental_feature}](@docroot@/contributing/experimental-features.md#xp-feature-{builtin.experimental_feature}) experimental feature is enabled.
|
||||
|
||||
''' if builtin.experimental_feature is not None else '') + '''</dd>
|
||||
|
||||
''')
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
from typing import NamedTuple
|
||||
|
||||
from common import cxx_literal, generate_file, load_data
|
||||
|
||||
KNOWN_KEYS = set([
|
||||
'name',
|
||||
'internalName',
|
||||
])
|
||||
|
||||
class ExperimentalFeature(NamedTuple):
|
||||
name: str
|
||||
internal_name: str
|
||||
description: str
|
||||
|
||||
def parse(datum):
|
||||
unknown_keys = set(datum.keys()) - KNOWN_KEYS
|
||||
if unknown_keys:
|
||||
raise ValueError('unknown keys', unknown_keys)
|
||||
return ExperimentalFeature(
|
||||
name = datum['name'],
|
||||
internal_name = datum['internalName'],
|
||||
description = datum.content,
|
||||
)
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('--deprecated', action='store_true', help='Generate deprecated features')
|
||||
ap.add_argument('--header', help='Path of the declaration header to generate')
|
||||
ap.add_argument('--impl-header', help='Path of the implementation header to generate')
|
||||
ap.add_argument('--descriptions', help='Path of the description file to generate')
|
||||
ap.add_argument('--shortlist', help='Path of the shortlist file to generate')
|
||||
ap.add_argument('defs', help='Experimental feature definition files', nargs='+')
|
||||
args = ap.parse_args()
|
||||
|
||||
features = load_data(args.defs, ExperimentalFeature.parse)
|
||||
|
||||
generate_file(args.header, features, lambda feature: feature.name, lambda feature:
|
||||
f' {feature.internal_name},\n')
|
||||
generate_file(args.impl_header, features, lambda feature: feature.name, lambda feature:
|
||||
f''' {{
|
||||
.tag = {"Dep" if args.deprecated else "Xp"}::{feature.internal_name},
|
||||
.name = {cxx_literal(feature.name)},
|
||||
.description = {cxx_literal(feature.description)},
|
||||
}},
|
||||
''')
|
||||
generate_file(args.descriptions, features, lambda feature: feature.name, lambda feature:
|
||||
f'''## [`{feature.name}`]{{#{"dp" if args.deprecated else "xp"}-feature-{feature.name}}}
|
||||
|
||||
{feature.description}
|
||||
|
||||
''')
|
||||
generate_file(args.shortlist, features, lambda feature: feature.name, lambda feature:
|
||||
f' - [`{feature.name}`](@docroot@/contributing/{"deprecated" if args.deprecated else "experimental"}-features.md#{"dp" if args.deprecated else "xp"}-feature-{feature.name})\n')
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,105 +0,0 @@
|
||||
import dataclasses
|
||||
from enum import Enum
|
||||
from textwrap import dedent
|
||||
from typing import ClassVar, NamedTuple
|
||||
|
||||
from common import cxx_literal, generate_file, load_data, get_argument_parser
|
||||
|
||||
|
||||
class FeatureTypeNames(NamedTuple):
|
||||
code_tag: str
|
||||
doc_tag: str
|
||||
|
||||
|
||||
class TimelineEvent(NamedTuple):
|
||||
date: str
|
||||
release: str
|
||||
message: str
|
||||
cls: list[int]
|
||||
|
||||
|
||||
class FeatureType(FeatureTypeNames, Enum):
|
||||
experimental = FeatureTypeNames("Xp", "xp")
|
||||
deprecated = FeatureTypeNames("Dep", "dp")
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ExtraFeature:
|
||||
name: str
|
||||
internal_name: str
|
||||
documentation: str
|
||||
timeline: list[TimelineEvent] = dataclasses.field(default_factory=list)
|
||||
|
||||
type: ClassVar[FeatureType]
|
||||
|
||||
@property
|
||||
def code(self) -> str:
|
||||
return dedent(f"""
|
||||
{{
|
||||
.tag = {ExtraFeature.type.code_tag}::{self.internal_name},
|
||||
.name = {cxx_literal(self.name)},
|
||||
.description = {cxx_literal(self.documentation)},
|
||||
}},
|
||||
""")
|
||||
|
||||
@property
|
||||
def docs(self) -> str:
|
||||
timeline = (
|
||||
f"""
|
||||
|
||||
### Timeline
|
||||
|
||||
{
|
||||
"\n ".join(
|
||||
[
|
||||
f"- {event.date}, {event.release}: {event.message} [{", ".join([f'[CL {cl}](https://git.lix.systems/c/lix/+/{cl})' for cl in event.cls])}]"
|
||||
for event in self.timeline
|
||||
]
|
||||
)
|
||||
}
|
||||
"""
|
||||
if self.timeline
|
||||
else ""
|
||||
)
|
||||
return dedent(f"""
|
||||
## [`{self.name}`]{{#{ExtraFeature.type.doc_tag}-feature-{self.name}}}
|
||||
|
||||
{self.documentation.replace("\n", f"\n{' ' * 3}")}
|
||||
|
||||
{timeline}
|
||||
|
||||
""")
|
||||
|
||||
@property
|
||||
def short_docs(self) -> str:
|
||||
return f" - [`{self.name}`](@docroot@/contributing/{ExtraFeature.type.name}-features.md#{ExtraFeature.type.doc_tag}-feature-{self.name})\n"
|
||||
|
||||
|
||||
def main():
|
||||
ap = get_argument_parser()
|
||||
ap.add_argument("--deprecated", action="store_true", help="Generate deprecated features")
|
||||
ap.add_argument("--impl-header", help="Path of the implementation header to generate")
|
||||
ap.add_argument("--shortlist", help="Path of the shortlist file to generate")
|
||||
args = ap.parse_args()
|
||||
|
||||
ExtraFeature.type = FeatureType.deprecated if args.deprecated else FeatureType.experimental
|
||||
|
||||
def load(**kwargs) -> ExtraFeature:
|
||||
kwargs["timeline"] = [TimelineEvent(**args) for args in kwargs.get("timeline", [])]
|
||||
return ExtraFeature(**kwargs)
|
||||
|
||||
features = load_data(args.defs, load)
|
||||
|
||||
generate_file(
|
||||
args.header,
|
||||
features,
|
||||
lambda feature: feature.name,
|
||||
lambda feature: f" {feature.internal_name},\n",
|
||||
)
|
||||
generate_file(args.impl_header, features, lambda feature: feature.name, lambda f: f.code)
|
||||
generate_file(args.docs, features, lambda feature: feature.name, lambda f: f.docs)
|
||||
generate_file(args.shortlist, features, lambda feature: feature.name, lambda f: f.short_docs)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,157 +1,141 @@
|
||||
import dataclasses
|
||||
from textwrap import dedent
|
||||
from typing import Any
|
||||
from typing import List, NamedTuple, Optional
|
||||
|
||||
from common import (
|
||||
cxx_literal,
|
||||
generate_file,
|
||||
load_data,
|
||||
get_experimental_features,
|
||||
get_argument_parser,
|
||||
)
|
||||
from build_experimental_features import ExperimentalFeature
|
||||
from common import cxx_literal, generate_file, load_data
|
||||
|
||||
KNOWN_KEYS = set([
|
||||
'name',
|
||||
'internalName',
|
||||
'platforms',
|
||||
'type',
|
||||
'settingType',
|
||||
'default',
|
||||
'defaultExpr',
|
||||
'defaultText',
|
||||
'aliases',
|
||||
'experimentalFeature',
|
||||
'deprecated',
|
||||
])
|
||||
|
||||
PLATFORM_WARNING = """
|
||||
> **Note**
|
||||
> This setting is only available on {platforms} systems.
|
||||
class Setting(NamedTuple):
|
||||
name: str
|
||||
internal_name: str
|
||||
description: str
|
||||
platforms: Optional[List[str]]
|
||||
setting_type: str
|
||||
default_expr: str
|
||||
default_text: str
|
||||
aliases: List[str]
|
||||
experimental_feature: Optional[str]
|
||||
deprecated: bool
|
||||
|
||||
"""
|
||||
def parse(datum):
|
||||
unknown_keys = set(datum.keys()) - KNOWN_KEYS
|
||||
if unknown_keys:
|
||||
raise ValueError('unknown keys', unknown_keys)
|
||||
default_text = f'`{nix_conf_literal(datum["default"])}`' if 'default' in datum else datum['defaultText']
|
||||
if default_text == '``':
|
||||
default_text = '*empty*'
|
||||
return Setting(
|
||||
name = datum['name'],
|
||||
internal_name = datum['internalName'],
|
||||
description = datum.content,
|
||||
platforms = datum.get('platforms', None),
|
||||
setting_type = f'Setting<{datum["type"]}>' if 'type' in datum else datum['settingType'],
|
||||
default_expr = cxx_literal(datum['default']) if 'default' in datum else datum['defaultExpr'],
|
||||
default_text = default_text,
|
||||
aliases = datum.get('aliases', []),
|
||||
experimental_feature = datum.get('experimentalFeature', None),
|
||||
deprecated = datum.get('deprecated', False),
|
||||
)
|
||||
|
||||
XP_WARNING = """
|
||||
> **Warning**
|
||||
platform_names = {
|
||||
'darwin': 'Darwin',
|
||||
'linux': 'Linux',
|
||||
}
|
||||
|
||||
def nix_conf_literal(v):
|
||||
if v is None:
|
||||
return ''
|
||||
elif isinstance(v, bool) and v == False: # 0 == False
|
||||
return 'false'
|
||||
elif isinstance(v, bool) and v == True: # 1 == True
|
||||
return 'true'
|
||||
elif isinstance(v, int):
|
||||
return str(v)
|
||||
elif isinstance(v, str):
|
||||
return v
|
||||
elif isinstance(v, list):
|
||||
return ' '.join([nix_conf_literal(item) for item in v])
|
||||
else:
|
||||
raise NotImplementedError(f'Cannot represent {repr(v)} in nix.conf')
|
||||
|
||||
def indent(prefix, body):
|
||||
return ''.join(['\n' if line == '' else f'{prefix}{line}\n' for line in body.split('\n')])
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('--kernel', help='Name of the kernel Lix will run on')
|
||||
ap.add_argument('--header', help='Path of the header to generate')
|
||||
ap.add_argument('--docs', help='Path of the documentation file to generate')
|
||||
ap.add_argument('--experimental-features', help='Directory containing the experimental feature definitions')
|
||||
ap.add_argument('defs', help='Setting definition files', nargs='+')
|
||||
args = ap.parse_args()
|
||||
|
||||
settings = load_data(args.defs, Setting.parse)
|
||||
|
||||
experimental_feature_names = set([setting.experimental_feature for (_, setting) in settings])
|
||||
experimental_feature_names.discard(None)
|
||||
experimental_feature_files = [f'{args.experimental_features}/{name}.md' for name in experimental_feature_names]
|
||||
experimental_features = load_data(experimental_feature_files, ExperimentalFeature.parse)
|
||||
experimental_features = dict(map(lambda path_and_feature:
|
||||
(path_and_feature[1].name, f'Xp::{path_and_feature[1].internal_name}'), experimental_features))
|
||||
experimental_features[None] = 'std::nullopt'
|
||||
|
||||
generate_file(args.header, settings, lambda setting: setting.name, lambda setting:
|
||||
f'''{setting.setting_type} {setting.internal_name} {{
|
||||
this,
|
||||
{setting.default_expr},
|
||||
{cxx_literal(setting.name)},
|
||||
{cxx_literal(setting.description)},
|
||||
{cxx_literal(setting.aliases)},
|
||||
true,
|
||||
{experimental_features[setting.experimental_feature]},
|
||||
{cxx_literal(setting.deprecated)}
|
||||
}};
|
||||
|
||||
''' if setting.platforms is None or args.kernel in setting.platforms else '')
|
||||
generate_file(args.docs, settings, lambda setting: setting.name, lambda setting:
|
||||
f'''- <span id="conf-{setting.name}">[`{setting.name}`](#conf-{setting.name})</span>
|
||||
|
||||
{indent(" ", setting.description)}
|
||||
''' + (f''' > **Note**
|
||||
> This setting is only available on {', '.join([platform_names[platform] for platform in setting.platforms])} systems.
|
||||
|
||||
''' if setting.platforms is not None else '') + (f''' > **Warning**
|
||||
> This setting is part of an
|
||||
> [experimental feature](@docroot@/contributing/experimental-features.md).
|
||||
|
||||
To change this setting, you need to make sure the corresponding experimental feature,
|
||||
[`{feature}`](@docroot@/contributing/experimental-features.md#xp-feature-{feature}),
|
||||
[`{setting.experimental_feature}`](@docroot@/contributing/experimental-features.md#xp-feature-{setting.experimental_feature}),
|
||||
is enabled.
|
||||
For example, include the following in [`nix.conf`](#):
|
||||
|
||||
```
|
||||
extra-experimental-features = {feature}
|
||||
{name} = ...
|
||||
extra-experimental-features = {setting.experimental_feature}
|
||||
{setting.name} = ...
|
||||
```
|
||||
|
||||
"""
|
||||
|
||||
DEPR_WARNING = """
|
||||
> **Warning**
|
||||
''' if setting.experimental_feature is not None else '') + (''' > **Warning**
|
||||
> This setting is deprecated and will be removed in a future version of Lix.
|
||||
|
||||
"""
|
||||
''' if setting.deprecated else '') + f''' **Default:** {setting.default_text}
|
||||
|
||||
''' + (f''' **Deprecated alias:** {', '.join([f'`{item}`' for item in setting.aliases])}
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Setting:
|
||||
name: str
|
||||
internal_name: str
|
||||
documentation: str
|
||||
''' if setting.aliases != [] else ''))
|
||||
|
||||
default_text: str = ""
|
||||
setting_type: str = ""
|
||||
default_expr: str = ""
|
||||
platforms: list[str] = dataclasses.field(default_factory=list)
|
||||
aliases: list[str] = dataclasses.field(default_factory=list)
|
||||
experimental_feature: str | None = None
|
||||
deprecated: bool = False
|
||||
|
||||
default: dataclasses.InitVar[str | None] = None
|
||||
type_str: dataclasses.InitVar[str | None] = None
|
||||
|
||||
def __post_init__(self, default: Any, type_str: str | None):
|
||||
if default is not None: # is not None nor an empty String
|
||||
self.default_text = f"`{nix_conf_literal(default)}`"
|
||||
self.default_expr = self.default_expr or cxx_literal(default)
|
||||
self.default_text = self.default_text or "*empty*"
|
||||
|
||||
if type_str is not None:
|
||||
self.setting_type = f"Setting<{type_str}>"
|
||||
|
||||
def generate_code(self, experimental_features: dict[str | None, str]) -> str:
|
||||
indentation = " " * 4
|
||||
expr = (indent(indentation, self.default_expr) + indentation) if "\n" in self.default_expr else self.default_expr
|
||||
return dedent(f"""
|
||||
{self.setting_type} {self.internal_name} {{
|
||||
this,
|
||||
{expr},
|
||||
{cxx_literal(self.name)},
|
||||
{cxx_literal(self.documentation)},
|
||||
{cxx_literal(self.aliases)},
|
||||
true,
|
||||
{experimental_features[self.experimental_feature]},
|
||||
{cxx_literal(self.deprecated)}
|
||||
}};
|
||||
""")
|
||||
|
||||
@property
|
||||
def docs(self) -> str:
|
||||
indentation = " " * 3
|
||||
platforms = [p.capitalize() for p in self.platforms]
|
||||
aliases = [f"`{item}`" for item in self.aliases]
|
||||
description = dedent(f"""
|
||||
|
||||
{indent(indentation, self.documentation)}
|
||||
|
||||
{indent(indentation, PLATFORM_WARNING.format(platforms=str(platforms)[1:-1])) if self.platforms else ""}
|
||||
{indent(indentation, XP_WARNING.format(feature=self.experimental_feature, name=self.name)) if self.experimental_feature is not None else ""}
|
||||
{indent(indentation, DEPR_WARNING) if self.deprecated else ""}
|
||||
**Default:** {self.default_text}
|
||||
{f"**Deprecated alias:** {str(aliases)[1:-1]}\n" if self.aliases else ""}
|
||||
""")
|
||||
return f'- <span id="conf-{self.name}">[`{self.name}`](#conf-{self.name})</span>' + indent(
|
||||
" ", # indent by two space to make it part of the list point
|
||||
description,
|
||||
)
|
||||
|
||||
|
||||
platform_names = {"darwin": "Darwin", "linux": "Linux"}
|
||||
|
||||
|
||||
def nix_conf_literal(v: Any) -> str:
|
||||
if v is None:
|
||||
return ""
|
||||
if v is False:
|
||||
return "false"
|
||||
if v is True:
|
||||
return "true"
|
||||
if isinstance(v, int):
|
||||
return str(v)
|
||||
if isinstance(v, str):
|
||||
return v
|
||||
if isinstance(v, list):
|
||||
return " ".join([nix_conf_literal(item) for item in v])
|
||||
msg = f"Cannot represent {v!r} in nix.conf"
|
||||
raise NotImplementedError(msg)
|
||||
|
||||
|
||||
def indent(prefix: str, body: str) -> str:
|
||||
return "".join(["\n" if not line else f"{prefix}{line}\n" for line in body.split("\n")])
|
||||
|
||||
|
||||
def main():
|
||||
ap = get_argument_parser()
|
||||
ap.add_argument("--kernel", help="Name of the kernel Lix will run on")
|
||||
ap.add_argument(
|
||||
"--experimental-features", help="Directory containing the experimental feature definitions"
|
||||
)
|
||||
args = ap.parse_args()
|
||||
|
||||
settings = load_data(args.defs, Setting)
|
||||
|
||||
experimental_features = get_experimental_features(
|
||||
args.experimental_features, [s.experimental_feature for (_, s) in settings]
|
||||
)
|
||||
|
||||
generate_file(
|
||||
args.header,
|
||||
settings,
|
||||
lambda setting: setting.name,
|
||||
lambda setting: setting.generate_code(experimental_features)
|
||||
if not setting.platforms or args.kernel in setting.platforms
|
||||
else "",
|
||||
)
|
||||
generate_file(args.docs, settings, lambda setting: setting.name, lambda setting: setting.docs)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
#!@python@
|
||||
# ruff: noqa: SIM112 # ignore lowercase env variable names for capnpc as we have them in lower case as arguments
|
||||
|
||||
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 = Path.cwd()
|
||||
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_file in request.requestedFiles:
|
||||
deps += " ".join(f"{input_file.filename}.{o}" for o in outputs)
|
||||
deps += ":"
|
||||
for dep in input_file.imports:
|
||||
if dep.name.startswith("/"):
|
||||
for candidate in (Path(i + dep.name) for i in include):
|
||||
if candidate.exists():
|
||||
deps += " " + str(candidate)
|
||||
break
|
||||
else:
|
||||
msg = "not handling relative includes"
|
||||
raise RuntimeError(msg)
|
||||
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"] = str(Path.cwd())
|
||||
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()
|
||||
@@ -1,113 +1,60 @@
|
||||
import argparse
|
||||
import re
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import frontmatter
|
||||
import pathlib
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
def cxx_escape_character(c: str) -> str:
|
||||
if 0x20 <= ord(c) < 0x7F and c != '"' and c != "?" and c != "\\":
|
||||
def cxx_escape_character(c):
|
||||
if ord(c) >= 0x20 and ord(c) < 0x7f and c != '"' and c != '?' and c != '\\':
|
||||
return c
|
||||
if c == "\t":
|
||||
return r"\t"
|
||||
if c == "\n":
|
||||
return r"\n"
|
||||
if c == "\r":
|
||||
return r"\r"
|
||||
if c == '"':
|
||||
return r"\""
|
||||
if c == "?":
|
||||
return r"\?"
|
||||
if c == "\\":
|
||||
return r"\\"
|
||||
if ord(c) <= 0xFFFF:
|
||||
return str.format(r"\u{:04x}", ord(c))
|
||||
return str.format(r"\U{:08x}", ord(c))
|
||||
elif c == '\t':
|
||||
return r'\t'
|
||||
elif c == '\n':
|
||||
return r'\n'
|
||||
elif c == '\r':
|
||||
return r'\r'
|
||||
elif c == '"':
|
||||
return r'\"'
|
||||
elif c == '?':
|
||||
return r'\?'
|
||||
elif c == '\\':
|
||||
return r'\\'
|
||||
elif ord(c) <= 0xffff:
|
||||
return str.format(r'\u{:04x}', ord(c))
|
||||
else:
|
||||
return str.format(r'\U{:08x}', ord(c))
|
||||
|
||||
|
||||
def cxx_literal(v: Any) -> str:
|
||||
def cxx_literal(v):
|
||||
if v is None:
|
||||
return "std::nullopt"
|
||||
if v is False:
|
||||
return "false"
|
||||
if v is True:
|
||||
return "true"
|
||||
if isinstance(v, int):
|
||||
return 'std::nullopt'
|
||||
elif isinstance(v, bool) and v == False: # 0 == False
|
||||
return 'false'
|
||||
elif isinstance(v, bool) and v == True: # 1 == True
|
||||
return 'true'
|
||||
elif isinstance(v, int):
|
||||
return str(v)
|
||||
if isinstance(v, str):
|
||||
return "".join(['"', *(cxx_escape_character(c) for c in v), '"'])
|
||||
if isinstance(v, list):
|
||||
return f"{{{', '.join([cxx_literal(item) for item in v])}}}"
|
||||
msg = f"cannot represent {v!r} in C++"
|
||||
raise NotImplementedError(msg)
|
||||
elif isinstance(v, str):
|
||||
return ''.join(['"', *(cxx_escape_character(c) for c in v), '"'])
|
||||
elif isinstance(v, list):
|
||||
return f'{{{", ".join([cxx_literal(item) for item in v])}}}'
|
||||
else:
|
||||
raise NotImplementedError(f'cannot represent {repr(v)} in C++')
|
||||
|
||||
|
||||
def get_experimental_features(
|
||||
base_path: str, human_names: list[str | None]
|
||||
) -> dict[str | None, str]:
|
||||
experimental_feature_files = {
|
||||
f"{base_path}/{xp_name}.md" for xp_name in human_names if xp_name is not None
|
||||
}
|
||||
|
||||
from build_extra_features import ExtraFeature # noqa: PLC0415 # Avoid cyclic import
|
||||
|
||||
experimental_features_data = load_data(list(experimental_feature_files), ExtraFeature)
|
||||
experimental_features: dict[str | None, str] = {
|
||||
xf.name: f"Xp::{xf.internal_name}" for _, xf in experimental_features_data
|
||||
}
|
||||
experimental_features[None] = "std::nullopt"
|
||||
return experimental_features
|
||||
|
||||
|
||||
FIELD_RENAMES = {"type": "type_str", "content": "documentation"}
|
||||
|
||||
|
||||
def load_data[T](defs: list[str], parse_function: type[T]) -> list[tuple[str, T]]:
|
||||
def load_data(defs, parse_function):
|
||||
data = []
|
||||
for path in defs:
|
||||
try:
|
||||
datum = {
|
||||
# convert camelCase to snake_case
|
||||
re.sub(r"(?<=.)([A-Z])", lambda m: f"_{m.group(1).lower()}", k): v
|
||||
for k, v in frontmatter.load(path).to_dict().items()
|
||||
}
|
||||
|
||||
for post_name, field_name in FIELD_RENAMES.items():
|
||||
if post_name in datum:
|
||||
datum[field_name] = datum.pop(post_name)
|
||||
|
||||
data.append((path, parse_function(**datum)))
|
||||
datum = frontmatter.load(path)
|
||||
data.append((path, parse_function(datum)))
|
||||
except Exception as e:
|
||||
e.add_note(f"in {path}")
|
||||
e.add_note(f'in {path}')
|
||||
raise
|
||||
return data
|
||||
|
||||
|
||||
def generate_file[T](
|
||||
path: str | None,
|
||||
data: list[T],
|
||||
sort_key_function: Callable[[T], str],
|
||||
generate_function: Callable[[T], str],
|
||||
):
|
||||
def generate_file(path, data, sort_key_function, generate_function):
|
||||
if path is not None:
|
||||
with Path(path).open("w") as out:
|
||||
for path, datum in sorted(
|
||||
data, key=lambda path_and_datum: sort_key_function(path_and_datum[1])
|
||||
):
|
||||
with open(path, 'w') as out:
|
||||
for path, datum in sorted(data, key=lambda pathAndDatum: sort_key_function(pathAndDatum[1])):
|
||||
try:
|
||||
text = generate_function(datum)
|
||||
out.write(text)
|
||||
out.write(generate_function(datum))
|
||||
except Exception as e:
|
||||
e.add_note(f"in {path}")
|
||||
e.add_note(f'in {path}')
|
||||
raise
|
||||
|
||||
|
||||
def get_argument_parser() -> argparse.ArgumentParser:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--header", help="Path of the header to generate")
|
||||
ap.add_argument("--docs", help="Path of the documentation file to generate")
|
||||
ap.add_argument("defs", help="Builtin definition files", nargs="+")
|
||||
|
||||
return ap
|
||||
|
||||
+241
-476
@@ -1,22 +1,7 @@
|
||||
#include "lix/libstore/path.hh"
|
||||
#include "lix/libutil/async.hh"
|
||||
#include "lix/libutil/c-calls.hh"
|
||||
#include "lix/libutil/error.hh"
|
||||
#include "lix/libutil/file-descriptor.hh"
|
||||
#include "lix/libutil/logging-rpc.hh"
|
||||
#include "lix/libutil/logging.hh"
|
||||
#include "lix/libutil/rpc.hh"
|
||||
#include "lix/libutil/types-rpc.hh" // IWYU pragma: keep
|
||||
#include "lix/libutil/types.hh"
|
||||
#include <algorithm>
|
||||
#include <capnp/rpc-twoparty.h>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <kj/async.h>
|
||||
#include <kj/time.h>
|
||||
#include <chrono>
|
||||
#include <set>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#if __APPLE__
|
||||
#include <sys/time.h>
|
||||
@@ -32,29 +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;
|
||||
bool initialized = false, used = false;
|
||||
|
||||
kj::Promise<void> init(InitContext context) override;
|
||||
|
||||
kj::Promise<void> buildImpl(BuildContext context);
|
||||
kj::Promise<void> build(BuildContext context) override;
|
||||
};
|
||||
}
|
||||
|
||||
std::string escapeUri(std::string uri)
|
||||
{
|
||||
std::replace(uri.begin(), uri.end(), '/', '_');
|
||||
@@ -69,7 +38,7 @@ static std::string makeLockFilename(const std::string & storeUri) {
|
||||
// This avoids issues with the escaped URI being very long and causing
|
||||
// path too long errors, while also avoiding any possibility of collision
|
||||
// caused by simple truncation.
|
||||
auto hash = hashString(HashType::SHA256, storeUri).to_string(HashFormat::Base32, false);
|
||||
auto hash = hashString(HashType::SHA256, storeUri).to_string(Base::Base32, false);
|
||||
return escapeUri(storeUri).substr(0, 48) + "-" + hash.substr(0, 16);
|
||||
}
|
||||
|
||||
@@ -84,278 +53,11 @@ 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
|
||||
kj::Promise<Result<void>> startLogThread(std::string buildDescription, std::string drvPath)
|
||||
try {
|
||||
if (!logPipe.readSide) {
|
||||
co_return result::success();
|
||||
}
|
||||
|
||||
logPipe.writeSide.close();
|
||||
|
||||
// NOTE this is very similar to handleBuilderOutput in DerivationGoal, but unlike
|
||||
// the derivation goal we do not need to handle EIO from a pty here. we also have
|
||||
// no timeouts or limits to keep track of, which makes deduplication less useful.
|
||||
auto act = logger->startActivity(
|
||||
lvlInfo, actBuild, buildDescription, Logger::Fields{drvPath, storeUri, 1, 1}
|
||||
);
|
||||
|
||||
std::map<ActivityId, Activity> activities;
|
||||
|
||||
auto reader = AIO().lowLevelProvider.wrapInputFd(logPipe.readSide.get());
|
||||
|
||||
LogLineSplitter splitter;
|
||||
|
||||
auto flushLine = [&](const std::string & line) {
|
||||
if (const auto state =
|
||||
handleJSONLogMessage(line, act, activities, "the derivation builder"))
|
||||
{
|
||||
return *state;
|
||||
} else {
|
||||
return act.result(resBuildLogLine, line);
|
||||
}
|
||||
};
|
||||
|
||||
auto buf = kj::heapArray<char>(4096);
|
||||
while (true) {
|
||||
const auto got = co_await reader->tryRead(buf.begin(), 1, buf.size());
|
||||
if (got == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
std::string_view data{buf.begin(), got};
|
||||
while (!data.empty()) {
|
||||
if (auto line = splitter.feed(data)) {
|
||||
if (flushLine(*line) == Logger::BufferState::NeedsFlush) {
|
||||
TRY_AWAIT(act.getLogger().flush());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (auto line = splitter.finish(); !line.empty()) {
|
||||
(void) flushLine(line);
|
||||
TRY_AWAIT(act.getLogger().flush());
|
||||
}
|
||||
|
||||
co_return result::success();
|
||||
} catch (...) {
|
||||
co_return result::current_exception();
|
||||
}
|
||||
};
|
||||
|
||||
struct AcceptedBuild final : rpc::build_remote::HookInstance::AcceptedBuild::Server
|
||||
{
|
||||
ref<Store> store;
|
||||
StorePath drvPath;
|
||||
BuilderConnection builder;
|
||||
bool used = false;
|
||||
|
||||
AcceptedBuild(ref<Store> store, StorePath drvPath, BuilderConnection builder)
|
||||
: store(store)
|
||||
, drvPath(drvPath)
|
||||
, builder(std::move(builder))
|
||||
{
|
||||
}
|
||||
|
||||
kj::Promise<void> runImpl(RunContext context);
|
||||
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 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 */
|
||||
(void) sys::mkdir(currentLoad, 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.to_string(),
|
||||
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 {
|
||||
auto act = logger->startActivity(
|
||||
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)
|
||||
{
|
||||
{
|
||||
logger = makeJSONLogger(*logger);
|
||||
|
||||
/* Ensure we don't get any SSH passphrase or host key popups. */
|
||||
unsetenv("DISPLAY");
|
||||
unsetenv("SSH_ASKPASS");
|
||||
@@ -368,183 +70,234 @@ static int main_build_remote(AsyncIoRoot & aio, std::string programName, Strings
|
||||
|
||||
verbosity = (Verbosity) std::stoll(argv.front());
|
||||
|
||||
auto conn = aio.kj.lowLevelProvider->wrapUnixSocketFd(1);
|
||||
capnp::TwoPartyServer srv(kj::heap<Instance>());
|
||||
srv.accept(*conn, 1).wait(aio.kj.waitScope);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
kj::Promise<void> Instance::init(InitContext context)
|
||||
{
|
||||
try {
|
||||
if (initialized) {
|
||||
throw Error("build hook can only be initialized once");
|
||||
}
|
||||
|
||||
logger = rpc::log::makeRpcLoggerClient(context.getParams().getLogger());
|
||||
FdSource source(STDIN_FILENO);
|
||||
|
||||
/* Read the parent's settings. */
|
||||
for (const auto & [name, value] : rpc::to<StringMap>(context.getParams().getSettings())) {
|
||||
while (readInt(source)) {
|
||||
auto name = readString(source);
|
||||
auto value = readString(source);
|
||||
settings.set(name, value);
|
||||
}
|
||||
|
||||
maxBuildJobs = settings.maxBuildJobs;
|
||||
auto maxBuildJobs = settings.maxBuildJobs;
|
||||
settings.maxBuildJobs.set("1"); // hack to make tests with local?root= work
|
||||
|
||||
initPlugins();
|
||||
|
||||
initialized = true;
|
||||
auto store = aio.blockOn(openStore());
|
||||
|
||||
context.getResults().initResult().setGood();
|
||||
} catch (...) {
|
||||
RPC_FILL(context.getResults(), initResult, std::current_exception());
|
||||
}
|
||||
/* It would be more appropriate to use $XDG_RUNTIME_DIR, since
|
||||
that gets cleared on reboot, but it wouldn't work on macOS. */
|
||||
auto currentLoadName = "/current-load";
|
||||
if (auto localStore = store.try_cast_shared<LocalFSStore>())
|
||||
currentLoad = std::string { localStore->config().stateDir } + currentLoadName;
|
||||
else
|
||||
currentLoad = settings.nixStateDir + currentLoadName;
|
||||
|
||||
return kj::READY_NOW;
|
||||
}
|
||||
std::shared_ptr<Store> sshStore;
|
||||
AutoCloseFD bestSlotLock;
|
||||
|
||||
kj::Promise<void> Instance::buildImpl(BuildContext context)
|
||||
try {
|
||||
if (!initialized) {
|
||||
throw Error("build hook not fully initialized");
|
||||
}
|
||||
auto machines = getMachines();
|
||||
debug("got %d remote builders", machines.size());
|
||||
|
||||
// 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));
|
||||
|
||||
/* It would be more appropriate to use $XDG_RUNTIME_DIR, since
|
||||
that gets cleared on reboot, but it wouldn't work on macOS. */
|
||||
auto currentLoadName = "/current-load";
|
||||
if (auto localStore = store.try_cast_shared<LocalFSStore>()) {
|
||||
currentLoad = std::string{localStore->config().stateDir} + currentLoadName;
|
||||
} else {
|
||||
currentLoad = settings.nixStateDir + currentLoadName;
|
||||
}
|
||||
|
||||
auto machines = getMachines();
|
||||
debug("got %d remote builders", machines.size());
|
||||
|
||||
if (machines.empty()) {
|
||||
context.getResults().initResult().initGood().setDeclinePermanently();
|
||||
co_return;
|
||||
}
|
||||
|
||||
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 result = TRY_AWAIT(connectToBuilder(
|
||||
store, drvPath, machines, maxBuildJobs, amWilling, neededSystem, requiredFeatures
|
||||
));
|
||||
|
||||
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;
|
||||
if (machines.empty()) {
|
||||
std::cerr << "# decline-permanently\n";
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto builder = std::get_if<BuilderConnection>(&result);
|
||||
assert(builder);
|
||||
std::optional<StorePath> drvPath;
|
||||
std::string storeUri;
|
||||
|
||||
auto ac = context.getResults().initResult().initGood().initAccept();
|
||||
ac.setMachine(kj::heap<AcceptedBuild>(store, drvPath, std::move(*builder)));
|
||||
} catch (...) {
|
||||
RPC_FILL(context.getResults(), initResult, std::current_exception());
|
||||
}
|
||||
while (true) {
|
||||
|
||||
kj::Promise<void> Instance::build(BuildContext context)
|
||||
try {
|
||||
if (used) {
|
||||
throw Error("build hooks can only accept a single job");
|
||||
}
|
||||
used = true; // lock out other rpc calls during processing
|
||||
co_await buildImpl(context);
|
||||
TRY_AWAIT(logger->flush());
|
||||
used = context.getResults().getResult().getGood().isAccept();
|
||||
} catch (...) {
|
||||
RPC_FILL(context.getResults(), getResult, std::current_exception());
|
||||
}
|
||||
try {
|
||||
auto s = readString(source);
|
||||
if (s != "try") return 0;
|
||||
} catch (EndOfFile &) { return 0; }
|
||||
|
||||
kj::Promise<void> AcceptedBuild::run(RunContext context)
|
||||
{
|
||||
try {
|
||||
auto oldLogger = logger;
|
||||
logger = rpc::log::makeRpcLoggerClient(context.getParams().getLogger());
|
||||
TRY_AWAIT(oldLogger->flush());
|
||||
KJ_DEFER({
|
||||
delete logger;
|
||||
logger = oldLogger;
|
||||
});
|
||||
auto amWilling = readInt(source);
|
||||
auto neededSystem = readString(source);
|
||||
drvPath = store->parseStorePath(readString(source));
|
||||
auto requiredFeatures = readStrings<std::set<std::string>>(source);
|
||||
|
||||
if (used) {
|
||||
throw Error("build hooks builds are single-use items");
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
used = true;
|
||||
co_await runImpl(context);
|
||||
TRY_AWAIT(logger->flush());
|
||||
} catch (...) {
|
||||
RPC_FILL(context.getResults(), getResult, std::current_exception());
|
||||
}
|
||||
}
|
||||
|
||||
kj::Promise<void> AcceptedBuild::runImpl(RunContext context)
|
||||
{
|
||||
try {
|
||||
auto logHandler = builder.startLogThread(
|
||||
fmt("%s on '%s'",
|
||||
rpc::to<std::string_view>(context.getParams().getDescription()),
|
||||
builder.storeUri),
|
||||
store->printStorePath(drvPath)
|
||||
);
|
||||
connected:
|
||||
close(5);
|
||||
|
||||
auto & sshStore = builder.sshStore;
|
||||
auto & storeUri = builder.storeUri;
|
||||
assert(sshStore);
|
||||
|
||||
auto inputs = rpc::to<std::set<StorePath>>(context.getParams().getInputs(), *store);
|
||||
auto wantedOutputs = rpc::to<std::set<std::string>>(context.getParams().getWantedOutputs());
|
||||
std::cerr << "# accept\n" << storeUri << "\n";
|
||||
|
||||
auto inputs = readStrings<PathSet>(source);
|
||||
auto wantedOutputs = readStrings<StringSet>(source);
|
||||
|
||||
auto lockFileName = currentLoad + "/" + makeLockFilename(storeUri) + ".upload-lock";
|
||||
|
||||
AutoCloseFD uploadLock = openLockFile(lockFileName, true);
|
||||
|
||||
{
|
||||
auto act = logger->startActivity(
|
||||
lvlTalkative, actUnknown, fmt("waiting for the upload lock to '%s'", storeUri)
|
||||
);
|
||||
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) {
|
||||
printError("somebody is hogging the upload lock for '%s', continuing...", storeUri);
|
||||
}
|
||||
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;
|
||||
|
||||
{
|
||||
auto act = logger->startActivity(
|
||||
lvlTalkative, actUnknown, fmt("copying dependencies to '%s'", storeUri)
|
||||
);
|
||||
TRY_AWAIT(copyPaths(*store, *sshStore, inputs, NoRepair, NoCheckSigs, substitute));
|
||||
Activity act(*logger, lvlTalkative, actUnknown, fmt("copying dependencies to '%s'", storeUri));
|
||||
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;
|
||||
|
||||
@@ -552,7 +305,7 @@ kj::Promise<void> AcceptedBuild::runImpl(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;
|
||||
});
|
||||
|
||||
@@ -571,61 +324,73 @@ kj::Promise<void> AcceptedBuild::runImpl(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.map.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 = makeConstantStorePathRef(*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
|
||||
);
|
||||
}
|
||||
|
||||
auto outputHashes = aio.blockOn(staticOutputHashes(*store, drv));
|
||||
std::set<Realisation> missingRealisations;
|
||||
StorePathSet missingPaths;
|
||||
auto outputPaths = drv.outputsAndPaths(*store);
|
||||
for (auto & [outputName, outputPath] : outputPaths) {
|
||||
if (!TRY_AWAIT(store->isValidPath(outputPath.second))) {
|
||||
missingPaths.insert(outputPath.second);
|
||||
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations) && !drv.type().hasKnownOutputPaths()) {
|
||||
for (auto & outputName : wantedOutputs) {
|
||||
auto thisOutputHash = outputHashes.at(outputName);
|
||||
auto thisOutputId = DrvOutput{ thisOutputHash, outputName };
|
||||
if (!aio.blockOn(store->queryRealisation(thisOutputId))) {
|
||||
debug("missing output %s", outputName);
|
||||
assert(optResult);
|
||||
auto & result = *optResult;
|
||||
auto i = result.builtOutputs.find(outputName);
|
||||
assert(i != result.builtOutputs.end());
|
||||
auto & newRealisation = i->second;
|
||||
missingRealisations.insert(newRealisation);
|
||||
missingPaths.insert(newRealisation.outPath);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto outputPaths = drv.outputsAndOptPaths(*store);
|
||||
for (auto & [outputName, hopefullyOutputPath] : outputPaths) {
|
||||
assert(hopefullyOutputPath.second);
|
||||
if (!aio.blockOn(store->isValidPath(*hopefullyOutputPath.second)))
|
||||
missingPaths.insert(*hopefullyOutputPath.second);
|
||||
}
|
||||
}
|
||||
|
||||
if (!missingPaths.empty()) {
|
||||
auto act = logger->startActivity(
|
||||
lvlTalkative, actUnknown, fmt("copying outputs from '%s'", storeUri)
|
||||
);
|
||||
Activity act(*logger, lvlTalkative, actUnknown, fmt("copying outputs from '%s'", storeUri));
|
||||
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)
|
||||
);
|
||||
}
|
||||
// XXX: Should be done as part of `copyPaths`
|
||||
for (auto & realisation : missingRealisations) {
|
||||
// Should hold, because if the feature isn't enabled the set
|
||||
// of missing realisations should be empty
|
||||
experimentalFeatureSettings.require(Xp::CaDerivations);
|
||||
aio.blockOn(store->registerDrvOutput(realisation));
|
||||
}
|
||||
|
||||
// drop store connection, let log handler process any remaining input
|
||||
builder.sshStore = nullptr;
|
||||
TRY_AWAIT(logHandler);
|
||||
|
||||
context.getResults().initResult().setGood();
|
||||
} catch (...) {
|
||||
RPC_FILL(context.getResults(), initResult, std::current_exception());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
#include "lix/libcmd/legacy.hh"
|
||||
#include "lix/libstore/builtins.hh"
|
||||
#include "lix/libstore/builtins/buildenv.hh"
|
||||
#include "lix/libutil/async.hh"
|
||||
#include "lix/libutil/error.hh"
|
||||
#include "lix/libutil/file-system.hh"
|
||||
#include "lix/libutil/logging.hh"
|
||||
#include "lix/libutil/strings.hh"
|
||||
#include "lix/libutil/types.hh"
|
||||
#include <string_view>
|
||||
|
||||
using std::literals::operator""sv;
|
||||
|
||||
namespace nix {
|
||||
|
||||
static int main_builtin_builder(AsyncIoRoot & aio, std::string programName, Strings argv)
|
||||
{
|
||||
logger = makeJSONLogger(*logger);
|
||||
|
||||
std::map<std::string, std::string> env;
|
||||
|
||||
auto argvIt = argv.begin();
|
||||
const auto argvEnd = argv.end();
|
||||
|
||||
// we do not use the argument parsing functions we have in libmain here, neither
|
||||
// the legacy versions nor the newer ones. the legacy version could work, but we
|
||||
// want to provide two sets of arguments separated by `--` and would need rather
|
||||
// unpleasant state handling to use the legacy parser. the more modern parser is
|
||||
// entirely incapable of doing this for us since it's all statically configured.
|
||||
const auto getArg = [&](std::string_view desc) {
|
||||
if (argvIt == argvEnd) {
|
||||
throw Error("expected a value for %s", desc);
|
||||
}
|
||||
return *argvIt++;
|
||||
};
|
||||
|
||||
if (auto val = string2Int<int>(getArg("verbosity"))) {
|
||||
verbosity = verbosityFromIntClamped(*val);
|
||||
} else {
|
||||
throw Error("expected a verbosity argument");
|
||||
}
|
||||
|
||||
while (argvIt != argvEnd) {
|
||||
const auto arg = getArg("option");
|
||||
if (arg == "--") {
|
||||
break;
|
||||
} else if (!arg.starts_with("--")) {
|
||||
throw Error("unexpected builtin option %s", arg);
|
||||
}
|
||||
auto value = unescapeNul(getArg(arg));
|
||||
globalConfig.set(arg.substr(2), value);
|
||||
}
|
||||
|
||||
while (argvIt != argvEnd) {
|
||||
const auto key = getArg("builder argument");
|
||||
if (!key.starts_with("--")) {
|
||||
throw Error("unexpected builtin builder argument %s", key);
|
||||
}
|
||||
env[unescapeNul(key.substr(2))] = unescapeNul(getArg(key));
|
||||
}
|
||||
|
||||
auto getAttr = [&](const std::string & name) {
|
||||
auto i = env.find(name);
|
||||
if (i == env.end()) {
|
||||
throw Error("attribute '%s' missing", name);
|
||||
}
|
||||
return i->second;
|
||||
};
|
||||
|
||||
const auto builder = getAttr("builder");
|
||||
|
||||
if (builder == "builtin:fetchurl") {
|
||||
const auto outputHashMode = getAttr("outputHashMode");
|
||||
const auto hash = outputHashMode == "flat" ? [&] -> std::optional<Hash> {
|
||||
const auto ht = parseHashTypeOpt(getAttr("outputHashAlgo"));
|
||||
return newHashAllowEmpty(getAttr("outputHash"), ht);
|
||||
}()
|
||||
: std::nullopt;
|
||||
BuiltinFetchurl{
|
||||
.storePath = getAttr("out"),
|
||||
.mainUrl = getAttr("url"),
|
||||
.unpack = getOr(env, "unpack", "0") == "1",
|
||||
.executable = getOr(env, "executable", "0") == "1",
|
||||
.hash = hash,
|
||||
}
|
||||
.run(aio);
|
||||
} else if (builder == "builtin:buildenv") {
|
||||
builtinBuildenv(getAttr("out"), tokenizeString<Strings>(getAttr("derivations")), getAttr("manifest"));
|
||||
} else if (builder == "builtin:unpack-channel") {
|
||||
builtinUnpackChannel(getAttr("out"), getAttr("channelName"), getAttr("src"));
|
||||
} else {
|
||||
throw Error("unknown builtin builder %s", builder);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void registerLegacyBuiltinBuilder()
|
||||
{
|
||||
LegacyCommandRegistry::add("builtin-builder", main_builtin_builder);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
namespace nix {
|
||||
void registerLegacyBuiltinBuilder();
|
||||
}
|
||||
+10
-8
@@ -4,7 +4,9 @@
|
||||
#include "lix/libutil/result.hh"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
using std::cout;
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -40,31 +42,31 @@ static std::string makeNode(std::string_view id, std::string_view label,
|
||||
dotQuote(id), dotQuote(label), dotQuote(colour));
|
||||
}
|
||||
|
||||
kj::Promise<Result<std::string>> formatDotGraph(ref<Store> store, StorePathSet && roots)
|
||||
|
||||
kj::Promise<Result<void>> printDotGraph(ref<Store> store, StorePathSet && roots)
|
||||
try {
|
||||
StorePathSet workList(std::move(roots));
|
||||
StorePathSet doneSet;
|
||||
std::stringstream result;
|
||||
|
||||
result << "digraph G {\n";
|
||||
cout << "digraph G {\n";
|
||||
|
||||
while (!workList.empty()) {
|
||||
auto path = std::move(workList.extract(workList.begin()).value());
|
||||
|
||||
if (!doneSet.insert(path).second) continue;
|
||||
|
||||
result << makeNode(std::string(path.to_string()), path.name(), "#ff0000");
|
||||
cout << makeNode(std::string(path.to_string()), path.name(), "#ff0000");
|
||||
|
||||
for (auto & p : TRY_AWAIT(store->queryPathInfo(path))->references) {
|
||||
if (p != path) {
|
||||
workList.insert(p);
|
||||
result << makeEdge(std::string(p.to_string()), std::string(path.to_string()));
|
||||
cout << makeEdge(std::string(p.to_string()), std::string(path.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result << "}\n";
|
||||
co_return result.str();
|
||||
cout << "}\n";
|
||||
co_return result::success();
|
||||
} catch (...) {
|
||||
co_return result::current_exception();
|
||||
}
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
|
||||
namespace nix {
|
||||
|
||||
kj::Promise<Result<std::string>> formatDotGraph(ref<Store> store, StorePathSet && roots);
|
||||
kj::Promise<Result<void>> printDotGraph(ref<Store> store, StorePathSet && roots);
|
||||
|
||||
}
|
||||
|
||||
+18
-16
@@ -5,7 +5,9 @@
|
||||
#include "lix/libutil/result.hh"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
|
||||
using std::cout;
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -45,21 +47,21 @@ static std::string makeNode(const ValidPathInfo & info)
|
||||
(info.path.isDerivation() ? "derivation" : "output-path"));
|
||||
}
|
||||
|
||||
kj::Promise<Result<std::string>> formatGraphML(ref<Store> store, StorePathSet && roots)
|
||||
|
||||
kj::Promise<Result<void>> printGraphML(ref<Store> store, StorePathSet && roots)
|
||||
try {
|
||||
StorePathSet workList(std::move(roots));
|
||||
StorePathSet doneSet;
|
||||
std::pair<StorePathSet::iterator, bool> ret;
|
||||
std::stringstream result;
|
||||
|
||||
result << "<?xml version='1.0' encoding='utf-8'?>\n"
|
||||
<< "<graphml xmlns='http://graphml.graphdrawing.org/xmlns'\n"
|
||||
<< " xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'\n"
|
||||
<< " xsi:schemaLocation='http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd'>\n"
|
||||
<< "<key id='narSize' for='node' attr.name='narSize' attr.type='long'/>"
|
||||
<< "<key id='name' for='node' attr.name='name' attr.type='string'/>"
|
||||
<< "<key id='type' for='node' attr.name='type' attr.type='string'/>"
|
||||
<< "<graph id='G' edgedefault='directed'>\n";
|
||||
cout << "<?xml version='1.0' encoding='utf-8'?>\n"
|
||||
<< "<graphml xmlns='http://graphml.graphdrawing.org/xmlns'\n"
|
||||
<< " xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'\n"
|
||||
<< " xsi:schemaLocation='http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd'>\n"
|
||||
<< "<key id='narSize' for='node' attr.name='narSize' attr.type='long'/>"
|
||||
<< "<key id='name' for='node' attr.name='name' attr.type='string'/>"
|
||||
<< "<key id='type' for='node' attr.name='type' attr.type='string'/>"
|
||||
<< "<graph id='G' edgedefault='directed'>\n";
|
||||
|
||||
while (!workList.empty()) {
|
||||
auto path = std::move(workList.extract(workList.begin()).value());
|
||||
@@ -68,20 +70,20 @@ try {
|
||||
if (ret.second == false) continue;
|
||||
|
||||
auto info = TRY_AWAIT(store->queryPathInfo(path));
|
||||
result << makeNode(*info);
|
||||
cout << makeNode(*info);
|
||||
|
||||
for (auto & p : info->references) {
|
||||
if (p != path) {
|
||||
workList.insert(p);
|
||||
result << makeEdge(path.to_string(), p.to_string());
|
||||
cout << makeEdge(path.to_string(), p.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
result << "</graph>\n";
|
||||
result << "</graphml>\n";
|
||||
co_return result.str();
|
||||
cout << "</graph>\n";
|
||||
cout << "</graphml>\n";
|
||||
co_return result::success();
|
||||
} catch (...) {
|
||||
co_return result::current_exception();
|
||||
}
|
||||
|
||||
@@ -5,5 +5,6 @@
|
||||
|
||||
namespace nix {
|
||||
|
||||
kj::Promise<Result<std::string>> formatGraphML(ref<Store> store, StorePathSet && roots);
|
||||
kj::Promise<Result<void>> printGraphML(ref<Store> store, StorePathSet && roots);
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ legacy_sources = files(
|
||||
# `build-remote` is not really legacy (it powers all remote builds), but it's
|
||||
# not a `nix3` command.
|
||||
'build-remote.cc',
|
||||
'builtin-builder.cc',
|
||||
'dotgraph.cc',
|
||||
'graphml.cc',
|
||||
'nix-build.cc',
|
||||
@@ -20,7 +19,6 @@ legacy_sources = files(
|
||||
|
||||
legacy_headers = files(
|
||||
'build-remote.hh',
|
||||
'builtin-builder.hh',
|
||||
'nix-build.hh',
|
||||
'nix-channel.hh',
|
||||
'nix-collect-garbage.hh',
|
||||
|
||||
+91
-95
@@ -9,7 +9,6 @@
|
||||
#include "lix/libstore/store-api.hh"
|
||||
#include "lix/libstore/local-fs-store.hh"
|
||||
#include "lix/libstore/globals.hh"
|
||||
#include "lix/libutil/c-calls.hh"
|
||||
#include "lix/libutil/current-process.hh"
|
||||
#include "lix/libstore/derivations.hh"
|
||||
#include "lix/libmain/shared.hh"
|
||||
@@ -19,13 +18,10 @@
|
||||
#include "lix/libcmd/common-eval-args.hh"
|
||||
#include "lix/libexpr/attr-path.hh"
|
||||
#include "lix/libcmd/legacy.hh"
|
||||
#include "lix/libutil/finally.hh"
|
||||
#include "lix/libutil/processes.hh"
|
||||
#include "lix/libutil/regex.hh"
|
||||
#include "lix/libutil/shlex.hh"
|
||||
#include "nix-build.hh"
|
||||
#include "lix/libstore/temporary-dir.hh"
|
||||
#include "lix/libutil/strings.hh"
|
||||
|
||||
extern char * * environ __attribute__((weak)); // Man what even is this
|
||||
|
||||
@@ -33,7 +29,7 @@ namespace nix {
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings argv)
|
||||
static void main_nix_build(AsyncIoRoot & aio, std::string programName, Strings argv)
|
||||
{
|
||||
auto dryRun = false;
|
||||
auto runEnv = std::regex_search(programName, regex::parse("nix-shell$"));
|
||||
@@ -192,11 +188,7 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
throw UsageError("'-p' and '-E' are mutually exclusive");
|
||||
|
||||
AutoDelete tmpDir(createTempDir(myName));
|
||||
// NOTE: we assume there's no `build-top` directory created inside of `tmpDir` and we have
|
||||
// ownership of this.
|
||||
auto buildTopTmpDir = tmpDir + "/build-top";
|
||||
createDirs(buildTopTmpDir);
|
||||
|
||||
AutoDelete buildTopTmpDir(createTempSubdir(tmpDir, "build-top"));
|
||||
if (outLink.empty())
|
||||
outLink = (Path) tmpDir + "/result";
|
||||
|
||||
@@ -213,7 +205,7 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
auto autoArgsWithInNixShell = autoArgs;
|
||||
if (runEnv) {
|
||||
auto newArgs = evaluator->buildBindings(autoArgsWithInNixShell->size() + 1);
|
||||
newArgs.alloc("inNixShell") = {NewValueAs::boolean, true};
|
||||
newArgs.alloc("inNixShell").mkBool(true);
|
||||
for (auto & i : *autoArgs) newArgs.insert(i);
|
||||
autoArgsWithInNixShell = newArgs.finish();
|
||||
}
|
||||
@@ -233,9 +225,8 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
left = {"default.nix"};
|
||||
}
|
||||
|
||||
if (runEnv) {
|
||||
(void) sys::setenv("IN_NIX_SHELL", pure ? "pure" : "impure", 1);
|
||||
}
|
||||
if (runEnv)
|
||||
setenv("IN_NIX_SHELL", pure ? "pure" : "impure", 1);
|
||||
|
||||
DrvInfos drvs;
|
||||
|
||||
@@ -272,7 +263,8 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
if (attrPaths.empty()) attrPaths = {""};
|
||||
|
||||
for (auto e : exprs) {
|
||||
Value vRoot = state->eval(e);
|
||||
Value vRoot;
|
||||
state->eval(e, vRoot);
|
||||
|
||||
std::function<bool(const Value & v)> takesNixShellAttr;
|
||||
takesNixShellAttr = [&](const Value & v) {
|
||||
@@ -281,7 +273,7 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
}
|
||||
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;
|
||||
@@ -294,12 +286,12 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
};
|
||||
|
||||
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,
|
||||
@@ -355,7 +347,8 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
"(import <nixpkgs> {}).bashInteractive",
|
||||
CanonPath::fromCwd());
|
||||
|
||||
Value v = state->eval(expr);
|
||||
Value v;
|
||||
state->eval(expr, v);
|
||||
|
||||
auto drv = getDerivation(*state, v, false);
|
||||
if (!drv)
|
||||
@@ -363,7 +356,7 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
|
||||
auto bashDrv = drv->requireDrvPath(*state);
|
||||
pathsToBuild.push_back(DerivedPath::Built {
|
||||
.drvPath = makeConstantStorePath(bashDrv),
|
||||
.drvPath = makeConstantStorePathRef(bashDrv),
|
||||
.outputs = OutputsSpec::Names {"out"},
|
||||
});
|
||||
pathsToCopy.insert(bashDrv);
|
||||
@@ -376,16 +369,22 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
}
|
||||
}
|
||||
|
||||
auto accumDerivedPath = [&](SingleDerivedPath::Opaque inputDrv, const StringSet & inputNode) {
|
||||
if (!inputNode.empty())
|
||||
std::function<void(ref<SingleDerivedPath>, const DerivedPathMap<StringSet>::ChildNode &)> accumDerivedPath;
|
||||
|
||||
accumDerivedPath = [&](ref<SingleDerivedPath> inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
|
||||
if (!inputNode.value.empty())
|
||||
pathsToBuild.push_back(DerivedPath::Built {
|
||||
.drvPath = inputDrv,
|
||||
.outputs = OutputsSpec::Names { inputNode },
|
||||
.outputs = OutputsSpec::Names { inputNode.value },
|
||||
});
|
||||
for (const auto & [outputName, childNode] : inputNode.childMap)
|
||||
accumDerivedPath(
|
||||
make_ref<SingleDerivedPath>(SingleDerivedPath::Built { inputDrv, outputName }),
|
||||
childNode);
|
||||
};
|
||||
|
||||
// Build or fetch all dependencies of the derivation.
|
||||
for (const auto & [inputDrv0, inputNode] : drv.inputDrvs) {
|
||||
for (const auto & [inputDrv0, inputNode] : drv.inputDrvs.map) {
|
||||
// To get around lambda capturing restrictions in the
|
||||
// standard.
|
||||
const auto & inputDrv = inputDrv0;
|
||||
@@ -394,7 +393,7 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
return !std::regex_search(store->printStorePath(inputDrv), regex::parse(exclude));
|
||||
}))
|
||||
{
|
||||
accumDerivedPath(makeConstantStorePath(inputDrv), inputNode);
|
||||
accumDerivedPath(makeConstantStorePathRef(inputDrv), inputNode);
|
||||
pathsToCopy.insert(inputDrv);
|
||||
}
|
||||
}
|
||||
@@ -405,14 +404,18 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
|
||||
buildPaths(pathsToBuild);
|
||||
|
||||
if (dryRun) {
|
||||
return 0;
|
||||
}
|
||||
if (dryRun) return;
|
||||
|
||||
if (shellDrv) {
|
||||
auto shellDrvOutputs =
|
||||
aio.blockOn(store->queryDerivationOutputMap(shellDrv.value(), &*evalStore));
|
||||
shell = store->printStorePath(shellDrvOutputs.at("out")) + "/bin/bash";
|
||||
aio.blockOn(store->queryPartialDerivationOutputMap(shellDrv.value(), &*evalStore));
|
||||
shell = store->printStorePath(shellDrvOutputs.at("out").value()) + "/bin/bash";
|
||||
}
|
||||
|
||||
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
|
||||
auto resolvedDrv = aio.blockOn(drv.tryResolve(*store));
|
||||
assert(resolvedDrv && "Successfully resolved the derivation");
|
||||
drv = *resolvedDrv;
|
||||
}
|
||||
|
||||
// Set the environment.
|
||||
@@ -428,18 +431,6 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
env["__ETC_PROFILE_SOURCED"] = "1";
|
||||
}
|
||||
|
||||
// Set NIX_SHELL_LEVEL
|
||||
env["NIX_SHELL_LEVEL"] = std::to_string(
|
||||
getEnvNonEmpty("NIX_SHELL_LEVEL")
|
||||
.and_then([](std::string lvl) { return string2Int<size_t>(lvl); })
|
||||
.value_or(0)
|
||||
+ 1
|
||||
);
|
||||
|
||||
// We re-export similarly to what occurs inside of a derivation goal `NIX_LOG_FD` to stderr.
|
||||
// So that stdenv hooks that logs information can be observed inside this debugging tool.
|
||||
env["NIX_LOG_FD"] = "2";
|
||||
|
||||
// Don't use defaultTempDir() here! We want to preserve the user's TMPDIR for the shell
|
||||
env["NIX_BUILD_TOP"] = env["TMPDIR"] = env["TEMPDIR"] = env["TMP"] = env["TEMP"] =
|
||||
getEnvNonEmpty("TMPDIR").value_or(buildTopTmpDir);
|
||||
@@ -448,33 +439,38 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
|
||||
auto passAsFile = tokenizeString<StringSet>(getOr(drv.env, "passAsFile", ""));
|
||||
|
||||
bool keepTmp = false;
|
||||
int fileNr = 0;
|
||||
|
||||
for (auto & var : drv.env)
|
||||
if (passAsFile.count(var.first)) {
|
||||
keepTmp = true;
|
||||
auto fn = ".attr-" + std::to_string(fileNr++);
|
||||
Path p = (Path) tmpDir + "/" + fn;
|
||||
writeFile(p, var.second);
|
||||
env[var.first + "Path"] = p;
|
||||
} else {
|
||||
} else
|
||||
env[var.first] = var.second;
|
||||
}
|
||||
|
||||
std::string structuredAttrsRC;
|
||||
|
||||
if (env.count("__json")) {
|
||||
StorePathSet inputs;
|
||||
|
||||
auto accumInputClosure = [&](const StorePath & inputDrv, const StringSet & inputNode) {
|
||||
std::function<void(const StorePath &, const DerivedPathMap<StringSet>::ChildNode &)> accumInputClosure;
|
||||
|
||||
accumInputClosure = [&](const StorePath & inputDrv, const DerivedPathMap<StringSet>::ChildNode & inputNode) {
|
||||
auto outputs =
|
||||
aio.blockOn(store->queryDerivationOutputMap(inputDrv, &*evalStore));
|
||||
for (auto & i : inputNode) {
|
||||
aio.blockOn(store->queryPartialDerivationOutputMap(inputDrv, &*evalStore));
|
||||
for (auto & i : inputNode.value) {
|
||||
auto o = outputs.at(i);
|
||||
aio.blockOn(store->computeFSClosure(o, inputs));
|
||||
aio.blockOn(store->computeFSClosure(*o, inputs));
|
||||
}
|
||||
for (const auto & [outputName, childNode] : inputNode.childMap)
|
||||
accumInputClosure(*outputs.at(outputName), childNode);
|
||||
};
|
||||
|
||||
for (const auto & [inputDrv, inputNode] : drv.inputDrvs)
|
||||
for (const auto & [inputDrv, inputNode] : drv.inputDrvs.map)
|
||||
accumInputClosure(inputDrv, inputNode);
|
||||
|
||||
ParsedDerivation parsedDrv(drvInfo.requireDrvPath(*state), drv);
|
||||
@@ -491,6 +487,7 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
|
||||
env["NIX_ATTRS_SH_FILE"] = attrsSH;
|
||||
env["NIX_ATTRS_JSON_FILE"] = attrsJSON;
|
||||
keepTmp = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,13 +497,24 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
lose the current $PATH directories. */
|
||||
auto rcfile = (Path) tmpDir + "/rc";
|
||||
auto tz = getEnv("TZ");
|
||||
std::string rc =
|
||||
fmt("%1%"
|
||||
// always clear PATH.
|
||||
// when nix-shell is run impure, we rehydrate it with the `p=$PATH` above
|
||||
"unset PATH;"
|
||||
"dontAddDisableDepTrack=1;\n",
|
||||
(pure ? "" : "[ -n \"$PS1\" ] && [ -e ~/.bashrc ] && source ~/.bashrc; p=$PATH; "));
|
||||
std::string rc = fmt(
|
||||
R"(_nix_shell_clean_tmpdir() { command rm -rf %1%; }; )"
|
||||
"%2%"
|
||||
"%3%"
|
||||
// always clear PATH.
|
||||
// when nix-shell is run impure, we rehydrate it with the `p=$PATH` above
|
||||
"unset PATH;"
|
||||
"dontAddDisableDepTrack=1;\n",
|
||||
shellEscape(tmpDir),
|
||||
(keepTmp
|
||||
? "trap _nix_shell_clean_tmpdir EXIT; "
|
||||
"exitHooks+=(_nix_shell_clean_tmpdir); "
|
||||
"failureHooks+=(_nix_shell_clean_tmpdir); "
|
||||
: "_nix_shell_clean_tmpdir; "),
|
||||
(pure
|
||||
? ""
|
||||
: "[ -n \"$PS1\" ] && [ -e ~/.bashrc ] && source ~/.bashrc; p=$PATH; ")
|
||||
);
|
||||
rc += structuredAttrsRC;
|
||||
rc += fmt(
|
||||
"\n[ -e $stdenv/setup ] && source $stdenv/setup; "
|
||||
@@ -536,37 +544,27 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
vomit("Sourcing nix-shell with file %s and contents:\n%s", rcfile, rc);
|
||||
writeFile(rcfile, rc);
|
||||
|
||||
auto args = interactive ? Strings{"--rcfile", rcfile} : Strings{rcfile};
|
||||
Strings envStrs;
|
||||
for (auto & i : env)
|
||||
envStrs.push_back(i.first + "=" + i.second);
|
||||
|
||||
auto args = interactive
|
||||
? Strings{"bash", "--rcfile", rcfile}
|
||||
: Strings{"bash", rcfile};
|
||||
|
||||
auto envPtrs = stringsToCharPtrs(envStrs);
|
||||
|
||||
environ = envPtrs.data();
|
||||
|
||||
auto argPtrs = stringsToCharPtrs(args);
|
||||
|
||||
restoreProcessContext();
|
||||
|
||||
// We are going to run an interactive command, do not let the logger send a line.
|
||||
logger->pause();
|
||||
|
||||
printMsg(lvlChatty, "running shell: %s", concatMapStringsSep(" ", args, shellEscape));
|
||||
execvp(shell->c_str(), argPtrs.data());
|
||||
|
||||
RunningProgram proc = runProgram2({
|
||||
.program = *shell,
|
||||
.searchPath = true,
|
||||
.args = args,
|
||||
.environment = env,
|
||||
});
|
||||
|
||||
// NOTE: we wait and return the status check immediately.
|
||||
// If there's interruption, we will swallow it and wait again for termination.
|
||||
auto toExitStatus = [](int waitRes) {
|
||||
if (WIFEXITED(waitRes)) {
|
||||
return WEXITSTATUS(waitRes);
|
||||
} else if (WIFSIGNALED(waitRes)) {
|
||||
return 128 + WTERMSIG(waitRes);
|
||||
} else {
|
||||
return 255;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
return toExitStatus(proc.wait());
|
||||
} catch (Interrupted &) {
|
||||
return toExitStatus(proc.wait());
|
||||
}
|
||||
throw SysError("executing shell '%s'", *shell);
|
||||
}
|
||||
|
||||
else {
|
||||
@@ -585,7 +583,7 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
throw Error("derivation '%s' lacks an 'outputName' attribute", store->printStorePath(drvPath));
|
||||
|
||||
pathsToBuild.push_back(DerivedPath::Built{
|
||||
.drvPath = makeConstantStorePath(drvPath),
|
||||
.drvPath = makeConstantStorePathRef(drvPath),
|
||||
.outputs = OutputsSpec::Names{outputName},
|
||||
});
|
||||
pathsToBuildOrdered.push_back({drvPath, {outputName}});
|
||||
@@ -600,9 +598,7 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
|
||||
buildPaths(pathsToBuild);
|
||||
|
||||
if (dryRun) {
|
||||
return 0;
|
||||
}
|
||||
if (dryRun) return;
|
||||
|
||||
std::vector<StorePath> outPaths;
|
||||
|
||||
@@ -613,9 +609,11 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
drvPrefix += fmt("-%d", counter + 1);
|
||||
|
||||
auto builtOutputs =
|
||||
aio.blockOn(store->queryDerivationOutputMap(drvPath, &*evalStore));
|
||||
aio.blockOn(store->queryPartialDerivationOutputMap(drvPath, &*evalStore));
|
||||
|
||||
auto outputPath = builtOutputs.at(outputName);
|
||||
auto maybeOutputPath = builtOutputs.at(outputName);
|
||||
assert(maybeOutputPath);
|
||||
auto outputPath = *maybeOutputPath;
|
||||
|
||||
if (auto store2 = store.try_cast_shared<LocalFSStore>()) {
|
||||
std::string symlink = drvPrefix;
|
||||
@@ -631,8 +629,6 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
for (auto & path : outPaths)
|
||||
std::cout << store->printStorePath(path) << '\n';
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void registerLegacyNixBuildAndNixShell() {
|
||||
|
||||
+12
-30
@@ -8,9 +8,7 @@
|
||||
#include "lix/libexpr/eval-settings.hh" // for defexpr
|
||||
#include "lix/libstore/temporary-dir.hh"
|
||||
#include "lix/libutil/async.hh"
|
||||
#include "lix/libutil/c-calls.hh"
|
||||
#include "lix/libutil/regex.hh"
|
||||
#include "lix/libutil/result.hh"
|
||||
#include "lix/libutil/users.hh"
|
||||
#include "nix-channel.hh"
|
||||
|
||||
@@ -45,7 +43,7 @@ static void readChannels()
|
||||
// Writes the list of channels.
|
||||
static void writeChannels()
|
||||
{
|
||||
auto channelsFD = sys::open(channelsList, O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC, 0644);
|
||||
auto channelsFD = AutoCloseFD{open(channelsList.c_str(), O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC, 0644)};
|
||||
if (!channelsFD)
|
||||
throw SysError("opening '%1%' for writing", channelsList);
|
||||
for (const auto & channel : channels)
|
||||
@@ -67,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;
|
||||
@@ -134,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;
|
||||
}
|
||||
|
||||
@@ -171,16 +158,15 @@ 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;
|
||||
if (sys::lstat(nixDefExpr, &st) == 0) {
|
||||
if (lstat(nixDefExpr.c_str(), &st) == 0) {
|
||||
if (S_ISLNK(st.st_mode))
|
||||
// old-skool ~/.nix-defexpr
|
||||
if (sys::unlink(nixDefExpr) == -1) {
|
||||
if (unlink(nixDefExpr.c_str()) == -1)
|
||||
throw SysError("unlinking %1%", nixDefExpr);
|
||||
}
|
||||
} else if (errno != ENOENT) {
|
||||
throw SysError("getting status of %1%", nixDefExpr);
|
||||
}
|
||||
@@ -258,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())
|
||||
@@ -273,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)
|
||||
@@ -289,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,4 +1,3 @@
|
||||
#include "lix/libutil/c-calls.hh"
|
||||
#include "lix/libutil/file-system.hh"
|
||||
#include "lix/libstore/store-api.hh"
|
||||
#include "lix/libstore/store-cast.hh"
|
||||
@@ -25,11 +24,9 @@ bool dryRun = false;
|
||||
|
||||
static void removeOldGenerations(std::string dir, NeverAsync = {})
|
||||
{
|
||||
if (sys::access(dir, R_OK) != 0) {
|
||||
return;
|
||||
}
|
||||
if (access(dir.c_str(), R_OK) != 0) return;
|
||||
|
||||
bool canWrite = sys::access(dir, W_OK) == 0;
|
||||
bool canWrite = access(dir.c_str(), W_OK) == 0;
|
||||
|
||||
for (auto & i : readDirectory(dir)) {
|
||||
checkInterrupt();
|
||||
@@ -64,7 +61,7 @@ static int main_nix_collect_garbage(AsyncIoRoot & aio, std::string programName,
|
||||
{
|
||||
bool removeOld = false;
|
||||
|
||||
GCOptions options = {.action = GCOptions::gcDeleteDead};
|
||||
GCOptions options;
|
||||
|
||||
LegacyArgs(aio, programName, [&](Strings::iterator & arg, const Strings::iterator & end) {
|
||||
if (*arg == "--help")
|
||||
@@ -75,13 +72,12 @@ static int main_nix_collect_garbage(AsyncIoRoot & aio, std::string programName,
|
||||
else if (*arg == "--delete-older-than") {
|
||||
removeOld = true;
|
||||
deleteOlderThan = getArg(*arg, arg, end);
|
||||
} else if (*arg == "--dry-run") {
|
||||
options.action = GCOptions::gcReturnDead;
|
||||
} else if (*arg == "--max-freed") {
|
||||
options.maxFreed = std::max(getIntArg<int64_t>(*arg, arg, end, true), (int64_t) 0);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
else if (*arg == "--dry-run") dryRun = true;
|
||||
else if (*arg == "--max-freed")
|
||||
options.maxFreed = std::max(getIntArg<int64_t>(*arg, arg, end, true), (int64_t) 0);
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
}).parseCmdline(argv);
|
||||
|
||||
@@ -93,12 +89,24 @@ static int main_nix_collect_garbage(AsyncIoRoot & aio, std::string programName,
|
||||
}
|
||||
|
||||
// Run the actual garbage collector.
|
||||
if (!dryRun) {
|
||||
options.action = GCOptions::gcDeleteDead;
|
||||
} else {
|
||||
options.action = GCOptions::gcReturnDead;
|
||||
}
|
||||
auto store = aio.blockOn(openStore());
|
||||
auto & gcStore = require<GcStore>(*store);
|
||||
GCResults results;
|
||||
PrintFreed freed(options.action, results);
|
||||
PrintFreed freed(true, results);
|
||||
aio.blockOn(gcStore.collectGarbage(options, results));
|
||||
|
||||
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", i);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
+215
-268
@@ -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,11 +150,11 @@ static void getAllExprs(Evaluator & state,
|
||||
continue;
|
||||
}
|
||||
/* Load the expression on demand. */
|
||||
Value vArg = {NewValueAs::string, 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);
|
||||
@@ -164,13 +163,15 @@ static void getAllExprs(Evaluator & state,
|
||||
}
|
||||
}
|
||||
|
||||
static Value loadSourceExpr(EvalState & state, const SourcePath & path_)
|
||||
|
||||
|
||||
static void loadSourceExpr(EvalState & state, const SourcePath & path_, Value & v)
|
||||
{
|
||||
auto path = state.ctx.paths.checkSourcePath(path_);
|
||||
auto st = path.stat();
|
||||
|
||||
if (isNixExpr(state.ctx.paths, path, st))
|
||||
return state.evalFile(path);
|
||||
state.evalFile(path, v);
|
||||
|
||||
/* The path is a directory. Put the Nix expressions in the
|
||||
directory in a set, with the file name of each expression as
|
||||
@@ -180,10 +181,10 @@ static Value loadSourceExpr(EvalState & state, const SourcePath & path_)
|
||||
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);
|
||||
return {NewValueAs::attrs, attrs};
|
||||
v.mkAttrs(attrs);
|
||||
}
|
||||
|
||||
else throw Error("path '%s' is not a directory or a Nix expression", path);
|
||||
@@ -194,9 +195,10 @@ static void loadDerivations(EvalState & state, const SourcePath & nixExprPath,
|
||||
std::string systemFilter, Bindings & autoArgs,
|
||||
const std::string & pathPrefix, DrvInfos & elems)
|
||||
{
|
||||
Value vRoot = loadSourceExpr(state, nixExprPath);
|
||||
Value vRoot;
|
||||
loadSourceExpr(state, nixExprPath, vRoot);
|
||||
|
||||
Value v(findAlongAttrPath(state, pathPrefix, autoArgs, vRoot).first);
|
||||
Value & v(*findAlongAttrPath(state, pathPrefix, autoArgs, vRoot).first);
|
||||
|
||||
getDerivations(state, v, pathPrefix, autoArgs, elems, true);
|
||||
|
||||
@@ -315,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);
|
||||
}
|
||||
|
||||
@@ -416,12 +418,14 @@ static void queryInstSources(EvalState & state,
|
||||
(import ./foo.nix)' = `(import ./foo.nix).bar'. */
|
||||
case srcNixExprs: {
|
||||
|
||||
Value vArg = loadSourceExpr(state, *instSource.nixExprPath);
|
||||
Value vArg;
|
||||
loadSourceExpr(state, *instSource.nixExprPath, vArg);
|
||||
|
||||
for (auto & i : args) {
|
||||
Expr & eFun = state.ctx.parseExprFromString(i, CanonPath::fromCwd());
|
||||
Value vFun = state.eval(eFun);
|
||||
Value vTmp = {NewValueAs::app, state.ctx.mem, vFun, vArg};
|
||||
Value vFun, vTmp;
|
||||
state.eval(eFun, vFun);
|
||||
vTmp.mkApp(&vFun, &vArg);
|
||||
getDerivations(state, vTmp, "", *instSource.autoArgs, elems, true);
|
||||
}
|
||||
|
||||
@@ -473,9 +477,10 @@ static void queryInstSources(EvalState & state,
|
||||
}
|
||||
|
||||
case srcAttrPath: {
|
||||
Value vRoot = loadSourceExpr(state, *instSource.nixExprPath);
|
||||
Value vRoot;
|
||||
loadSourceExpr(state, *instSource.nixExprPath, vRoot);
|
||||
for (auto & i : args) {
|
||||
Value v(findAlongAttrPath(state, i, *instSource.autoArgs, vRoot).first);
|
||||
Value & v(*findAlongAttrPath(state, i, *instSource.autoArgs, vRoot).first);
|
||||
getDerivations(state, v, "", *instSource.autoArgs, elems, true);
|
||||
}
|
||||
break;
|
||||
@@ -490,7 +495,7 @@ static void printMissing(EvalState & state, DrvInfos & elems)
|
||||
for (auto & i : elems)
|
||||
if (auto drvPath = i.queryDrvPath(state))
|
||||
targets.emplace_back(DerivedPath::Built{
|
||||
.drvPath = makeConstantStorePath(*drvPath),
|
||||
.drvPath = makeConstantStorePathRef(*drvPath),
|
||||
.outputs = OutputsSpec::All { },
|
||||
});
|
||||
else
|
||||
@@ -510,7 +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 = {NewValueAs::string, value};
|
||||
auto v = state.ctx.mem.allocValue();
|
||||
v->mkString(value);
|
||||
drv.setMeta(state, name, v);
|
||||
}
|
||||
|
||||
@@ -680,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);
|
||||
|
||||
@@ -790,7 +792,7 @@ static void opSet(Globals & globals, Strings opFlags, Strings opArgs)
|
||||
std::vector<DerivedPath> paths {
|
||||
drvPath
|
||||
? (DerivedPath) (DerivedPath::Built {
|
||||
.drvPath = makeConstantStorePath(*drvPath),
|
||||
.drvPath = makeConstantStorePathRef(*drvPath),
|
||||
.outputs = OutputsSpec::All { },
|
||||
})
|
||||
: (DerivedPath) (DerivedPath::Opaque {
|
||||
@@ -843,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));
|
||||
}
|
||||
@@ -884,7 +886,8 @@ static bool cmpElemByName(EvalState & state, DrvInfo & a, DrvInfo & b)
|
||||
|
||||
typedef std::list<Strings> Table;
|
||||
|
||||
std::string formatTable(Table & table)
|
||||
|
||||
void printTable(Table & table)
|
||||
{
|
||||
auto nrColumns = table.size() > 0 ? table.front().size() : 0;
|
||||
|
||||
@@ -899,22 +902,18 @@ std::string formatTable(Table & table)
|
||||
if (j->size() > widths[column]) widths[column] = j->size();
|
||||
}
|
||||
|
||||
std::stringstream result;
|
||||
|
||||
for (auto & i : table) {
|
||||
Strings::iterator j;
|
||||
size_t column;
|
||||
for (j = i.begin(), column = 0; j != i.end(); ++j, ++column) {
|
||||
std::string s = *j;
|
||||
replace(s.begin(), s.end(), '\n', ' ');
|
||||
result << s;
|
||||
cout << s;
|
||||
if (column < nrColumns - 1)
|
||||
result << std::string(widths[column] - s.size() + 2, ' ');
|
||||
cout << std::string(widths[column] - s.size() + 2, ' ');
|
||||
}
|
||||
result << std::endl;
|
||||
cout << std::endl;
|
||||
}
|
||||
|
||||
return result.str();
|
||||
}
|
||||
|
||||
|
||||
@@ -1125,250 +1124,205 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
|
||||
return;
|
||||
}
|
||||
|
||||
withPager([&](Pager & pager) {
|
||||
Table table;
|
||||
std::ostringstream xmlStream;
|
||||
XMLWriter xml(true, xmlStream);
|
||||
xml.openElement("items");
|
||||
RunPager pager;
|
||||
|
||||
for (auto & i : elems) {
|
||||
try {
|
||||
if (i.hasFailed()) {
|
||||
continue;
|
||||
}
|
||||
Table table;
|
||||
std::ostringstream dummy;
|
||||
XMLWriter xml(true, *(xmlOutput ? &cout : &dummy));
|
||||
XMLOpenElement xmlRoot(xml, "items");
|
||||
|
||||
// Activity act(*logger, lvlDebug, "outputting query result '%1%'", i.attrPath);
|
||||
for (auto & i : elems) {
|
||||
try {
|
||||
if (i.hasFailed()) continue;
|
||||
|
||||
if (globals.prebuiltOnly && !validPaths.count(i.queryOutPath(*state))
|
||||
&& !substitutablePaths.count(i.queryOutPath(*state)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//Activity act(*logger, lvlDebug, "outputting query result '%1%'", i.attrPath);
|
||||
|
||||
/* For table output. */
|
||||
Strings columns;
|
||||
if (globals.prebuiltOnly &&
|
||||
!validPaths.count(i.queryOutPath(*state)) &&
|
||||
!substitutablePaths.count(i.queryOutPath(*state)))
|
||||
continue;
|
||||
|
||||
/* For XML output. */
|
||||
XMLAttrs attrs;
|
||||
/* For table output. */
|
||||
Strings columns;
|
||||
|
||||
if (printStatus) {
|
||||
auto outPath = i.queryOutPath(*state);
|
||||
bool hasSubs = substitutablePaths.count(outPath);
|
||||
bool isInstalled = installed.count(outPath);
|
||||
bool isValid = validPaths.count(outPath);
|
||||
if (xmlOutput) {
|
||||
attrs["installed"] = isInstalled ? "1" : "0";
|
||||
attrs["valid"] = isValid ? "1" : "0";
|
||||
attrs["substitutable"] = hasSubs ? "1" : "0";
|
||||
} else {
|
||||
columns.push_back(
|
||||
(std::string) (isInstalled ? "I" : "-") + (isValid ? "P" : "-")
|
||||
+ (hasSubs ? "S" : "-")
|
||||
);
|
||||
}
|
||||
/* For XML output. */
|
||||
XMLAttrs attrs;
|
||||
|
||||
if (printStatus) {
|
||||
auto outPath = i.queryOutPath(*state);
|
||||
bool hasSubs = substitutablePaths.count(outPath);
|
||||
bool isInstalled = installed.count(outPath);
|
||||
bool isValid = validPaths.count(outPath);
|
||||
if (xmlOutput) {
|
||||
attrs["installed"] = isInstalled ? "1" : "0";
|
||||
attrs["valid"] = isValid ? "1" : "0";
|
||||
attrs["substitutable"] = hasSubs ? "1" : "0";
|
||||
} else
|
||||
columns.push_back(
|
||||
(std::string) (isInstalled ? "I" : "-")
|
||||
+ (isValid ? "P" : "-")
|
||||
+ (hasSubs ? "S" : "-"));
|
||||
}
|
||||
|
||||
if (xmlOutput)
|
||||
attrs["attrPath"] = i.attrPath;
|
||||
else if (printAttrPath)
|
||||
columns.push_back(i.attrPath);
|
||||
|
||||
if (xmlOutput) {
|
||||
auto drvName = DrvName(i.queryName(*state));
|
||||
attrs["name"] = drvName.fullName;
|
||||
attrs["pname"] = drvName.name;
|
||||
attrs["version"] = drvName.version;
|
||||
} else if (printName) {
|
||||
columns.push_back(i.queryName(*state));
|
||||
}
|
||||
|
||||
if (compareVersions) {
|
||||
/* Compare this element against the versions of the
|
||||
same named packages in either the set of available
|
||||
elements, or the set of installed elements. !!!
|
||||
This is O(N * M), should be O(N * lg M). */
|
||||
std::string version;
|
||||
VersionDiff diff = compareVersionAgainstSet(*state, i, otherElems, version);
|
||||
|
||||
char ch;
|
||||
switch (diff) {
|
||||
case cvLess: ch = '>'; break;
|
||||
case cvEqual: ch = '='; break;
|
||||
case cvGreater: ch = '<'; break;
|
||||
case cvUnavail: ch = '-'; break;
|
||||
default: abort();
|
||||
}
|
||||
|
||||
if (xmlOutput) {
|
||||
attrs["attrPath"] = i.attrPath;
|
||||
} else if (printAttrPath) {
|
||||
columns.push_back(i.attrPath);
|
||||
if (diff != cvUnavail) {
|
||||
attrs["versionDiff"] = ch;
|
||||
attrs["maxComparedVersion"] = version;
|
||||
}
|
||||
} else {
|
||||
auto column = (std::string) "" + ch + " " + version;
|
||||
if (diff == cvGreater && shouldANSI(StandardOutputStream::Stdout))
|
||||
column = ANSI_RED + column + ANSI_NORMAL;
|
||||
columns.push_back(column);
|
||||
}
|
||||
}
|
||||
|
||||
if (xmlOutput) {
|
||||
if (i.querySystem(*state) != "") attrs["system"] = i.querySystem(*state);
|
||||
}
|
||||
else if (printSystem)
|
||||
columns.push_back(i.querySystem(*state));
|
||||
|
||||
if (printDrvPath) {
|
||||
auto drvPath = i.queryDrvPath(*state);
|
||||
if (xmlOutput) {
|
||||
auto drvName = DrvName(i.queryName(*state));
|
||||
attrs["name"] = drvName.fullName;
|
||||
attrs["pname"] = drvName.name;
|
||||
attrs["version"] = drvName.version;
|
||||
} else if (printName) {
|
||||
columns.push_back(i.queryName(*state));
|
||||
}
|
||||
if (drvPath) attrs["drvPath"] = store.printStorePath(*drvPath);
|
||||
} else
|
||||
columns.push_back(drvPath ? store.printStorePath(*drvPath) : "-");
|
||||
}
|
||||
|
||||
if (compareVersions) {
|
||||
/* Compare this element against the versions of the
|
||||
same named packages in either the set of available
|
||||
elements, or the set of installed elements. !!!
|
||||
This is O(N * M), should be O(N * lg M). */
|
||||
std::string version;
|
||||
VersionDiff diff = compareVersionAgainstSet(*state, i, otherElems, version);
|
||||
if (xmlOutput)
|
||||
attrs["outputName"] = i.queryOutputName(*state);
|
||||
|
||||
char ch;
|
||||
switch (diff) {
|
||||
case cvLess:
|
||||
ch = '>';
|
||||
break;
|
||||
case cvEqual:
|
||||
ch = '=';
|
||||
break;
|
||||
case cvGreater:
|
||||
ch = '<';
|
||||
break;
|
||||
case cvUnavail:
|
||||
ch = '-';
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
|
||||
if (xmlOutput) {
|
||||
if (diff != cvUnavail) {
|
||||
attrs["versionDiff"] = ch;
|
||||
attrs["maxComparedVersion"] = version;
|
||||
}
|
||||
} else {
|
||||
auto column = (std::string) "" + ch + " " + version;
|
||||
if (diff == cvGreater && shouldANSI(StandardOutputStream::Stdout)) {
|
||||
column = ANSI_RED + column + ANSI_NORMAL;
|
||||
}
|
||||
columns.push_back(column);
|
||||
}
|
||||
if (printOutPath && !xmlOutput) {
|
||||
DrvInfo::Outputs outputs = i.queryOutputs(*state);
|
||||
std::string s;
|
||||
for (auto & j : outputs) {
|
||||
if (!s.empty()) s += ';';
|
||||
if (j.first != "out") { s += j.first; s += "="; }
|
||||
s += store.printStorePath(*j.second);
|
||||
}
|
||||
columns.push_back(s);
|
||||
}
|
||||
|
||||
if (printDescription) {
|
||||
auto descr = i.queryMetaString(*state, "description");
|
||||
if (xmlOutput) {
|
||||
if (i.querySystem(*state) != "") {
|
||||
attrs["system"] = i.querySystem(*state);
|
||||
}
|
||||
} else if (printSystem) {
|
||||
columns.push_back(i.querySystem(*state));
|
||||
}
|
||||
if (descr != "") attrs["description"] = descr;
|
||||
} else
|
||||
columns.push_back(descr);
|
||||
}
|
||||
|
||||
if (printDrvPath) {
|
||||
auto drvPath = i.queryDrvPath(*state);
|
||||
if (xmlOutput) {
|
||||
if (drvPath) {
|
||||
attrs["drvPath"] = store.printStorePath(*drvPath);
|
||||
}
|
||||
} else {
|
||||
columns.push_back(drvPath ? store.printStorePath(*drvPath) : "-");
|
||||
}
|
||||
if (xmlOutput) {
|
||||
XMLOpenElement item(xml, "item", attrs);
|
||||
DrvInfo::Outputs outputs = i.queryOutputs(*state, printOutPath);
|
||||
for (auto & j : outputs) {
|
||||
XMLAttrs attrs2;
|
||||
attrs2["name"] = j.first;
|
||||
if (j.second)
|
||||
attrs2["path"] = store.printStorePath(*j.second);
|
||||
xml.writeEmptyElement("output", attrs2);
|
||||
}
|
||||
|
||||
if (xmlOutput) {
|
||||
attrs["outputName"] = i.queryOutputName(*state);
|
||||
}
|
||||
|
||||
if (printOutPath && !xmlOutput) {
|
||||
DrvInfo::Outputs outputs = i.queryOutputs(*state);
|
||||
std::string s;
|
||||
for (auto & j : outputs) {
|
||||
if (!s.empty()) {
|
||||
s += ';';
|
||||
}
|
||||
if (j.first != "out") {
|
||||
s += j.first;
|
||||
s += "=";
|
||||
}
|
||||
s += store.printStorePath(*j.second);
|
||||
}
|
||||
columns.push_back(s);
|
||||
}
|
||||
|
||||
if (printDescription) {
|
||||
auto descr = i.queryMetaString(*state, "description");
|
||||
if (xmlOutput) {
|
||||
if (descr != "") {
|
||||
attrs["description"] = descr;
|
||||
}
|
||||
} else {
|
||||
columns.push_back(descr);
|
||||
}
|
||||
}
|
||||
|
||||
if (xmlOutput) {
|
||||
XMLOpenElement item(xml, "item", attrs);
|
||||
DrvInfo::Outputs outputs = i.queryOutputs(*state, printOutPath);
|
||||
for (auto & j : outputs) {
|
||||
if (printMeta) {
|
||||
StringSet metaNames = i.queryMetaNames(*state);
|
||||
for (auto & j : metaNames) {
|
||||
XMLAttrs attrs2;
|
||||
attrs2["name"] = j.first;
|
||||
if (j.second) {
|
||||
attrs2["path"] = store.printStorePath(*j.second);
|
||||
}
|
||||
xml.writeEmptyElement("output", attrs2);
|
||||
}
|
||||
if (printMeta) {
|
||||
StringSet metaNames = i.queryMetaNames(*state);
|
||||
for (auto & j : metaNames) {
|
||||
XMLAttrs attrs2;
|
||||
attrs2["name"] = j;
|
||||
Value * v = i.queryMeta(*state, j);
|
||||
if (!v) {
|
||||
printError(
|
||||
"derivation '%s' has invalid meta attribute '%s'", i.queryName(*state), j
|
||||
);
|
||||
} else {
|
||||
if (v->type() == nString) {
|
||||
attrs2["type"] = "string";
|
||||
attrs2["value"] = v->str();
|
||||
xml.writeEmptyElement("meta", attrs2);
|
||||
} else if (v->type() == nInt) {
|
||||
attrs2["type"] = "int";
|
||||
attrs2["value"] = fmt("%1%", v->integer());
|
||||
xml.writeEmptyElement("meta", attrs2);
|
||||
} else if (v->type() == nFloat) {
|
||||
attrs2["type"] = "float";
|
||||
attrs2["value"] = fmt("%1%", v->fpoint());
|
||||
xml.writeEmptyElement("meta", attrs2);
|
||||
} else if (v->type() == nBool) {
|
||||
attrs2["type"] = "bool";
|
||||
attrs2["value"] = v->boolean() ? "true" : "false";
|
||||
xml.writeEmptyElement("meta", attrs2);
|
||||
} else if (v->type() == nList) {
|
||||
attrs2["type"] = "strings";
|
||||
XMLOpenElement m(xml, "meta", attrs2);
|
||||
for (auto & elem : v->listItems()) {
|
||||
if (elem.type() != nString) {
|
||||
continue;
|
||||
}
|
||||
XMLAttrs attrs3;
|
||||
attrs3["value"] = elem.str();
|
||||
xml.writeEmptyElement("string", attrs3);
|
||||
}
|
||||
} else if (v->type() == nAttrs) {
|
||||
attrs2["type"] = "strings";
|
||||
XMLOpenElement m(xml, "meta", attrs2);
|
||||
Bindings & attrs = *v->attrs();
|
||||
for (auto & i : attrs) {
|
||||
const Attr & a(*attrs.get(i.name));
|
||||
if (a.value.type() != nString) {
|
||||
continue;
|
||||
}
|
||||
XMLAttrs attrs3;
|
||||
attrs3["type"] = globals.state->symbols[i.name];
|
||||
attrs3["value"] = a.value.str();
|
||||
xml.writeEmptyElement("string", attrs3);
|
||||
}
|
||||
attrs2["name"] = j;
|
||||
Value * v = i.queryMeta(*state, j);
|
||||
if (!v)
|
||||
printError(
|
||||
"derivation '%s' has invalid meta attribute '%s'",
|
||||
i.queryName(*state), j);
|
||||
else {
|
||||
if (v->type() == nString) {
|
||||
attrs2["type"] = "string";
|
||||
attrs2["value"] = v->string.s;
|
||||
xml.writeEmptyElement("meta", attrs2);
|
||||
} else if (v->type() == nInt) {
|
||||
attrs2["type"] = "int";
|
||||
attrs2["value"] = fmt("%1%", v->integer);
|
||||
xml.writeEmptyElement("meta", attrs2);
|
||||
} else if (v->type() == nFloat) {
|
||||
attrs2["type"] = "float";
|
||||
attrs2["value"] = fmt("%1%", v->fpoint);
|
||||
xml.writeEmptyElement("meta", attrs2);
|
||||
} else if (v->type() == nBool) {
|
||||
attrs2["type"] = "bool";
|
||||
attrs2["value"] = v->boolean ? "true" : "false";
|
||||
xml.writeEmptyElement("meta", attrs2);
|
||||
} else if (v->type() == nList) {
|
||||
attrs2["type"] = "strings";
|
||||
XMLOpenElement m(xml, "meta", attrs2);
|
||||
for (auto elem : v->listItems()) {
|
||||
if (elem->type() != nString) continue;
|
||||
XMLAttrs attrs3;
|
||||
attrs3["value"] = elem->string.s;
|
||||
xml.writeEmptyElement("string", attrs3);
|
||||
}
|
||||
} else if (v->type() == nAttrs) {
|
||||
attrs2["type"] = "strings";
|
||||
XMLOpenElement m(xml, "meta", attrs2);
|
||||
Bindings & attrs = *v->attrs;
|
||||
for (auto &i : attrs) {
|
||||
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->string.s;
|
||||
xml.writeEmptyElement("string", attrs3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
table.push_back(columns);
|
||||
}
|
||||
} else
|
||||
table.push_back(columns);
|
||||
|
||||
cout.flush();
|
||||
cout.flush();
|
||||
|
||||
} catch (AssertionError & e) {
|
||||
printMsg(
|
||||
lvlTalkative,
|
||||
"skipping derivation named '%1%' which gives an assertion failure",
|
||||
i.queryName(*state)
|
||||
);
|
||||
} catch (Error & e) {
|
||||
e.addTrace(nullptr, "while querying the derivation named '%1%'", i.queryName(*state));
|
||||
throw;
|
||||
}
|
||||
} catch (AssertionError & e) {
|
||||
printMsg(lvlTalkative, "skipping derivation named '%1%' which gives an assertion failure", i.queryName(*state));
|
||||
} catch (Error & e) {
|
||||
e.addTrace(nullptr, "while querying the derivation named '%1%'", i.queryName(*state));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// </items>
|
||||
xml.closeElement();
|
||||
|
||||
if (!xmlOutput) {
|
||||
pager << formatTable(table);
|
||||
} else {
|
||||
pager << xmlStream.str();
|
||||
}
|
||||
});
|
||||
if (!xmlOutput) printTable(table);
|
||||
}
|
||||
|
||||
|
||||
static void opSwitchProfile(Globals & globals, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
if (opFlags.size() > 0)
|
||||
@@ -1419,27 +1373,20 @@ static void opListGenerations(Globals & globals, Strings opFlags, Strings opArgs
|
||||
|
||||
auto [gens, curGen] = findGenerations(globals.profile);
|
||||
|
||||
withPager([&](Pager & pager) {
|
||||
for (auto & i : gens) {
|
||||
tm t;
|
||||
if (!localtime_r(&i.creationTime, &t)) {
|
||||
throw Error("cannot convert time");
|
||||
}
|
||||
pager << fmt(
|
||||
"%|4| %|4|-%|02|-%|02| %|02|:%|02|:%|02| %||\n",
|
||||
i.number,
|
||||
t.tm_year + 1900,
|
||||
t.tm_mon + 1,
|
||||
t.tm_mday,
|
||||
t.tm_hour,
|
||||
t.tm_min,
|
||||
t.tm_sec,
|
||||
i.number == curGen ? "(current)" : ""
|
||||
);
|
||||
}
|
||||
});
|
||||
RunPager pager;
|
||||
|
||||
for (auto & i : gens) {
|
||||
tm t;
|
||||
if (!localtime_r(&i.creationTime, &t)) throw Error("cannot convert time");
|
||||
logger->cout("%|4| %|4|-%|02|-%|02| %|02|:%|02|:%|02| %||",
|
||||
i.number,
|
||||
t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,
|
||||
t.tm_hour, t.tm_min, t.tm_sec,
|
||||
i.number == curGen ? "(current)" : "");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void opDeleteGenerations(Globals & globals, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
if (opFlags.size() > 0)
|
||||
|
||||
@@ -22,7 +22,7 @@ static Path gcRoot;
|
||||
static int rootNr = 0;
|
||||
|
||||
|
||||
enum OutputKind { okPlain, okRaw, okXML, okJSON };
|
||||
enum OutputKind { okPlain, okXML, okJSON };
|
||||
|
||||
void processExpr(EvalState & state, const Strings & attrPaths,
|
||||
bool parseOnly, bool strict, Bindings & autoArgs,
|
||||
@@ -34,10 +34,11 @@ void processExpr(EvalState & state, const Strings & attrPaths,
|
||||
return;
|
||||
}
|
||||
|
||||
Value vRoot = state.eval(e);
|
||||
Value vRoot;
|
||||
state.eval(e, vRoot);
|
||||
|
||||
for (auto & i : attrPaths) {
|
||||
Value v(findAlongAttrPath(state, i, autoArgs, vRoot).first);
|
||||
Value & v(*findAlongAttrPath(state, i, autoArgs, vRoot).first);
|
||||
state.forceValue(v, noPos);
|
||||
|
||||
NixStringContext context;
|
||||
@@ -46,12 +47,8 @@ void processExpr(EvalState & state, const Strings & attrPaths,
|
||||
if (autoArgs.empty())
|
||||
vRes = v;
|
||||
else
|
||||
vRes = state.autoCallFunction(autoArgs, v, noPos);
|
||||
if (output == okRaw)
|
||||
std::cout << *state.coerceToString(noPos, vRes, context, "while generating the nix-instantiate output", StringCoercionMode::Strict);
|
||||
// We intentionally don't output a newline here. The default PS1 for Bash in NixOS starts with a newline
|
||||
// and other interactive shells like Zsh are smart enough to print a missing newline before the prompt.
|
||||
else if (output == okXML)
|
||||
state.autoCallFunction(autoArgs, v, vRes, noPos);
|
||||
if (output == okXML)
|
||||
printValueAsXML(state, strict, location, vRes, std::cout, context, noPos);
|
||||
else if (output == okJSON) {
|
||||
printValueAsJSON(state, strict, vRes, noPos, std::cout, context);
|
||||
@@ -133,8 +130,6 @@ static int main_nix_instantiate(AsyncIoRoot & aio, std::string programName, Stri
|
||||
gcRoot = getArg(*arg, arg, end);
|
||||
else if (*arg == "--indirect")
|
||||
;
|
||||
else if (*arg == "--raw")
|
||||
outputKind = okRaw;
|
||||
else if (*arg == "--xml")
|
||||
outputKind = okXML;
|
||||
else if (*arg == "--json")
|
||||
|
||||
+161
-272
@@ -17,17 +17,11 @@
|
||||
#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>
|
||||
|
||||
#include <ostream>
|
||||
#include <ranges>
|
||||
#include <sstream>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
@@ -39,23 +33,25 @@ namespace nix {
|
||||
using std::cin;
|
||||
using std::cout;
|
||||
|
||||
typedef void (*Operation)(
|
||||
std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs
|
||||
);
|
||||
|
||||
typedef void (* Operation) (AsyncIoRoot & aio, Strings opFlags, Strings opArgs);
|
||||
|
||||
|
||||
static Path gcRoot;
|
||||
static int rootNr = 0;
|
||||
static bool noOutput = false;
|
||||
static std::shared_ptr<Store> store;
|
||||
|
||||
ref<LocalStore> ensureLocalStore(std::shared_ptr<Store> store)
|
||||
|
||||
ref<LocalStore> ensureLocalStore()
|
||||
{
|
||||
auto store2 = std::dynamic_pointer_cast<LocalStore>(store);
|
||||
if (!store2) throw Error("you don't have sufficient rights to use this command");
|
||||
return ref<LocalStore>::unsafeFromPtr(store2);
|
||||
}
|
||||
|
||||
static kj::Promise<Result<StorePath>>
|
||||
useDeriver(std::shared_ptr<Store> store, const StorePath & path)
|
||||
|
||||
static kj::Promise<Result<StorePath>> useDeriver(const StorePath & path)
|
||||
try {
|
||||
if (path.isDerivation()) co_return path;
|
||||
auto info = TRY_AWAIT(store->queryPathInfo(path));
|
||||
@@ -69,8 +65,7 @@ try {
|
||||
|
||||
/* Realise the given path. For a derivation that means build it; for
|
||||
other paths it means ensure their validity. */
|
||||
static kj::Promise<Result<PathSet>>
|
||||
realisePath(std::shared_ptr<Store> store, StorePathWithOutputs path, bool build = true)
|
||||
static kj::Promise<Result<PathSet>> realisePath(StorePathWithOutputs path, bool build = true)
|
||||
try {
|
||||
auto store2 = std::dynamic_pointer_cast<LocalFSStore>(store);
|
||||
|
||||
@@ -91,16 +86,6 @@ try {
|
||||
if (i == drv.outputs.end())
|
||||
throw Error("derivation '%s' does not have an output named '%s'",
|
||||
store2->printStorePath(path.path), j);
|
||||
if (!outputPaths.contains(i->first)) {
|
||||
throw Error(
|
||||
"Possible SQLite database corruption: derivation '%s' output map contains only "
|
||||
"outputs '{%s}', not '%s'\n"
|
||||
"Note: derivation output maps are stored in the SQLite database.",
|
||||
store2->printStorePath(path.path),
|
||||
concatStringsSep(", ", std::views::keys(outputPaths)),
|
||||
i->first
|
||||
);
|
||||
}
|
||||
auto outPath = outputPaths.at(i->first);
|
||||
auto retPath = store->printStorePath(outPath);
|
||||
if (store2) {
|
||||
@@ -140,8 +125,7 @@ try {
|
||||
|
||||
|
||||
/* Realise the given paths. */
|
||||
static void
|
||||
opRealise(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
static void opRealise(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
bool dryRun = false;
|
||||
BuildMode buildMode = bmNormal;
|
||||
@@ -186,7 +170,7 @@ opRealise(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Stri
|
||||
|
||||
if (!ignoreUnknown)
|
||||
for (auto & i : paths) {
|
||||
auto paths2 = aio.blockOn(realisePath(store, i, false));
|
||||
auto paths2 = aio.blockOn(realisePath(i, false));
|
||||
if (!noOutput)
|
||||
for (auto & j : paths2)
|
||||
cout << fmt("%1%\n", j);
|
||||
@@ -195,7 +179,7 @@ opRealise(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Stri
|
||||
|
||||
|
||||
/* Add files to the Nix store and print the resulting paths. */
|
||||
static void opAdd(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
static void opAdd(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
if (!opFlags.empty()) throw UsageError("unknown flag");
|
||||
|
||||
@@ -212,8 +196,7 @@ static void opAdd(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFla
|
||||
|
||||
/* Preload the output of a fixed-output derivation into the Nix
|
||||
store. */
|
||||
static void
|
||||
opAddFixed(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
static void opAddFixed(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
auto method = FileIngestionMethod::Flat;
|
||||
|
||||
@@ -239,8 +222,7 @@ opAddFixed(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Str
|
||||
|
||||
|
||||
/* Hack to support caching in `nix-prefetch-url'. */
|
||||
static void
|
||||
opPrintFixedPath(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
static void opPrintFixedPath(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
auto method = FileIngestionMethod::Flat;
|
||||
|
||||
@@ -263,20 +245,19 @@ opPrintFixedPath(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlag
|
||||
})));
|
||||
}
|
||||
|
||||
static kj::Promise<Result<StorePathSet>> maybeUseOutputs(
|
||||
std::shared_ptr<Store> store, const StorePath & storePath, bool useOutput, bool forceRealise
|
||||
)
|
||||
|
||||
static kj::Promise<Result<StorePathSet>> maybeUseOutputs(const StorePath & storePath, bool useOutput, bool forceRealise)
|
||||
try {
|
||||
if (forceRealise) {
|
||||
TRY_AWAIT(realisePath(store, {storePath}));
|
||||
}
|
||||
if (forceRealise) TRY_AWAIT(realisePath({storePath}));
|
||||
if (useOutput && storePath.isDerivation()) {
|
||||
auto drv = TRY_AWAIT(store->derivationFromPath(storePath));
|
||||
StorePathSet outputs;
|
||||
if (forceRealise)
|
||||
co_return TRY_AWAIT(store->queryDerivationOutputs(storePath));
|
||||
for (auto & i : drv.outputsAndPaths(*store)) {
|
||||
outputs.insert(i.second.second);
|
||||
for (auto & i : drv.outputsAndOptPaths(*store)) {
|
||||
if (!i.second.second)
|
||||
throw UsageError("Cannot use output path of floating content-addressed derivation until we know what it is (e.g. by building it)");
|
||||
outputs.insert(*i.second.second);
|
||||
}
|
||||
co_return outputs;
|
||||
}
|
||||
@@ -289,22 +270,15 @@ try {
|
||||
/* Some code to print a tree representation of a derivation dependency
|
||||
graph. Topological sorting is used to keep the tree relatively
|
||||
flat. */
|
||||
static void printTree(
|
||||
std::ostream & ostream,
|
||||
std::shared_ptr<Store> store,
|
||||
AsyncIoRoot & aio,
|
||||
const StorePath & path,
|
||||
const std::string & firstPad,
|
||||
const std::string & tailPad,
|
||||
StorePathSet & done
|
||||
)
|
||||
static void printTree(AsyncIoRoot & aio, const StorePath & path,
|
||||
const std::string & firstPad, const std::string & tailPad, StorePathSet & done)
|
||||
{
|
||||
if (!done.insert(path).second) {
|
||||
ostream << fmt("%s%s [...]\n", firstPad, store->printStorePath(path));
|
||||
cout << fmt("%s%s [...]\n", firstPad, store->printStorePath(path));
|
||||
return;
|
||||
}
|
||||
|
||||
ostream << fmt("%s%s\n", firstPad, store->printStorePath(path));
|
||||
cout << fmt("%s%s\n", firstPad, store->printStorePath(path));
|
||||
|
||||
auto info = aio.blockOn(store->queryPathInfo(path));
|
||||
|
||||
@@ -317,22 +291,16 @@ static void printTree(
|
||||
|
||||
for (const auto &[n, i] : enumerate(sorted)) {
|
||||
bool last = n + 1 == sorted.size();
|
||||
printTree(
|
||||
ostream,
|
||||
store,
|
||||
aio,
|
||||
i,
|
||||
printTree(aio, i,
|
||||
tailPad + (last ? treeLast : treeConn),
|
||||
tailPad + (last ? treeNull : treeLine),
|
||||
done
|
||||
);
|
||||
done);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Perform various sorts of queries. */
|
||||
static void
|
||||
opQuery(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
static void opQuery(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
enum QueryType
|
||||
{ qOutputs, qRequisites, qReferences, qReferrers
|
||||
@@ -377,15 +345,15 @@ opQuery(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
|
||||
|
||||
if (!query) query = qOutputs;
|
||||
|
||||
withPager([&](Pager & pager) {
|
||||
switch (*query) {
|
||||
RunPager pager;
|
||||
|
||||
switch (*query) {
|
||||
|
||||
case qOutputs: {
|
||||
for (auto & i : opArgs) {
|
||||
auto outputs =
|
||||
aio.blockOn(maybeUseOutputs(store, store->followLinksToStorePath(i), true, forceRealise));
|
||||
for (auto & outputPath : outputs) {
|
||||
pager << fmt("%1%\n", store->printStorePath(outputPath));
|
||||
}
|
||||
auto outputs = aio.blockOn(maybeUseOutputs(store->followLinksToStorePath(i), true, forceRealise));
|
||||
for (auto & outputPath : outputs)
|
||||
cout << fmt("%1%\n", store->printStorePath(outputPath));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -396,173 +364,138 @@ opQuery(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
|
||||
case qReferrersClosure: {
|
||||
StorePathSet paths;
|
||||
for (auto & i : opArgs) {
|
||||
auto ps = aio.blockOn(
|
||||
maybeUseOutputs(store, store->followLinksToStorePath(i), useOutput, forceRealise)
|
||||
);
|
||||
auto ps = aio.blockOn(maybeUseOutputs(store->followLinksToStorePath(i), useOutput, forceRealise));
|
||||
for (auto & j : ps) {
|
||||
if (query == qRequisites) {
|
||||
aio.blockOn(store->computeFSClosure(j, paths, false, includeOutputs));
|
||||
} else if (query == qReferences) {
|
||||
for (auto & p : aio.blockOn(store->queryPathInfo(j))->references) {
|
||||
}
|
||||
else if (query == qReferences) {
|
||||
for (auto & p : aio.blockOn(store->queryPathInfo(j))->references)
|
||||
paths.insert(p);
|
||||
}
|
||||
} else if (query == qReferrers) {
|
||||
}
|
||||
else if (query == qReferrers) {
|
||||
StorePathSet tmp;
|
||||
aio.blockOn(store->queryReferrers(j, tmp));
|
||||
for (auto & i : tmp) {
|
||||
for (auto & i : tmp)
|
||||
paths.insert(i);
|
||||
}
|
||||
} else if (query == qReferrersClosure) {
|
||||
aio.blockOn(store->computeFSClosure(j, paths, true));
|
||||
}
|
||||
else if (query == qReferrersClosure)
|
||||
aio.blockOn(store->computeFSClosure(j, paths, true));
|
||||
}
|
||||
}
|
||||
auto sorted = aio.blockOn(store->topoSortPaths(paths));
|
||||
for (StorePaths::reverse_iterator i = sorted.rbegin(); i != sorted.rend(); ++i) {
|
||||
pager << fmt("%s\n", store->printStorePath(*i));
|
||||
}
|
||||
for (StorePaths::reverse_iterator i = sorted.rbegin();
|
||||
i != sorted.rend(); ++i)
|
||||
cout << fmt("%s\n", store->printStorePath(*i));
|
||||
break;
|
||||
}
|
||||
|
||||
case qDeriver:
|
||||
for (auto & i : opArgs) {
|
||||
auto info = aio.blockOn(store->queryPathInfo(store->followLinksToStorePath(i)));
|
||||
pager << fmt(
|
||||
"%s\n", info->deriver ? store->printStorePath(*info->deriver) : "unknown-deriver"
|
||||
);
|
||||
cout << fmt("%s\n", info->deriver ? store->printStorePath(*info->deriver) : "unknown-deriver");
|
||||
}
|
||||
break;
|
||||
|
||||
case qValidDerivers: {
|
||||
StorePathSet result;
|
||||
for (auto & i : opArgs) {
|
||||
auto derivers = aio.blockOn(store->queryValidDerivers(store->followLinksToStorePath(i)));
|
||||
auto derivers =
|
||||
aio.blockOn(store->queryValidDerivers(store->followLinksToStorePath(i)));
|
||||
for (const auto & i : derivers) {
|
||||
result.insert(i);
|
||||
}
|
||||
}
|
||||
auto sorted = aio.blockOn(store->topoSortPaths(result));
|
||||
for (StorePaths::reverse_iterator i = sorted.rbegin(); i != sorted.rend(); ++i) {
|
||||
pager << fmt("%s\n", store->printStorePath(*i));
|
||||
}
|
||||
for (StorePaths::reverse_iterator i = sorted.rbegin();
|
||||
i != sorted.rend(); ++i)
|
||||
cout << fmt("%s\n", store->printStorePath(*i));
|
||||
break;
|
||||
}
|
||||
|
||||
case qBinding:
|
||||
for (auto & i : opArgs) {
|
||||
auto path = aio.blockOn(useDeriver(store, store->followLinksToStorePath(i)));
|
||||
auto path = aio.blockOn(useDeriver(store->followLinksToStorePath(i)));
|
||||
Derivation drv = aio.blockOn(store->derivationFromPath(path));
|
||||
StringPairs::iterator j = drv.env.find(bindingName);
|
||||
if (j == drv.env.end()) {
|
||||
throw Error(
|
||||
"derivation '%s' has no environment binding named '%s'",
|
||||
store->printStorePath(path),
|
||||
bindingName
|
||||
);
|
||||
}
|
||||
pager << fmt("%s\n", j->second);
|
||||
if (j == drv.env.end())
|
||||
throw Error("derivation '%s' has no environment binding named '%s'",
|
||||
store->printStorePath(path), bindingName);
|
||||
cout << fmt("%s\n", j->second);
|
||||
}
|
||||
break;
|
||||
|
||||
case qHash:
|
||||
case qSize:
|
||||
for (auto & i : opArgs) {
|
||||
for (auto & j : aio.blockOn(
|
||||
maybeUseOutputs(store, store->followLinksToStorePath(i), useOutput, forceRealise)
|
||||
))
|
||||
{
|
||||
for (auto & j : aio.blockOn(maybeUseOutputs(store->followLinksToStorePath(i), useOutput, forceRealise))) {
|
||||
auto info = aio.blockOn(store->queryPathInfo(j));
|
||||
if (query == qHash) {
|
||||
assert(info->narHash.type == HashType::SHA256);
|
||||
pager << fmt("%s\n", info->narHash.to_string(HashFormat::Base32));
|
||||
} else if (query == qSize) {
|
||||
pager << fmt("%d\n", info->narSize);
|
||||
}
|
||||
cout << fmt("%s\n", info->narHash.to_string(Base::Base32, true));
|
||||
} else if (query == qSize)
|
||||
cout << fmt("%d\n", info->narSize);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case qTree: {
|
||||
StorePathSet done;
|
||||
for (auto & i : opArgs) {
|
||||
std::stringstream tmp;
|
||||
printTree(tmp, store, aio, store->followLinksToStorePath(i), "", "", done);
|
||||
pager << tmp.str();
|
||||
}
|
||||
for (auto & i : opArgs)
|
||||
printTree(aio, store->followLinksToStorePath(i), "", "", done);
|
||||
break;
|
||||
}
|
||||
|
||||
case qGraph: {
|
||||
StorePathSet roots;
|
||||
for (auto & i : opArgs) {
|
||||
for (auto & j : aio.blockOn(
|
||||
maybeUseOutputs(store, store->followLinksToStorePath(i), useOutput, forceRealise)
|
||||
))
|
||||
{
|
||||
for (auto & i : opArgs)
|
||||
for (auto & j : aio.blockOn(maybeUseOutputs(store->followLinksToStorePath(i), useOutput, forceRealise)))
|
||||
roots.insert(j);
|
||||
}
|
||||
}
|
||||
pager << aio.blockOn(formatDotGraph(ref<Store>::unsafeFromPtr(store), std::move(roots)));
|
||||
aio.blockOn(printDotGraph(ref<Store>::unsafeFromPtr(store), std::move(roots)));
|
||||
break;
|
||||
}
|
||||
|
||||
case qGraphML: {
|
||||
StorePathSet roots;
|
||||
for (auto & i : opArgs) {
|
||||
for (auto & j : aio.blockOn(
|
||||
maybeUseOutputs(store, store->followLinksToStorePath(i), useOutput, forceRealise)
|
||||
))
|
||||
{
|
||||
for (auto & i : opArgs)
|
||||
for (auto & j : aio.blockOn(maybeUseOutputs(store->followLinksToStorePath(i), useOutput, forceRealise)))
|
||||
roots.insert(j);
|
||||
}
|
||||
}
|
||||
pager << aio.blockOn(formatGraphML(ref<Store>::unsafeFromPtr(store), std::move(roots)));
|
||||
aio.blockOn(printGraphML(ref<Store>::unsafeFromPtr(store), std::move(roots)));
|
||||
break;
|
||||
}
|
||||
|
||||
case qResolve: {
|
||||
for (auto & i : opArgs) {
|
||||
pager << fmt("%s\n", store->printStorePath(store->followLinksToStorePath(i)));
|
||||
}
|
||||
for (auto & i : opArgs)
|
||||
cout << fmt("%s\n", store->printStorePath(store->followLinksToStorePath(i)));
|
||||
break;
|
||||
}
|
||||
|
||||
case qRoots: {
|
||||
StorePathSet args;
|
||||
for (auto & i : opArgs) {
|
||||
for (auto & p : aio.blockOn(
|
||||
maybeUseOutputs(store, store->followLinksToStorePath(i), useOutput, forceRealise)
|
||||
))
|
||||
{
|
||||
for (auto & i : opArgs)
|
||||
for (auto & p : aio.blockOn(maybeUseOutputs(store->followLinksToStorePath(i), useOutput, forceRealise)))
|
||||
args.insert(p);
|
||||
}
|
||||
}
|
||||
|
||||
StorePathSet referrers;
|
||||
aio.blockOn(store->computeFSClosure(
|
||||
args, referrers, true, settings.gcKeepOutputs, settings.gcKeepDerivations
|
||||
));
|
||||
args, referrers, true, settings.gcKeepOutputs, settings.gcKeepDerivations));
|
||||
|
||||
auto & gcStore = require<GcStore>(*store);
|
||||
Roots roots = aio.blockOn(gcStore.findRoots(false));
|
||||
for (auto & [target, links] : roots) {
|
||||
if (referrers.find(target) != referrers.end()) {
|
||||
for (auto & link : links) {
|
||||
pager << fmt("%1% -> %2%\n", link, gcStore.printStorePath(target));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto & [target, links] : roots)
|
||||
if (referrers.find(target) != referrers.end())
|
||||
for (auto & link : links)
|
||||
cout << fmt("%1% -> %2%\n", link, gcStore.printStorePath(target));
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
opPrintEnv(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
|
||||
static void opPrintEnv(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
if (!opFlags.empty()) throw UsageError("unknown flag");
|
||||
if (opArgs.size() != 1) throw UsageError("'--print-env' requires one derivation store path");
|
||||
@@ -587,27 +520,26 @@ opPrintEnv(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Str
|
||||
cout << "'\n";
|
||||
}
|
||||
|
||||
static void
|
||||
opReadLog(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
|
||||
static void opReadLog(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
if (!opFlags.empty()) throw UsageError("unknown flag");
|
||||
|
||||
auto & logStore = require<LogStore>(*store);
|
||||
|
||||
withPager([&](Pager & pager) {
|
||||
for (auto & i : opArgs) {
|
||||
auto path = logStore.followLinksToStorePath(i);
|
||||
auto log = aio.blockOn(logStore.getBuildLog(path));
|
||||
if (!log) {
|
||||
throw Error("build log of derivation '%s' is not available", logStore.printStorePath(path));
|
||||
}
|
||||
pager << *log;
|
||||
}
|
||||
});
|
||||
RunPager pager;
|
||||
|
||||
for (auto & i : opArgs) {
|
||||
auto path = logStore.followLinksToStorePath(i);
|
||||
auto log = aio.blockOn(logStore.getBuildLog(path));
|
||||
if (!log)
|
||||
throw Error("build log of derivation '%s' is not available", logStore.printStorePath(path));
|
||||
std::cout << *log;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
opDumpDB(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
|
||||
static void opDumpDB(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
if (!opFlags.empty()) throw UsageError("unknown flag");
|
||||
if (!opArgs.empty()) {
|
||||
@@ -622,13 +554,8 @@ opDumpDB(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strin
|
||||
}
|
||||
}
|
||||
|
||||
static void registerValidity(
|
||||
std::shared_ptr<Store> store,
|
||||
AsyncIoRoot & aio,
|
||||
bool reregister,
|
||||
bool hashGiven,
|
||||
bool canonicalise
|
||||
)
|
||||
|
||||
static void registerValidity(AsyncIoRoot & aio, bool reregister, bool hashGiven, bool canonicalise)
|
||||
{
|
||||
ValidPathInfos infos;
|
||||
|
||||
@@ -651,20 +578,20 @@ static void registerValidity(
|
||||
}
|
||||
}
|
||||
|
||||
aio.blockOn(ensureLocalStore(store)->registerValidPaths(infos));
|
||||
aio.blockOn(ensureLocalStore()->registerValidPaths(infos));
|
||||
}
|
||||
|
||||
static void
|
||||
opLoadDB(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
|
||||
static void opLoadDB(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
if (!opFlags.empty()) throw UsageError("unknown flag");
|
||||
if (!opArgs.empty())
|
||||
throw UsageError("no arguments expected");
|
||||
registerValidity(store, aio, true, true, false);
|
||||
registerValidity(aio, true, true, false);
|
||||
}
|
||||
|
||||
static void
|
||||
opRegisterValidity(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
|
||||
static void opRegisterValidity(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
bool reregister = false; // !!! maybe this should be the default
|
||||
bool hashGiven = false;
|
||||
@@ -676,11 +603,11 @@ opRegisterValidity(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFl
|
||||
|
||||
if (!opArgs.empty()) throw UsageError("no arguments expected");
|
||||
|
||||
registerValidity(store, aio, reregister, hashGiven, true);
|
||||
registerValidity(aio, reregister, hashGiven, true);
|
||||
}
|
||||
|
||||
static void
|
||||
opCheckValidity(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
|
||||
static void opCheckValidity(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
bool printInvalid = false;
|
||||
|
||||
@@ -699,7 +626,8 @@ opCheckValidity(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags
|
||||
}
|
||||
}
|
||||
|
||||
static void opGC(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
|
||||
static void opGC(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
bool printRoots = false;
|
||||
GCOptions options;
|
||||
@@ -732,8 +660,12 @@ static void opGC(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlag
|
||||
}
|
||||
|
||||
else {
|
||||
PrintFreed freed(options.action, results);
|
||||
PrintFreed freed(options.action == GCOptions::gcDeleteDead, results);
|
||||
aio.blockOn(gcStore.collectGarbage(options, results));
|
||||
|
||||
if (options.action != GCOptions::gcDeleteDead)
|
||||
for (auto & i : results.paths)
|
||||
cout << i << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -741,8 +673,7 @@ static void opGC(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlag
|
||||
/* Remove paths from the Nix store if possible (i.e., if they do not
|
||||
have any remaining referrers and are not reachable from any GC
|
||||
roots). */
|
||||
static void
|
||||
opDelete(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
static void opDelete(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
GCOptions options;
|
||||
options.action = GCOptions::gcDeleteSpecific;
|
||||
@@ -766,13 +697,13 @@ opDelete(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strin
|
||||
auto & gcStore = require<GcStore>(*store);
|
||||
|
||||
GCResults results;
|
||||
PrintFreed freed(options.action, results);
|
||||
PrintFreed freed(true, results);
|
||||
aio.blockOn(gcStore.collectGarbage(options, results));
|
||||
}
|
||||
|
||||
|
||||
/* Dump a path as a Nix archive. The archive is written to stdout */
|
||||
static void opDump(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
static void opDump(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
if (!opFlags.empty()) throw UsageError("unknown flag");
|
||||
if (opArgs.size() != 1) throw UsageError("only one argument allowed");
|
||||
@@ -785,8 +716,7 @@ static void opDump(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFl
|
||||
|
||||
|
||||
/* Restore a value from a Nix archive. The archive is read from stdin. */
|
||||
static void
|
||||
opRestore(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
static void opRestore(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
if (!opFlags.empty()) throw UsageError("unknown flag");
|
||||
if (opArgs.size() != 1) throw UsageError("only one argument allowed");
|
||||
@@ -795,8 +725,8 @@ opRestore(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Stri
|
||||
restorePath(*opArgs.begin(), source);
|
||||
}
|
||||
|
||||
static void
|
||||
opExport(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
|
||||
static void opExport(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
for (auto & i : opFlags)
|
||||
throw UsageError("unknown flag '%1%'", i);
|
||||
@@ -811,8 +741,8 @@ opExport(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strin
|
||||
sink.flush();
|
||||
}
|
||||
|
||||
static void
|
||||
opImport(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
|
||||
static void opImport(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
for (auto & i : opFlags)
|
||||
throw UsageError("unknown flag '%1%'", i);
|
||||
@@ -828,7 +758,7 @@ opImport(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strin
|
||||
|
||||
|
||||
/* Initialise the Nix databases. */
|
||||
static void opInit(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
static void opInit(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
if (!opFlags.empty()) throw UsageError("unknown flag");
|
||||
if (!opArgs.empty())
|
||||
@@ -839,8 +769,7 @@ static void opInit(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFl
|
||||
|
||||
|
||||
/* Verify the consistency of the Nix environment. */
|
||||
static void
|
||||
opVerify(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
static void opVerify(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
if (!opArgs.empty())
|
||||
throw UsageError("no arguments expected");
|
||||
@@ -854,15 +783,14 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Verify whether the contents of the given store path have not changed. */
|
||||
static void
|
||||
opVerifyPath(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
static void opVerifyPath(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
if (!opFlags.empty())
|
||||
throw UsageError("no flags expected");
|
||||
@@ -874,15 +802,13 @@ opVerifyPath(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, S
|
||||
printMsg(lvlTalkative, "checking path '%s'...", store->printStorePath(path));
|
||||
auto info = aio.blockOn(store->queryPathInfo(path));
|
||||
HashSink sink(info->narHash.type);
|
||||
aio.blockOn(aio.blockOn(store->narFromPath(path))->drainInto(sink));
|
||||
aio.blockOn(store->narFromPath(path))->drainInto(sink);
|
||||
auto current = sink.finish();
|
||||
if (current.first != info->narHash) {
|
||||
printError(
|
||||
"path '%s' was modified! expected hash '%s', got '%s'",
|
||||
printError("path '%s' was modified! expected hash '%s', got '%s'",
|
||||
store->printStorePath(path),
|
||||
info->narHash.to_string(),
|
||||
current.first.to_string()
|
||||
);
|
||||
info->narHash.to_string(Base::SRI, true),
|
||||
current.first.to_string(Base::SRI, true));
|
||||
status = 1;
|
||||
}
|
||||
}
|
||||
@@ -893,8 +819,7 @@ opVerifyPath(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, S
|
||||
|
||||
/* Repair the contents of the given path by redownloading it using a
|
||||
substituter (if available). */
|
||||
static void
|
||||
opRepairPath(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
static void opRepairPath(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
if (!opFlags.empty())
|
||||
throw UsageError("no flags expected");
|
||||
@@ -905,8 +830,7 @@ opRepairPath(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, S
|
||||
|
||||
/* Optimise the disk space usage of the Nix store by hard-linking
|
||||
files with the same contents. */
|
||||
static void
|
||||
opOptimise(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
static void opOptimise(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
if (!opArgs.empty() || !opFlags.empty())
|
||||
throw UsageError("no arguments expected");
|
||||
@@ -915,8 +839,7 @@ opOptimise(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Str
|
||||
}
|
||||
|
||||
/* Serve the nix store in a way usable by a restricted ssh user. */
|
||||
static void
|
||||
opServe(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
static void opServe(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
bool writeAllowed = false;
|
||||
for (auto & i : opFlags)
|
||||
@@ -929,19 +852,17 @@ 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,
|
||||
.store = *store,
|
||||
.version = clientVersion,
|
||||
};
|
||||
ServeProto::WriteConn wconn {
|
||||
.store = *store,
|
||||
.version = clientVersion,
|
||||
};
|
||||
|
||||
@@ -951,12 +872,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");
|
||||
}
|
||||
@@ -966,19 +887,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;
|
||||
}
|
||||
@@ -986,9 +907,9 @@ 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);
|
||||
auto paths = ServeProto::Serialise<StorePathSet>::read(rconn);
|
||||
bool lock = readInt(in);
|
||||
bool substitute = readInt(in);
|
||||
auto paths = ServeProto::Serialise<StorePathSet>::read(*store, rconn);
|
||||
if (lock && writeAllowed)
|
||||
for (auto & path : paths)
|
||||
aio.blockOn(store->addTempRoot(path));
|
||||
@@ -998,18 +919,18 @@ opServe(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
|
||||
}
|
||||
|
||||
auto valid = aio.blockOn(store->queryValidPaths(paths));
|
||||
out << ServeProto::write(wconn, valid);
|
||||
out << ServeProto::write(*store, wconn, valid);
|
||||
break;
|
||||
}
|
||||
|
||||
case ServeProto::Command::QueryPathInfos: {
|
||||
auto paths = ServeProto::Serialise<StorePathSet>::read(rconn);
|
||||
auto paths = ServeProto::Serialise<StorePathSet>::read(*store, rconn);
|
||||
// !!! Maybe we want a queryPathInfos?
|
||||
for (auto & i : paths) {
|
||||
try {
|
||||
auto info = aio.blockOn(store->queryPathInfo(i));
|
||||
out << store->printStorePath(info->path);
|
||||
out << ServeProto::write(wconn, static_cast<const UnkeyedValidPathInfo &>(*info));
|
||||
out << ServeProto::write(*store, wconn, static_cast<const UnkeyedValidPathInfo &>(*info));
|
||||
} catch (InvalidPath &) {
|
||||
}
|
||||
}
|
||||
@@ -1018,8 +939,8 @@ opServe(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
|
||||
}
|
||||
|
||||
case ServeProto::Command::DumpStorePath:
|
||||
aio.blockOn(aio.blockOn(store->narFromPath(store->parseStorePath(readString(in))))
|
||||
->drainInto(out));
|
||||
aio.blockOn(store->narFromPath(store->parseStorePath(readString(in))))
|
||||
->drainInto(out);
|
||||
break;
|
||||
|
||||
case ServeProto::Command::ImportPaths: {
|
||||
@@ -1030,9 +951,9 @@ 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
|
||||
ServeProto::Serialise<StorePathSet>::read(*store, rconn), out
|
||||
));
|
||||
break;
|
||||
}
|
||||
@@ -1071,20 +992,20 @@ opServe(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
|
||||
MonitorFdHup monitor(in.fd);
|
||||
auto status = aio.blockOn(store->buildDerivation(drvPath, drv));
|
||||
|
||||
out << ServeProto::write(wconn, status);
|
||||
out << ServeProto::write(*store, wconn, status);
|
||||
break;
|
||||
}
|
||||
|
||||
case ServeProto::Command::QueryClosure: {
|
||||
bool includeOutputs = readNum<unsigned>(in);
|
||||
bool includeOutputs = readInt(in);
|
||||
StorePathSet closure;
|
||||
aio.blockOn(store->computeFSClosure(
|
||||
ServeProto::Serialise<StorePathSet>::read(rconn),
|
||||
ServeProto::Serialise<StorePathSet>::read(*store, rconn),
|
||||
closure,
|
||||
false,
|
||||
includeOutputs
|
||||
));
|
||||
out << ServeProto::write(wconn, closure);
|
||||
out << ServeProto::write(*store, wconn, closure);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1099,44 +1020,14 @@ 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);
|
||||
info.references = ServeProto::Serialise<StorePathSet>::read(*store, rconn);
|
||||
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};
|
||||
|
||||
@@ -1158,9 +1049,8 @@ opServe(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
|
||||
}
|
||||
}
|
||||
|
||||
static void opGenerateBinaryCacheKey(
|
||||
std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs
|
||||
)
|
||||
|
||||
static void opGenerateBinaryCacheKey(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
for (auto & i : opFlags)
|
||||
throw UsageError("unknown flag '%1%'", i);
|
||||
@@ -1178,8 +1068,8 @@ static void opGenerateBinaryCacheKey(
|
||||
writeFile(secretKeyFile, secretKey.to_string());
|
||||
}
|
||||
|
||||
static void
|
||||
opVersion(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
|
||||
static void opVersion(AsyncIoRoot & aio, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
printVersion("nix-store");
|
||||
}
|
||||
@@ -1324,11 +1214,10 @@ static int main_nix_store(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
if (showHelp) showManPage("nix-store" + opName);
|
||||
if (!op) throw UsageError("no operation specified");
|
||||
|
||||
std::shared_ptr<Store> store;
|
||||
if (op != opDump && op != opRestore) /* !!! hack */
|
||||
store = aio.blockOn(openStore());
|
||||
|
||||
op(store, aio, std::move(opFlags), std::move(opArgs));
|
||||
op(aio, std::move(opFlags), std::move(opArgs));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
+28
-35
@@ -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,
|
||||
@@ -46,30 +44,23 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
|
||||
|
||||
auto attrs = state.ctx.buildBindings(7 + outputs.size());
|
||||
|
||||
attrs.alloc(state.ctx.symbols.sym_type) = {NewValueAs::string, "derivation"};
|
||||
attrs.alloc(state.ctx.symbols.sym_name) = {NewValueAs::string, i.queryName(state)};
|
||||
attrs.alloc(state.ctx.s.type).mkString("derivation");
|
||||
attrs.alloc(state.ctx.s.name).mkString(i.queryName(state));
|
||||
auto system = i.querySystem(state);
|
||||
if (!system.empty())
|
||||
attrs.alloc(state.ctx.symbols.sym_system) = {NewValueAs::string, system};
|
||||
attrs.alloc(state.ctx.symbols.sym_outPath) = {
|
||||
NewValueAs::string, state.ctx.store->printStorePath(i.queryOutPath(state))
|
||||
};
|
||||
attrs.alloc(state.ctx.s.system).mkString(system);
|
||||
attrs.alloc(state.ctx.s.outPath).mkString(state.ctx.store->printStorePath(i.queryOutPath(state)));
|
||||
if (drvPath)
|
||||
attrs.alloc(state.ctx.symbols.sym_drvPath) = {
|
||||
NewValueAs::string, state.ctx.store->printStorePath(*drvPath)
|
||||
};
|
||||
attrs.alloc(state.ctx.s.drvPath).mkString(state.ctx.store->printStorePath(*drvPath));
|
||||
|
||||
// Copy each output meant for installation.
|
||||
auto & vOutputs = attrs.alloc(state.ctx.symbols.sym_outputs);
|
||||
auto outputsList = state.ctx.mem.newList(outputs.size());
|
||||
vOutputs = {NewValueAs::list, outputsList};
|
||||
auto & vOutputs = attrs.alloc(state.ctx.s.outputs);
|
||||
vOutputs = state.ctx.mem.newList(outputs.size());
|
||||
for (const auto & [m, j] : enumerate(outputs)) {
|
||||
outputsList->elems[m] = {NewValueAs::string, j.first};
|
||||
(vOutputs.listElems()[m] = state.ctx.mem.allocValue())->mkString(j.first);
|
||||
auto outputAttrs = state.ctx.buildBindings(2);
|
||||
outputAttrs.alloc(state.ctx.symbols.sym_outPath) = {
|
||||
NewValueAs::string, state.ctx.store->printStorePath(*j.second)
|
||||
};
|
||||
attrs.alloc(j.first) = {NewValueAs::attrs, outputAttrs};
|
||||
outputAttrs.alloc(state.ctx.s.outPath).mkString(state.ctx.store->printStorePath(*j.second));
|
||||
attrs.alloc(j.first).mkAttrs(outputAttrs);
|
||||
|
||||
/* This is only necessary when installing store paths, e.g.,
|
||||
`nix-env -i /nix/store/abcd...-foo'. */
|
||||
@@ -84,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.symbols.sym_meta) = {NewValueAs::attrs, meta};
|
||||
attrs.alloc(state.ctx.s.meta).mkAttrs(meta);
|
||||
|
||||
manifest->elems[n++] = {NewValueAs::attrs, attrs};
|
||||
(manifest.listElems()[n++] = state.ctx.mem.allocValue())->mkAttrs(attrs);
|
||||
|
||||
if (drvPath) references.insert(*drvPath);
|
||||
}
|
||||
@@ -98,33 +89,35 @@ 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));
|
||||
|
||||
/* Get the environment builder expression. */
|
||||
Value envBuilder = state.eval(state.ctx.parseExprFromString(
|
||||
#include "buildenv.nix.gen.hh"
|
||||
, CanonPath::root
|
||||
));
|
||||
Value envBuilder;
|
||||
state.eval(state.ctx.parseExprFromString(
|
||||
#include "buildenv.nix.gen.hh"
|
||||
, CanonPath::root), envBuilder);
|
||||
|
||||
/* Construct a Nix expression that calls the user environment
|
||||
builder with the manifest as argument. */
|
||||
auto attrs = state.ctx.buildBindings(3);
|
||||
state.ctx.paths.mkStorePathString(manifestFile, attrs.alloc("manifest"));
|
||||
attrs.insert(state.ctx.symbols.create("derivations"), vManifest);
|
||||
Value args = {NewValueAs::attrs, attrs};
|
||||
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.symbols.sym_drvPath));
|
||||
auto topLevelDrv = state.coerceToStorePath(aDrvPath.pos, aDrvPath.value, context, "");
|
||||
const Attr & aOutPath(*topLevel.attrs()->get(state.ctx.symbols.sym_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");
|
||||
|
||||
@@ -13,9 +13,9 @@ namespace nix {
|
||||
bool MY_TYPE ::operator COMPARATOR (const MY_TYPE & other) const \
|
||||
{ \
|
||||
const MY_TYPE* me = this; \
|
||||
auto fields1 = std::tie(me->drvPath, me->FIELD); \
|
||||
auto fields1 = std::tie(*me->drvPath, me->FIELD); \
|
||||
me = &other; \
|
||||
auto fields2 = std::tie(me->drvPath, me->FIELD); \
|
||||
auto fields2 = std::tie(*me->drvPath, me->FIELD); \
|
||||
return fields1 COMPARATOR fields2; \
|
||||
}
|
||||
#define CMP(CHILD_TYPE, MY_TYPE, FIELD) \
|
||||
@@ -23,6 +23,10 @@ namespace nix {
|
||||
CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, !=) \
|
||||
CMP_ONE(CHILD_TYPE, MY_TYPE, FIELD, <)
|
||||
|
||||
#define FIELD_TYPE std::pair<std::string, StorePath>
|
||||
CMP(SingleBuiltPath, SingleBuiltPathBuilt, output)
|
||||
#undef FIELD_TYPE
|
||||
|
||||
#define FIELD_TYPE std::map<std::string, StorePath>
|
||||
CMP(SingleBuiltPath, BuiltPathBuilt, outputs)
|
||||
#undef FIELD_TYPE
|
||||
@@ -30,6 +34,16 @@ CMP(SingleBuiltPath, BuiltPathBuilt, outputs)
|
||||
#undef CMP
|
||||
#undef CMP_ONE
|
||||
|
||||
StorePath SingleBuiltPath::outPath() const
|
||||
{
|
||||
return std::visit(
|
||||
overloaded{
|
||||
[](const SingleBuiltPath::Opaque & p) { return p.path; },
|
||||
[](const SingleBuiltPath::Built & b) { return b.output.second; },
|
||||
}, raw()
|
||||
);
|
||||
}
|
||||
|
||||
StorePathSet BuiltPath::outPaths() const
|
||||
{
|
||||
return std::visit(
|
||||
@@ -45,10 +59,32 @@ StorePathSet BuiltPath::outPaths() const
|
||||
);
|
||||
}
|
||||
|
||||
SingleDerivedPath::Built SingleBuiltPath::Built::discardOutputPath() const
|
||||
{
|
||||
return SingleDerivedPath::Built {
|
||||
.drvPath = make_ref<SingleDerivedPath>(drvPath->discardOutputPath()),
|
||||
.output = output.first,
|
||||
};
|
||||
}
|
||||
|
||||
SingleDerivedPath SingleBuiltPath::discardOutputPath() const
|
||||
{
|
||||
return std::visit(
|
||||
overloaded{
|
||||
[](const SingleBuiltPath::Opaque & p) -> SingleDerivedPath {
|
||||
return p;
|
||||
},
|
||||
[](const SingleBuiltPath::Built & b) -> SingleDerivedPath {
|
||||
return b.discardOutputPath();
|
||||
},
|
||||
}, raw()
|
||||
);
|
||||
}
|
||||
|
||||
kj::Promise<Result<JSON>> BuiltPath::Built::toJSON(const Store & store) const
|
||||
try {
|
||||
JSON res;
|
||||
res["drvPath"] = TRY_AWAIT(drvPath.toJSON(store));
|
||||
res["drvPath"] = TRY_AWAIT(drvPath->toJSON(store));
|
||||
for (const auto & [outputName, outputPath] : outputs) {
|
||||
res["outputs"][outputName] = store.printStorePath(outputPath);
|
||||
}
|
||||
@@ -57,6 +93,36 @@ try {
|
||||
co_return result::current_exception();
|
||||
}
|
||||
|
||||
kj::Promise<Result<JSON>> SingleBuiltPath::Built::toJSON(const Store & store) const
|
||||
try {
|
||||
JSON res;
|
||||
res["drvPath"] = TRY_AWAIT(drvPath->toJSON(store));
|
||||
auto & [outputName, outputPath] = output;
|
||||
res["output"] = outputName;
|
||||
res["outputPath"] = store.printStorePath(outputPath);
|
||||
co_return res;
|
||||
} catch (...) {
|
||||
co_return result::current_exception();
|
||||
}
|
||||
|
||||
kj::Promise<Result<JSON>> SingleBuiltPath::toJSON(const Store & store) const
|
||||
try {
|
||||
co_return TRY_AWAIT(std::visit([&](const auto & buildable) {
|
||||
return buildable.toJSON(store);
|
||||
}, raw()));
|
||||
} catch (...) {
|
||||
co_return result::current_exception();
|
||||
}
|
||||
|
||||
|
||||
kj::Promise<Result<JSON>> BuiltPath::toJSON(const Store & store) const
|
||||
try {
|
||||
co_return TRY_AWAIT(std::visit([&](const auto & buildable) {
|
||||
return buildable.toJSON(store);
|
||||
}, raw()));
|
||||
} catch (...) {
|
||||
co_return result::current_exception();
|
||||
}
|
||||
|
||||
kj::Promise<Result<RealisedPath::Set>> BuiltPath::toRealisedPaths(Store & store) const
|
||||
try {
|
||||
@@ -75,10 +141,24 @@ try {
|
||||
[&](const BuiltPath::Built & p) -> kj::Promise<Result<void>> {
|
||||
try {
|
||||
auto drvHashes = TRY_AWAIT(
|
||||
staticOutputHashes(store, TRY_AWAIT(store.readDerivation(p.drvPath.path)))
|
||||
staticOutputHashes(store, TRY_AWAIT(store.readDerivation(p.drvPath->outPath())))
|
||||
);
|
||||
for (auto& [outputName, outputPath] : p.outputs) {
|
||||
res.insert(outputPath);
|
||||
if (experimentalFeatureSettings.isEnabled(
|
||||
Xp::CaDerivations)) {
|
||||
auto drvOutput = get(drvHashes, outputName);
|
||||
if (!drvOutput)
|
||||
throw Error(
|
||||
"the derivation '%s' has unrealised output '%s' (derived-path.cc/toRealisedPaths)",
|
||||
store.printStorePath(p.drvPath->outPath()), outputName);
|
||||
auto thisRealisation = TRY_AWAIT(store.queryRealisation(
|
||||
DrvOutput{*drvOutput, outputName}));
|
||||
assert(thisRealisation); // We’ve built it, so we must
|
||||
// have the realisation
|
||||
res.insert(*thisRealisation);
|
||||
} else {
|
||||
res.insert(outputPath);
|
||||
}
|
||||
}
|
||||
co_return result::success();
|
||||
} catch (...) {
|
||||
|
||||
@@ -7,15 +7,63 @@
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct SingleBuiltPath;
|
||||
|
||||
struct SingleBuiltPathBuilt {
|
||||
ref<SingleBuiltPath> drvPath;
|
||||
std::pair<std::string, StorePath> output;
|
||||
|
||||
SingleDerivedPathBuilt discardOutputPath() const;
|
||||
|
||||
std::string to_string(const Store & store) const;
|
||||
static SingleBuiltPathBuilt parse(const Store & store, std::string_view, std::string_view);
|
||||
kj::Promise<Result<JSON>> toJSON(const Store & store) const;
|
||||
|
||||
DECLARE_CMP(SingleBuiltPathBuilt);
|
||||
};
|
||||
|
||||
namespace built_path::detail {
|
||||
using SingleBuiltPathRaw = std::variant<
|
||||
DerivedPathOpaque,
|
||||
SingleBuiltPathBuilt
|
||||
>;
|
||||
}
|
||||
|
||||
struct SingleBuiltPath : built_path::detail::SingleBuiltPathRaw {
|
||||
using Raw = built_path::detail::SingleBuiltPathRaw;
|
||||
using Raw::Raw;
|
||||
|
||||
using Opaque = DerivedPathOpaque;
|
||||
using Built = SingleBuiltPathBuilt;
|
||||
|
||||
inline const Raw & raw() const {
|
||||
return static_cast<const Raw &>(*this);
|
||||
}
|
||||
|
||||
StorePath outPath() const;
|
||||
|
||||
SingleDerivedPath discardOutputPath() const;
|
||||
|
||||
static SingleBuiltPath parse(const Store & store, std::string_view);
|
||||
kj::Promise<Result<JSON>> toJSON(const Store & store) const;
|
||||
};
|
||||
|
||||
static inline ref<SingleBuiltPath> staticDrv(StorePath drvPath)
|
||||
{
|
||||
return make_ref<SingleBuiltPath>(SingleBuiltPath::Opaque { drvPath });
|
||||
}
|
||||
|
||||
/**
|
||||
* A built derived path with hints in the form of optional concrete output paths.
|
||||
*
|
||||
* See 'BuiltPath' for more an explanation.
|
||||
*/
|
||||
struct BuiltPathBuilt {
|
||||
DerivedPathOpaque drvPath;
|
||||
ref<SingleBuiltPath> drvPath;
|
||||
std::map<std::string, StorePath> outputs;
|
||||
|
||||
std::string to_string(const Store & store) const;
|
||||
static BuiltPathBuilt parse(const Store & store, std::string_view, std::string_view);
|
||||
kj::Promise<Result<JSON>> toJSON(const Store & store) const;
|
||||
|
||||
DECLARE_CMP(BuiltPathBuilt);
|
||||
@@ -45,6 +93,8 @@ struct BuiltPath : built_path::detail::BuiltPathRaw {
|
||||
|
||||
StorePathSet outPaths() const;
|
||||
kj::Promise<Result<RealisedPath::Set>> toRealisedPaths(Store & store) const;
|
||||
|
||||
kj::Promise<Result<JSON>> toJSON(const Store & store) const;
|
||||
};
|
||||
|
||||
typedef std::vector<BuiltPath> BuiltPaths;
|
||||
|
||||
@@ -20,7 +20,8 @@ DrvInfos queryInstalled(EvalState & state, const Path & userEnv)
|
||||
throw Error("profile '%s' is incompatible with 'nix-env'; please use 'nix profile' instead", userEnv);
|
||||
auto manifestFile = userEnv + "/manifest.nix";
|
||||
if (pathExists(manifestFile)) {
|
||||
Value v = state.evalFile(CanonPath(manifestFile));
|
||||
Value v;
|
||||
state.evalFile(CanonPath(manifestFile), v);
|
||||
Bindings & bindings(*state.ctx.mem.allocBindings(0));
|
||||
getDerivations(state, v, "", bindings, elems, false);
|
||||
}
|
||||
@@ -98,10 +99,6 @@ void ProfileElement::updateStorePaths(
|
||||
for (auto & output : bfd.outputs) {
|
||||
storePaths.insert(output.second);
|
||||
}
|
||||
|
||||
if (settings.envKeepDerivations) {
|
||||
storePaths.insert(bfd.drvPath.path);
|
||||
}
|
||||
},
|
||||
},
|
||||
buildable.raw()
|
||||
|
||||
+12
-24
@@ -4,9 +4,6 @@
|
||||
#include "lix/libstore/derivations.hh"
|
||||
#include "lix/libstore/profiles.hh"
|
||||
#include "lix/libcmd/repl.hh"
|
||||
#include "lix/libutil/async.hh"
|
||||
#include "lix/libutil/c-calls.hh"
|
||||
#include "lix/libutil/error.hh"
|
||||
|
||||
extern char * * environ __attribute__((weak));
|
||||
|
||||
@@ -42,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()
|
||||
@@ -75,22 +71,16 @@ CopyCommand::CopyCommand()
|
||||
});
|
||||
}
|
||||
|
||||
void CopyCommand::run()
|
||||
ref<Store> CopyCommand::createStore()
|
||||
{
|
||||
if (requireStore && srcUri.empty() && dstUri.empty()) {
|
||||
throw UsageError("you must pass '--from' and/or '--to'");
|
||||
}
|
||||
|
||||
StoreCommand::run();
|
||||
}
|
||||
|
||||
ref<Store> CopyCommand::createStore(AsyncIoRoot & in)
|
||||
{
|
||||
return srcUri.empty() ? StoreCommand::createStore(in) : in.blockOn(openStore(srcUri));
|
||||
return srcUri.empty() ? StoreCommand::createStore() : aio().blockOn(openStore(srcUri));
|
||||
}
|
||||
|
||||
ref<Store> CopyCommand::getDstStore()
|
||||
{
|
||||
if (srcUri.empty() && dstUri.empty())
|
||||
throw UsageError("you must pass '--from' and/or '--to'");
|
||||
|
||||
return aio().blockOn(dstUri.empty() ? openStore() : openStore(dstUri));
|
||||
}
|
||||
|
||||
@@ -311,10 +301,8 @@ void MixEnvironment::setEnviron() {
|
||||
throw UsageError("--unset does not make sense with --ignore-environment");
|
||||
|
||||
for (const auto & var : keep) {
|
||||
auto val = sys::getenv(var);
|
||||
if (val) {
|
||||
stringsEnv.emplace_back(fmt("%s=%s", var, val));
|
||||
}
|
||||
auto val = getenv(var.c_str());
|
||||
if (val) stringsEnv.emplace_back(fmt("%s=%s", var.c_str(), val));
|
||||
}
|
||||
|
||||
vectorEnv = stringsToCharPtrs(stringsEnv);
|
||||
@@ -324,7 +312,7 @@ void MixEnvironment::setEnviron() {
|
||||
throw UsageError("--keep does not make sense without --ignore-environment");
|
||||
|
||||
for (const auto & var : unset)
|
||||
(void) sys::unsetenv(var);
|
||||
unsetenv(var.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace nix {
|
||||
|
||||
extern std::string programPath;
|
||||
|
||||
extern char * * savedArgv;
|
||||
|
||||
class EvalState;
|
||||
struct Pos;
|
||||
class Store;
|
||||
@@ -37,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
|
||||
*/
|
||||
@@ -54,12 +56,10 @@ private:
|
||||
struct CopyCommand : virtual StoreCommand
|
||||
{
|
||||
std::string srcUri, dstUri;
|
||||
bool requireStore = true;
|
||||
|
||||
CopyCommand();
|
||||
|
||||
void run() override;
|
||||
ref<Store> createStore(AsyncIoRoot & in) override;
|
||||
ref<Store> createStore() override;
|
||||
|
||||
ref<Store> getDstStore();
|
||||
};
|
||||
@@ -171,7 +171,7 @@ struct RawInstallablesCommand : virtual Args, SourceExprCommand
|
||||
|
||||
std::vector<FlakeRef> getFlakeRefsForCompletion() override;
|
||||
|
||||
protected:
|
||||
private:
|
||||
|
||||
std::vector<std::string> rawInstallables;
|
||||
};
|
||||
@@ -220,11 +220,13 @@ struct MixOperateOnOptions : virtual Args
|
||||
*/
|
||||
struct BuiltPathsCommand : InstallablesCommand, virtual MixOperateOnOptions
|
||||
{
|
||||
protected:
|
||||
private:
|
||||
|
||||
bool recursive = false;
|
||||
bool all = false;
|
||||
|
||||
protected:
|
||||
|
||||
Realise realiseMode = Realise::Derivation;
|
||||
|
||||
public:
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "lix/libstore/store-api.hh"
|
||||
#include "lix/libcmd/command.hh"
|
||||
#include "lix/libutil/async.hh"
|
||||
#include "lix/libutil/error.hh"
|
||||
#include "lix/libutil/regex.hh"
|
||||
|
||||
#include <regex>
|
||||
@@ -17,36 +16,31 @@
|
||||
namespace nix {
|
||||
|
||||
static std::regex const identifierRegex = regex::parse("^[A-Za-z_][A-Za-z0-9_'-]*$");
|
||||
static void checkValidNixIdentifier(const std::string & name)
|
||||
static void warnInvalidNixIdentifier(const std::string & name)
|
||||
{
|
||||
std::smatch match;
|
||||
if (!std::regex_match(name, match, identifierRegex)) {
|
||||
throw UsageError(
|
||||
"This invocation specifies a value for argument '%s' "
|
||||
"which isn't a valid Nix identifier. "
|
||||
"The project is dropping support for this so that it's possible to make e.g. "
|
||||
"'%s' evaluating to '%s' in the future. "
|
||||
"If you depend on this behavior, please reach out in "
|
||||
"<https://git.lix.systems/lix-project/lix/issues/496> so we can discuss your use-case.",
|
||||
name,
|
||||
"--arg config.allowUnfree true",
|
||||
"{ config.allowUnfree = true; }"
|
||||
);
|
||||
warn("This Nix invocation specifies a value for argument '%s' which isn't a valid \
|
||||
Nix identifier. The project is considering to drop support for this \
|
||||
or to require quotes around args that aren't valid Nix identifiers. \
|
||||
If you depend on this behvior, please reach out in \
|
||||
https://git.lix.systems/lix-project/lix/issues/496 so we can discuss \
|
||||
your use-case.", name);
|
||||
}
|
||||
}
|
||||
|
||||
MixEvalArgs::MixEvalArgs()
|
||||
{
|
||||
addFlag(
|
||||
{.longName = "arg",
|
||||
.description = "Pass the value *expr* as the argument *name* to Nix functions.",
|
||||
.category = category,
|
||||
.labels = {"name", "expr"},
|
||||
.handler = {[&](std::string name, std::string expr) {
|
||||
checkValidNixIdentifier(name);
|
||||
autoArgs[name] = 'E' + expr;
|
||||
}}}
|
||||
);
|
||||
addFlag({
|
||||
.longName = "arg",
|
||||
.description = "Pass the value *expr* as the argument *name* to Nix functions.",
|
||||
.category = category,
|
||||
.labels = {"name", "expr"},
|
||||
.handler = {[&](std::string name, std::string expr) {
|
||||
warnInvalidNixIdentifier(name);
|
||||
autoArgs[name] = 'E' + expr;
|
||||
}}
|
||||
});
|
||||
|
||||
addFlag({
|
||||
.longName = "argstr",
|
||||
@@ -54,7 +48,7 @@ MixEvalArgs::MixEvalArgs()
|
||||
.category = category,
|
||||
.labels = {"name", "string"},
|
||||
.handler = {[&](std::string name, std::string s) {
|
||||
checkValidNixIdentifier(name);
|
||||
warnInvalidNixIdentifier(name);
|
||||
autoArgs[name] = 'S' + s;
|
||||
}},
|
||||
});
|
||||
@@ -113,7 +107,7 @@ MixEvalArgs::MixEvalArgs()
|
||||
|
||||
```
|
||||
-I nixpkgs=channel:nixos-21.05
|
||||
-I nixpkgs=https://channels.nixos.org/nixos-21.05/nixexprs.tar.xz
|
||||
-I nixpkgs=https://nixos.org/channels/nixos-21.05/nixexprs.tar.xz
|
||||
```
|
||||
|
||||
You can also fetch source trees using [flake URLs](./nix3-flake.md#url-like-syntax) and add them to the
|
||||
@@ -183,11 +177,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')
|
||||
v = state.evalLazily(state.parseExprFromString(i.second.substr(1), CanonPath::fromCwd()));
|
||||
state.evalLazily(
|
||||
state.parseExprFromString(i.second.substr(1), CanonPath::fromCwd()), *v
|
||||
);
|
||||
else
|
||||
v = {NewValueAs::string, ((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();
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
|
||||
#include "lix/libutil/args.hh"
|
||||
|
||||
namespace nix::cli {
|
||||
template<typename Enum>
|
||||
struct enum_cli_traits;
|
||||
|
||||
template<typename Enum>
|
||||
constexpr std::string_view toString(Enum value)
|
||||
{
|
||||
for (const auto & [name, val] : enum_cli_traits<Enum>::values) {
|
||||
if (val == value) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
std::terminate();
|
||||
}
|
||||
|
||||
template<typename Enum>
|
||||
std::optional<Enum> fromString(std::string_view str)
|
||||
{
|
||||
for (const auto & [name, val] : enum_cli_traits<Enum>::values) {
|
||||
if (name == str) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template<typename Enum>
|
||||
void completeAmongEnumChoices(AddCompletions & completions, size_t, std::string_view prefix)
|
||||
{
|
||||
for (const auto & [name, _] : enum_cli_traits<Enum>::values) {
|
||||
if (name.starts_with(prefix)) {
|
||||
completions.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Enum>
|
||||
Enum parseEnumArg(std::string text)
|
||||
{
|
||||
auto valueOpt = fromString<Enum>(text);
|
||||
|
||||
if (valueOpt) {
|
||||
return *valueOpt;
|
||||
} else {
|
||||
auto names = std::ranges::views::keys(enum_cli_traits<Enum>::values)
|
||||
| std::ranges::to<std::set<std::string>>();
|
||||
auto suggestions = Suggestions::bestMatches(names, text);
|
||||
throw UsageError(suggestions, "'%s' is not a recognised '%s'", text, enum_cli_traits<Enum>::typeName);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Enum>
|
||||
std::optional<Enum> parseOptionalEnumArg(std::string text)
|
||||
{
|
||||
auto target = fromString<Enum>(text);
|
||||
|
||||
if (!target && text != "") {
|
||||
auto names = std::ranges::views::keys(enum_cli_traits<Enum>::values) | std::ranges::to<std::set>();
|
||||
auto suggestions = Suggestions::bestMatches(names, text);
|
||||
throw UsageError(suggestions, "'%s' is not a recognised '%s'", text, enum_cli_traits<Enum>::typeName);
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -77,7 +76,7 @@ DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths(EvalState & state)
|
||||
for (auto & [drvPath, outputs] : byDrvPath)
|
||||
res.push_back({
|
||||
.path = DerivedPath::Built {
|
||||
.drvPath = makeConstantStorePath(drvPath),
|
||||
.drvPath = makeConstantStorePathRef(drvPath),
|
||||
.outputs = outputs,
|
||||
},
|
||||
.info = make_ref<ExtraPathInfoValue>(ExtraPathInfoValue::Value {
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -26,27 +26,36 @@ 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 = makeConstantStorePathRef(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 = make_ref<SingleDerivedPath>(SingleDerivedPath::parse(*store, prefix));
|
||||
drvRequireExperiment(*drv);
|
||||
return DerivedPath::Built {
|
||||
.drvPath = std::move(drv),
|
||||
.outputs = outputSpec,
|
||||
};
|
||||
},
|
||||
}, extendedOutputsSpec.raw);
|
||||
return InstallableDerivedPath {
|
||||
store,
|
||||
std::move(derivedPath),
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include "lix/libexpr/flake/flake.hh"
|
||||
#include "lix/libexpr/eval-cache.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace nix {
|
||||
|
||||
std::vector<std::string> InstallableFlake::getActualAttrPaths()
|
||||
@@ -58,8 +60,7 @@ InstallableFlake::InstallableFlake(
|
||||
|
||||
DerivedPathsWithInfo InstallableFlake::toDerivedPaths(EvalState & state)
|
||||
{
|
||||
auto act =
|
||||
logger->startActivity(lvlTalkative, actUnknown, fmt("evaluating derivation '%s'", what()));
|
||||
Activity act(*logger, lvlTalkative, actUnknown, fmt("evaluating derivation '%s'", what()));
|
||||
|
||||
auto attr = getCursor(state);
|
||||
|
||||
@@ -97,7 +98,7 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths(EvalState & state)
|
||||
|
||||
return {{
|
||||
.path = DerivedPath::Built {
|
||||
.drvPath = makeConstantStorePath(std::move(drvPath)),
|
||||
.drvPath = makeConstantStorePathRef(std::move(drvPath)),
|
||||
.outputs = std::visit(overloaded {
|
||||
[&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec {
|
||||
std::set<std::string> outputsToInstall;
|
||||
@@ -135,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>>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()};
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
+55
-20
@@ -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'");
|
||||
}}
|
||||
});
|
||||
|
||||
@@ -219,7 +219,8 @@ void SourceExprCommand::completeInstallable(EvalState & state, AddCompletions &
|
||||
state.aio.blockOn(lookupFileArg(*evaluator, *file)).unwrap()
|
||||
));
|
||||
|
||||
Value root = state.eval(e);
|
||||
Value root;
|
||||
state.eval(e, root);
|
||||
|
||||
auto autoArgs = getAutoArgs(*evaluator);
|
||||
|
||||
@@ -234,13 +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, 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);
|
||||
@@ -341,7 +344,7 @@ void completeFlakeRefWithFragment(
|
||||
}
|
||||
}
|
||||
} catch (Error & e) {
|
||||
printTaggedWarning("%1%", Uncolored(e.msg()));
|
||||
warn(e.msg());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,11 +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);
|
||||
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;
|
||||
@@ -446,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();
|
||||
vFile = state.eval(e);
|
||||
state.eval(e, *vFile);
|
||||
}
|
||||
else if (file)
|
||||
vFile = state.evalFile(state.aio.blockOn(lookupFileArg(*evaluator, *file)).unwrap());
|
||||
state.evalFile(state.aio.blockOn(lookupFileArg(*evaluator, *file)).unwrap(), *vFile);
|
||||
else {
|
||||
auto & e = evaluator->parseExprFromString(*expr, CanonPath::fromCwd());
|
||||
vFile = state.eval(e);
|
||||
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 {
|
||||
@@ -519,6 +524,36 @@ ref<Installable> SourceExprCommand::parseInstallable(
|
||||
return installables.front();
|
||||
}
|
||||
|
||||
static kj::Promise<Result<SingleBuiltPath>> getBuiltPath(ref<Store> evalStore, ref<Store> store, const SingleDerivedPath & b)
|
||||
try {
|
||||
auto handlers = overloaded{
|
||||
[&](const SingleDerivedPath::Opaque & bo) -> kj::Promise<Result<SingleBuiltPath>> {
|
||||
return {SingleBuiltPath::Opaque { bo.path }};
|
||||
},
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-capturing-lambda-coroutines)
|
||||
[&](const SingleDerivedPath::Built & bfd) -> kj::Promise<Result<SingleBuiltPath>> {
|
||||
try {
|
||||
auto drvPath = TRY_AWAIT(getBuiltPath(evalStore, store, *bfd.drvPath));
|
||||
// Resolving this instead of `bfd` will yield the same result, but avoid duplicative work.
|
||||
SingleDerivedPath::Built truncatedBfd {
|
||||
.drvPath = makeConstantStorePathRef(drvPath.outPath()),
|
||||
.output = bfd.output,
|
||||
};
|
||||
auto outputPath = TRY_AWAIT(resolveDerivedPath(*store, truncatedBfd, &*evalStore));
|
||||
co_return SingleBuiltPath::Built {
|
||||
.drvPath = make_ref<SingleBuiltPath>(std::move(drvPath)),
|
||||
.output = { bfd.output, outputPath },
|
||||
};
|
||||
} catch (...) {
|
||||
co_return result::current_exception();
|
||||
}
|
||||
},
|
||||
};
|
||||
co_return TRY_AWAIT(std::visit(handlers, b.raw()));
|
||||
} catch (...) {
|
||||
co_return result::current_exception();
|
||||
}
|
||||
|
||||
std::vector<BuiltPathWithResult> Installable::build(
|
||||
EvalState & state,
|
||||
ref<Store> evalStore,
|
||||
@@ -607,7 +642,7 @@ std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> Installable::build
|
||||
state.aio.blockOn(resolveDerivedPath(*store, bfd, &*evalStore));
|
||||
res.push_back({aux.installable, {
|
||||
.path = BuiltPath::Built {
|
||||
.drvPath = bfd.drvPath,
|
||||
.drvPath = make_ref<SingleBuiltPath>(state.aio.blockOn(getBuiltPath(evalStore, store, *bfd.drvPath))),
|
||||
.outputs = outputs,
|
||||
},
|
||||
.info = aux.info}});
|
||||
@@ -639,7 +674,7 @@ std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> Installable::build
|
||||
outputs.emplace(outputName, realisation.outPath);
|
||||
res.push_back({aux.installable, {
|
||||
.path = BuiltPath::Built {
|
||||
.drvPath = bfd.drvPath,
|
||||
.drvPath = make_ref<SingleBuiltPath>(state.aio.blockOn(getBuiltPath(evalStore, store, *bfd.drvPath))),
|
||||
.outputs = outputs,
|
||||
},
|
||||
.info = aux.info,
|
||||
@@ -754,7 +789,7 @@ StorePathSet Installable::toDerivations(
|
||||
: throw Error("argument '%s' did not evaluate to a derivation", i->what()));
|
||||
},
|
||||
[&](const DerivedPath::Built & bfd) {
|
||||
drvPaths.insert(bfd.drvPath.path);
|
||||
drvPaths.insert(state.aio.blockOn(resolveDerivedPath(*store, *bfd.drvPath)));
|
||||
},
|
||||
}, b.path.raw());
|
||||
|
||||
|
||||
+3
-17
@@ -5,32 +5,18 @@
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
namespace nix {
|
||||
|
||||
typedef std::function<void(AsyncIoRoot &, std::string, std::list<std::string>)> MainFunction;
|
||||
|
||||
struct LegacyCommandRegistry
|
||||
{
|
||||
typedef std::function<int(AsyncIoRoot &, std::string, std::list<std::string>)> MainFunction;
|
||||
typedef std::function<
|
||||
int(AsyncIoRoot &, std::string, std::list<std::string>, std::span<char *>)>
|
||||
RawMainFunction;
|
||||
|
||||
using LegacyCommandMap = std::map<std::string, RawMainFunction>;
|
||||
using LegacyCommandMap = std::map<std::string, MainFunction>;
|
||||
static LegacyCommandMap * commands;
|
||||
|
||||
static void add(const std::string & name, MainFunction fun)
|
||||
{
|
||||
addWithRaw(
|
||||
name,
|
||||
[fun](AsyncIoRoot & aio, std::string name, std::list<std::string> args, std::span<char *>) {
|
||||
return fun(aio, name, args);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static void addWithRaw(const std::string & name, RawMainFunction fun)
|
||||
{
|
||||
if (!commands) commands = new LegacyCommandMap;
|
||||
(*commands)[name] = fun;
|
||||
|
||||
@@ -5,4 +5,5 @@ includedir=@includedir@
|
||||
Name: Lix (libcmd)
|
||||
Description: Lix Package Manager (libcmd)
|
||||
Version: @PACKAGE_VERSION@
|
||||
Requires: lix
|
||||
Requires: lix-base lix-util
|
||||
Libs: -L${libdir} -llixcmd
|
||||
|
||||
+2
-44
@@ -3,52 +3,12 @@
|
||||
#include "lix/libutil/finally.hh"
|
||||
#include "lix/libutil/terminal.hh"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
#include <new>
|
||||
#include <regex>
|
||||
#include <sys/queue.h>
|
||||
#include <lowdown.h>
|
||||
|
||||
namespace nix {
|
||||
|
||||
static const std::string DOCROOT = "@docroot@";
|
||||
static const std::string DOCROOT_URL = "https://docs.lix.systems/manual/lix/stable";
|
||||
|
||||
static void processLinks(struct lowdown_node * node)
|
||||
{
|
||||
if (node->type == LOWDOWN_LINK) {
|
||||
struct lowdown_buf *link = &node->rndr_link.link;
|
||||
if (link && link->size && std::string_view(link->data, link->size).starts_with(DOCROOT)) {
|
||||
// link starts with @docroot@, replace that and check the path extension too.
|
||||
static std::regex mdRewrite{"\\.md(#.*)?$"}; // NOLINT(lix-foreign-exceptions)
|
||||
|
||||
auto oldLink = std::string_view(link->data, link->size).substr(DOCROOT.size());
|
||||
std::string newLink = DOCROOT_URL;
|
||||
std::regex_replace(
|
||||
std::back_inserter(newLink), oldLink.begin(), oldLink.end(), mdRewrite, ".html$1"
|
||||
);
|
||||
if (link->maxsize < newLink.size()) {
|
||||
// the existing link buffer doesn't have enough space for the new string
|
||||
char *newData;
|
||||
if (!(newData = static_cast<char *>(std::realloc(link->data, newLink.size())))) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
link->data = newData;
|
||||
link->maxsize = newLink.size();
|
||||
}
|
||||
newLink.copy(link->data, newLink.size());
|
||||
link->size = newLink.size();
|
||||
}
|
||||
} else {
|
||||
// recurse into children
|
||||
struct lowdown_node *child;
|
||||
TAILQ_FOREACH(child, &node->children, entries)
|
||||
processLinks(child);
|
||||
}
|
||||
}
|
||||
|
||||
std::string renderMarkdownToTerminal(std::string_view markdown, StandardOutputStream fileno)
|
||||
std::string renderMarkdownToTerminal(std::string_view markdown)
|
||||
{
|
||||
int windowWidth = getWindowSize().second;
|
||||
size_t lowdown_cols = std::max(windowWidth - 5, 60);
|
||||
@@ -80,7 +40,7 @@ std::string renderMarkdownToTerminal(std::string_view markdown, StandardOutputSt
|
||||
.oflags = LOWDOWN_TERM_NOLINK,
|
||||
#endif /* LOWDOWN_CONSOLIDATED_OFLAGS */
|
||||
};
|
||||
if (!shouldANSI(fileno)) {
|
||||
if (!shouldANSI()) {
|
||||
opts.oflags |= LOWDOWN_TERM_NOANSI;
|
||||
}
|
||||
|
||||
@@ -95,8 +55,6 @@ std::string renderMarkdownToTerminal(std::string_view markdown, StandardOutputSt
|
||||
throw Error("cannot parse Markdown document");
|
||||
Finally freeNode([&]() { lowdown_node_free(node); });
|
||||
|
||||
processLinks(node);
|
||||
|
||||
auto renderer = lowdown_term_new(&opts);
|
||||
if (!renderer)
|
||||
throw Error("cannot allocate Markdown renderer");
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "lix/libutil/terminal.hh"
|
||||
#include "lix/libutil/types.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
std::string renderMarkdownToTerminal(std::string_view markdown, StandardOutputStream fileno = StandardOutputStream::Stdout);
|
||||
std::string renderMarkdownToTerminal(std::string_view markdown);
|
||||
|
||||
}
|
||||
|
||||
+54
-3
@@ -1,4 +1,4 @@
|
||||
liblix_sources += files(
|
||||
libcmd_sources = files(
|
||||
'built-path.cc',
|
||||
'cmd-profiles.cc',
|
||||
'command.cc',
|
||||
@@ -21,7 +21,6 @@ libcmd_headers = files(
|
||||
'command.hh',
|
||||
'common-eval-args.hh',
|
||||
'editor-for.hh',
|
||||
'enum-traits.hh',
|
||||
'installable-attr-path.hh',
|
||||
'installable-derived-path.hh',
|
||||
'installable-flake.hh',
|
||||
@@ -33,8 +32,60 @@ libcmd_headers = files(
|
||||
'repl.hh',
|
||||
)
|
||||
|
||||
liblix_generated_headers += [
|
||||
libcmd_generated_headers = [
|
||||
gen_header.process('repl-overlays.nix', preserve_path_from: meson.current_source_dir()),
|
||||
]
|
||||
|
||||
libcmd = library(
|
||||
'lixcmd',
|
||||
libcmd_generated_headers,
|
||||
libcmd_sources,
|
||||
dependencies : [
|
||||
liblixutil,
|
||||
liblixstore,
|
||||
liblixexpr,
|
||||
liblixfetchers,
|
||||
liblixmain,
|
||||
ncurses,
|
||||
editline,
|
||||
lowdown,
|
||||
nlohmann_json,
|
||||
liblix_doc,
|
||||
kj,
|
||||
],
|
||||
# '../..' for self references like "lix/libcmd/*.hh"
|
||||
include_directories : [ '../..' ],
|
||||
cpp_pch : cpp_pch,
|
||||
install : true,
|
||||
# FIXME(Qyriad): is this right?
|
||||
install_rpath : libdir,
|
||||
)
|
||||
|
||||
install_headers(libcmd_headers, subdir : 'lix/libcmd', preserve_path : true)
|
||||
custom_target(
|
||||
command : [ 'cp', '@INPUT@', '@OUTPUT@' ],
|
||||
input : libcmd_generated_headers,
|
||||
output : '@PLAINNAME@',
|
||||
install : true,
|
||||
install_dir : includedir / 'lix/libcmd',
|
||||
)
|
||||
|
||||
liblixcmd = declare_dependency(
|
||||
include_directories : include_directories('../..'),
|
||||
link_with : libcmd,
|
||||
)
|
||||
meson.override_dependency('lix-cmd', liblixcmd)
|
||||
|
||||
# FIXME: not using the pkg-config module because it creates way too many deps
|
||||
# while meson migration is in progress, and we want to not include boost here
|
||||
configure_file(
|
||||
input : 'lix-cmd.pc.in',
|
||||
output : 'lix-cmd.pc',
|
||||
install_dir : libdir / 'pkgconfig',
|
||||
configuration : {
|
||||
'prefix' : prefix,
|
||||
'libdir' : libdir,
|
||||
'includedir' : includedir,
|
||||
'PACKAGE_VERSION' : meson.project_version(),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#include "lix/libutil/c-calls.hh"
|
||||
#include "lix/libutil/error.hh"
|
||||
#include "lix/libutil/file-system.hh"
|
||||
#include "lix/libutil/logging.hh"
|
||||
@@ -48,7 +47,7 @@ char ** copyCompletions(const StringSet& possible)
|
||||
if (vp) {
|
||||
while (--ac >= 0)
|
||||
free(vp[ac]);
|
||||
free(static_cast<void *>(vp));
|
||||
free(vp);
|
||||
}
|
||||
throw Error("allocation failure");
|
||||
}
|
||||
@@ -111,7 +110,7 @@ static el_status_t doCompletion() {
|
||||
if (possible.size() == 1) {
|
||||
const auto completion = *possible.cbegin();
|
||||
if (completion.size() > s.size()) {
|
||||
rl_insert_text(requireCString(completion.substr(s.size())));
|
||||
rl_insert_text(completion.c_str() + s.size());
|
||||
return redisplay();
|
||||
}
|
||||
|
||||
@@ -135,7 +134,7 @@ static el_status_t doCompletion() {
|
||||
}
|
||||
if (len > 0) {
|
||||
auto commonPrefix = possible.begin()->substr(start, len);
|
||||
rl_insert_text(requireCString(commonPrefix));
|
||||
rl_insert_text(commonPrefix.c_str());
|
||||
el_ring_bell();
|
||||
return redisplay();
|
||||
}
|
||||
@@ -155,7 +154,7 @@ ReadlineLikeInteracter::Guard ReadlineLikeInteracter::init(detail::ReplCompleter
|
||||
logWarning(e.info());
|
||||
}
|
||||
el_hist_size = 1000;
|
||||
read_history(requireCString(historyFile));
|
||||
read_history(historyFile.c_str());
|
||||
auto oldRepl = curRepl;
|
||||
curRepl = repl;
|
||||
Guard restoreRepl([oldRepl] { curRepl = oldRepl; });
|
||||
@@ -203,7 +202,7 @@ bool ReadlineLikeInteracter::getLine(std::string & input, ReplPromptType promptT
|
||||
};
|
||||
|
||||
setupSignals();
|
||||
char * s = readline(promptForType(promptType)); // NOLINT(lix-unsafe-c-calls)
|
||||
char * s = readline(promptForType(promptType));
|
||||
Finally doFree([&]() { free(s); });
|
||||
restoreSignals();
|
||||
|
||||
@@ -224,7 +223,7 @@ bool ReadlineLikeInteracter::getLine(std::string & input, ReplPromptType promptT
|
||||
|
||||
void ReadlineLikeInteracter::writeHistory()
|
||||
{
|
||||
int ret = write_history(requireCString(historyFile));
|
||||
int ret = write_history(historyFile.c_str());
|
||||
int writeHistErr = errno;
|
||||
|
||||
if (ret == 0) {
|
||||
@@ -245,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()
|
||||
|
||||
@@ -46,7 +46,7 @@ public:
|
||||
*
|
||||
* This function logs but ignores errors from readline's write_history().
|
||||
*/
|
||||
void writeHistory();
|
||||
virtual void writeHistory();
|
||||
virtual ~ReadlineLikeInteracter() override;
|
||||
};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user