Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f92aaa9f59 | ||
|
|
e70bbff278 | ||
|
|
d2f8263fb8 | ||
|
|
15421962e6 | ||
|
|
a10fd676f6 | ||
|
|
bc506e4f67 | ||
|
|
f156a27cf4 | ||
|
|
7166c00efc | ||
|
|
c08f2336d0 | ||
|
|
dad8a30906 | ||
|
|
dc7d5c45a4 | ||
|
|
fc5ab134d9 | ||
|
|
a05dbd2d7f | ||
|
|
fada71a749 | ||
|
|
7c53a2cf9c | ||
|
|
5789f4b0a9 | ||
|
|
ca40ce8671 | ||
|
|
7f9b734aa5 | ||
|
|
03edd9f75d | ||
|
|
e00ee5954e | ||
|
|
861e5a7471 | ||
|
|
8c03cc961f | ||
|
|
35fbfea6f8 | ||
|
|
fb9a575766 | ||
|
|
c914b9fc53 | ||
|
|
981dd6e9b2 | ||
|
|
31a4f4547a | ||
|
|
b7cf773540 | ||
|
|
6f25c5468d | ||
|
|
8b38edaf1e | ||
|
|
37ccd28c3b | ||
|
|
e8015836e3 | ||
|
|
fc649250ec | ||
|
|
b28723c5b9 | ||
|
|
da37164630 | ||
|
|
135c28be53 | ||
|
|
a2f1369d18 | ||
|
|
b55260ce4a | ||
|
|
7a0a85b543 | ||
|
|
092064db4c | ||
|
|
bd108b7e61 | ||
|
|
ac6c9a9c0a | ||
|
|
6ddc59de47 | ||
|
|
9a408cbeb7 | ||
|
|
74300fd0a7 | ||
|
|
12cee5cb22 | ||
|
|
8623dade34 | ||
|
|
2be998eeb4 | ||
|
|
c6b4f30be9 | ||
|
|
27bf276304 | ||
|
|
30e9387cf9 | ||
|
|
8d389fcccf | ||
|
|
f4c92f8872 | ||
|
|
fae0242f69 | ||
|
|
e8cb30b11c | ||
|
|
f856d4fd25 | ||
|
|
debf5554e0 | ||
|
|
be3a40e5dc | ||
|
|
d52033fc77 | ||
|
|
052d04512e | ||
|
|
dda934965d | ||
|
|
378c86eadf | ||
|
|
298373615e | ||
|
|
14d8209198 | ||
|
|
b2ae7c3965 | ||
|
|
32a96169d6 | ||
|
|
5fb0dd156d | ||
|
|
5f2c771b5a | ||
|
|
b8b69dde05 | ||
|
|
26f1397b75 | ||
|
|
8cf21fcf75 | ||
|
|
4caa622d78 | ||
|
|
f13566a3f6 | ||
|
|
0bcdfe2c43 | ||
|
|
bcab3d5da3 | ||
|
|
72a6d5ff48 | ||
|
|
0741e24266 | ||
|
|
dde7bddbd4 | ||
|
|
f2df62439c | ||
|
|
30ce32e2f6 | ||
|
|
b92f1aa865 | ||
|
|
cbe4d1b339 | ||
|
|
2634585a48 | ||
|
|
f0f8117d5b | ||
|
|
93af25a706 | ||
|
|
c45e34bbc5 | ||
|
|
f864626a1c | ||
|
|
c090a9d1ec | ||
|
|
3327a1fcae | ||
|
|
7976f9fb6e | ||
|
|
b4846d4e51 | ||
|
|
5d11ed07f5 | ||
|
|
4ef9fb3aee | ||
|
|
126ef6321d | ||
|
|
987a906317 | ||
|
|
1df00b7db0 | ||
|
|
e76c6afd8d | ||
|
|
6182a5e659 | ||
|
|
53c0e884d8 | ||
|
|
d14613459b | ||
|
|
3b0e2ddd0a | ||
|
|
6b4de44461 | ||
|
|
1ef7ccfc72 | ||
|
|
62a2663436 | ||
|
|
d641a829bb | ||
|
|
864c5e7507 | ||
|
|
23a0b78a16 | ||
|
|
492d7bbe1f | ||
|
|
6b9a121637 | ||
|
|
b015654133 | ||
|
|
3c1c130c59 | ||
|
|
e8f161ead1 | ||
|
|
d07bbb109e | ||
|
|
c2b61c5da3 | ||
|
|
7a3f352105 | ||
|
|
8b1ca49fef | ||
|
|
d69048e92b | ||
|
|
01ff67595b | ||
|
|
ff8a10f9cd | ||
|
|
46f99487de | ||
|
|
86268bd618 | ||
|
|
c040a9d8e4 | ||
|
|
4fcb434998 | ||
|
|
4deed5ab35 | ||
|
|
03b8a6dcc2 | ||
|
|
fedb4d5ead | ||
|
|
28e1cccb49 | ||
|
|
fc7165401f | ||
|
|
b1776ce4e2 | ||
|
|
fa7edb9e01 | ||
|
|
9adc840eda | ||
|
|
899c5fe9f0 | ||
|
|
2d73b59679 | ||
|
|
8500339d95 | ||
|
|
762a28a698 | ||
|
|
ffefebd9f4 | ||
|
|
c6931c4176 | ||
|
|
04988fa318 | ||
|
|
f31ece23da | ||
|
|
f1ce35f53c | ||
|
|
9520e663c8 | ||
|
|
6e3a7711ca | ||
|
|
9723dc2c89 | ||
|
|
bffbe73493 | ||
|
|
b34f136f10 | ||
|
|
505d0669dc | ||
|
|
ef2fd27467 | ||
|
|
ae4a000576 | ||
|
|
a2c91c000e | ||
|
|
45dfadeafa | ||
|
|
51f1294bfc | ||
|
|
95ac829cf3 | ||
|
|
a99b6f18c8 | ||
|
|
f761785099 | ||
|
|
2b4f1e076c | ||
|
|
879b07fd3d | ||
|
|
b983c15336 | ||
|
|
6063ffead9 | ||
|
|
2cc49da1ec | ||
|
|
909cbb0e40 | ||
|
|
684aba046a | ||
|
|
ba4f4f917a | ||
|
|
8b73cbbb67 | ||
|
|
aa896041e0 | ||
|
|
ac64c727b5 | ||
|
|
1079fe9884 | ||
|
|
b8a9eaf1c8 | ||
|
|
adf17a7b39 | ||
|
|
8bd344718b | ||
|
|
e575a3a930 | ||
|
|
3d77ee8d94 | ||
|
|
3896e265da | ||
|
|
0cc9d01ad0 | ||
|
|
ac7a622b38 | ||
|
|
ced825791a | ||
|
|
f09ed729b5 | ||
|
|
9a31cc7da0 | ||
|
|
52590089df | ||
|
|
dd54f45bc7 | ||
|
|
61e57329eb | ||
|
|
ab6d8f513b | ||
|
|
9ad8136825 | ||
|
|
b908f9135c | ||
|
|
c20aaf6ca7 | ||
|
|
1d227cbe07 | ||
|
|
576ff2f598 | ||
|
|
64d610fddc | ||
|
|
16bd27fb78 | ||
|
|
c76632e17d | ||
|
|
adfbc836c0 | ||
|
|
1042d26c31 | ||
|
|
cf9735f82e | ||
|
|
f5a2251966 | ||
|
|
b41af64d1e | ||
|
|
0d4baa5528 | ||
|
|
7e68f93ed7 | ||
|
|
98d0215ca2 | ||
|
|
17f1bcfd2c | ||
|
|
af166146ff | ||
|
|
51dcc6ac0d | ||
|
|
cbaa172775 | ||
|
|
56dee9186f | ||
|
|
f1fbd1d095 | ||
|
|
4e1c216fd3 | ||
|
|
9ad0ace8c0 | ||
|
|
f289462c59 | ||
|
|
7bd3202175 | ||
|
|
8c857790a3 | ||
|
|
279926d215 | ||
|
|
2e0cc67ec8 | ||
|
|
a2500db977 | ||
|
|
d43c6dd100 | ||
|
|
ab4f8718f9 | ||
|
|
b6c8f412a2 | ||
|
|
05fed0687d | ||
|
|
b23bbd5b19 | ||
|
|
56988d8605 | ||
|
|
bf3d4e8721 | ||
|
|
c9fe3befc1 | ||
|
|
7e8f7d0e66 | ||
|
|
7d3dde851a | ||
|
|
bd458f9f89 | ||
|
|
57373cba6f | ||
|
|
c493fb668e | ||
|
|
9e55dd6b8f | ||
|
|
dbb3edcfea | ||
|
|
986191534e | ||
|
|
ef8a6cc5f5 | ||
|
|
e083a68a9f | ||
|
|
cfabc37828 | ||
|
|
bdc220b8ec | ||
|
|
f4458b8e46 | ||
|
|
99d674b785 | ||
|
|
049c7b3369 | ||
|
|
58c3ee4c8e | ||
|
|
5abf26a19f | ||
|
|
c50a3a426f | ||
|
|
ed6a1e58ea | ||
|
|
21e8347bb6 | ||
|
|
2f7644c420 | ||
|
|
cb34b56fea | ||
|
|
0453be06b6 | ||
|
|
b5a25f0fb6 | ||
|
|
a7f4a675b6 | ||
|
|
0b03ae1a13 | ||
|
|
5351518c75 | ||
|
|
d7b0d322f7 | ||
|
|
36168de584 | ||
|
|
728d2bfee7 | ||
|
|
7068cbf010 | ||
|
|
6da0389d0f | ||
|
|
5ba9a3961c | ||
|
|
95c65ce637 | ||
|
|
6edbef7338 | ||
|
|
c39488d2a4 | ||
|
|
113c6fd618 | ||
|
|
3937eb9ecc | ||
|
|
697a86c4af | ||
|
|
be27e9696b | ||
|
|
41a68f206f | ||
|
|
761f8ab6eb | ||
|
|
8039d69818 | ||
|
|
d9187b4ee0 | ||
|
|
9921615410 | ||
|
|
9b334faa81 | ||
|
|
7009944370 | ||
|
|
eca222c7ca | ||
|
|
cdd8508bc9 | ||
|
|
d5bdc1c240 | ||
|
|
e2991e1245 | ||
|
|
f2432be62d | ||
|
|
39869c8b35 | ||
|
|
6c7a80e449 | ||
|
|
a28dc8f77f | ||
|
|
1719d60531 | ||
|
|
7d764670c8 | ||
|
|
54180f4c35 | ||
|
|
a7bc1be03f | ||
|
|
46b86ffa49 | ||
|
|
efd8e7cb8d | ||
|
|
3194e3b6f4 | ||
|
|
98cb3014e8 | ||
|
|
49c698cf7f | ||
|
|
7be922b3c8 | ||
|
|
8b114520a2 | ||
|
|
48d5e9f07d | ||
|
|
cef9572c24 | ||
|
|
dbdc3ee28c | ||
|
|
62519f8d83 | ||
|
|
b6d5e3f05a | ||
|
|
8f474399f5 | ||
|
|
042a147aff | ||
|
|
60dec3eeed | ||
|
|
9183c5b182 | ||
|
|
40b5f53755 | ||
|
|
455724d9dc | ||
|
|
63b0a01d75 | ||
|
|
8285288540 | ||
|
|
6cc2ef7c6d | ||
|
|
14de5fa627 | ||
|
|
e0d6271fc1 | ||
|
|
543bc5cf6b | ||
|
|
58ffd67704 | ||
|
|
f431464382 | ||
|
|
45783a0435 | ||
|
|
e29263b638 | ||
|
|
16d884bd1f | ||
|
|
e9cccd47e2 | ||
|
|
8f9ed687da | ||
|
|
0d3fdbdb54 | ||
|
|
68a72d2bdf | ||
|
|
0800b1588e | ||
|
|
6cbbe19b5c | ||
|
|
f4be01b4be | ||
|
|
0f1208c406 | ||
|
|
7be72ca667 | ||
|
|
851c4e372e | ||
|
|
1bd381654c | ||
|
|
43434d675e | ||
|
|
1ae5610a4e | ||
|
|
ed16987d47 | ||
|
|
03780ad850 | ||
|
|
b97b2e858b | ||
|
|
c3b70a8968 | ||
|
|
03dc13a314 | ||
|
|
357ee01002 | ||
|
|
b482ebbbc8 | ||
|
|
b730fab286 | ||
|
|
9588b1ad1b | ||
|
|
7b8947ea63 | ||
|
|
cfedc951b6 | ||
|
|
f683332bb3 | ||
|
|
f981162bf3 | ||
|
|
8b5c085aca | ||
|
|
5ae8f62146 | ||
|
|
63871751ce | ||
|
|
56d6ac3114 | ||
|
|
8798cdcd30 | ||
|
|
e1f5a981ef | ||
|
|
ee608784dd | ||
|
|
d387c9113c | ||
|
|
eac96ef6b5 | ||
|
|
80d88ce407 | ||
|
|
3917e6b63a | ||
|
|
406fde33eb | ||
|
|
9ab386880d | ||
|
|
7dff4efc4a | ||
|
|
cc5c62ad72 | ||
|
|
7cf17f56f4 | ||
|
|
8984fddeae | ||
|
|
f580444348 | ||
|
|
02a9c784d5 | ||
|
|
a7bd1a8a80 | ||
|
|
bdd6bd5e38 | ||
|
|
e798115c76 | ||
|
|
25d8495451 | ||
|
|
163060f4df | ||
|
|
22b33b9d08 | ||
|
|
570f5b8d1f | ||
|
|
bad5a4dcc5 | ||
|
|
1c42640347 | ||
|
|
ac1b8e91d6 | ||
|
|
fae54ba5a8 | ||
|
|
76254181c7 | ||
|
|
8d21057d49 | ||
|
|
fa62109164 | ||
|
|
1546ea0d5f | ||
|
|
9ebe5acf39 | ||
|
|
4d67200b80 | ||
|
|
b2feecf7e7 | ||
|
|
c0f177081b | ||
|
|
8f27c09a08 | ||
|
|
2b1c6b6e5e | ||
|
|
8eb57801a5 | ||
|
|
39ee6829b2 | ||
|
|
fde68055df | ||
|
|
521b42866d | ||
|
|
c1debca36b | ||
|
|
605de55fed | ||
|
|
32d7c02b96 | ||
|
|
72b120faf5 | ||
|
|
fc0073f540 | ||
|
|
b2ee6c36a0 | ||
|
|
a078462013 | ||
|
|
70319f1840 | ||
|
|
88cea98b82 | ||
|
|
0ebb1c524d | ||
|
|
b2b95775bd | ||
|
|
5157fbaf2c | ||
|
|
3e3573cb58 | ||
|
|
6bf187537a | ||
|
|
43b1b63df9 | ||
|
|
9d6e71bd56 | ||
|
|
a6f0e59c2c | ||
|
|
227852f221 | ||
|
|
36a61d922c | ||
|
|
5d21b8262b | ||
|
|
50c47c340f | ||
|
|
2c68244142 | ||
|
|
15969c51c3 | ||
|
|
3db533c637 | ||
|
|
b2bcd99d59 | ||
|
|
a99842e0af | ||
|
|
b5a8720a47 | ||
|
|
908acdfa2f | ||
|
|
be3e4bf78e | ||
|
|
0a5f474a25 | ||
|
|
6410748c9f | ||
|
|
aba740a276 | ||
|
|
1e8f7c7c76 | ||
|
|
400b55a410 | ||
|
|
0714a21b03 | ||
|
|
0bcaffa640 | ||
|
|
f6f03d0a52 | ||
|
|
8ebe893854 | ||
|
|
1a304964aa | ||
|
|
543ae33d26 | ||
|
|
6928c6f952 | ||
|
|
7f32b71b6b | ||
|
|
d2ca1810b1 | ||
|
|
7fbb366ce2 | ||
|
|
74f5d66b39 | ||
|
|
07dccd67d6 | ||
|
|
f65b83df3b | ||
|
|
85443e0fd8 | ||
|
|
1e98c01912 | ||
|
|
8f5bf1e905 | ||
|
|
3fcfedc216 | ||
|
|
23c341d76d | ||
|
|
d0a655fc8a | ||
|
|
00dfcc81b4 | ||
|
|
8353b3cc6a | ||
|
|
06f987fb0c | ||
|
|
716a885def | ||
|
|
2ae74802c4 | ||
|
|
084a8c3711 | ||
|
|
97dd9ba1e2 | ||
|
|
f3ed72f30b | ||
|
|
31b6c823ae | ||
|
|
afd7faf385 | ||
|
|
7f176002c6 | ||
|
|
b4e2abdf5c | ||
|
|
1bfbbe2415 | ||
|
|
0c6d299e16 | ||
|
|
3981458d8c | ||
|
|
ecf0d4c5a4 | ||
|
|
7440c4ef75 | ||
|
|
24497d08bc | ||
|
|
258c5b76eb | ||
|
|
9baa4b5b74 | ||
|
|
d5d03cd8de | ||
|
|
0423f0ef38 | ||
|
|
94c9b43b4a | ||
|
|
b92c85848f | ||
|
|
54df89f601 | ||
|
|
cb0f9b9590 | ||
|
|
0c70cc6259 | ||
|
|
9f12981b30 | ||
|
|
6bb532ed03 | ||
|
|
3661f34aba | ||
|
|
df607dd39e | ||
|
|
441f7db5f1 | ||
|
|
9d01255046 | ||
|
|
27a94369a9 | ||
|
|
8765266dae | ||
|
|
5484351523 | ||
|
|
4ce8d809ea | ||
|
|
6da3519297 | ||
|
|
45e4bec59e | ||
|
|
0167bf56f5 | ||
|
|
26de2b79c2 | ||
|
|
395fe68127 | ||
|
|
0f3a66f856 | ||
|
|
748a975aa0 | ||
|
|
ea99f26b25 | ||
|
|
3cf5ad0164 | ||
|
|
fd273186c6 | ||
|
|
be18b7dc25 | ||
|
|
b3e24cb3e5 | ||
|
|
2d85d4c7b6 | ||
|
|
94ca4fd7b4 | ||
|
|
b707403a30 | ||
|
|
40416d103f | ||
|
|
8558cf781a | ||
|
|
428a0bcb5e | ||
|
|
9be0c2ee15 | ||
|
|
6192cbebac | ||
|
|
0ade82d23a | ||
|
|
dd3ba39384 | ||
|
|
170f092ce2 | ||
|
|
ff231b9b52 | ||
|
|
f01555105a | ||
|
|
2a308bca00 | ||
|
|
2c73f3c492 | ||
|
|
f3b2f3496b | ||
|
|
04a4b15991 | ||
|
|
1fa8df82df | ||
|
|
1b7ad3a7ad | ||
|
|
2497e0101e | ||
|
|
3eeb5d10ba | ||
|
|
c589a95546 | ||
|
|
4da70ab3bd | ||
|
|
68a39b1d6e | ||
|
|
86964fb440 | ||
|
|
8d75a61f73 | ||
|
|
89a9d6e529 | ||
|
|
daefbb8a44 | ||
|
|
2df8dc1178 | ||
|
|
dbb6098ff4 | ||
|
|
a852ceb0c8 | ||
|
|
d470530ec7 | ||
|
|
68fdce3548 | ||
|
|
db59bb63c8 | ||
|
|
1dc174b4df | ||
|
|
7a005ebe5d | ||
|
|
e6a672d024 | ||
|
|
ab764d9831 | ||
|
|
776bb81e28 | ||
|
|
4aedfd346f | ||
|
|
c350bf8b5e | ||
|
|
e7bd41ece6 | ||
|
|
ca41ea5e3b | ||
|
|
8b33caa506 | ||
|
|
449ddbe324 | ||
|
|
eba84b51d6 | ||
|
|
721db28037 | ||
|
|
cbac63cfe5 | ||
|
|
cb29f3b060 | ||
|
|
5f7e032d28 | ||
|
|
b912f91f62 | ||
|
|
1a5fddbf99 | ||
|
|
59dd03bc39 | ||
|
|
108beda9ae | ||
|
|
fef822494e | ||
|
|
d864159f34 | ||
|
|
5bbd2946fa | ||
|
|
ad5d044b35 | ||
|
|
7bb79d0ae6 | ||
|
|
fdfe21954f | ||
|
|
d0ef150349 | ||
|
|
08b0ce8736 |
+4
-3
@@ -4,9 +4,10 @@ AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: BlockIndent
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: DontAlign
|
||||
AllowShortBlocksOnASingleLine: Always
|
||||
AlignTrailingComments: false
|
||||
AllowShortBlocksOnASingleLine: Empty
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
AllowShortIfStatementsOnASingleLine: WithoutElse
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BinPackArguments: false
|
||||
@@ -35,7 +36,7 @@ BreakAfterAttributes: Always
|
||||
BreakBeforeBinaryOperators: NonAssignment
|
||||
BreakBeforeBraces: Custom
|
||||
BreakConstructorInitializers: BeforeComma
|
||||
ColumnLimit: 100
|
||||
ColumnLimit: 110
|
||||
EmptyLineAfterAccessModifier: Leave
|
||||
EmptyLineBeforeAccessModifier: Leave
|
||||
FixNamespaceComments: false
|
||||
|
||||
+2
-7
@@ -8,16 +8,12 @@ Checks:
|
||||
- -bugprone-narrowing-conversions
|
||||
# kind of nonsense
|
||||
- -bugprone-easily-swappable-parameters
|
||||
# too many warnings for now
|
||||
- -bugprone-implicit-widening-of-multiplication-result
|
||||
# Lix's exception handling is Questionable
|
||||
- -bugprone-empty-catch
|
||||
# many warnings
|
||||
- -bugprone-unchecked-optional-access
|
||||
# many warnings, seems like a questionable lint
|
||||
- -bugprone-branch-clone
|
||||
# extremely noisy before clang 19: https://github.com/llvm/llvm-project/issues/93959
|
||||
- -bugprone-multi-level-implicit-pointer-conversion
|
||||
# we don't compile out our asserts
|
||||
- -bugprone-assert-side-effect
|
||||
# FIXME(jade): figure out if this warning is any good
|
||||
@@ -29,9 +25,6 @@ Checks:
|
||||
# crimes must be appropriately declared as crimes
|
||||
- cppcoreguidelines-pro-type-cstyle-cast
|
||||
- lix-*
|
||||
# This can not yet be applied to Lix itself since we need to do source
|
||||
# reorganization so that lix/ include paths work.
|
||||
- -lix-fixincludes
|
||||
# This lint is included as an example, but the lib function it replaces is
|
||||
# already gone.
|
||||
- -lix-hasprefixsuffix
|
||||
@@ -40,3 +33,5 @@ Checks:
|
||||
CheckOptions:
|
||||
bugprone-reserved-identifier.AllowedIdentifiers: '__asan_default_options'
|
||||
bugprone-unused-return-value.AllowCastToVoid: true
|
||||
|
||||
ExtraArgs: ["-Werror=unnecessary-virtual-specifier"]
|
||||
|
||||
@@ -41,3 +41,6 @@ buildtime.bin
|
||||
*.pyc
|
||||
|
||||
**/.idea
|
||||
|
||||
# Yeah, I've got no clue.
|
||||
/subprojects/.wraplock
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
Fiona Behrens <me@kloenk.dev>
|
||||
Fiona Behrens <me@kloenk.dev> <me@kloenk.de>
|
||||
rootile <lix@rootile.de>
|
||||
rootile <lix@rootile.de> <commentator2.0@crystal-cavern.systems>
|
||||
rootile <lix@rootile.de> <lix@crystal-cavern.systems>
|
||||
|
||||
Generated
+4
@@ -39,6 +39,10 @@ dependencies = [
|
||||
"rowan",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lixutil-rs"
|
||||
version = "0.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["lix/lix-doc"]
|
||||
members = ["lix/lix-doc", "lix/libutil"]
|
||||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# 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,
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
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.
|
||||
|
||||
)"
|
||||
@@ -0,0 +1,102 @@
|
||||
#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>();
|
||||
}
|
||||
+15
-9
@@ -1,9 +1,15 @@
|
||||
(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
|
||||
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
|
||||
|
||||
@@ -24,8 +24,8 @@ def map_contents_recursively(transformer):
|
||||
def process_command:
|
||||
.[0] as $context |
|
||||
.[1] as $body |
|
||||
$body + {
|
||||
sections: $body.sections | map(map_contents_recursively(if $context.renderer == "html" then transform_anchors_html else transform_anchors_strip end)),
|
||||
};
|
||||
# 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))
|
||||
;
|
||||
|
||||
process_command
|
||||
|
||||
@@ -22,20 +22,21 @@ 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 doc/manual/substitute.py"
|
||||
command = "python3 substitute.py"
|
||||
before = ["anchors", "links"]
|
||||
|
||||
[preprocessor.anchors]
|
||||
renderers = ["html"]
|
||||
command = "jq --from-file doc/manual/anchors.jq"
|
||||
command = "jq --from-file anchors.jq"
|
||||
|
||||
[output.markdown]
|
||||
|
||||
[output.linkcheck]
|
||||
# XXX FUTURE: may be reenabled once mdBook 0.5.0 or above and matching mdbook-linkchecker are in nixpkgs
|
||||
#[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"
|
||||
|
||||
@@ -99,6 +99,9 @@ goldstein:
|
||||
forgejo: goldstein
|
||||
github: GoldsteinE
|
||||
|
||||
gustavderdrache:
|
||||
github: gustavderdrache
|
||||
|
||||
horrors:
|
||||
display_name: eldritch horrors
|
||||
forgejo: pennae
|
||||
@@ -125,6 +128,11 @@ jade:
|
||||
just1602:
|
||||
forgejo: just1602
|
||||
|
||||
k900:
|
||||
display_name: K900
|
||||
forgejo: K900
|
||||
github: K900
|
||||
|
||||
kasimeka:
|
||||
display_name: ورد
|
||||
forgejo: janw4ld
|
||||
@@ -176,6 +184,11 @@ midnightveil:
|
||||
forgejo: midnightveil
|
||||
github: midnightveil
|
||||
|
||||
milibopp:
|
||||
display_name: Emilia Bopp
|
||||
forgejo: milibopp
|
||||
github: milibopp
|
||||
|
||||
nan-git:
|
||||
display_name: NaN-git
|
||||
github: NaN-git
|
||||
@@ -183,6 +196,9 @@ nan-git:
|
||||
ncfavier:
|
||||
github: ncfavier
|
||||
|
||||
nkk0:
|
||||
github: nkk0
|
||||
|
||||
not-my-profile:
|
||||
display_name: Martin Fischer
|
||||
github: not-my-profile
|
||||
@@ -229,9 +245,16 @@ roberth:
|
||||
display_name: Robert Hensing
|
||||
github: roberth
|
||||
|
||||
rootile:
|
||||
display_name: rootile (Rutile)
|
||||
forgejo: rootile
|
||||
|
||||
seppel3210:
|
||||
github: Seppel3210
|
||||
|
||||
stevalkr:
|
||||
github: stevalkr
|
||||
|
||||
teofilc:
|
||||
forgejo: teofilc
|
||||
github: TeofilC
|
||||
@@ -258,6 +281,9 @@ vigress8:
|
||||
forgejo: vigress8
|
||||
github: vigress8
|
||||
|
||||
vlaci:
|
||||
github: vlaci
|
||||
|
||||
vlinkz:
|
||||
display_name: Victor Fuentes
|
||||
forgejo: vlinkz
|
||||
@@ -273,6 +299,10 @@ xanderio:
|
||||
xokdvium:
|
||||
github: xokdvium
|
||||
|
||||
xyenon:
|
||||
forgejo: xyenon
|
||||
github: xyenon
|
||||
|
||||
yorickvp:
|
||||
github: yorickvp
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -41,9 +41,13 @@ manual = custom_target(
|
||||
'-euo', 'pipefail',
|
||||
'-c',
|
||||
'''
|
||||
@0@ @INPUT0@ @CURRENT_SOURCE_DIR@ > @DEPFILE@
|
||||
cd @SOURCE_ROOT@
|
||||
@1@ build doc/manual -d @2@ | { grep -Fv "because fragment resolution isn't implemented" || :; }
|
||||
@0@ @INPUT0@ @3@ > @DEPFILE@
|
||||
|
||||
# Needs to be in lix/doc/manual for e.g. substitute.py
|
||||
pushd @3@
|
||||
@1@ build . -d @2@
|
||||
popd
|
||||
|
||||
rm -rf @2@/manual
|
||||
mv @2@/html @2@/manual
|
||||
find @2@/manual -iname meson.build -delete
|
||||
@@ -51,6 +55,7 @@ manual = custom_target(
|
||||
python.full_path(),
|
||||
mdbook.full_path(),
|
||||
meson.current_build_dir(),
|
||||
meson.current_source_dir()
|
||||
),
|
||||
],
|
||||
input : [
|
||||
@@ -81,7 +86,7 @@ manual = custom_target(
|
||||
depfile : 'manual.d',
|
||||
env : {
|
||||
'RUST_LOG': 'info',
|
||||
'MDBOOK_SUBSTITUTE_SEARCH': meson.current_build_dir() / 'src',
|
||||
'MANUAL_SUBSTITUTE_SEARCH': meson.current_build_dir() / 'src',
|
||||
},
|
||||
)
|
||||
manual_md = manual[1]
|
||||
|
||||
@@ -200,6 +200,7 @@
|
||||
- [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)
|
||||
|
||||
@@ -41,106 +41,17 @@ 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 USER@HOST | sudo tee -a ~root/.ssh/known_hosts`.
|
||||
> To add them to `known_hosts` for root, do `ssh-keyscan 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. 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.
|
||||
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.
|
||||
|
||||
Remote builders can also be configured in `nix.conf`, e.g.
|
||||
|
||||
@@ -159,3 +70,180 @@ 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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -141,6 +141,21 @@ 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.
|
||||
|
||||
@@ -383,7 +383,10 @@ 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
|
||||
- `NIX_DAEMON_SOCKET_PATH` (optional) - Overrides the daemon socket path from `$NIX_STATE_DIR/daemon-socket/socket`.
|
||||
- `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.
|
||||
|
||||
**Expected value**: path to a socket
|
||||
- `NIX_LOG_FD` (output) - An FD number for logs in `internal-json` format to be sent to.
|
||||
@@ -401,7 +404,6 @@ 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`.
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
|
||||
[store path]: #gloss-store-path
|
||||
|
||||
- [file system object]{#gloss-store-object}
|
||||
- [file system object]{#gloss-file-system-object}
|
||||
|
||||
The Nix data model for representing simplified file system data.
|
||||
|
||||
|
||||
@@ -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://lix.systems/resources) page on the Lix site.
|
||||
For more information on modern practices, see the [resources](https://wiki.lix.systems/books/lix-users/page/nix-resources) page on the Lix site.
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,564 @@
|
||||
# Lix 2.95 "Kakigōri" (2026-03-13)
|
||||
# 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,10 +70,15 @@ 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']
|
||||
@@ -119,10 +124,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']['src']
|
||||
book_root = Path(context['root']) / context['config']['book'].get('src', 'src')
|
||||
|
||||
# includes pointing into @generated@ will look here
|
||||
search_path = Path(os.environ['MDBOOK_SUBSTITUTE_SEARCH'])
|
||||
search_path = Path(os.environ['MANUAL_SUBSTITUTE_SEARCH'])
|
||||
|
||||
# Find @var@ in all parts of our recursive book structure.
|
||||
replaced_content = recursive_replace(book, book_root, search_path)
|
||||
|
||||
+1
-1
@@ -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.system}";
|
||||
meta.description = "Docker image tarball with Lix for ${pkgs.stdenv.hostPlatform.system}";
|
||||
}
|
||||
''
|
||||
mkdir -p $out/nix-support
|
||||
|
||||
Generated
+17
-19
@@ -3,17 +3,15 @@
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1696426674,
|
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
|
||||
"type": "github"
|
||||
"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"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
"type": "tarball",
|
||||
"url": "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz"
|
||||
}
|
||||
},
|
||||
"lowdown-src": {
|
||||
@@ -35,11 +33,11 @@
|
||||
"nix2container": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1724996935,
|
||||
"narHash": "sha256-njRK9vvZ1JJsP8oV2OgkBrpJhgQezI03S7gzskCcHos=",
|
||||
"lastModified": 1767195068,
|
||||
"narHash": "sha256-+OMnL79ZjqM/PCz2hoQ12MnXNoSSfBGnsYBOZnA9XbI=",
|
||||
"owner": "nlewo",
|
||||
"repo": "nix2container",
|
||||
"rev": "fa6bb0a1159f55d071ba99331355955ae30b3401",
|
||||
"rev": "bb6801be998ba857a62c002cb77ece66b0a57298",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -108,16 +106,16 @@
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1757198069,
|
||||
"narHash": "sha256-m3VUcOD4rTs8J7S+3dOjWMrAjw6RcITC3XYQ98zhEFs=",
|
||||
"lastModified": 1773082486,
|
||||
"narHash": "sha256-TKUDrM0nKUo5s/b8jhjXa2prcu5KU5Cck3HBTRLDjfo=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "0747026fc57ecb9c28901c7f7a2b5dc40e8af43c",
|
||||
"rev": "7f8b8875bdb38a70c7b5ceb9ba6a6a8d69859e16",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-25.05-small",
|
||||
"ref": "nixos-25.11-small",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
@@ -125,11 +123,11 @@
|
||||
"pre-commit-hooks": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1733318908,
|
||||
"narHash": "sha256-SVQVsbafSM1dJ4fpgyBqLZ+Lft+jcQuMtEL3lQWx2Sk=",
|
||||
"lastModified": 1769939035,
|
||||
"narHash": "sha256-Fok2AmefgVA0+eprw2NDwqKkPGEI5wvR+twiZagBvrg=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "6f4e2a2112050951a314d2733a994fbab94864c6",
|
||||
"rev": "a8ca480175326551d6c4121498316261cbb5b260",
|
||||
"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.05-small";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11-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 = "github:edolstra/flake-compat";
|
||||
url = "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz";
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
@@ -177,13 +177,12 @@
|
||||
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;
|
||||
nix_2_18 =
|
||||
nix_2_18.outputs.packages.${currentStdenv.hostPlatform.system}.default.overrideAttrs
|
||||
(_: {
|
||||
pname = "nix";
|
||||
});
|
||||
};
|
||||
|
||||
# Forward from the previous stage as we don’t want it to pick the lowdown override
|
||||
@@ -220,11 +219,15 @@
|
||||
inherit versionSuffix officialRelease;
|
||||
stdenv = currentStdenv;
|
||||
busybox-sandbox-shell = final.busybox-sandbox-shell or final.default-busybox-sandbox-shell;
|
||||
# See below
|
||||
lowdown = final.lowdown_3_0;
|
||||
lowdown-unsandboxed = final.lowdown_3_0.override { enableDarwinSandbox = false; };
|
||||
};
|
||||
|
||||
lix-clang-tidy = final.callPackage ./subprojects/lix-clang-tidy { };
|
||||
|
||||
nix-eval-jobs = final.callPackage ./subprojects/nix-eval-jobs {
|
||||
stdenv = currentStdenv;
|
||||
srcDir = ./subprojects/nix-eval-jobs;
|
||||
};
|
||||
|
||||
@@ -245,21 +248,21 @@
|
||||
# And same thing for our build-release-notes package.
|
||||
build-release-notes = final.nix.passthru.build-release-notes;
|
||||
|
||||
lowdown_1_3 =
|
||||
# If the stable channel we are using ships lowdown >= 1.4, we need
|
||||
# to swap this around, take the default lowdown from the stable
|
||||
# channel and add an overridden one for the legacy version.
|
||||
assert lib.versionOlder prev.lowdown.version "1.4.0";
|
||||
prev.lowdown;
|
||||
lowdown = prev.lowdown.overrideAttrs (prevAttrs: rec {
|
||||
version = "2.0.2";
|
||||
src = final.fetchurl {
|
||||
url = "https://kristaps.bsd.lv/lowdown/snapshots/lowdown-${version}.tar.gz";
|
||||
sha512 = "2a4d0rqh8gkw4ca3gkzddp0hjpmmw74cbks8k0inhh0vizmgbn188zdv6m1kgmr019b99g7insli8js3ci1ji7y4n5nk704bswf3z3i";
|
||||
};
|
||||
nativeBuildInputs = prevAttrs.nativeBuildInputs ++ [ final.buildPackages.bmake ];
|
||||
postInstall = lib.replaceStrings [ "lowdown.so.1" ] [ "lowdown.so.2" ] prevAttrs.postInstall;
|
||||
});
|
||||
# As soon as Nixpkgs updates to >= 3.0.0, change to lowdown_2_0!
|
||||
# We don't change the default version in order to not change the hash
|
||||
# of Nix/Lix from upstream Nixpkgs.
|
||||
lowdown_3_0 =
|
||||
assert lib.versionOlder prev.lowdown.version "3.0.0";
|
||||
prev.lowdown.overrideAttrs (
|
||||
finalAttrs: _prevAttrs: {
|
||||
version = "3.0.0";
|
||||
|
||||
src = final.fetchurl {
|
||||
url = "https://kristaps.bsd.lv/lowdown/snapshots/lowdown-${finalAttrs.version}.tar.gz";
|
||||
sha512 = "94e97234d598382c3c3dc27f9bfdb3a3a2fcf7dbb6a8df3c85ee09f27f792449034a41d49d9cfd3d8450d2de01b8562c20c3d120e65c81af4d7d6c9454119e93";
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
capnproto = prev.capnproto.overrideAttrs (old: {
|
||||
patches =
|
||||
@@ -287,6 +290,15 @@
|
||||
# Binary package for various platforms.
|
||||
build = forAllSystems (system: self.packages.${system}.nix);
|
||||
|
||||
# Ensure support for lowdown < 3.0 doesn't regress for NixOS 25.11
|
||||
build-lowdown_2_0.aarch64-linux = lib.genAttrs [ "aarch64-linux" ] (
|
||||
system:
|
||||
self.packages.${system}.nix.override {
|
||||
lowdown = nixpkgsFor.${system}.native.lowdown;
|
||||
lowdown-unsandboxed = nixpkgsFor.${system}.native.lowdown-unsandboxed;
|
||||
}
|
||||
);
|
||||
|
||||
# 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 (
|
||||
@@ -303,14 +315,6 @@
|
||||
}
|
||||
);
|
||||
|
||||
# Ensure support for lowdown < 1.4 doesn't regress
|
||||
build-lowdown_1_3 = forAllSystems (
|
||||
system:
|
||||
self.packages.${system}.nix.override {
|
||||
lowdown = nixpkgsFor.${system}.native.lowdown_1_3;
|
||||
}
|
||||
);
|
||||
|
||||
devShell = forAllSystems (system: {
|
||||
default = self.devShells.${system}.default;
|
||||
clang = self.devShells.${system}.native-clangStdenvPackages;
|
||||
@@ -427,49 +431,30 @@
|
||||
'';
|
||||
|
||||
# clang-tidy run against the Lix codebase using the Lix clang-tidy plugin
|
||||
clang-tidy =
|
||||
clang-tidy = forAllSystems (
|
||||
system:
|
||||
let
|
||||
nixpkgs = nixpkgsFor.x86_64-linux.native;
|
||||
inherit (nixpkgs) pkgs;
|
||||
pkgs = nixpkgsFor.${system}.native;
|
||||
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 =
|
||||
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
|
||||
'';
|
||||
evalNixpkgs = nixpkgsFor.x86_64-linux.native.callPackage ./tests/nixpkgs/eval.nix {
|
||||
inherit nixpkgs-regression;
|
||||
};
|
||||
|
||||
nixpkgsLibTests = forAllSystems (
|
||||
system:
|
||||
let
|
||||
nixpkgsFor.${system}.native.callPackage ./tests/nixpkgs/lib.nix {
|
||||
inherit nixpkgs system;
|
||||
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;
|
||||
})
|
||||
];
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -518,7 +503,6 @@
|
||||
# devShells and packages already get checked by nix flake check, so
|
||||
# this is just jobs that are special
|
||||
|
||||
build-lowdown_1_3 = self.hydraJobs.build-lowdown_1_3.${system};
|
||||
binaryTarball = self.hydraJobs.binaryTarball.${system};
|
||||
perlBindings = self.hydraJobs.perlBindings.${system};
|
||||
nix-eval-jobs = self.hydraJobs.nix-eval-jobs.${system};
|
||||
@@ -550,7 +534,7 @@
|
||||
dockerImage =
|
||||
let
|
||||
pkgs = nixpkgsFor.${system}.native;
|
||||
nix2container' = import nix2container { inherit pkgs system; };
|
||||
nix2container' = import nix2container { inherit pkgs; };
|
||||
in
|
||||
import ./docker.nix {
|
||||
inherit pkgs;
|
||||
@@ -582,8 +566,11 @@
|
||||
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.system} or { };
|
||||
pre-commit = self.hydraJobs.pre-commit.${pkgs.stdenv.hostPlatform.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 {{ OPTIONS }}
|
||||
meson test -C build --print-errorlogs --max-lines 10000 {{ OPTIONS }}
|
||||
|
||||
# Run unit tests only
|
||||
test-unit *OPTIONS: (test "--suite" "check")
|
||||
@@ -43,7 +43,7 @@ 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 && python -m pytest -v {{ OPTIONS }} functional2
|
||||
cd tests/functional2 && python -m pytest -v {{ OPTIONS }}
|
||||
|
||||
alias clang-tidy := lint
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# noqa: N999 # consistency with rest of the codebase
|
||||
@@ -1,93 +1,113 @@
|
||||
from typing import List, NamedTuple
|
||||
import dataclasses
|
||||
from enum import Enum
|
||||
from textwrap import dedent, indent
|
||||
from typing import NamedTuple
|
||||
|
||||
from common import cxx_literal, generate_file, load_data
|
||||
|
||||
KNOWN_KEYS = set([
|
||||
'name',
|
||||
'type',
|
||||
'constructorArgs',
|
||||
'implementation',
|
||||
'impure',
|
||||
'renameInGlobalScope',
|
||||
])
|
||||
from common import cxx_literal, generate_file, load_data, get_argument_parser
|
||||
|
||||
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**
|
||||
IMPURE_NOTE = """
|
||||
> **Note**
|
||||
>
|
||||
> Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval).
|
||||
|
||||
''' if constant.impure else '') + '''</dd>
|
||||
"""
|
||||
|
||||
''')
|
||||
|
||||
if __name__ == '__main__':
|
||||
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__":
|
||||
main()
|
||||
|
||||
@@ -1,82 +1,90 @@
|
||||
from typing import List, NamedTuple, Optional
|
||||
import dataclasses
|
||||
from textwrap import dedent, indent
|
||||
|
||||
from build_experimental_features import ExperimentalFeature
|
||||
from common import cxx_literal, generate_file, load_data
|
||||
from common import (
|
||||
cxx_literal,
|
||||
generate_file,
|
||||
load_data,
|
||||
get_argument_parser,
|
||||
get_experimental_features,
|
||||
)
|
||||
|
||||
KNOWN_KEYS = set([
|
||||
'name',
|
||||
'implementation',
|
||||
'renameInGlobalScope',
|
||||
'args',
|
||||
'experimentalFeature',
|
||||
])
|
||||
|
||||
class Builtin(NamedTuple):
|
||||
@dataclasses.dataclass
|
||||
class Builtin:
|
||||
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 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,
|
||||
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 ""
|
||||
)
|
||||
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():
|
||||
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='+')
|
||||
ap = get_argument_parser()
|
||||
ap.add_argument(
|
||||
"--experimental-features", help="Directory containing the experimental feature definitions"
|
||||
)
|
||||
args = ap.parse_args()
|
||||
|
||||
builtins = load_data(args.defs, Builtin.parse)
|
||||
builtins = load_data(args.defs, Builtin)
|
||||
|
||||
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'
|
||||
experimental_features = get_experimental_features(
|
||||
args.experimental_features, [b.experimental_feature for (_, b) in builtins]
|
||||
)
|
||||
|
||||
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>
|
||||
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)
|
||||
|
||||
{builtin.documentation}
|
||||
|
||||
''' + (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__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
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()
|
||||
@@ -0,0 +1,105 @@
|
||||
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,141 +1,157 @@
|
||||
from typing import List, NamedTuple, Optional
|
||||
import dataclasses
|
||||
from textwrap import dedent
|
||||
from typing import Any
|
||||
|
||||
from build_experimental_features import ExperimentalFeature
|
||||
from common import cxx_literal, generate_file, load_data
|
||||
from common import (
|
||||
cxx_literal,
|
||||
generate_file,
|
||||
load_data,
|
||||
get_experimental_features,
|
||||
get_argument_parser,
|
||||
)
|
||||
|
||||
KNOWN_KEYS = set([
|
||||
'name',
|
||||
'internalName',
|
||||
'platforms',
|
||||
'type',
|
||||
'settingType',
|
||||
'default',
|
||||
'defaultExpr',
|
||||
'defaultText',
|
||||
'aliases',
|
||||
'experimentalFeature',
|
||||
'deprecated',
|
||||
])
|
||||
|
||||
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
|
||||
PLATFORM_WARNING = """
|
||||
> **Note**
|
||||
> This setting is only available on {platforms} systems.
|
||||
|
||||
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),
|
||||
)
|
||||
"""
|
||||
|
||||
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**
|
||||
XP_WARNING = """
|
||||
> **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,
|
||||
[`{setting.experimental_feature}`](@docroot@/contributing/experimental-features.md#xp-feature-{setting.experimental_feature}),
|
||||
[`{feature}`](@docroot@/contributing/experimental-features.md#xp-feature-{feature}),
|
||||
is enabled.
|
||||
For example, include the following in [`nix.conf`](#):
|
||||
|
||||
```
|
||||
extra-experimental-features = {setting.experimental_feature}
|
||||
{setting.name} = ...
|
||||
extra-experimental-features = {feature}
|
||||
{name} = ...
|
||||
```
|
||||
|
||||
''' if setting.experimental_feature is not None else '') + (''' > **Warning**
|
||||
"""
|
||||
|
||||
DEPR_WARNING = """
|
||||
> **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])}
|
||||
|
||||
''' if setting.aliases != [] else ''))
|
||||
@dataclasses.dataclass
|
||||
class Setting:
|
||||
name: str
|
||||
internal_name: str
|
||||
documentation: str
|
||||
|
||||
if __name__ == '__main__':
|
||||
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__":
|
||||
main()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!@python@
|
||||
# ruff: noqa: SIM112 # ignore lowercase env variable names for capnpc as we have them in lower case as arguments
|
||||
|
||||
import argparse
|
||||
import capnp
|
||||
@@ -7,58 +8,61 @@ import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
if lang := os.environ.get('lix_capnp_lang'):
|
||||
outputs = os.environ['lix_capnp_outputs'].split()
|
||||
old_cwd = os.environ['lix_capnp_old_cwd']
|
||||
schema = capnp.load('@capnp_include@/capnp/schema.capnp', imports=['@capnp_include@'])
|
||||
if lang := os.environ.get("lix_capnp_lang"):
|
||||
outputs = os.environ["lix_capnp_outputs"].split()
|
||||
old_cwd = os.environ["lix_capnp_old_cwd"]
|
||||
schema = capnp.load("@capnp_include@/capnp/schema.capnp", imports=["@capnp_include@"])
|
||||
request = schema.CodeGeneratorRequest.read(sys.stdin)
|
||||
|
||||
subprocess.run([lang], input=request.as_builder().to_bytes()).check_returncode()
|
||||
|
||||
base_dir = os.getcwd()
|
||||
base_dir = Path.cwd()
|
||||
os.chdir(old_cwd)
|
||||
|
||||
include = [ str(Path(p).resolve()) for p in os.environ['lix_capnp_include'].split(':') ]
|
||||
include = [str(Path(p).resolve()) for p in os.environ["lix_capnp_include"].split(":")]
|
||||
|
||||
if depfile := os.environ['lix_capnp_depfile']:
|
||||
if depfile := os.environ["lix_capnp_depfile"]:
|
||||
deps = ""
|
||||
for input in request.requestedFiles:
|
||||
deps += " ".join(f"{input.filename}.{o}" for o in outputs)
|
||||
for input_file in request.requestedFiles:
|
||||
deps += " ".join(f"{input_file.filename}.{o}" for o in outputs)
|
||||
deps += ":"
|
||||
for dep in input.imports:
|
||||
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:
|
||||
raise RuntimeError("not handling relative includes")
|
||||
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='+')
|
||||
parser.add_argument("--language")
|
||||
parser.add_argument("--outdir")
|
||||
parser.add_argument("--src-prefix")
|
||||
parser.add_argument("--depfile", default="")
|
||||
parser.add_argument("-I", "--include", action="append", default=["@capnp_include@"])
|
||||
parser.add_argument("inputs", nargs="+")
|
||||
args = parser.parse_args()
|
||||
|
||||
for infile in args.inputs:
|
||||
os.environ['lix_capnp_lang'] = f"capnpc-{args.language}"
|
||||
os.environ['lix_capnp_include'] = ':'.join(args.include)
|
||||
os.environ['lix_capnp_depfile'] = args.depfile
|
||||
os.environ['lix_capnp_old_cwd'] = os.getcwd()
|
||||
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"
|
||||
os.environ["lix_capnp_outputs"] = "c++ h"
|
||||
else:
|
||||
raise RuntimeError("unknown language " + args.language)
|
||||
subprocess.run([
|
||||
'@capnp@',
|
||||
'compile',
|
||||
f'-o{sys.argv[0]}:{args.outdir}',
|
||||
f'--src-prefix={args.src_prefix}',
|
||||
*(f"-I{i}" for i in args.include),
|
||||
infile
|
||||
]).check_returncode()
|
||||
subprocess.run(
|
||||
[
|
||||
"@capnp@",
|
||||
"compile",
|
||||
f"-o{sys.argv[0]}:{args.outdir}",
|
||||
f"--src-prefix={args.src_prefix}",
|
||||
*(f"-I{i}" for i in args.include),
|
||||
infile,
|
||||
]
|
||||
).check_returncode()
|
||||
|
||||
@@ -1,60 +1,113 @@
|
||||
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):
|
||||
if ord(c) >= 0x20 and ord(c) < 0x7f and c != '"' and c != '?' and c != '\\':
|
||||
|
||||
def cxx_escape_character(c: str) -> str:
|
||||
if 0x20 <= ord(c) < 0x7F and c != '"' and c != "?" and c != "\\":
|
||||
return 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))
|
||||
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))
|
||||
|
||||
def cxx_literal(v):
|
||||
|
||||
def cxx_literal(v: Any) -> str:
|
||||
if v is None:
|
||||
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 "std::nullopt"
|
||||
if v is False:
|
||||
return "false"
|
||||
if v is True:
|
||||
return "true"
|
||||
if isinstance(v, int):
|
||||
return str(v)
|
||||
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++')
|
||||
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)
|
||||
|
||||
def load_data(defs, parse_function):
|
||||
|
||||
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]]:
|
||||
data = []
|
||||
for path in defs:
|
||||
try:
|
||||
datum = frontmatter.load(path)
|
||||
data.append((path, parse_function(datum)))
|
||||
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)))
|
||||
except Exception as e:
|
||||
e.add_note(f'in {path}')
|
||||
e.add_note(f"in {path}")
|
||||
raise
|
||||
return data
|
||||
|
||||
def generate_file(path, data, sort_key_function, generate_function):
|
||||
|
||||
def generate_file[T](
|
||||
path: str | None,
|
||||
data: list[T],
|
||||
sort_key_function: Callable[[T], str],
|
||||
generate_function: Callable[[T], str],
|
||||
):
|
||||
if path is not None:
|
||||
with open(path, 'w') as out:
|
||||
for path, datum in sorted(data, key=lambda pathAndDatum: sort_key_function(pathAndDatum[1])):
|
||||
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])
|
||||
):
|
||||
try:
|
||||
out.write(generate_function(datum))
|
||||
text = generate_function(datum)
|
||||
out.write(text)
|
||||
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
|
||||
|
||||
@@ -69,7 +69,7 @@ static std::string makeLockFilename(const std::string & storeUri) {
|
||||
// This avoids issues with the escaped URI being very long and causing
|
||||
// path too long errors, while also avoiding any possibility of collision
|
||||
// caused by simple truncation.
|
||||
auto hash = hashString(HashType::SHA256, storeUri).to_string(Base::Base32, false);
|
||||
auto hash = hashString(HashType::SHA256, storeUri).to_string(HashFormat::Base32, false);
|
||||
return escapeUri(storeUri).substr(0, 48) + "-" + hash.substr(0, 16);
|
||||
}
|
||||
|
||||
@@ -405,7 +405,7 @@ kj::Promise<void> Instance::init(InitContext context)
|
||||
}
|
||||
|
||||
kj::Promise<void> Instance::buildImpl(BuildContext context)
|
||||
{
|
||||
try {
|
||||
if (!initialized) {
|
||||
throw Error("build hook not fully initialized");
|
||||
}
|
||||
@@ -461,6 +461,8 @@ kj::Promise<void> Instance::buildImpl(BuildContext context)
|
||||
|
||||
auto ac = context.getResults().initResult().initGood().initAccept();
|
||||
ac.setMachine(kj::heap<AcceptedBuild>(store, drvPath, std::move(*builder)));
|
||||
} catch (...) {
|
||||
RPC_FILL(context.getResults(), initResult, std::current_exception());
|
||||
}
|
||||
|
||||
kj::Promise<void> Instance::build(BuildContext context)
|
||||
@@ -527,7 +529,7 @@ kj::Promise<void> AcceptedBuild::runImpl(RunContext context)
|
||||
AIO().timeoutAfter(15 * kj::MINUTES, lockFileAsync(uploadLock.get(), ltWrite))
|
||||
);
|
||||
if (!result) {
|
||||
printError("somebody is hogging the upload lock for '%s', continuing...");
|
||||
printError("somebody is hogging the upload lock for '%s', continuing...", storeUri);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
#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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
namespace nix {
|
||||
void registerLegacyBuiltinBuilder();
|
||||
}
|
||||
+8
-10
@@ -4,9 +4,7 @@
|
||||
#include "lix/libutil/result.hh"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
|
||||
using std::cout;
|
||||
#include <sstream>
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -42,31 +40,31 @@ static std::string makeNode(std::string_view id, std::string_view label,
|
||||
dotQuote(id), dotQuote(label), dotQuote(colour));
|
||||
}
|
||||
|
||||
|
||||
kj::Promise<Result<void>> printDotGraph(ref<Store> store, StorePathSet && roots)
|
||||
kj::Promise<Result<std::string>> formatDotGraph(ref<Store> store, StorePathSet && roots)
|
||||
try {
|
||||
StorePathSet workList(std::move(roots));
|
||||
StorePathSet doneSet;
|
||||
std::stringstream result;
|
||||
|
||||
cout << "digraph G {\n";
|
||||
result << "digraph G {\n";
|
||||
|
||||
while (!workList.empty()) {
|
||||
auto path = std::move(workList.extract(workList.begin()).value());
|
||||
|
||||
if (!doneSet.insert(path).second) continue;
|
||||
|
||||
cout << makeNode(std::string(path.to_string()), path.name(), "#ff0000");
|
||||
result << makeNode(std::string(path.to_string()), path.name(), "#ff0000");
|
||||
|
||||
for (auto & p : TRY_AWAIT(store->queryPathInfo(path))->references) {
|
||||
if (p != path) {
|
||||
workList.insert(p);
|
||||
cout << makeEdge(std::string(p.to_string()), std::string(path.to_string()));
|
||||
result << makeEdge(std::string(p.to_string()), std::string(path.to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cout << "}\n";
|
||||
co_return result::success();
|
||||
result << "}\n";
|
||||
co_return result.str();
|
||||
} catch (...) {
|
||||
co_return result::current_exception();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,5 @@
|
||||
|
||||
namespace nix {
|
||||
|
||||
kj::Promise<Result<void>> printDotGraph(ref<Store> store, StorePathSet && roots);
|
||||
|
||||
kj::Promise<Result<std::string>> formatDotGraph(ref<Store> store, StorePathSet && roots);
|
||||
}
|
||||
|
||||
+16
-18
@@ -5,9 +5,7 @@
|
||||
#include "lix/libutil/result.hh"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
|
||||
using std::cout;
|
||||
#include <sstream>
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -47,21 +45,21 @@ static std::string makeNode(const ValidPathInfo & info)
|
||||
(info.path.isDerivation() ? "derivation" : "output-path"));
|
||||
}
|
||||
|
||||
|
||||
kj::Promise<Result<void>> printGraphML(ref<Store> store, StorePathSet && roots)
|
||||
kj::Promise<Result<std::string>> formatGraphML(ref<Store> store, StorePathSet && roots)
|
||||
try {
|
||||
StorePathSet workList(std::move(roots));
|
||||
StorePathSet doneSet;
|
||||
std::pair<StorePathSet::iterator, bool> ret;
|
||||
std::stringstream result;
|
||||
|
||||
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";
|
||||
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";
|
||||
|
||||
while (!workList.empty()) {
|
||||
auto path = std::move(workList.extract(workList.begin()).value());
|
||||
@@ -70,20 +68,20 @@ try {
|
||||
if (ret.second == false) continue;
|
||||
|
||||
auto info = TRY_AWAIT(store->queryPathInfo(path));
|
||||
cout << makeNode(*info);
|
||||
result << makeNode(*info);
|
||||
|
||||
for (auto & p : info->references) {
|
||||
if (p != path) {
|
||||
workList.insert(p);
|
||||
cout << makeEdge(path.to_string(), p.to_string());
|
||||
result << makeEdge(path.to_string(), p.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cout << "</graph>\n";
|
||||
cout << "</graphml>\n";
|
||||
co_return result::success();
|
||||
result << "</graph>\n";
|
||||
result << "</graphml>\n";
|
||||
co_return result.str();
|
||||
} catch (...) {
|
||||
co_return result::current_exception();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,5 @@
|
||||
|
||||
namespace nix {
|
||||
|
||||
kj::Promise<Result<void>> printGraphML(ref<Store> store, StorePathSet && roots);
|
||||
|
||||
kj::Promise<Result<std::string>> formatGraphML(ref<Store> store, StorePathSet && roots);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ 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',
|
||||
@@ -19,6 +20,7 @@ legacy_sources = files(
|
||||
|
||||
legacy_headers = files(
|
||||
'build-remote.hh',
|
||||
'builtin-builder.hh',
|
||||
'nix-build.hh',
|
||||
'nix-channel.hh',
|
||||
'nix-collect-garbage.hh',
|
||||
|
||||
+27
-13
@@ -25,6 +25,7 @@
|
||||
#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
|
||||
|
||||
@@ -191,7 +192,11 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
throw UsageError("'-p' and '-E' are mutually exclusive");
|
||||
|
||||
AutoDelete tmpDir(createTempDir(myName));
|
||||
AutoDelete buildTopTmpDir(createTempSubdir(tmpDir, "build-top"));
|
||||
// NOTE: we assume there's no `build-top` directory created inside of `tmpDir` and we have
|
||||
// ownership of this.
|
||||
auto buildTopTmpDir = tmpDir + "/build-top";
|
||||
createDirs(buildTopTmpDir);
|
||||
|
||||
if (outLink.empty())
|
||||
outLink = (Path) tmpDir + "/result";
|
||||
|
||||
@@ -208,7 +213,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").mkBool(true);
|
||||
newArgs.alloc("inNixShell") = {NewValueAs::boolean, true};
|
||||
for (auto & i : *autoArgs) newArgs.insert(i);
|
||||
autoArgsWithInNixShell = newArgs.finish();
|
||||
}
|
||||
@@ -267,8 +272,7 @@ 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, vRoot);
|
||||
Value vRoot = state->eval(e);
|
||||
|
||||
std::function<bool(const Value & v)> takesNixShellAttr;
|
||||
takesNixShellAttr = [&](const Value & v) {
|
||||
@@ -351,8 +355,7 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
"(import <nixpkgs> {}).bashInteractive",
|
||||
CanonPath::fromCwd());
|
||||
|
||||
Value v;
|
||||
state->eval(expr, v);
|
||||
Value v = state->eval(expr);
|
||||
|
||||
auto drv = getDerivation(*state, v, false);
|
||||
if (!drv)
|
||||
@@ -425,6 +428,18 @@ 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);
|
||||
@@ -528,13 +543,12 @@ static int main_nix_build(AsyncIoRoot & aio, std::string programName, Strings ar
|
||||
|
||||
printMsg(lvlChatty, "running shell: %s", concatMapStringsSep(" ", args, shellEscape));
|
||||
|
||||
RunningProgram proc = runProgram2(
|
||||
{.program = *shell,
|
||||
.searchPath = true,
|
||||
.args = args,
|
||||
.environment = env,
|
||||
.dieWithParent = true}
|
||||
);
|
||||
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.
|
||||
|
||||
@@ -64,7 +64,7 @@ static int main_nix_collect_garbage(AsyncIoRoot & aio, std::string programName,
|
||||
{
|
||||
bool removeOld = false;
|
||||
|
||||
GCOptions options;
|
||||
GCOptions options = {.action = GCOptions::gcDeleteDead};
|
||||
|
||||
LegacyArgs(aio, programName, [&](Strings::iterator & arg, const Strings::iterator & end) {
|
||||
if (*arg == "--help")
|
||||
@@ -75,12 +75,13 @@ 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") dryRun = true;
|
||||
else if (*arg == "--max-freed")
|
||||
} 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
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).parseCmdline(argv);
|
||||
|
||||
@@ -92,24 +93,12 @@ 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(true, results);
|
||||
PrintFreed freed(options.action, 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", Uncolored(i));
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
+257
-214
@@ -151,8 +151,7 @@ static void getAllExprs(Evaluator & state,
|
||||
continue;
|
||||
}
|
||||
/* Load the expression on demand. */
|
||||
Value vArg;
|
||||
vArg.mkString(path2.canonical().abs());
|
||||
Value vArg = {NewValueAs::string, path2.canonical().abs()};
|
||||
if (seen.size() == maxAttrs)
|
||||
throw Error("too many Nix expressions in directory '%1%'", path);
|
||||
attrs.alloc(attrName
|
||||
@@ -165,15 +164,13 @@ static void getAllExprs(Evaluator & state,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void loadSourceExpr(EvalState & state, const SourcePath & path_, Value & v)
|
||||
static Value loadSourceExpr(EvalState & state, const SourcePath & path_)
|
||||
{
|
||||
auto path = state.ctx.paths.checkSourcePath(path_);
|
||||
auto st = path.stat();
|
||||
|
||||
if (isNixExpr(state.ctx.paths, path, st))
|
||||
state.evalFile(path, v);
|
||||
return state.evalFile(path);
|
||||
|
||||
/* The path is a directory. Put the Nix expressions in the
|
||||
directory in a set, with the file name of each expression as
|
||||
@@ -186,7 +183,7 @@ static void loadSourceExpr(EvalState & state, const SourcePath & path_, Value &
|
||||
attrs.alloc("_combineChannels") = Value::EMPTY_LIST;
|
||||
StringSet seen;
|
||||
getAllExprs(state.ctx, path, seen, attrs);
|
||||
v.mkAttrs(attrs);
|
||||
return {NewValueAs::attrs, attrs};
|
||||
}
|
||||
|
||||
else throw Error("path '%s' is not a directory or a Nix expression", path);
|
||||
@@ -197,8 +194,7 @@ static void loadDerivations(EvalState & state, const SourcePath & nixExprPath,
|
||||
std::string systemFilter, Bindings & autoArgs,
|
||||
const std::string & pathPrefix, DrvInfos & elems)
|
||||
{
|
||||
Value vRoot;
|
||||
loadSourceExpr(state, nixExprPath, vRoot);
|
||||
Value vRoot = loadSourceExpr(state, nixExprPath);
|
||||
|
||||
Value v(findAlongAttrPath(state, pathPrefix, autoArgs, vRoot).first);
|
||||
|
||||
@@ -420,14 +416,12 @@ static void queryInstSources(EvalState & state,
|
||||
(import ./foo.nix)' = `(import ./foo.nix).bar'. */
|
||||
case srcNixExprs: {
|
||||
|
||||
Value vArg;
|
||||
loadSourceExpr(state, *instSource.nixExprPath, vArg);
|
||||
Value vArg = loadSourceExpr(state, *instSource.nixExprPath);
|
||||
|
||||
for (auto & i : args) {
|
||||
Expr & eFun = state.ctx.parseExprFromString(i, CanonPath::fromCwd());
|
||||
Value vFun, vTmp;
|
||||
state.eval(eFun, vFun);
|
||||
vTmp = {NewValueAs::app, state.ctx.mem, vFun, vArg};
|
||||
Value vFun = state.eval(eFun);
|
||||
Value vTmp = {NewValueAs::app, state.ctx.mem, vFun, vArg};
|
||||
getDerivations(state, vTmp, "", *instSource.autoArgs, elems, true);
|
||||
}
|
||||
|
||||
@@ -479,8 +473,7 @@ static void queryInstSources(EvalState & state,
|
||||
}
|
||||
|
||||
case srcAttrPath: {
|
||||
Value vRoot;
|
||||
loadSourceExpr(state, *instSource.nixExprPath, vRoot);
|
||||
Value vRoot = loadSourceExpr(state, *instSource.nixExprPath);
|
||||
for (auto & i : args) {
|
||||
Value v(findAlongAttrPath(state, i, *instSource.autoArgs, vRoot).first);
|
||||
getDerivations(state, v, "", *instSource.autoArgs, elems, true);
|
||||
@@ -517,8 +510,7 @@ static bool keep(EvalState & state, DrvInfo & drv)
|
||||
static void setMetaFlag(EvalState & state, DrvInfo & drv,
|
||||
const std::string & name, const std::string & value)
|
||||
{
|
||||
Value v;
|
||||
v.mkString(value);
|
||||
Value v = {NewValueAs::string, value};
|
||||
drv.setMeta(state, name, v);
|
||||
}
|
||||
|
||||
@@ -892,8 +884,7 @@ static bool cmpElemByName(EvalState & state, DrvInfo & a, DrvInfo & b)
|
||||
|
||||
typedef std::list<Strings> Table;
|
||||
|
||||
|
||||
void printTable(Table & table)
|
||||
std::string formatTable(Table & table)
|
||||
{
|
||||
auto nrColumns = table.size() > 0 ? table.front().size() : 0;
|
||||
|
||||
@@ -908,18 +899,22 @@ void printTable(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', ' ');
|
||||
cout << s;
|
||||
result << s;
|
||||
if (column < nrColumns - 1)
|
||||
cout << std::string(widths[column] - s.size() + 2, ' ');
|
||||
result << std::string(widths[column] - s.size() + 2, ' ');
|
||||
}
|
||||
cout << std::endl;
|
||||
result << std::endl;
|
||||
}
|
||||
|
||||
return result.str();
|
||||
}
|
||||
|
||||
|
||||
@@ -1130,209 +1125,250 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs)
|
||||
return;
|
||||
}
|
||||
|
||||
RunPager pager;
|
||||
withPager([&](Pager & pager) {
|
||||
Table table;
|
||||
std::ostringstream xmlStream;
|
||||
XMLWriter xml(true, xmlStream);
|
||||
xml.openElement("items");
|
||||
|
||||
Table table;
|
||||
std::ostringstream dummy;
|
||||
XMLWriter xml(true, *(xmlOutput ? &cout : &dummy));
|
||||
XMLOpenElement xmlRoot(xml, "items");
|
||||
|
||||
for (auto & i : elems) {
|
||||
try {
|
||||
if (i.hasFailed()) continue;
|
||||
|
||||
//Activity act(*logger, lvlDebug, "outputting query result '%1%'", i.attrPath);
|
||||
|
||||
if (globals.prebuiltOnly &&
|
||||
!validPaths.count(i.queryOutPath(*state)) &&
|
||||
!substitutablePaths.count(i.queryOutPath(*state)))
|
||||
continue;
|
||||
|
||||
/* For table output. */
|
||||
Strings columns;
|
||||
|
||||
/* 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();
|
||||
for (auto & i : elems) {
|
||||
try {
|
||||
if (i.hasFailed()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (xmlOutput) {
|
||||
if (diff != cvUnavail) {
|
||||
attrs["versionDiff"] = ch;
|
||||
attrs["maxComparedVersion"] = version;
|
||||
// Activity act(*logger, lvlDebug, "outputting query result '%1%'", i.attrPath);
|
||||
|
||||
if (globals.prebuiltOnly && !validPaths.count(i.queryOutPath(*state))
|
||||
&& !substitutablePaths.count(i.queryOutPath(*state)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/* For table output. */
|
||||
Strings columns;
|
||||
|
||||
/* 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" : "-")
|
||||
);
|
||||
}
|
||||
} 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) {
|
||||
if (drvPath) attrs["drvPath"] = store.printStorePath(*drvPath);
|
||||
} else
|
||||
columns.push_back(drvPath ? store.printStorePath(*drvPath) : "-");
|
||||
}
|
||||
|
||||
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);
|
||||
attrs["attrPath"] = i.attrPath;
|
||||
} else if (printAttrPath) {
|
||||
columns.push_back(i.attrPath);
|
||||
}
|
||||
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) {
|
||||
XMLAttrs attrs2;
|
||||
attrs2["name"] = j.first;
|
||||
if (j.second)
|
||||
attrs2["path"] = store.printStorePath(*j.second);
|
||||
xml.writeEmptyElement("output", attrs2);
|
||||
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 (printMeta) {
|
||||
StringSet metaNames = i.queryMetaNames(*state);
|
||||
for (auto & j : metaNames) {
|
||||
|
||||
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) {
|
||||
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) {
|
||||
if (drvPath) {
|
||||
attrs["drvPath"] = store.printStorePath(*drvPath);
|
||||
}
|
||||
} else {
|
||||
columns.push_back(drvPath ? store.printStorePath(*drvPath) : "-");
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
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);
|
||||
}
|
||||
XMLAttrs attrs3;
|
||||
attrs3["value"] = elem.str();
|
||||
xml.writeEmptyElement("string", attrs3);
|
||||
}
|
||||
} else if (v->type() == nAttrs) {
|
||||
attrs2["type"] = "strings";
|
||||
XMLOpenElement m(xml, "meta", attrs2);
|
||||
Bindings & attrs = *v->attrs();
|
||||
for (auto &i : attrs) {
|
||||
const Attr & a(*attrs.get(i.name));
|
||||
if (a.value.type() != nString) {
|
||||
continue;
|
||||
}
|
||||
XMLAttrs attrs3;
|
||||
attrs3["type"] = globals.state->symbols[i.name];
|
||||
attrs3["value"] = a.value.str();
|
||||
xml.writeEmptyElement("string", attrs3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
table.push_back(columns);
|
||||
}
|
||||
} else
|
||||
table.push_back(columns);
|
||||
|
||||
cout.flush();
|
||||
cout.flush();
|
||||
|
||||
} catch (AssertionError & e) {
|
||||
printMsg(lvlTalkative, "skipping derivation named '%1%' which gives an assertion failure", i.queryName(*state));
|
||||
} catch (Error & e) {
|
||||
e.addTrace(nullptr, "while querying the derivation named '%1%'", i.queryName(*state));
|
||||
throw;
|
||||
} catch (AssertionError & e) {
|
||||
printMsg(
|
||||
lvlTalkative,
|
||||
"skipping derivation named '%1%' which gives an assertion failure",
|
||||
i.queryName(*state)
|
||||
);
|
||||
} catch (Error & e) {
|
||||
e.addTrace(nullptr, "while querying the derivation named '%1%'", i.queryName(*state));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!xmlOutput) printTable(table);
|
||||
// </items>
|
||||
xml.closeElement();
|
||||
|
||||
if (!xmlOutput) {
|
||||
pager << formatTable(table);
|
||||
} else {
|
||||
pager << xmlStream.str();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
static void opSwitchProfile(Globals & globals, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
if (opFlags.size() > 0)
|
||||
@@ -1383,20 +1419,27 @@ static void opListGenerations(Globals & globals, Strings opFlags, Strings opArgs
|
||||
|
||||
auto [gens, curGen] = findGenerations(globals.profile);
|
||||
|
||||
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)" : "");
|
||||
}
|
||||
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)" : ""
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
static void opDeleteGenerations(Globals & globals, Strings opFlags, Strings opArgs)
|
||||
{
|
||||
if (opFlags.size() > 0)
|
||||
|
||||
@@ -34,8 +34,7 @@ void processExpr(EvalState & state, const Strings & attrPaths,
|
||||
return;
|
||||
}
|
||||
|
||||
Value vRoot;
|
||||
state.eval(e, vRoot);
|
||||
Value vRoot = state.eval(e);
|
||||
|
||||
for (auto & i : attrPaths) {
|
||||
Value v(findAlongAttrPath(state, i, autoArgs, vRoot).first);
|
||||
@@ -47,7 +46,7 @@ void processExpr(EvalState & state, const Strings & attrPaths,
|
||||
if (autoArgs.empty())
|
||||
vRes = v;
|
||||
else
|
||||
state.autoCallFunction(autoArgs, v, vRes, noPos);
|
||||
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
|
||||
|
||||
+102
-83
@@ -25,7 +25,9 @@
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
#include <ostream>
|
||||
#include <ranges>
|
||||
#include <sstream>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
@@ -288,6 +290,7 @@ try {
|
||||
graph. Topological sorting is used to keep the tree relatively
|
||||
flat. */
|
||||
static void printTree(
|
||||
std::ostream & ostream,
|
||||
std::shared_ptr<Store> store,
|
||||
AsyncIoRoot & aio,
|
||||
const StorePath & path,
|
||||
@@ -297,11 +300,11 @@ static void printTree(
|
||||
)
|
||||
{
|
||||
if (!done.insert(path).second) {
|
||||
cout << fmt("%s%s [...]\n", firstPad, store->printStorePath(path));
|
||||
ostream << fmt("%s%s [...]\n", firstPad, store->printStorePath(path));
|
||||
return;
|
||||
}
|
||||
|
||||
cout << fmt("%s%s\n", firstPad, store->printStorePath(path));
|
||||
ostream << fmt("%s%s\n", firstPad, store->printStorePath(path));
|
||||
|
||||
auto info = aio.blockOn(store->queryPathInfo(path));
|
||||
|
||||
@@ -315,6 +318,7 @@ static void printTree(
|
||||
for (const auto &[n, i] : enumerate(sorted)) {
|
||||
bool last = n + 1 == sorted.size();
|
||||
printTree(
|
||||
ostream,
|
||||
store,
|
||||
aio,
|
||||
i,
|
||||
@@ -373,17 +377,15 @@ opQuery(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
|
||||
|
||||
if (!query) query = qOutputs;
|
||||
|
||||
RunPager pager;
|
||||
|
||||
switch (*query) {
|
||||
|
||||
withPager([&](Pager & pager) {
|
||||
switch (*query) {
|
||||
case qOutputs: {
|
||||
for (auto & i : opArgs) {
|
||||
auto outputs = aio.blockOn(
|
||||
maybeUseOutputs(store, store->followLinksToStorePath(i), true, forceRealise)
|
||||
);
|
||||
for (auto & outputPath : outputs)
|
||||
cout << fmt("%1%\n", store->printStorePath(outputPath));
|
||||
auto outputs =
|
||||
aio.blockOn(maybeUseOutputs(store, store->followLinksToStorePath(i), true, forceRealise));
|
||||
for (auto & outputPath : outputs) {
|
||||
pager << fmt("%1%\n", store->printStorePath(outputPath));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -394,54 +396,55 @@ opQuery(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
|
||||
case qReferrersClosure: {
|
||||
StorePathSet paths;
|
||||
for (auto & i : opArgs) {
|
||||
auto ps = aio.blockOn(maybeUseOutputs(
|
||||
store, store->followLinksToStorePath(i), useOutput, forceRealise
|
||||
));
|
||||
auto ps = aio.blockOn(
|
||||
maybeUseOutputs(store, store->followLinksToStorePath(i), useOutput, forceRealise)
|
||||
);
|
||||
for (auto & j : ps) {
|
||||
if (query == qRequisites) {
|
||||
aio.blockOn(store->computeFSClosure(j, paths, false, includeOutputs));
|
||||
}
|
||||
else if (query == qReferences) {
|
||||
for (auto & p : aio.blockOn(store->queryPathInfo(j))->references)
|
||||
} else if (query == qReferences) {
|
||||
for (auto & p : aio.blockOn(store->queryPathInfo(j))->references) {
|
||||
paths.insert(p);
|
||||
}
|
||||
else if (query == qReferrers) {
|
||||
}
|
||||
} else if (query == qReferrers) {
|
||||
StorePathSet tmp;
|
||||
aio.blockOn(store->queryReferrers(j, tmp));
|
||||
for (auto & i : tmp)
|
||||
for (auto & i : tmp) {
|
||||
paths.insert(i);
|
||||
}
|
||||
else if (query == qReferrersClosure)
|
||||
}
|
||||
} 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)
|
||||
cout << fmt("%s\n", store->printStorePath(*i));
|
||||
for (StorePaths::reverse_iterator i = sorted.rbegin(); i != sorted.rend(); ++i) {
|
||||
pager << fmt("%s\n", store->printStorePath(*i));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case qDeriver:
|
||||
for (auto & i : opArgs) {
|
||||
auto info = aio.blockOn(store->queryPathInfo(store->followLinksToStorePath(i)));
|
||||
cout << fmt("%s\n", info->deriver ? store->printStorePath(*info->deriver) : "unknown-deriver");
|
||||
pager << 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)
|
||||
cout << fmt("%s\n", store->printStorePath(*i));
|
||||
for (StorePaths::reverse_iterator i = sorted.rbegin(); i != sorted.rend(); ++i) {
|
||||
pager << fmt("%s\n", store->printStorePath(*i));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -450,95 +453,112 @@ opQuery(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
|
||||
auto path = aio.blockOn(useDeriver(store, store->followLinksToStorePath(i)));
|
||||
Derivation drv = aio.blockOn(store->derivationFromPath(path));
|
||||
StringPairs::iterator j = drv.env.find(bindingName);
|
||||
if (j == drv.env.end())
|
||||
throw Error("derivation '%s' has no environment binding named '%s'",
|
||||
store->printStorePath(path), bindingName);
|
||||
cout << fmt("%s\n", j->second);
|
||||
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);
|
||||
}
|
||||
break;
|
||||
|
||||
case qHash:
|
||||
case qSize:
|
||||
for (auto & i : opArgs) {
|
||||
for (auto & j : aio.blockOn(maybeUseOutputs(
|
||||
store, store->followLinksToStorePath(i), useOutput, forceRealise
|
||||
)))
|
||||
for (auto & j : aio.blockOn(
|
||||
maybeUseOutputs(store, store->followLinksToStorePath(i), useOutput, forceRealise)
|
||||
))
|
||||
{
|
||||
auto info = aio.blockOn(store->queryPathInfo(j));
|
||||
if (query == qHash) {
|
||||
assert(info->narHash.type == HashType::SHA256);
|
||||
cout << fmt("%s\n", info->narHash.to_string(Base::Base32, true));
|
||||
} else if (query == qSize)
|
||||
cout << fmt("%d\n", info->narSize);
|
||||
pager << fmt("%s\n", info->narHash.to_string(HashFormat::Base32));
|
||||
} else if (query == qSize) {
|
||||
pager << fmt("%d\n", info->narSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case qTree: {
|
||||
StorePathSet done;
|
||||
for (auto & i : opArgs)
|
||||
printTree(store, aio, store->followLinksToStorePath(i), "", "", done);
|
||||
for (auto & i : opArgs) {
|
||||
std::stringstream tmp;
|
||||
printTree(tmp, store, aio, store->followLinksToStorePath(i), "", "", done);
|
||||
pager << tmp.str();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case qGraph: {
|
||||
StorePathSet roots;
|
||||
for (auto & i : opArgs)
|
||||
for (auto & j : aio.blockOn(maybeUseOutputs(
|
||||
store, store->followLinksToStorePath(i), useOutput, forceRealise
|
||||
)))
|
||||
for (auto & i : opArgs) {
|
||||
for (auto & j : aio.blockOn(
|
||||
maybeUseOutputs(store, store->followLinksToStorePath(i), useOutput, forceRealise)
|
||||
))
|
||||
{
|
||||
roots.insert(j);
|
||||
}
|
||||
aio.blockOn(printDotGraph(ref<Store>::unsafeFromPtr(store), std::move(roots)));
|
||||
}
|
||||
pager << aio.blockOn(formatDotGraph(ref<Store>::unsafeFromPtr(store), std::move(roots)));
|
||||
break;
|
||||
}
|
||||
|
||||
case qGraphML: {
|
||||
StorePathSet roots;
|
||||
for (auto & i : opArgs)
|
||||
for (auto & j : aio.blockOn(maybeUseOutputs(
|
||||
store, store->followLinksToStorePath(i), useOutput, forceRealise
|
||||
)))
|
||||
for (auto & i : opArgs) {
|
||||
for (auto & j : aio.blockOn(
|
||||
maybeUseOutputs(store, store->followLinksToStorePath(i), useOutput, forceRealise)
|
||||
))
|
||||
{
|
||||
roots.insert(j);
|
||||
}
|
||||
aio.blockOn(printGraphML(ref<Store>::unsafeFromPtr(store), std::move(roots)));
|
||||
}
|
||||
pager << aio.blockOn(formatGraphML(ref<Store>::unsafeFromPtr(store), std::move(roots)));
|
||||
break;
|
||||
}
|
||||
|
||||
case qResolve: {
|
||||
for (auto & i : opArgs)
|
||||
cout << fmt("%s\n", store->printStorePath(store->followLinksToStorePath(i)));
|
||||
for (auto & i : opArgs) {
|
||||
pager << fmt("%s\n", store->printStorePath(store->followLinksToStorePath(i)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case qRoots: {
|
||||
StorePathSet args;
|
||||
for (auto & i : opArgs)
|
||||
for (auto & p : aio.blockOn(maybeUseOutputs(
|
||||
store, store->followLinksToStorePath(i), useOutput, forceRealise
|
||||
)))
|
||||
for (auto & i : opArgs) {
|
||||
for (auto & p : aio.blockOn(
|
||||
maybeUseOutputs(store, store->followLinksToStorePath(i), useOutput, forceRealise)
|
||||
))
|
||||
{
|
||||
args.insert(p);
|
||||
}
|
||||
}
|
||||
|
||||
StorePathSet referrers;
|
||||
aio.blockOn(store->computeFSClosure(
|
||||
args, referrers, true, settings.gcKeepOutputs, settings.gcKeepDerivations));
|
||||
args, referrers, true, settings.gcKeepOutputs, settings.gcKeepDerivations
|
||||
));
|
||||
|
||||
auto & gcStore = require<GcStore>(*store);
|
||||
Roots roots = aio.blockOn(gcStore.findRoots(false));
|
||||
for (auto & [target, links] : roots)
|
||||
if (referrers.find(target) != referrers.end())
|
||||
for (auto & link : links)
|
||||
cout << fmt("%1% -> %2%\n", link, gcStore.printStorePath(target));
|
||||
for (auto & [target, links] : roots) {
|
||||
if (referrers.find(target) != referrers.end()) {
|
||||
for (auto & link : links) {
|
||||
pager << fmt("%1% -> %2%\n", link, gcStore.printStorePath(target));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -574,15 +594,16 @@ opReadLog(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Stri
|
||||
|
||||
auto & logStore = require<LogStore>(*store);
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -711,12 +732,8 @@ static void opGC(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlag
|
||||
}
|
||||
|
||||
else {
|
||||
PrintFreed freed(options.action == GCOptions::gcDeleteDead, results);
|
||||
PrintFreed freed(options.action, results);
|
||||
aio.blockOn(gcStore.collectGarbage(options, results));
|
||||
|
||||
if (options.action != GCOptions::gcDeleteDead)
|
||||
for (auto & i : results.paths)
|
||||
cout << i << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -749,7 +766,7 @@ opDelete(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, Strin
|
||||
auto & gcStore = require<GcStore>(*store);
|
||||
|
||||
GCResults results;
|
||||
PrintFreed freed(true, results);
|
||||
PrintFreed freed(options.action, results);
|
||||
aio.blockOn(gcStore.collectGarbage(options, results));
|
||||
}
|
||||
|
||||
@@ -860,10 +877,12 @@ opVerifyPath(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, S
|
||||
aio.blockOn(aio.blockOn(store->narFromPath(path))->drainInto(sink));
|
||||
auto current = sink.finish();
|
||||
if (current.first != info->narHash) {
|
||||
printError("path '%s' was modified! expected hash '%s', got '%s'",
|
||||
printError(
|
||||
"path '%s' was modified! expected hash '%s', got '%s'",
|
||||
store->printStorePath(path),
|
||||
info->narHash.to_string(Base::SRI, true),
|
||||
current.first.to_string(Base::SRI, true));
|
||||
info->narHash.to_string(),
|
||||
current.first.to_string()
|
||||
);
|
||||
status = 1;
|
||||
}
|
||||
}
|
||||
|
||||
+24
-19
@@ -46,24 +46,30 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
|
||||
|
||||
auto attrs = state.ctx.buildBindings(7 + outputs.size());
|
||||
|
||||
attrs.alloc(state.ctx.s.type).mkString("derivation");
|
||||
attrs.alloc(state.ctx.s.name).mkString(i.queryName(state));
|
||||
attrs.alloc(state.ctx.symbols.sym_type) = {NewValueAs::string, "derivation"};
|
||||
attrs.alloc(state.ctx.symbols.sym_name) = {NewValueAs::string, i.queryName(state)};
|
||||
auto system = i.querySystem(state);
|
||||
if (!system.empty())
|
||||
attrs.alloc(state.ctx.s.system).mkString(system);
|
||||
attrs.alloc(state.ctx.s.outPath).mkString(state.ctx.store->printStorePath(i.queryOutPath(state)));
|
||||
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))
|
||||
};
|
||||
if (drvPath)
|
||||
attrs.alloc(state.ctx.s.drvPath).mkString(state.ctx.store->printStorePath(*drvPath));
|
||||
attrs.alloc(state.ctx.symbols.sym_drvPath) = {
|
||||
NewValueAs::string, state.ctx.store->printStorePath(*drvPath)
|
||||
};
|
||||
|
||||
// Copy each output meant for installation.
|
||||
auto & vOutputs = attrs.alloc(state.ctx.s.outputs);
|
||||
auto & vOutputs = attrs.alloc(state.ctx.symbols.sym_outputs);
|
||||
auto outputsList = state.ctx.mem.newList(outputs.size());
|
||||
vOutputs = {NewValueAs::list, outputsList};
|
||||
for (const auto & [m, j] : enumerate(outputs)) {
|
||||
outputsList->elems[m].mkString(j.first);
|
||||
outputsList->elems[m] = {NewValueAs::string, j.first};
|
||||
auto outputAttrs = state.ctx.buildBindings(2);
|
||||
outputAttrs.alloc(state.ctx.s.outPath).mkString(state.ctx.store->printStorePath(*j.second));
|
||||
attrs.alloc(j.first).mkAttrs(outputAttrs);
|
||||
outputAttrs.alloc(state.ctx.symbols.sym_outPath) = {
|
||||
NewValueAs::string, state.ctx.store->printStorePath(*j.second)
|
||||
};
|
||||
attrs.alloc(j.first) = {NewValueAs::attrs, outputAttrs};
|
||||
|
||||
/* This is only necessary when installing store paths, e.g.,
|
||||
`nix-env -i /nix/store/abcd...-foo'. */
|
||||
@@ -81,9 +87,9 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
|
||||
meta.insert(state.ctx.symbols.create(j), *v);
|
||||
}
|
||||
|
||||
attrs.alloc(state.ctx.s.meta).mkAttrs(meta);
|
||||
attrs.alloc(state.ctx.symbols.sym_meta) = {NewValueAs::attrs, meta};
|
||||
|
||||
manifest->elems[n++].mkAttrs(attrs);
|
||||
manifest->elems[n++] = {NewValueAs::attrs, attrs};
|
||||
|
||||
if (drvPath) references.insert(*drvPath);
|
||||
}
|
||||
@@ -97,18 +103,17 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
|
||||
str.str(), references));
|
||||
|
||||
/* Get the environment builder expression. */
|
||||
Value envBuilder;
|
||||
state.eval(state.ctx.parseExprFromString(
|
||||
#include "buildenv.nix.gen.hh"
|
||||
, CanonPath::root), envBuilder);
|
||||
Value envBuilder = state.eval(state.ctx.parseExprFromString(
|
||||
#include "buildenv.nix.gen.hh"
|
||||
, CanonPath::root
|
||||
));
|
||||
|
||||
/* 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;
|
||||
args.mkAttrs(attrs);
|
||||
Value args = {NewValueAs::attrs, attrs};
|
||||
|
||||
Value topLevel{NewValueAs::app, state.ctx.mem, envBuilder, args};
|
||||
|
||||
@@ -116,9 +121,9 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
|
||||
debug("evaluating user environment builder");
|
||||
state.forceValue(topLevel, noPos);
|
||||
NixStringContext context;
|
||||
const Attr & aDrvPath(*topLevel.attrs()->get(state.ctx.s.drvPath));
|
||||
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.s.outPath));
|
||||
const Attr & aOutPath(*topLevel.attrs()->get(state.ctx.symbols.sym_outPath));
|
||||
auto topLevelOut = state.coerceToStorePath(aOutPath.pos, aOutPath.value, context, "");
|
||||
|
||||
/* Realise the resulting store expression. */
|
||||
|
||||
@@ -20,8 +20,7 @@ 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), v);
|
||||
Value v = state.evalFile(CanonPath(manifestFile));
|
||||
Bindings & bindings(*state.ctx.mem.allocBindings(0));
|
||||
getDerivations(state, v, "", bindings, elems, false);
|
||||
}
|
||||
@@ -99,6 +98,10 @@ void ProfileElement::updateStorePaths(
|
||||
for (auto & output : bfd.outputs) {
|
||||
storePaths.insert(output.second);
|
||||
}
|
||||
|
||||
if (settings.envKeepDerivations) {
|
||||
storePaths.insert(bfd.drvPath.path);
|
||||
}
|
||||
},
|
||||
},
|
||||
buildable.raw()
|
||||
|
||||
@@ -75,6 +75,15 @@ CopyCommand::CopyCommand()
|
||||
});
|
||||
}
|
||||
|
||||
void CopyCommand::run()
|
||||
{
|
||||
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));
|
||||
@@ -82,9 +91,6 @@ ref<Store> CopyCommand::createStore(AsyncIoRoot & in)
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,6 @@ namespace nix {
|
||||
|
||||
extern std::string programPath;
|
||||
|
||||
extern char * * savedArgv;
|
||||
|
||||
class EvalState;
|
||||
struct Pos;
|
||||
class Store;
|
||||
@@ -56,9 +54,11 @@ private:
|
||||
struct CopyCommand : virtual StoreCommand
|
||||
{
|
||||
std::string srcUri, dstUri;
|
||||
bool requireStore = true;
|
||||
|
||||
CopyCommand();
|
||||
|
||||
void run() override;
|
||||
ref<Store> createStore(AsyncIoRoot & in) override;
|
||||
|
||||
ref<Store> getDstStore();
|
||||
@@ -171,7 +171,7 @@ struct RawInstallablesCommand : virtual Args, SourceExprCommand
|
||||
|
||||
std::vector<FlakeRef> getFlakeRefsForCompletion() override;
|
||||
|
||||
private:
|
||||
protected:
|
||||
|
||||
std::vector<std::string> rawInstallables;
|
||||
};
|
||||
@@ -220,13 +220,11 @@ struct MixOperateOnOptions : virtual Args
|
||||
*/
|
||||
struct BuiltPathsCommand : InstallablesCommand, virtual MixOperateOnOptions
|
||||
{
|
||||
private:
|
||||
protected:
|
||||
|
||||
bool recursive = false;
|
||||
bool all = false;
|
||||
|
||||
protected:
|
||||
|
||||
Realise realiseMode = Realise::Derivation;
|
||||
|
||||
public:
|
||||
|
||||
@@ -185,11 +185,9 @@ Bindings * MixEvalArgs::getAutoArgs(Evaluator & state)
|
||||
for (auto & i : autoArgs) {
|
||||
Value v;
|
||||
if (i.second[0] == 'E')
|
||||
state.evalLazily(
|
||||
state.parseExprFromString(i.second.substr(1), CanonPath::fromCwd()), v
|
||||
);
|
||||
v = state.evalLazily(state.parseExprFromString(i.second.substr(1), CanonPath::fromCwd()));
|
||||
else
|
||||
v.mkString(((std::string_view) i.second).substr(1));
|
||||
v = {NewValueAs::string, ((std::string_view) i.second).substr(1)};
|
||||
res.insert(state.symbols.create(i.first), v);
|
||||
}
|
||||
return res.finish();
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#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;
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,6 @@
|
||||
#include "lix/libexpr/flake/flake.hh"
|
||||
#include "lix/libexpr/eval-cache.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace nix {
|
||||
|
||||
std::vector<std::string> InstallableFlake::getActualAttrPaths()
|
||||
|
||||
@@ -219,8 +219,7 @@ void SourceExprCommand::completeInstallable(EvalState & state, AddCompletions &
|
||||
state.aio.blockOn(lookupFileArg(*evaluator, *file)).unwrap()
|
||||
));
|
||||
|
||||
Value root;
|
||||
state.eval(e, root);
|
||||
Value root = state.eval(e);
|
||||
|
||||
auto autoArgs = getAutoArgs(*evaluator);
|
||||
|
||||
@@ -237,8 +236,7 @@ void SourceExprCommand::completeInstallable(EvalState & state, AddCompletions &
|
||||
|
||||
auto [v1, pos] = findAlongAttrPath(state, prefix_, *autoArgs, root);
|
||||
state.forceValue(v1, pos);
|
||||
Value v2;
|
||||
state.autoCallFunction(*autoArgs, v1, v2, pos);
|
||||
Value v2 = state.autoCallFunction(*autoArgs, v1, pos);
|
||||
|
||||
if (v2.type() == nAttrs) {
|
||||
for (auto & i : *v2.attrs()) {
|
||||
@@ -411,8 +409,7 @@ ref<eval_cache::EvalCache> openEvalCache(
|
||||
if (getEnv("NIX_ALLOW_EVAL").value_or("1") == "0")
|
||||
throw Error("not everything is cached, but evaluation is not allowed");
|
||||
|
||||
Value vFlake;
|
||||
flake::callFlake(state, *lockedFlake, vFlake);
|
||||
Value vFlake = flake::callFlake(state, *lockedFlake);
|
||||
|
||||
state.forceAttrs(vFlake, noPos, "while parsing cached flake data");
|
||||
|
||||
@@ -453,13 +450,13 @@ Installables SourceExprCommand::parseInstallables(
|
||||
|
||||
if (file == "-") {
|
||||
auto & e = evaluator->parseStdin();
|
||||
state.eval(e, vFile);
|
||||
vFile = state.eval(e);
|
||||
}
|
||||
else if (file)
|
||||
state.evalFile(state.aio.blockOn(lookupFileArg(*evaluator, *file)).unwrap(), vFile);
|
||||
vFile = state.evalFile(state.aio.blockOn(lookupFileArg(*evaluator, *file)).unwrap());
|
||||
else {
|
||||
auto & e = evaluator->parseExprFromString(*expr, CanonPath::fromCwd());
|
||||
state.eval(e, vFile);
|
||||
vFile = state.eval(e);
|
||||
}
|
||||
|
||||
for (auto & s : ss) {
|
||||
|
||||
+17
-3
@@ -5,18 +5,32 @@
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
namespace nix {
|
||||
|
||||
typedef std::function<int(AsyncIoRoot &, std::string, std::list<std::string>)> MainFunction;
|
||||
|
||||
struct LegacyCommandRegistry
|
||||
{
|
||||
using LegacyCommandMap = std::map<std::string, MainFunction>;
|
||||
typedef std::function<int(AsyncIoRoot &, std::string, std::list<std::string>)> MainFunction;
|
||||
typedef std::function<
|
||||
int(AsyncIoRoot &, std::string, std::list<std::string>, std::span<char *>)>
|
||||
RawMainFunction;
|
||||
|
||||
using LegacyCommandMap = std::map<std::string, RawMainFunction>;
|
||||
static LegacyCommandMap * commands;
|
||||
|
||||
static void add(const std::string & name, MainFunction fun)
|
||||
{
|
||||
addWithRaw(
|
||||
name,
|
||||
[fun](AsyncIoRoot & aio, std::string name, std::list<std::string> args, std::span<char *>) {
|
||||
return fun(aio, name, args);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static void addWithRaw(const std::string & name, RawMainFunction fun)
|
||||
{
|
||||
if (!commands) commands = new LegacyCommandMap;
|
||||
(*commands)[name] = fun;
|
||||
|
||||
@@ -5,6 +5,4 @@ includedir=@includedir@
|
||||
Name: Lix (libcmd)
|
||||
Description: Lix Package Manager (libcmd)
|
||||
Version: @PACKAGE_VERSION@
|
||||
Requires: lix-base lix-util lix-store
|
||||
Requires.private: lix-fetchers lix-expr lix-main @BOEHM_IF_FOUND@ libeditline lowdown ncurses
|
||||
Libs: -L${libdir} @LIBLIX_DOC_IF_STATIC@ -llixcmd
|
||||
Requires: lix
|
||||
|
||||
@@ -74,7 +74,11 @@ std::string renderMarkdownToTerminal(std::string_view markdown, StandardOutputSt
|
||||
.vmargin = 0,
|
||||
#endif /* LOWDOWN_SEPARATE_TERM_OPTS */
|
||||
.feat = LOWDOWN_COMMONMARK | LOWDOWN_FENCED | LOWDOWN_DEFLIST | LOWDOWN_TABLES,
|
||||
#ifdef LOWDOWN_CONSOLIDATED_OFLAGS
|
||||
.oflags = LOWDOWN_NOLINK,
|
||||
#else
|
||||
.oflags = LOWDOWN_TERM_NOLINK,
|
||||
#endif /* LOWDOWN_CONSOLIDATED_OFLAGS */
|
||||
};
|
||||
if (!shouldANSI(fileno)) {
|
||||
opts.oflags |= LOWDOWN_TERM_NOANSI;
|
||||
|
||||
+3
-62
@@ -1,4 +1,4 @@
|
||||
libcmd_sources = files(
|
||||
liblix_sources += files(
|
||||
'built-path.cc',
|
||||
'cmd-profiles.cc',
|
||||
'command.cc',
|
||||
@@ -21,6 +21,7 @@ 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',
|
||||
@@ -32,68 +33,8 @@ libcmd_headers = files(
|
||||
'repl.hh',
|
||||
)
|
||||
|
||||
libcmd_generated_headers = [
|
||||
liblix_generated_headers += [
|
||||
gen_header.process('repl-overlays.nix', preserve_path_from: meson.current_source_dir()),
|
||||
]
|
||||
|
||||
libcmd = library(
|
||||
'lixcmd',
|
||||
libcmd_generated_headers,
|
||||
libcmd_sources,
|
||||
dependencies : [
|
||||
liblixutil,
|
||||
liblixstore,
|
||||
liblixfetchers,
|
||||
liblixexpr,
|
||||
liblixmain,
|
||||
liblix_doc,
|
||||
boehm,
|
||||
editline,
|
||||
kj,
|
||||
lowdown,
|
||||
ncurses,
|
||||
nlohmann_json,
|
||||
],
|
||||
# '../..' for self references like "lix/libcmd/*.hh"
|
||||
include_directories : [ '../..' ],
|
||||
cpp_pch : cpp_pch,
|
||||
install : true,
|
||||
# FIXME(Qyriad): is this right?
|
||||
install_rpath : libdir,
|
||||
)
|
||||
|
||||
install_headers(libcmd_headers, subdir : 'lix/libcmd', preserve_path : true)
|
||||
custom_target(
|
||||
command : [ 'cp', '@INPUT@', '@OUTPUT@' ],
|
||||
input : libcmd_generated_headers,
|
||||
output : '@PLAINNAME@',
|
||||
install : true,
|
||||
install_dir : includedir / 'lix/libcmd',
|
||||
)
|
||||
|
||||
liblixcmd = declare_dependency(
|
||||
include_directories : include_directories('../..'),
|
||||
dependencies : [
|
||||
liblixutil,
|
||||
liblixstore,
|
||||
kj,
|
||||
],
|
||||
link_with : libcmd,
|
||||
)
|
||||
meson.override_dependency('lix-cmd', liblixcmd)
|
||||
|
||||
# FIXME: not using the pkg-config module because it creates way too many deps
|
||||
# while meson migration is in progress, and we want to not include boost here
|
||||
configure_file(
|
||||
input : 'lix-cmd.pc.in',
|
||||
output : 'lix-cmd.pc',
|
||||
install_dir : libdir / 'pkgconfig',
|
||||
configuration : {
|
||||
'prefix' : prefix,
|
||||
'libdir' : libdir,
|
||||
'includedir' : includedir,
|
||||
'PACKAGE_VERSION' : meson.project_version(),
|
||||
'BOEHM_IF_FOUND' : boehm.found() ? 'bdw-gc' : '',
|
||||
'LIBLIX_DOC_IF_STATIC' : is_static ? '-llix_doc' : '',
|
||||
},
|
||||
)
|
||||
|
||||
@@ -48,7 +48,7 @@ char ** copyCompletions(const StringSet& possible)
|
||||
if (vp) {
|
||||
while (--ac >= 0)
|
||||
free(vp[ac]);
|
||||
free(vp);
|
||||
free(static_cast<void *>(vp));
|
||||
}
|
||||
throw Error("allocation failure");
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ public:
|
||||
*
|
||||
* This function logs but ignores errors from readline's write_history().
|
||||
*/
|
||||
virtual void writeHistory();
|
||||
void writeHistory();
|
||||
virtual ~ReadlineLikeInteracter() override;
|
||||
};
|
||||
|
||||
|
||||
+909
-450
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,86 @@
|
||||
#include "common.hh"
|
||||
#include <csignal>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <format>
|
||||
#include <sched.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
LIBEXEC_HELPER(0)
|
||||
|
||||
static int waitFor(pid_t child)
|
||||
{
|
||||
int status;
|
||||
while (true) {
|
||||
if (waitpid(child, &status, 0) == -1) {
|
||||
if (errno != EINTR) {
|
||||
DIE_UNLESS_SYS("waitpid()", -1);
|
||||
}
|
||||
} else if (WIFEXITED(status)) {
|
||||
return WEXITSTATUS(status);
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
die(std::format("child died with signal {}", WTERMSIG(status)));
|
||||
} else {
|
||||
die(std::format("child exited {}", status));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int helperMain(const char * name, std::span<char *> args) noexcept
|
||||
{
|
||||
size_t stackSize = 1ul * 1024 * 1024;
|
||||
auto stack = static_cast<char *>(
|
||||
mmap(0, stackSize, PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0)
|
||||
);
|
||||
if (stack == MAP_FAILED) {
|
||||
die(std::format("mmap(): {}", strerror(errno)));
|
||||
}
|
||||
|
||||
const bool haveUserNS = [&] {
|
||||
auto child = clone([](void *) { return 0; }, stack + stackSize, CLONE_NEWUSER | SIGCHLD, nullptr);
|
||||
if (child == -1) {
|
||||
printf("user %s\n", strerror(errno));
|
||||
return false;
|
||||
} else if (auto status = waitFor(child)) {
|
||||
die(std::format("userns check child failed unexpectedly with status {}", status));
|
||||
} else {
|
||||
printf("user\n");
|
||||
return true;
|
||||
}
|
||||
}();
|
||||
|
||||
{
|
||||
auto child = clone(
|
||||
[](void *) {
|
||||
/* Make sure we don't remount the parent's /proc. */
|
||||
if (mount(0, "/", 0, MS_PRIVATE | MS_REC, 0) == -1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Test whether we can remount /proc. The kernel disallows
|
||||
this if /proc is not fully visible, i.e. if there are
|
||||
filesystems mounted on top of files inside /proc. See
|
||||
https://lore.kernel.org/lkml/87tvsrjai0.fsf@xmission.com/T/. */
|
||||
if (mount("none", "/proc", "proc", 0, 0) == -1) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
},
|
||||
stack + stackSize,
|
||||
CLONE_NEWNS | CLONE_NEWPID | (haveUserNS ? CLONE_NEWUSER : 0) | SIGCHLD,
|
||||
nullptr
|
||||
);
|
||||
if (child == -1) {
|
||||
printf("mount-pid %s\n", strerror(errno));
|
||||
} else if (waitFor(child) != 0) {
|
||||
printf("mount-pid failed to remount /proc\n");
|
||||
} else {
|
||||
printf("mount-pid\n");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
///@file common setup/utility header for libexec helpers
|
||||
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <format> // IWYU pragma: keep
|
||||
#include <limits>
|
||||
#include <span>
|
||||
#include <string> // IWYU pragma: keep
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <unistd.h>
|
||||
|
||||
/// file descriptor of the error reporting pipe. anything written to this pipe
|
||||
/// will be treated as a fatal error message regardless of helper exit status.
|
||||
/// an empty line (a single `\n` byte) will be treated as successful startup,
|
||||
/// any errors encountered later can be retrieved by the parent in due course.
|
||||
inline int ERR_PIPE;
|
||||
|
||||
inline void writeErrPipe(std::string_view msg)
|
||||
{
|
||||
while (!msg.empty()) {
|
||||
if (auto wrote = write(ERR_PIPE, msg.data(), msg.size()); wrote >= 0) {
|
||||
msg.remove_prefix(size_t(wrote));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// immediately terminate helper execution with a fatal error.
|
||||
[[noreturn]]
|
||||
inline void die(std::string_view msg)
|
||||
{
|
||||
writeErrPipe(msg);
|
||||
exit(252);
|
||||
}
|
||||
|
||||
/// converts an argument to an integer or dies with a message.
|
||||
template<typename T, size_t N>
|
||||
requires std::is_integral_v<T>
|
||||
T argToInt(const char (&argName)[N], const char * str)
|
||||
{
|
||||
// this should really just wrap std::from_chars, but macos doesn't have it.
|
||||
for (const auto c : std::string_view(str)) {
|
||||
if (c != '-' && !std::isdigit(c)) {
|
||||
die(std::format("invalid {} argument", argName));
|
||||
}
|
||||
}
|
||||
|
||||
char * end = nullptr;
|
||||
const auto tmp = [&] {
|
||||
if constexpr (std::is_signed_v<T>) {
|
||||
return std::strtoimax(str, &end, 10); // NOLINT(lix-unsafe-c-calls): str is a C string
|
||||
} else {
|
||||
return std::strtoumax(str, &end, 10); // NOLINT(lix-unsafe-c-calls): str is a C string
|
||||
}
|
||||
}();
|
||||
|
||||
if (!end || *end || tmp < std::numeric_limits<T>::min() || tmp > std::numeric_limits<T>::max()) {
|
||||
die(std::format("invalid {} argument", argName));
|
||||
}
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/// check syscall result and immediately terminate with a message on failure.
|
||||
#define DIE_UNLESS_SYS(name, expr) \
|
||||
([&] { \
|
||||
if ((expr) == -1) { \
|
||||
die(std::format("{}: {}", name, strerror(errno))); \
|
||||
} \
|
||||
}())
|
||||
|
||||
/// declare the TU expanding this as a libexec helper with at least `expectedArgs`
|
||||
/// arguments. more arguments may be passed, fewer args will be treated as a fatal
|
||||
/// error and reported immediately. a valid ERR_PIPE pipe must be passed as as the
|
||||
/// first argument and will be set to close-on-exec to not pass it on to children.
|
||||
#define LIBEXEC_HELPER(expectedArgs) \
|
||||
int main(int argc, char * argv[]) \
|
||||
{ \
|
||||
if (argc < (expectedArgs) + 2) { \
|
||||
_exit(254); \
|
||||
} \
|
||||
\
|
||||
try { \
|
||||
/* NOTE: we purposely accept imperfect conversion, only errors are fatal. \
|
||||
if our parent messes this up we have *much* bigger problems than this. */ \
|
||||
ERR_PIPE = std::stoi(argv[1]); \
|
||||
} catch (...) { \
|
||||
_exit(253); \
|
||||
} \
|
||||
\
|
||||
DIE_UNLESS_SYS("error pipe fcntl", fcntl(ERR_PIPE, F_SETFD, FD_CLOEXEC)); \
|
||||
return helperMain(argv[0], {argv + 2, argv + argc}); \
|
||||
}
|
||||
|
||||
int helperMain(const char * name, std::span<char *> args) noexcept;
|
||||
@@ -0,0 +1,60 @@
|
||||
#include "common.hh"
|
||||
#include <charconv>
|
||||
#include <cstring>
|
||||
#include <format>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if __APPLE__
|
||||
#include <sys/syscall.h>
|
||||
#endif
|
||||
|
||||
LIBEXEC_HELPER(1)
|
||||
|
||||
int helperMain(const char * name, std::span<char *> args) noexcept
|
||||
{
|
||||
std::string_view uidArg = args[0];
|
||||
uid_t uid;
|
||||
|
||||
if (auto res = std::from_chars(uidArg.begin(), uidArg.end(), uid);
|
||||
res.ptr != uidArg.end() || res.ec != std::errc())
|
||||
{
|
||||
die("invalid uid argument");
|
||||
}
|
||||
|
||||
/* The system call kill(-1, sig) sends the signal `sig' to all
|
||||
users to which the current process can send signals. So we
|
||||
switch to that uid and send a mass kill once we've done so. */
|
||||
|
||||
if (setuid(uid) == -1) {
|
||||
die(std::format("setuid(): {}", strerror(errno)));
|
||||
}
|
||||
|
||||
while (true) {
|
||||
#ifdef __APPLE__
|
||||
/* OSX's kill syscall takes a third parameter that, among
|
||||
other things, determines if kill(-1, signo) affects the
|
||||
calling process. In the OSX libc, it's set to true,
|
||||
which means "follow POSIX", which we don't want here */
|
||||
if (syscall(SYS_kill, -1, SIGKILL, false) == 0) {
|
||||
break;
|
||||
}
|
||||
#else
|
||||
if (kill(-1, SIGKILL) == 0) {
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
if (errno == ESRCH || errno == EPERM) {
|
||||
break; /* no more processes */
|
||||
}
|
||||
if (errno != EINTR) {
|
||||
die(std::format("cannot kill processes for uid {}: {}", uid, strerror(errno)));
|
||||
}
|
||||
}
|
||||
|
||||
/* !!! We should really do some check to make sure that there are
|
||||
no processes left running under `uid', but there is no portable
|
||||
way to do so (I think). The most reliable way may be `ps -eo
|
||||
uid | grep -q $uid'. */
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
#include "launch-builder.hh"
|
||||
#include "lix/libstore/build/request.capnp.h"
|
||||
#include "lix/libutil/rpc.hh"
|
||||
#include <cstdlib>
|
||||
#include <spawn.h>
|
||||
#include <string_view>
|
||||
#include <sys/sysctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace nix {
|
||||
|
||||
/* This definition is undocumented but depended upon by all major browsers. */
|
||||
extern "C" int sandbox_init_with_parameters(
|
||||
const char * profile, uint64_t flags, const char * const parameters[], char ** errorbuf
|
||||
);
|
||||
|
||||
bool prepareChildSetup(build::Request::Reader request)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void finishChildSetup(build::Request::Reader request)
|
||||
{
|
||||
const auto config = request.getPlatform().getDarwin();
|
||||
|
||||
/* The tmpDir in scope points at the temporary build directory for our derivation. Some packages try
|
||||
different mechanisms to find temporary directories, so we want to open up a broader place for them
|
||||
to put their files, if needed. */
|
||||
auto globalTmpDir = rpc::to<std::string>(config.getGlobalTempDir());
|
||||
|
||||
/* They don't like trailing slashes on subpath directives */
|
||||
if (globalTmpDir.back() == '/') {
|
||||
globalTmpDir.pop_back();
|
||||
}
|
||||
|
||||
if (auto env = getenv("_NIX_TEST_NO_SANDBOX"); env && env != std::string_view("1")) {
|
||||
std::vector<const char *> sandboxArgs;
|
||||
sandboxArgs.push_back("_NIX_BUILD_TOP");
|
||||
sandboxArgs.push_back(config.getTempDir().cStr());
|
||||
sandboxArgs.push_back("_GLOBAL_TMP_DIR");
|
||||
sandboxArgs.push_back(globalTmpDir.c_str());
|
||||
if (config.getAllowLocalNetworking()) {
|
||||
sandboxArgs.push_back("_ALLOW_LOCAL_NETWORKING");
|
||||
sandboxArgs.push_back("1");
|
||||
}
|
||||
sandboxArgs.push_back(nullptr);
|
||||
// NOLINTNEXTLINE(lix-unsafe-c-calls): all of these are env names or paths
|
||||
if (sandbox_init_with_parameters(config.getSandboxProfile().cStr(), 0, sandboxArgs.data(), nullptr)) {
|
||||
writeFull(STDERR_FILENO, "failed to configure sandbox\n");
|
||||
_exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]]
|
||||
void execBuilder(build::Request::Reader request)
|
||||
{
|
||||
const auto config = request.getPlatform().getDarwin();
|
||||
|
||||
posix_spawnattr_t attrp;
|
||||
|
||||
if (posix_spawnattr_init(&attrp)) {
|
||||
throw SysError("failed to initialize builder");
|
||||
}
|
||||
|
||||
if (posix_spawnattr_setflags(&attrp, POSIX_SPAWN_SETEXEC)) {
|
||||
throw SysError("failed to initialize builder");
|
||||
}
|
||||
|
||||
const auto platform = rpc::to<std::string_view>(config.getPlatform());
|
||||
|
||||
if (platform == "aarch64-darwin") {
|
||||
// Unset kern.curproc_arch_affinity so we can escape Rosetta
|
||||
int affinity = 0;
|
||||
sysctlbyname("kern.curproc_arch_affinity", nullptr, nullptr, &affinity, sizeof(affinity));
|
||||
|
||||
cpu_type_t cpu = CPU_TYPE_ARM64;
|
||||
posix_spawnattr_setbinpref_np(&attrp, 1, &cpu, nullptr);
|
||||
} else if (platform == "x86_64-darwin") {
|
||||
cpu_type_t cpu = CPU_TYPE_X86_64;
|
||||
posix_spawnattr_setbinpref_np(&attrp, 1, &cpu, nullptr);
|
||||
}
|
||||
|
||||
ExecRequest req{request};
|
||||
|
||||
posix_spawn(nullptr, req.builder.c_str(), nullptr, &attrp, req.args.data(), req.envs.data());
|
||||
throw SysError(errno, std::format("running {}", req.builder));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#include "launch-builder.hh"
|
||||
#include "lix/libstore/build/request.capnp.h"
|
||||
#include <format>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace nix {
|
||||
|
||||
bool prepareChildSetup(build::Request::Reader config)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void finishChildSetup(build::Request::Reader config) {}
|
||||
|
||||
void execBuilder(build::Request::Reader config)
|
||||
{
|
||||
ExecRequest req{config};
|
||||
|
||||
execve(req.builder.data(), req.args.data(), req.envs.data());
|
||||
throw SysError("running %s", req.builder);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,451 @@
|
||||
#include "launch-builder.hh"
|
||||
#include "lix/libstore/build/request.capnp.h"
|
||||
#include "lix/libutil/rpc.hh"
|
||||
#include <cassert>
|
||||
#include <csignal>
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <kj/io.h>
|
||||
#include <net/if.h>
|
||||
#include <netinet/in.h>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/personality.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if HAVE_SECCOMP
|
||||
#include <linux/filter.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <seccomp.h>
|
||||
#endif
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace nix {
|
||||
|
||||
// TODO dedup with libutil
|
||||
static void setPersonality(std::string_view system)
|
||||
{
|
||||
/* Change the personality to 32-bit if we're doing an
|
||||
i686-linux build on an x86_64-linux machine. */
|
||||
struct utsname utsbuf;
|
||||
uname(&utsbuf);
|
||||
if ((system == "i686-linux"
|
||||
&& (std::string_view(SYSTEM) == "x86_64-linux"
|
||||
|| (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "x86_64"))))
|
||||
|| system == "armv7l-linux" || system == "armv6l-linux" || system == "armv5tel-linux")
|
||||
{
|
||||
if (personality(PER_LINUX32) == -1) {
|
||||
throw SysError("cannot set 32-bit personality");
|
||||
}
|
||||
}
|
||||
|
||||
/* Disable address space randomization for improved
|
||||
determinism. */
|
||||
int cur = personality(0xffffffff);
|
||||
if (cur != -1) {
|
||||
personality(cur | ADDR_NO_RANDOMIZE);
|
||||
}
|
||||
}
|
||||
|
||||
bool pathExists(const fs::path & path)
|
||||
{
|
||||
return fs::exists(fs::symlink_status(path));
|
||||
}
|
||||
|
||||
void bindPath(const fs::path & source, const fs::path & target, bool optional = false)
|
||||
{
|
||||
debug("bind mounting %1% to %2%", source, target);
|
||||
|
||||
auto bindMount = [&]() {
|
||||
if (mount(source.c_str(), target.c_str(), "", MS_BIND | MS_REC, 0) == -1) {
|
||||
throw SysError("bind mount from %1% to %2% failed", source, target);
|
||||
}
|
||||
};
|
||||
|
||||
auto st = fs::symlink_status(source);
|
||||
if (st.type() == fs::file_type::not_found) {
|
||||
if (optional) {
|
||||
return;
|
||||
} else {
|
||||
throw SysError("getting attributes of path %1%", source);
|
||||
}
|
||||
}
|
||||
|
||||
if (st.type() == fs::file_type::directory) {
|
||||
fs::create_directories(target);
|
||||
bindMount();
|
||||
} else if (st.type() == fs::file_type::symlink) {
|
||||
// Symlinks can (apparently) not be bind-mounted, so just copy it
|
||||
fs::create_directories(target.parent_path());
|
||||
fs::copy_symlink(source, target);
|
||||
} else {
|
||||
fs::create_directories(target.parent_path());
|
||||
if (kj::AutoCloseFd file{open(target.c_str(), O_RDWR | O_CREAT, 0644)}; file == nullptr) {
|
||||
throw SysError("could not create %s", target);
|
||||
}
|
||||
bindMount();
|
||||
}
|
||||
}
|
||||
|
||||
bool prepareChildSetup(build::Request::Reader request)
|
||||
{
|
||||
auto config = request.getPlatform().getLinux();
|
||||
|
||||
// Set the NO_NEW_PRIVS prctl flag.
|
||||
// This both makes loading seccomp filters work for unprivileged users,
|
||||
// and is an additional security measure in its own right.
|
||||
if (prctl(PR_SET_NO_NEW_PRIVS, 1L, 0L, 0L, 0L) == -1) {
|
||||
throw SysError("PR_SET_NO_NEW_PRIVS failed");
|
||||
}
|
||||
#if HAVE_SECCOMP
|
||||
if (config.hasSeccompFilters()) {
|
||||
const auto seccompBPF = config.getSeccompFilters();
|
||||
const auto entries = seccompBPF.size() / sizeof(struct sock_filter);
|
||||
assert(entries <= std::numeric_limits<unsigned short>::max());
|
||||
struct sock_fprog fprog = {
|
||||
.len = static_cast<unsigned short>(entries),
|
||||
// the kernel does not actually write to the filter, and doesn't care about alignment
|
||||
.filter = const_cast<struct sock_filter *>(
|
||||
reinterpret_cast<const struct sock_filter *>(seccompBPF.begin())
|
||||
),
|
||||
};
|
||||
if (syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER, 0, &fprog) != 0) {
|
||||
throw SysError("unable to load seccomp BPF program");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
KJ_DEFER(setPersonality(rpc::to<std::string_view>(config.getPlatform())));
|
||||
|
||||
if (!config.hasSandbox()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto sandbox = config.getSandbox();
|
||||
|
||||
// NOLINTBEGIN(lix-unsafe-c-calls): we trust the parent that all sandbox config is correct.
|
||||
// no strings in the linux sandbox config can be set by normal users or derivation authors,
|
||||
// except (in single-user instances) storeDir and chrootRootDir, which must be valid paths.
|
||||
//
|
||||
// NOLINTBEGIN(lix-foreign-exceptions): they're all properly caught by the builder main fn.
|
||||
|
||||
const fs::path chrootRootDir{rpc::to<std::string_view>(sandbox.getChrootRootDir())};
|
||||
|
||||
if (sandbox.getPrivateNetwork()) {
|
||||
/* Initialise the loopback interface. */
|
||||
kj::AutoCloseFd fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP));
|
||||
if (fd == nullptr) {
|
||||
throw SysError("cannot open IP socket");
|
||||
}
|
||||
|
||||
struct ifreq ifr;
|
||||
strcpy(ifr.ifr_name, "lo");
|
||||
ifr.ifr_flags = IFF_UP | IFF_LOOPBACK | IFF_RUNNING;
|
||||
if (ioctl(fd.get(), SIOCSIFFLAGS, &ifr) == -1) {
|
||||
throw SysError("cannot set loopback interface flags");
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the hostname etc. to fixed values. */
|
||||
char hostname[] = "localhost";
|
||||
if (sethostname(hostname, sizeof(hostname)) == -1) {
|
||||
throw SysError("cannot set host name");
|
||||
}
|
||||
char domainname[] = "(none)"; // kernel default
|
||||
if (setdomainname(domainname, sizeof(domainname)) == -1) {
|
||||
throw SysError("cannot set domain name");
|
||||
}
|
||||
|
||||
/* Make all filesystems private. This is necessary
|
||||
because subtrees may have been mounted as "shared"
|
||||
(MS_SHARED). (Systemd does this, for instance.) Even
|
||||
though we have a private mount namespace, mounting
|
||||
filesystems on top of a shared subtree still propagates
|
||||
outside of the namespace. Making a subtree private is
|
||||
local to the namespace, though, so setting MS_PRIVATE
|
||||
does not affect the outside world. */
|
||||
const fs::path storeDir{rpc::to<std::string>(sandbox.getStoreDir())};
|
||||
const auto chrootStoreDir = chrootRootDir / storeDir.relative_path();
|
||||
|
||||
if (mount(0, "/", 0, MS_PRIVATE | MS_REC, 0) == -1) {
|
||||
throw SysError("unable to make '/' private");
|
||||
}
|
||||
|
||||
/* Bind-mount chroot directory to itself, to treat it as a
|
||||
different filesystem from /, as needed for pivot_root. */
|
||||
if (mount(chrootRootDir.c_str(), chrootRootDir.c_str(), "", MS_BIND, 0) == -1) {
|
||||
throw SysError("unable to bind mount %1%", chrootRootDir);
|
||||
}
|
||||
|
||||
/* Bind-mount the sandbox's Nix store onto itself so that
|
||||
we can mark it as a "shared" subtree, allowing bind
|
||||
mounts made in *this* mount namespace to be propagated
|
||||
into the child namespace created by the
|
||||
unshare(CLONE_NEWNS) call below.
|
||||
|
||||
Marking chrootRootDir as MS_SHARED causes pivot_root()
|
||||
to fail with EINVAL. Don't know why. */
|
||||
if (mount(chrootStoreDir.c_str(), chrootStoreDir.c_str(), "", MS_BIND, 0) == -1) {
|
||||
throw SysError("unable to bind mount the Nix store");
|
||||
}
|
||||
|
||||
if (mount("", chrootStoreDir.c_str(), "", MS_SHARED, 0) == -1) {
|
||||
throw SysError("unable to make %s shared", chrootStoreDir);
|
||||
}
|
||||
|
||||
bool devMounted = false;
|
||||
bool devPtsMounted = false;
|
||||
|
||||
/* Bind-mount all the directories from the "host"
|
||||
filesystem that we want in the chroot
|
||||
environment. */
|
||||
for (auto path : sandbox.getPaths()) {
|
||||
const fs::path source{rpc::to<std::string_view>(path.getSource())};
|
||||
const fs::path target{rpc::to<std::string_view>(path.getTarget())};
|
||||
devMounted |= target == "/dev";
|
||||
devPtsMounted |= target == "/dev/pts";
|
||||
if (source == "/proc") {
|
||||
continue; // backwards compatibility
|
||||
}
|
||||
|
||||
#if HAVE_EMBEDDED_SANDBOX_SHELL
|
||||
if (source == "__embedded_sandbox_shell__") {
|
||||
static unsigned char sh[] = {
|
||||
#include "embedded-sandbox-shell.gen.hh"
|
||||
};
|
||||
const fs::path dst = chrootRootDir / target.relative_path();
|
||||
fs::create_directories(dst.parent_path());
|
||||
kj::AutoCloseFd fd(open(dst.c_str(), O_RDWR | O_CREAT, 0755));
|
||||
if (fd == nullptr) {
|
||||
throw SysError("cannot create sandbox shell");
|
||||
}
|
||||
writeFull(fd.get(), std::string_view((const char *) sh, sizeof(sh)));
|
||||
fs::permissions(dst, fs::perms(0555));
|
||||
} else
|
||||
#endif
|
||||
bindPath(source, chrootRootDir / target.relative_path(), path.getOptional());
|
||||
}
|
||||
|
||||
/* Set up a nearly empty /dev, unless the user asked to
|
||||
bind-mount the host /dev. */
|
||||
if (!devMounted) {
|
||||
const auto bind = [&](fs::path item) { bindPath(item, chrootRootDir / item.relative_path()); };
|
||||
|
||||
fs::create_directories(chrootRootDir / "dev/shm");
|
||||
fs::create_directories(chrootRootDir / "dev/pts");
|
||||
bind("/dev/full");
|
||||
if (sandbox.getWantsKvm() && pathExists("/dev/kvm")) {
|
||||
bind("/dev/kvm");
|
||||
}
|
||||
bind("/dev/null");
|
||||
bind("/dev/random");
|
||||
bind("/dev/tty");
|
||||
bind("/dev/urandom");
|
||||
bind("/dev/zero");
|
||||
fs::create_symlink("/proc/self/fd", chrootRootDir / "dev/fd");
|
||||
fs::create_symlink("/proc/self/fd/0", chrootRootDir / "dev/stdin");
|
||||
fs::create_symlink("/proc/self/fd/1", chrootRootDir / "dev/stdout");
|
||||
fs::create_symlink("/proc/self/fd/2", chrootRootDir / "dev/stderr");
|
||||
}
|
||||
|
||||
/* Bind a new instance of procfs on /proc. */
|
||||
fs::create_directories(chrootRootDir / "proc");
|
||||
if (mount("none", (chrootRootDir / "proc").c_str(), "proc", 0, 0) == -1) {
|
||||
throw SysError("mounting /proc");
|
||||
}
|
||||
|
||||
/* Mount sysfs on /sys. */
|
||||
if (request.hasCredentials() && request.getCredentials().getUidCount() != 1) {
|
||||
fs::create_directories(chrootRootDir / "sys");
|
||||
if (mount("none", (chrootRootDir / "sys").c_str(), "sysfs", 0, 0) == -1) {
|
||||
throw SysError("mounting /sys");
|
||||
}
|
||||
}
|
||||
|
||||
/* Mount a new tmpfs on /dev/shm to ensure that whatever
|
||||
the builder puts in /dev/shm is cleaned up automatically. */
|
||||
if (pathExists("/dev/shm")
|
||||
&& mount("none", (chrootRootDir / "dev/shm").c_str(), "tmpfs", 0, sandbox.getSandboxShmFlags().cStr())
|
||||
== -1)
|
||||
{
|
||||
throw SysError("mounting /dev/shm");
|
||||
}
|
||||
|
||||
/* Mount a new devpts on /dev/pts. Note that this
|
||||
requires the kernel to be compiled with
|
||||
CONFIG_DEVPTS_MULTIPLE_INSTANCES=y (which is the case
|
||||
if /dev/ptx/ptmx exists). */
|
||||
if (pathExists("/dev/pts/ptmx") && !pathExists(chrootRootDir / "dev/ptmx") && !devPtsMounted) {
|
||||
if (mount("none", (chrootRootDir / "dev/pts").c_str(), "devpts", 0, "newinstance,mode=0620") == 0) {
|
||||
fs::create_symlink("/dev/pts/ptmx", chrootRootDir / "dev/ptmx");
|
||||
|
||||
/* Make sure /dev/pts/ptmx is world-writable. With some
|
||||
Linux versions, it is created with permissions 0. */
|
||||
fs::permissions(chrootRootDir / "dev/pts/ptmx", fs::perms(0666));
|
||||
} else {
|
||||
if (errno != EINVAL) {
|
||||
throw SysError("mounting /dev/pts");
|
||||
}
|
||||
bindPath("/dev/pts", chrootRootDir / "dev/pts");
|
||||
bindPath("/dev/ptmx", chrootRootDir / "dev/ptmx");
|
||||
}
|
||||
}
|
||||
|
||||
/* Make /etc unwritable */
|
||||
if (!sandbox.getUseUidRange()) {
|
||||
fs::permissions(chrootRootDir / "etc", fs::perms(0555));
|
||||
}
|
||||
|
||||
/* The comment below is now outdated. Recursive Nix has been removed.
|
||||
* So there's no need to make path appear in the sandbox.
|
||||
* TODO(Raito): cleanup before a merge.
|
||||
*/
|
||||
/* Unshare this mount namespace. This is necessary because
|
||||
pivot_root() below changes the root of the mount
|
||||
namespace. This means that the call to setns() in
|
||||
addDependency() would hide the host's filesystem,
|
||||
making it impossible to bind-mount paths from the host
|
||||
Nix store into the sandbox. Therefore, we save the
|
||||
pre-pivot_root namespace in
|
||||
sandboxMountNamespace. Since we made /nix/store a
|
||||
shared subtree above, this allows addDependency() to
|
||||
make paths appear in the sandbox. */
|
||||
if (unshare(CLONE_NEWNS) == -1) {
|
||||
throw SysError("unsharing mount namespace");
|
||||
}
|
||||
|
||||
/* Creating a new cgroup namespace is independent of whether we enabled the cgroup experimental feature.
|
||||
* We always create a new cgroup namespace from a sandboxing perspective. */
|
||||
/* Unshare the cgroup namespace. This means
|
||||
/proc/self/cgroup will show the child's cgroup as '/'
|
||||
rather than whatever it is in the parent. */
|
||||
if (unshare(CLONE_NEWCGROUP) == -1) {
|
||||
throw SysError("unsharing cgroup namespace");
|
||||
}
|
||||
|
||||
/* Do the chroot(). */
|
||||
if (chdir(chrootRootDir.c_str()) == -1) {
|
||||
throw SysError("cannot change directory to %1%", chrootRootDir);
|
||||
}
|
||||
|
||||
if (mkdir("real-root", 0) == -1) {
|
||||
throw SysError("cannot create real-root directory");
|
||||
}
|
||||
|
||||
if (syscall(SYS_pivot_root, ".", "real-root") == -1) {
|
||||
throw SysError("cannot pivot old root directory onto %1%", chrootRootDir / "real-root");
|
||||
}
|
||||
|
||||
if (chroot(".") == -1) {
|
||||
throw SysError("cannot change root directory to %1%", chrootRootDir);
|
||||
}
|
||||
|
||||
if (umount2("real-root", MNT_DETACH) == -1) {
|
||||
throw SysError("cannot unmount real root filesystem");
|
||||
}
|
||||
|
||||
if (rmdir("real-root") == -1) {
|
||||
throw SysError("cannot remove real-root directory");
|
||||
}
|
||||
|
||||
/* Switch to the sandbox uid/gid in the user namespace,
|
||||
which corresponds to the build user or calling user in
|
||||
the parent namespace. */
|
||||
if (setgid(sandbox.getGid()) == -1) {
|
||||
throw SysError("setgid failed");
|
||||
}
|
||||
if (setuid(sandbox.getUid()) == -1) {
|
||||
throw SysError("setuid failed");
|
||||
}
|
||||
|
||||
if (sandbox.hasWaitForInterface()) {
|
||||
// wait for the pasta interface to appear. pasta can't signal us when
|
||||
// it's done setting up the namespace, so we have to wait for a while
|
||||
kj::AutoCloseFd fd(socket(PF_INET, SOCK_DGRAM, IPPROTO_IP));
|
||||
if (fd == nullptr) {
|
||||
throw SysError("cannot open IP socket");
|
||||
}
|
||||
|
||||
struct ifreq ifr;
|
||||
strncpy(ifr.ifr_name, sandbox.getWaitForInterface().cStr(), sizeof(ifr.ifr_name));
|
||||
// wait two minutes for the interface to appear. if it does not do so
|
||||
// we are either grossly overloaded, or pasta startup failed somehow.
|
||||
static constexpr int SINGLE_WAIT_US = 1000;
|
||||
static constexpr int TOTAL_WAIT_US = 120'000'000;
|
||||
for (unsigned tries = 0;; tries++) {
|
||||
if (tries > TOTAL_WAIT_US / SINGLE_WAIT_US) {
|
||||
throw std::runtime_error(
|
||||
"sandbox network setup timed out, please check daemon logs for possible error output."
|
||||
);
|
||||
} else if (ioctl(fd.get(), SIOCGIFFLAGS, &ifr) == 0) {
|
||||
if ((ifr.ifr_ifru.ifru_flags & IFF_UP) != 0) {
|
||||
break;
|
||||
}
|
||||
} else if (errno == ENODEV) {
|
||||
usleep(SINGLE_WAIT_US);
|
||||
} else {
|
||||
throw SysError("cannot get loopback interface flags");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTEND(lix-foreign-exceptions)
|
||||
// NOLINTEND(lix-unsafe-c-calls)
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void finishChildSetup(build::Request::Reader request)
|
||||
{
|
||||
// clear all capabilities when not running as root in the sandbox.
|
||||
// we always clear ambient capabilities because they survive exec.
|
||||
if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0L, 0L, 0L) == -1) {
|
||||
throw SysError("clearing ambient caps");
|
||||
}
|
||||
if (!request.getPlatform().getLinux().getSandbox().getUseUidRange()) {
|
||||
static constexpr uint32_t LINUX_CAPABILITY_VERSION_3 = 0x20080522;
|
||||
static constexpr uint32_t LINUX_CAPABILITY_U32S_3 = 2;
|
||||
struct user_cap_header_struct
|
||||
{
|
||||
uint32_t version;
|
||||
int pid;
|
||||
} hdr = {LINUX_CAPABILITY_VERSION_3, 0};
|
||||
struct user_cap_data_struct
|
||||
{
|
||||
uint32_t effective;
|
||||
uint32_t permitted;
|
||||
uint32_t inheritable;
|
||||
} data[LINUX_CAPABILITY_U32S_3] = {};
|
||||
if (syscall(SYS_capset, &hdr, data)) {
|
||||
throw SysError("couldn't set capabilities");
|
||||
}
|
||||
}
|
||||
|
||||
if (prctl(PR_SET_PDEATHSIG, SIGKILL) == -1) {
|
||||
throw SysError("setting death signal");
|
||||
}
|
||||
if (getppid() != request.getPlatform().getLinux().getParentPid()) {
|
||||
raise(SIGKILL);
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]]
|
||||
void execBuilder(build::Request::Reader request)
|
||||
{
|
||||
ExecRequest req{request};
|
||||
|
||||
execve(req.builder.data(), req.args.data(), req.envs.data());
|
||||
throw SysError("running %s", req.builder);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
#include "launch-builder.hh"
|
||||
#include "lix/libstore/build/request.capnp.h"
|
||||
#include "lix/libutil/rpc.hh"
|
||||
#include <capnp/message.h>
|
||||
#include <capnp/serialize.h>
|
||||
#include <csignal>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <fcntl.h>
|
||||
#include <filesystem>
|
||||
#include <grp.h>
|
||||
#include <limits>
|
||||
#include <sys/resource.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
namespace nix {
|
||||
bool printDebugLogs = false;
|
||||
|
||||
static void requireCString(const char * context, const std::string & s)
|
||||
{
|
||||
if (s.contains('\0')) {
|
||||
std::string p{s};
|
||||
for (auto pos = p.find('\0'); pos != p.npos; pos = p.find('\0')) {
|
||||
p.replace(pos, 1, "␀");
|
||||
}
|
||||
// NOLINTNEXTLINE(lix-foreign-exceptions)
|
||||
throw std::runtime_error(std::format("derivation {} {} contains NUL bytes", context, p));
|
||||
}
|
||||
}
|
||||
|
||||
ExecRequest::ExecRequest(build::Request::Reader request)
|
||||
{
|
||||
const auto fill = [](auto context, auto & strings, auto & pointers, auto from) {
|
||||
strings.reserve(from.size());
|
||||
for (auto arg : from) {
|
||||
strings.push_back(rpc::to<std::string>(arg));
|
||||
requireCString(context, strings.back());
|
||||
pointers.push_back(strings.back().data());
|
||||
}
|
||||
pointers.push_back(nullptr);
|
||||
};
|
||||
|
||||
builder = rpc::to<std::string>(request.getBuilder());
|
||||
requireCString("derivation builder", builder);
|
||||
|
||||
fill("derivation argument", argsStorage, args, request.getArgs());
|
||||
fill("derivation environment entry", envsStorage, envs, request.getEnvironment());
|
||||
}
|
||||
|
||||
void writeFull(int fd, std::string_view data)
|
||||
{
|
||||
while (!data.empty()) {
|
||||
const auto wrote = ::write(fd, data.data(), data.size());
|
||||
if (wrote < 0) {
|
||||
throw SysError("write()");
|
||||
} else {
|
||||
data.remove_prefix(size_t(wrote));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void closeExtraFDs()
|
||||
{
|
||||
constexpr int MAX_KEPT_FD = 2;
|
||||
static_assert(std::max({STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}) == MAX_KEPT_FD);
|
||||
|
||||
// Both Linux and FreeBSD support close_range.
|
||||
#if __linux__ || __FreeBSD__
|
||||
auto closeRange = [](unsigned int first, unsigned int last, int flags) -> int {
|
||||
// musl does not have close_range as of 2024-08-10
|
||||
// patch: https://www.openwall.com/lists/musl/2024/08/01/9
|
||||
#if HAVE_CLOSE_RANGE
|
||||
return close_range(first, last, flags);
|
||||
#else
|
||||
return syscall(SYS_close_range, first, last, flags);
|
||||
#endif
|
||||
};
|
||||
// first try to close_range everything we don't care about. if this
|
||||
// returns an error with these parameters we're running on a kernel
|
||||
// that does not implement close_range (i.e. pre 5.9) and fall back
|
||||
// to the old method. we should remove that though, in some future.
|
||||
if (closeRange(3, ~0U, 0) == 0) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if __linux__
|
||||
try {
|
||||
for (auto & s : std::filesystem::directory_iterator("/proc/self/fd")) {
|
||||
auto fd = std::stoi(s.path().filename().c_str());
|
||||
if (fd > MAX_KEPT_FD) {
|
||||
debug("closing leaked FD %d", fd);
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
return;
|
||||
} catch (std::exception &) { // NOLINT(lix-foreign-exceptions): that's what std::filesystem throws
|
||||
}
|
||||
#endif
|
||||
|
||||
int maxFD = 0;
|
||||
maxFD = sysconf(_SC_OPEN_MAX);
|
||||
for (int fd = MAX_KEPT_FD + 1; fd < maxFD; ++fd) {
|
||||
close(fd); /* ignore result */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char * argv[])
|
||||
{
|
||||
using namespace nix;
|
||||
|
||||
if (argc < 1) {
|
||||
return 255;
|
||||
}
|
||||
|
||||
bool sendException = true;
|
||||
|
||||
try {
|
||||
capnp::MallocMessageBuilder buf;
|
||||
capnp::readMessageCopyFromFd(
|
||||
STDIN_FILENO, buf, {.traversalLimitInWords = std::numeric_limits<uint64_t>::max()}
|
||||
);
|
||||
|
||||
auto request = buf.getRoot<build::Request>().asReader();
|
||||
|
||||
printDebugLogs = request.getDebug();
|
||||
|
||||
{
|
||||
sigset_t set;
|
||||
sigemptyset(&set);
|
||||
if (sigprocmask(SIG_SETMASK, &set, nullptr)) {
|
||||
throw SysError("failed to unmask signals");
|
||||
}
|
||||
}
|
||||
|
||||
/* Put the child in a separate session (and thus a separate
|
||||
process group) so that it has no controlling terminal (meaning
|
||||
that e.g. ssh cannot open /dev/tty) and it doesn't receive
|
||||
terminal signals. */
|
||||
if (setsid() == -1) {
|
||||
throw SysError("creating a new session");
|
||||
}
|
||||
|
||||
/* Dup stderr to stdout. */
|
||||
if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1) {
|
||||
throw SysError("cannot dup stderr into stdout");
|
||||
}
|
||||
|
||||
const bool setUser = prepareChildSetup(request);
|
||||
|
||||
// NOLINTNEXTLINE(lix-unsafe-c-calls): we trust the parent here
|
||||
if (chdir(rpc::to<std::string>(request.getWorkingDir()).c_str()) == -1) {
|
||||
throw SysError("changing into %s", rpc::to<std::string>(request.getWorkingDir()));
|
||||
}
|
||||
|
||||
/* Disable core dumps by default. */
|
||||
struct rlimit limit = {0, RLIM_INFINITY};
|
||||
if (request.getEnableCoreDumps()) {
|
||||
limit.rlim_cur = RLIM_INFINITY;
|
||||
}
|
||||
setrlimit(RLIMIT_CORE, &limit);
|
||||
|
||||
// FIXME: set other limits to deterministic values?
|
||||
|
||||
/* If we are running in `build-users' mode, then switch to the
|
||||
user we allocated above. Make sure that we drop all root
|
||||
privileges. Note that above we have closed all file
|
||||
descriptors except std*, so that's safe. Also note that
|
||||
setuid() when run as root sets the real, effective and
|
||||
saved UIDs. */
|
||||
if (setUser && request.hasCredentials()) {
|
||||
auto creds = request.getCredentials();
|
||||
/* Preserve supplementary groups of the build user, to allow
|
||||
admins to specify groups such as "kvm". */
|
||||
std::vector<gid_t> gids;
|
||||
std::copy(
|
||||
creds.getSupplementaryGroups().begin(),
|
||||
creds.getSupplementaryGroups().end(),
|
||||
std::back_inserter(gids)
|
||||
);
|
||||
if (setgroups(gids.size(), gids.data()) == -1) {
|
||||
throw SysError("cannot set supplementary groups of build user");
|
||||
}
|
||||
|
||||
if (setgid(creds.getGid()) == -1 || getgid() != creds.getGid() || getegid() != creds.getGid()) {
|
||||
throw SysError("setgid failed");
|
||||
}
|
||||
|
||||
if (setuid(creds.getUid()) == -1 || getuid() != creds.getUid() || geteuid() != creds.getUid()) {
|
||||
throw SysError("setuid failed");
|
||||
}
|
||||
}
|
||||
|
||||
finishChildSetup(request);
|
||||
|
||||
/* Close all other file descriptors. */
|
||||
closeExtraFDs();
|
||||
|
||||
// Reroute stdin to /dev/null. closing the setup socket fd also signals
|
||||
// successful setup of the builder, all other errors must go to stderr.
|
||||
kj::AutoCloseFd fdDevNull{open("/dev/null", O_RDWR | O_CLOEXEC)};
|
||||
if (fdDevNull == nullptr) {
|
||||
throw SysError("cannot open /dev/null");
|
||||
}
|
||||
if (dup2(fdDevNull.get(), STDIN_FILENO) == -1) {
|
||||
throw SysError("cannot dup null device into stdin");
|
||||
}
|
||||
|
||||
sendException = false;
|
||||
|
||||
execBuilder(request);
|
||||
} catch (std::exception & e) { // NOLINT(lix-foreign-exceptions)
|
||||
if (sendException) {
|
||||
capnp::MallocMessageBuilder builder;
|
||||
auto error = builder.getRoot<build::SetupResponse>();
|
||||
RPC_FILL(error, setFatalError, e.what());
|
||||
capnp::writeMessageToFd(STDIN_FILENO, builder);
|
||||
} else {
|
||||
writeFull(STDERR_FILENO, e.what());
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "lix/libstore/build/request.capnp.h"
|
||||
#include "lix/libutil/rpc.hh"
|
||||
#include <boost/format.hpp>
|
||||
#include <capnp/message.h>
|
||||
#include <capnp/serialize.h>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
namespace nix {
|
||||
bool prepareChildSetup(nix::build::Request::Reader request);
|
||||
void finishChildSetup(nix::build::Request::Reader request);
|
||||
|
||||
[[noreturn]]
|
||||
void execBuilder(nix::build::Request::Reader request);
|
||||
|
||||
// silence the foreign exception lint for this helper
|
||||
class BaseException : public std::exception
|
||||
{};
|
||||
|
||||
class SysError : public BaseException
|
||||
{
|
||||
private:
|
||||
std::shared_ptr<std::string> msg;
|
||||
|
||||
public:
|
||||
explicit SysError(auto fmt, const auto &... args) : SysError(errno, fmt, args...) {}
|
||||
SysError(int error, auto fmt, const auto &... args)
|
||||
{
|
||||
const auto errstr = strerror(error);
|
||||
auto format = boost::format(fmt);
|
||||
((format % args), ...);
|
||||
msg = std::make_shared<std::string>(format.str() + ": " + errstr);
|
||||
}
|
||||
|
||||
const char * what() const noexcept override
|
||||
{
|
||||
return msg->c_str();
|
||||
}
|
||||
};
|
||||
|
||||
struct ExecRequest
|
||||
{
|
||||
std::string builder;
|
||||
std::vector<std::string> argsStorage, envsStorage;
|
||||
std::vector<char *> args, envs;
|
||||
|
||||
ExecRequest(nix::build::Request::Reader request);
|
||||
};
|
||||
|
||||
void writeFull(int fd, std::string_view data);
|
||||
|
||||
extern bool printDebugLogs;
|
||||
|
||||
inline void printDebugLog(auto fmt, const auto &... args)
|
||||
{
|
||||
auto format = boost::format(fmt);
|
||||
((format % args), ...);
|
||||
|
||||
capnp::MallocMessageBuilder builder;
|
||||
auto log = builder.getRoot<build::SetupResponse>();
|
||||
RPC_FILL(log, setLogLine, format.str());
|
||||
capnp::writeMessageToFd(STDIN_FILENO, builder);
|
||||
}
|
||||
|
||||
#define debug(msg, ...) \
|
||||
do { \
|
||||
if (::nix::printDebugLogs) { \
|
||||
printDebugLog(msg, __VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
if is_linux
|
||||
check_namespace_support = executable(
|
||||
'check-namespace-support',
|
||||
files('check-namespace-support.cc'),
|
||||
install : true,
|
||||
install_dir : libexecdir / 'lix',
|
||||
)
|
||||
endif
|
||||
|
||||
kill_user = executable(
|
||||
'kill-user',
|
||||
files('kill-user.cc'),
|
||||
install : true,
|
||||
install_dir : libexecdir / 'lix',
|
||||
)
|
||||
|
||||
if is_linux
|
||||
launch_builder_impl = 'linux'
|
||||
elif is_darwin
|
||||
launch_builder_impl = 'darwin'
|
||||
else
|
||||
launch_builder_impl = 'fallback'
|
||||
endif
|
||||
|
||||
launch_builder = executable(
|
||||
'launch-builder',
|
||||
files(
|
||||
'launch-builder.cc',
|
||||
f'launch-builder-@launch_builder_impl@.cc',
|
||||
),
|
||||
liblix_generated_headers,
|
||||
include_directories : [ '../..' ],
|
||||
dependencies : [
|
||||
capnp,
|
||||
],
|
||||
install : true,
|
||||
install_dir : libexecdir / 'lix',
|
||||
)
|
||||
|
||||
run_build_hook = executable(
|
||||
'run-build-hook',
|
||||
files('run-build-hook.cc'),
|
||||
install : true,
|
||||
install_dir : libexecdir / 'lix',
|
||||
)
|
||||
|
||||
run_diff_hook = executable(
|
||||
'run-diff-hook',
|
||||
files('run-diff-hook.cc'),
|
||||
install : true,
|
||||
install_dir : libexecdir / 'lix',
|
||||
)
|
||||
|
||||
run_pager = executable(
|
||||
'run-pager',
|
||||
files('run-pager.cc'),
|
||||
install : true,
|
||||
install_dir : libexecdir / 'lix',
|
||||
)
|
||||
|
||||
unix_bind_connect = executable(
|
||||
'unix-bind-connect',
|
||||
files('unix-bind-connect.cc'),
|
||||
install : true,
|
||||
install_dir : libexecdir / 'lix',
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
#include "common.hh"
|
||||
#include <unistd.h>
|
||||
|
||||
LIBEXEC_HELPER(2)
|
||||
|
||||
int helperMain(const char * name, std::span<char *> args) noexcept
|
||||
{
|
||||
DIE_UNLESS_SYS("chdir", chdir("/"));
|
||||
DIE_UNLESS_SYS("setsid", setsid());
|
||||
|
||||
static_assert(STDIN_FILENO == 0);
|
||||
DIE_UNLESS_SYS("close(stdin)", close(STDIN_FILENO));
|
||||
DIE_UNLESS_SYS("stdin = open(/dev/null)", open("/dev/null", O_RDWR));
|
||||
|
||||
execv(args[0], args.subspan(1).data());
|
||||
die("exec failed");
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#include "common.hh"
|
||||
|
||||
#include <grp.h>
|
||||
|
||||
using std::literals::operator""sv;
|
||||
|
||||
LIBEXEC_HELPER(3)
|
||||
|
||||
int helperMain(const char * name, std::span<char *> args) noexcept
|
||||
{
|
||||
const auto uid = args[0];
|
||||
const auto gid = args[1];
|
||||
const auto hook = args.subspan(2);
|
||||
|
||||
DIE_UNLESS_SYS("chdir", chdir("/"));
|
||||
if (gid != "-"sv) {
|
||||
DIE_UNLESS_SYS("setgid", setgid(argToInt<gid_t>("gid", gid)));
|
||||
/* Drop all other groups if we're setgid. */
|
||||
DIE_UNLESS_SYS("setgroups", setgroups(0, 0));
|
||||
}
|
||||
if (uid != "-"sv) {
|
||||
DIE_UNLESS_SYS("setuid", setuid(argToInt<uid_t>("uid", uid)));
|
||||
}
|
||||
|
||||
execvp(hook[0], hook.data());
|
||||
die("exec failed");
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "common.hh"
|
||||
|
||||
LIBEXEC_HELPER(0)
|
||||
|
||||
int helperMain(const char * name, std::span<char *> args) noexcept
|
||||
{
|
||||
auto pager = args.empty() ? nullptr : args[0];
|
||||
|
||||
if (!getenv("LESS")) {
|
||||
setenv("LESS", "FRSXMK", 1);
|
||||
}
|
||||
if (pager) {
|
||||
execl("/bin/sh", "sh", "-c", pager, nullptr);
|
||||
}
|
||||
execlp("pager", "pager", nullptr);
|
||||
execlp("less", "less", nullptr);
|
||||
execlp("more", "more", nullptr);
|
||||
die("could not find a pager to run, please set PAGER or NIX_PAGER");
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#include "common.hh"
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
LIBEXEC_HELPER(4)
|
||||
|
||||
int helperMain(const char *, std::span<char *> args) noexcept
|
||||
{
|
||||
int socket = argToInt<int>("socket", args[0]);
|
||||
std::string_view method = args[1];
|
||||
const auto dir = args[2];
|
||||
const auto name = args[3];
|
||||
|
||||
DIE_UNLESS_SYS("chdir", chdir(dir));
|
||||
|
||||
struct sockaddr_un addr;
|
||||
addr.sun_family = AF_UNIX;
|
||||
|
||||
if (auto nameLen = strlen(name); nameLen + 1 >= sizeof(addr.sun_path)) {
|
||||
die(std::format("socket path {}/{} is too long", dir, name));
|
||||
} else {
|
||||
memcpy(addr.sun_path, name, nameLen + 1);
|
||||
}
|
||||
|
||||
if (method == "bind") {
|
||||
DIE_UNLESS_SYS("bind", bind(socket, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)));
|
||||
} else if (method == "connect") {
|
||||
DIE_UNLESS_SYS("connect", connect(socket, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)));
|
||||
} else {
|
||||
die(std::format("invalid method %s", method));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -83,9 +83,7 @@ findAlongAttrPath(EvalState & state, const std::string & attrPath, Bindings & au
|
||||
auto attrIndex = string2Int<unsigned int>(attr);
|
||||
|
||||
/* Evaluate the expression. */
|
||||
Value vNew;
|
||||
state.autoCallFunction(autoArgs, v, vNew, pos);
|
||||
v = vNew;
|
||||
v = state.autoCallFunction(autoArgs, v, pos);
|
||||
state.forceValue(v, noPos);
|
||||
|
||||
/* It should evaluate to either a set or an expression,
|
||||
|
||||
@@ -41,13 +41,4 @@ void Bindings::sort()
|
||||
{
|
||||
if (size_) std::sort(begin(), end());
|
||||
}
|
||||
|
||||
|
||||
Value & Value::mkAttrs(BindingsBuilder & bindings)
|
||||
{
|
||||
mkAttrs(bindings.finish());
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -97,6 +97,8 @@ public:
|
||||
res.reserve(size_);
|
||||
for (Size n = 0; n < size_; n++)
|
||||
res.emplace_back(&attrs[n]);
|
||||
// NOTE: this sort uses a total order, so the iteration over pointers is not an issue
|
||||
// NOLINTNEXTLINE(bugprone-nondeterministic-pointer-iteration-order)
|
||||
std::sort(res.begin(), res.end(), [&](const Attr * a, const Attr * b) {
|
||||
std::string_view sa = symbols[a->name], sb = symbols[b->name];
|
||||
return sa < sb;
|
||||
@@ -119,7 +121,6 @@ public:
|
||||
|
||||
private:
|
||||
Bindings * bindings;
|
||||
EvalMemory & mem;
|
||||
SymbolTable & symbols;
|
||||
Size capacity;
|
||||
|
||||
@@ -127,9 +128,8 @@ public:
|
||||
// needed by std::back_inserter
|
||||
using value_type = Attr;
|
||||
|
||||
BindingsBuilder(EvalMemory & mem, SymbolTable & symbols, Bindings * bindings, Size capacity)
|
||||
BindingsBuilder(SymbolTable & symbols, Bindings * bindings, Size capacity)
|
||||
: bindings(bindings)
|
||||
, mem(mem)
|
||||
, symbols(symbols)
|
||||
, capacity(capacity)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
---
|
||||
name: addErrorContext
|
||||
args: [message, expr]
|
||||
---
|
||||
|
||||
This adds a `message` to be shown in the stacktrace in the event of
|
||||
a failure during the evaluation of `expr`.
|
||||
|
||||
For example, if a file `err.nix` contains the following:
|
||||
|
||||
```nix
|
||||
let
|
||||
countDown =
|
||||
n:
|
||||
if n == 0 then
|
||||
throw "kaboom"
|
||||
else
|
||||
builtins.addErrorContext "while counting down; n = ${toString n}" ("x" + countDown (n - 1));
|
||||
in
|
||||
countDown 2
|
||||
```
|
||||
|
||||
Then, evaluating the file will give the following stack trace:
|
||||
|
||||
```console
|
||||
$ nix-instantiate --show-trace err.nix
|
||||
error:
|
||||
… from call site
|
||||
at /home/plop/git.lix.systems/lix-project/lix/err.nix:9:1:
|
||||
8| in
|
||||
9| countDown 2
|
||||
| ^
|
||||
10|
|
||||
|
||||
… while calling 'countDown'
|
||||
at /home/plop/git.lix.systems/lix-project/lix/err.nix:3:5:
|
||||
2| countDown =
|
||||
3| n:
|
||||
| ^
|
||||
4| if n == 0 then
|
||||
|
||||
… while calling the 'addErrorContext' builtin
|
||||
at /home/plop/git.lix.systems/lix-project/lix/err.nix:7:7:
|
||||
6| else
|
||||
7| builtins.addErrorContext "while counting down; n = ${toString n}" ("x" + countDown (n - 1));
|
||||
| ^
|
||||
8| in
|
||||
|
||||
… while counting down; n = 2
|
||||
|
||||
… from call site
|
||||
at /home/plop/git.lix.systems/lix-project/lix/err.nix:7:80:
|
||||
6| else
|
||||
7| builtins.addErrorContext "while counting down; n = ${toString n}" ("x" + countDown (n - 1));
|
||||
| ^
|
||||
8| in
|
||||
|
||||
… while calling 'countDown'
|
||||
at /home/plop/git.lix.systems/lix-project/lix/err.nix:3:5:
|
||||
2| countDown =
|
||||
3| n:
|
||||
| ^
|
||||
4| if n == 0 then
|
||||
|
||||
… while calling the 'addErrorContext' builtin
|
||||
at /home/plop/git.lix.systems/lix-project/lix/err.nix:7:7:
|
||||
6| else
|
||||
7| builtins.addErrorContext "while counting down; n = ${toString n}" ("x" + countDown (n - 1));
|
||||
| ^
|
||||
8| in
|
||||
|
||||
… while counting down; n = 1
|
||||
|
||||
… from call site
|
||||
at /home/plop/git.lix.systems/lix-project/lix/err.nix:7:80:
|
||||
6| else
|
||||
7| builtins.addErrorContext "while counting down; n = ${toString n}" ("x" + countDown (n - 1));
|
||||
| ^
|
||||
8| in
|
||||
|
||||
… while calling 'countDown'
|
||||
at /home/plop/git.lix.systems/lix-project/lix/err.nix:3:5:
|
||||
2| countDown =
|
||||
3| n:
|
||||
| ^
|
||||
4| if n == 0 then
|
||||
|
||||
… caused by explicit throw
|
||||
at /home/plop/git.lix.systems/lix-project/lix/err.nix:5:7:
|
||||
4| if n == 0 then
|
||||
5| throw "kaboom"
|
||||
| ^
|
||||
6| else
|
||||
|
||||
error: kaboom
|
||||
```
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
name: appendContext
|
||||
args: [s, ctx]
|
||||
---
|
||||
|
||||
Appends the attribute set `ctx` as a context to the string `s`, see [`getContext`](#builtins-getContext) for details on the format of the context.
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: derivationStrict
|
||||
args: [args]
|
||||
renameInGlobalScope: false
|
||||
---
|
||||
|
||||
Constructs a [store derivation](../glossary.md#gloss-store-derivation) from the attribute set `args`
|
||||
(c.f. [derivation](#builtins-derivation). Unlike `derivation` the produced store derivation is placed
|
||||
in the store *immediately* when this builtin is called, while `derivation` may defer placing store
|
||||
derivations in the store until it is proven that they are used.
|
||||
|
||||
It then returns a new attrset with *only* the following attributes:
|
||||
|
||||
- `drvPath` containing the path of the store derivation;
|
||||
- For each output of the derivation (`out`, `dev`, etc): an attribute named after that output containing the output path
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> In contrast to [`builtins.derivation`](#builtins-derivation), this computes
|
||||
> the derivation set in a fully *strict* manner, i.e. the values of the attributes
|
||||
> directly computed, whereas using `builtins.derivation` will produce an attrset
|
||||
> whose values will be evaluated when they are used at a later point.
|
||||
@@ -1,72 +0,0 @@
|
||||
---
|
||||
name: fetchClosure
|
||||
args: [args]
|
||||
experimentalFeature: fetch-closure
|
||||
---
|
||||
Fetch a store path [closure](@docroot@/glossary.md#gloss-closure) from a binary cache, and return the store path as a string with context.
|
||||
|
||||
This function can be invoked in three ways, that we will discuss in order of preference.
|
||||
|
||||
**Fetch a content-addressed store path**
|
||||
|
||||
Example:
|
||||
|
||||
```nix
|
||||
builtins.fetchClosure {
|
||||
fromStore = "https://cache.nixos.org";
|
||||
fromPath = /nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1;
|
||||
}
|
||||
```
|
||||
|
||||
This is the simplest invocation, and it does not require the user of the expression to configure [`trusted-public-keys`](@docroot@/command-ref/conf-file.md#conf-trusted-public-keys) to ensure their authenticity.
|
||||
|
||||
If your store path is [input addressed](@docroot@/glossary.md#gloss-input-addressed-store-object) instead of content addressed, consider the other two invocations.
|
||||
|
||||
**Fetch any store path and rewrite it to a fully content-addressed store path**
|
||||
|
||||
Example:
|
||||
|
||||
```nix
|
||||
builtins.fetchClosure {
|
||||
fromStore = "https://cache.nixos.org";
|
||||
fromPath = /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1;
|
||||
toPath = /nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1;
|
||||
}
|
||||
```
|
||||
|
||||
This example fetches `/nix/store/r2jd...` from the specified binary cache,
|
||||
and rewrites it into the content-addressed store path
|
||||
`/nix/store/ldbh...`.
|
||||
|
||||
Like the previous example, no extra configuration or privileges are required.
|
||||
|
||||
To find out the correct value for `toPath` given a `fromPath`,
|
||||
use [`nix store make-content-addressed`](@docroot@/command-ref/new-cli/nix3-store-make-content-addressed.md):
|
||||
|
||||
```console
|
||||
# nix store make-content-addressed --from https://cache.nixos.org /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1
|
||||
rewrote '/nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1' to '/nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1'
|
||||
```
|
||||
|
||||
Alternatively, set `toPath = ""` and find the correct `toPath` in the error message.
|
||||
|
||||
**Fetch an input-addressed store path as is**
|
||||
|
||||
Example:
|
||||
|
||||
```nix
|
||||
builtins.fetchClosure {
|
||||
fromStore = "https://cache.nixos.org";
|
||||
fromPath = /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1;
|
||||
inputAddressed = true;
|
||||
}
|
||||
```
|
||||
|
||||
It is possible to fetch an [input-addressed store path](@docroot@/glossary.md#gloss-input-addressed-store-object) and return it as is.
|
||||
However, this is the least preferred way of invoking `fetchClosure`, because it requires that the input-addressed paths are trusted by the Lix configuration.
|
||||
|
||||
**`builtins.storePath`**
|
||||
|
||||
`fetchClosure` is similar to [`builtins.storePath`](#builtins-storePath) in that it allows you to use a previously built store path in a Nix expression.
|
||||
However, `fetchClosure` is more reproducible because it specifies a binary cache from which the path can be fetched.
|
||||
Also, using content-addressed store paths does not require users to configure [`trusted-public-keys`](@docroot@/command-ref/conf-file.md#conf-trusted-public-keys) to ensure their authenticity.
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: fetchMercurial
|
||||
args: [args]
|
||||
renameInGlobalScope: false
|
||||
---
|
||||
|
||||
Fetch a Mercurial repository. *args* can be a URL, in which case the default
|
||||
branch of the repo at that URL is fetched. Otherwise, it can be an
|
||||
attribute with the following attributes (all except `url` optional):
|
||||
|
||||
- `url`
|
||||
|
||||
The URL of the repo.
|
||||
|
||||
- `name` (default: `"source"`)
|
||||
|
||||
The name of the directory the repo should be exported to in the store.
|
||||
|
||||
- `rev`
|
||||
|
||||
The revision to fetch.
|
||||
This is typically a commit hash.
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> Currently, `rev` can either contain a revision or a branch/tag name.
|
||||
|
||||
The return value is an attrset containing the following keys:
|
||||
|
||||
- `outPath` (`string`)
|
||||
|
||||
Resulting store path of the fetch process.
|
||||
|
||||
- `branch` (`string`)
|
||||
|
||||
The branch of the fetch repository.
|
||||
|
||||
- `rev` (`string`)
|
||||
|
||||
The revision that was fetched.
|
||||
|
||||
- `revCount` (`int`)
|
||||
|
||||
The number of revsets for this branch.
|
||||
|
||||
- `shortRev` (`string`)
|
||||
|
||||
The first *12* characters of `rev`.
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
name: scopedImport
|
||||
implementation: "[](EvalState & state, Value ** args, Value & v) { import(state, *args[1], args[0], v); }"
|
||||
args: [scope, path]
|
||||
renameInGlobalScope: false
|
||||
---
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> This builtin's use is heavily discouraged, it has many drawbacks and may be removed
|
||||
> in a future version of Lix.
|
||||
|
||||
Functions like [`import`](#builtins-import) with the exception that
|
||||
it takes a `scope`, which is a set of attributes to be added to the
|
||||
lexical scope of the expression.
|
||||
|
||||
This essentially allows overriding the ambient builtin variables.
|
||||
|
||||
For example, if `foo.nix` is a file containing the following content:
|
||||
|
||||
```nix
|
||||
x
|
||||
```
|
||||
|
||||
then the following expression
|
||||
|
||||
```nix
|
||||
scopedImport { x = 1; } ./foo.nix
|
||||
```
|
||||
|
||||
will evaluate to `1`.
|
||||
|
||||
Another application is overriding builtin functions or constants, e.g. to
|
||||
trace all calls to `map`, one can do:
|
||||
|
||||
```nix
|
||||
let
|
||||
overrides = {
|
||||
map = f: xs: builtins.trace "map called!" (map f xs);
|
||||
|
||||
# Ensure that our override gets propagated by calls to
|
||||
# import/scopedImport.
|
||||
import = fn: scopedImport overrides fn;
|
||||
|
||||
scopedImport = attrs: fn: scopedImport (overrides // attrs) fn;
|
||||
|
||||
# Also update ‘builtins’.
|
||||
builtins = builtins // overrides;
|
||||
};
|
||||
in scopedImport overrides ./bla.nix
|
||||
```
|
||||
|
||||
Similarly, it can be used to extend the set of builtin functions.
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> One of the downsides of `scopedImport` is that it bypasses the evaluation cache.
|
||||
> This means that importing a file multiple times will lead to multiple expensive
|
||||
> parsings and evaluations.
|
||||
@@ -13,5 +13,3 @@ in a new path (e.g. `/nix/store/ld01dnzc…-source-source`).
|
||||
|
||||
Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval).
|
||||
Lix may change this, tracking issue: <https://git.lix.systems/lix-project/lix/issues/402>
|
||||
|
||||
See also [`builtins.fetchClosure`](#builtins-fetchClosure).
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: unsafeDiscardStringContext
|
||||
args: [s]
|
||||
---
|
||||
|
||||
Returns a copy of the string `s` with all string context associated with `s` removed.
|
||||
Since string context is used for dependency tracking the returned string will also have
|
||||
*no dependencies* on store objects, even when the original string `s` had such dependencies.
|
||||
|
||||
This function is mainly useful when discarding dependencies is explicitly required, e.g.
|
||||
to produce a string listing all inputs of a derivation without propagating these inputs
|
||||
as dependencies into all *users* of the listing. For example, the derivation `dep` in the
|
||||
following example will pull `hello` into its closure despite never using it while `nodep`
|
||||
will not:
|
||||
|
||||
```nix
|
||||
dep = runCommand "dep" {
|
||||
inherit hello;
|
||||
} "echo hello is at $hello >$out";
|
||||
|
||||
nodep = runCommand "nodep" {
|
||||
hello = builtins.unsafeDiscardStringContext hello;
|
||||
} "echo hello is at $hello >$out";
|
||||
```
|
||||
|
||||
This behavior also makes this function unsafe: if `s` contains the path of a
|
||||
store object that is not present in the store then any use of `s` in a
|
||||
derivation tree will attempt to realize that path in the store, but no use
|
||||
of `unsafeDiscardStringContext s` will. This can lead to derivation outputs that
|
||||
refer to paths that were never created.
|
||||
|
||||
Lix cannot determine whether such reference are safe or not and must pass
|
||||
this obligation to the user.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
name: warn
|
||||
args: [msg, e2]
|
||||
---
|
||||
Evaluate string *msg* and print it on standard error. Then return *e2*.
|
||||
This function is useful for warning about unexpected conditions without aborting evaluation.
|
||||
If the [`debugger-on-warn`](@docroot@/command-ref/conf-file.md#conf-debugger-on-trace) option is set to `true` and the `--debugger` flag is given, the interactive debugger will be started when `warn` is called (like [`break`](@docroot@/language/builtins.md#builtins-break)).
|
||||
@@ -42,7 +42,7 @@ struct AttrDb
|
||||
Path cacheDir = getCacheDir() + "/nix/eval-cache-v5";
|
||||
createDirs(cacheDir);
|
||||
|
||||
Path dbPath = cacheDir + "/" + fingerprint.to_string(Base::Base16, false) + ".sqlite";
|
||||
Path dbPath = cacheDir + "/" + fingerprint.to_string(HashFormat::Base16, false) + ".sqlite";
|
||||
|
||||
state->db = SQLite(dbPath);
|
||||
state->db.isCache();
|
||||
|
||||
@@ -0,0 +1,728 @@
|
||||
#include "eval.hh"
|
||||
#include "primops.hh"
|
||||
#include "gc-small-vector.hh"
|
||||
|
||||
/// This file contains all implementations of `Expr::eval`, and some other helper functions defined by
|
||||
/// `Expr` subtypes. Note that some of the evaluation helper functions on `EvalState` that do the heavy
|
||||
/// lifting are not in this file but kept in `eval.cc`. In the future, more logic from here will be factored
|
||||
/// out into helpers over at `eval.cc` until this file contains a readable and high-level implementation of
|
||||
/// the evaluator.
|
||||
|
||||
namespace nix {
|
||||
|
||||
/* Create a thunk for the delayed computation of the given expression
|
||||
in the given environment. But if the expression is a variable,
|
||||
then look it up right away. This significantly reduces the number
|
||||
of thunks allocated. */
|
||||
Value Expr::maybeThunk(EvalState & state, Env & env)
|
||||
{
|
||||
state.ctx.stats.nrThunks++;
|
||||
return {NewValueAs::thunk, state.ctx.mem, env, *this};
|
||||
}
|
||||
|
||||
Value ExprVar::maybeThunk(EvalState & state, Env & env)
|
||||
{
|
||||
Value * v = state.lookupVar(&env, *this, true);
|
||||
/* The value might not be initialised in the environment yet.
|
||||
In that case, ignore it. */
|
||||
if (v && !v->isInvalid()) {
|
||||
state.ctx.stats.nrAvoided++;
|
||||
return *v;
|
||||
}
|
||||
return Expr::maybeThunk(state, env);
|
||||
}
|
||||
|
||||
Value ExprLiteral::maybeThunk(EvalState & state, Env & env)
|
||||
{
|
||||
state.ctx.stats.nrAvoided++;
|
||||
return v;
|
||||
}
|
||||
|
||||
Value ExprList::maybeThunk(EvalState & state, Env & env)
|
||||
{
|
||||
if (elems.empty()) {
|
||||
return Value::EMPTY_LIST;
|
||||
}
|
||||
return Expr::maybeThunk(state, env);
|
||||
}
|
||||
|
||||
Value Expr::eval(EvalState & state, Env & env)
|
||||
{
|
||||
abort();
|
||||
}
|
||||
|
||||
Value ExprLiteral::eval(EvalState & state, Env & env)
|
||||
{
|
||||
return this->v;
|
||||
}
|
||||
|
||||
Value ExprInheritFrom::eval(EvalState & state, Env & env)
|
||||
{
|
||||
Value & v2 = env.values[displ];
|
||||
state.forceValue(v2, pos);
|
||||
return v2;
|
||||
}
|
||||
|
||||
Env * ExprAttrs::buildInheritFromEnv(EvalState & state, Env & up)
|
||||
{
|
||||
Env & inheritEnv = state.ctx.mem.allocEnv(inheritFromExprs->size());
|
||||
inheritEnv.up = &up;
|
||||
|
||||
Displacement displ = 0;
|
||||
for (auto & from : *inheritFromExprs) {
|
||||
inheritEnv.values[displ++] = from->maybeThunk(state, up);
|
||||
}
|
||||
|
||||
return &inheritEnv;
|
||||
}
|
||||
|
||||
Value ExprSet::eval(EvalState & state, Env & env)
|
||||
{
|
||||
Bindings::Size capacity = attrs.size() + dynamicAttrs.size();
|
||||
Value v = {NewValueAs::attrs, state.ctx.buildBindings(capacity).finish()};
|
||||
auto dynamicEnv = &env;
|
||||
|
||||
if (recursive) {
|
||||
|
||||
/* Create a new environment that contains the attributes in
|
||||
this `rec'. */
|
||||
Env & env2(state.ctx.mem.allocEnv(attrs.size()));
|
||||
env2.up = &env;
|
||||
dynamicEnv = &env2;
|
||||
Env * inheritEnv = inheritFromExprs ? buildInheritFromEnv(state, env2) : nullptr;
|
||||
|
||||
ExprAttrs::AttrDefs::iterator overrides = attrs.find(state.ctx.symbols.sym___overrides);
|
||||
bool hasOverrides = overrides != attrs.end();
|
||||
|
||||
/* The recursive attributes are evaluated in the new
|
||||
environment, while the inherited attributes are evaluated
|
||||
in the original environment. */
|
||||
Displacement displ = 0;
|
||||
for (auto & i : attrs) {
|
||||
Value vAttr;
|
||||
if (hasOverrides && i.second.kind != ExprAttrs::AttrDef::Kind::Inherited) {
|
||||
vAttr = {
|
||||
NewValueAs::thunk,
|
||||
state.ctx.mem,
|
||||
*i.second.chooseByKind(&env2, &env, inheritEnv),
|
||||
*i.second.e
|
||||
};
|
||||
state.ctx.stats.nrThunks++;
|
||||
} else {
|
||||
vAttr = i.second.e->maybeThunk(state, *i.second.chooseByKind(&env2, &env, inheritEnv));
|
||||
}
|
||||
env2.values[displ++] = vAttr;
|
||||
v.attrs()->push_back(Attr(i.first, vAttr, i.second.pos));
|
||||
}
|
||||
|
||||
/* If the rec contains an attribute called `__overrides', then
|
||||
evaluate it, and add the attributes in that set to the rec.
|
||||
This allows overriding of recursive attributes, which is
|
||||
otherwise not possible. (You can use the // operator to
|
||||
replace an attribute, but other attributes in the rec will
|
||||
still reference the original value, because that value has
|
||||
been substituted into the bodies of the other attributes.
|
||||
Hence we need __overrides.) */
|
||||
if (hasOverrides) {
|
||||
Value & vOverrides = (*v.attrs())[overrides->second.displ].value;
|
||||
state.forceAttrs(vOverrides, noPos, "while evaluating the `__overrides` attribute");
|
||||
Bindings * newBnds = state.ctx.mem.allocBindings(capacity + vOverrides.attrs()->size());
|
||||
for (auto & i : *v.attrs()) {
|
||||
newBnds->push_back(i);
|
||||
}
|
||||
for (auto & i : *vOverrides.attrs()) {
|
||||
ExprAttrs::AttrDefs::iterator j = attrs.find(i.name);
|
||||
if (j != attrs.end()) {
|
||||
(*newBnds)[j->second.displ] = i;
|
||||
env2.values[j->second.displ] = i.value;
|
||||
} else {
|
||||
newBnds->push_back(i);
|
||||
}
|
||||
}
|
||||
newBnds->sort();
|
||||
v = {NewValueAs::attrs, newBnds};
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
Env * inheritEnv = inheritFromExprs ? buildInheritFromEnv(state, env) : nullptr;
|
||||
for (auto & i : attrs) {
|
||||
v.attrs()->push_back(Attr(
|
||||
i.first,
|
||||
i.second.e->maybeThunk(state, *i.second.chooseByKind(&env, &env, inheritEnv)),
|
||||
i.second.pos
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/* Dynamic attrs apply *after* rec and __overrides. */
|
||||
for (auto & i : dynamicAttrs) {
|
||||
/* Before evaluating dynamic attrs, we blackhole the output attrset and only restore it after the operation.
|
||||
* This is to avoid exposing the partially constructed set as a value, see
|
||||
* http://github.com/NixOS/nix/issues/7012. Any accesses to the output attrset will thus infrec.
|
||||
*/
|
||||
Value vBackup = v;
|
||||
Value nameVal;
|
||||
{
|
||||
KJ_DEFER(v = vBackup);
|
||||
v = Value{NewValueAs::blackhole};
|
||||
nameVal = i.nameExpr->eval(state, *dynamicEnv);
|
||||
state.forceValue(nameVal, i.pos);
|
||||
if (nameVal.type() == nNull) {
|
||||
continue;
|
||||
}
|
||||
state.forceStringNoCtx(nameVal, i.pos, "while evaluating the name of a dynamic attribute");
|
||||
}
|
||||
auto nameSym = state.ctx.symbols.create(nameVal.str());
|
||||
auto j = v.attrs()->get(nameSym);
|
||||
if (j) {
|
||||
state.ctx.errors
|
||||
.make<EvalError>(
|
||||
"dynamic attribute '%1%' already defined at %2%",
|
||||
state.ctx.symbols[nameSym],
|
||||
state.ctx.positions[j->pos]
|
||||
)
|
||||
.atPos(i.pos)
|
||||
.withFrame(env, *this)
|
||||
.debugThrow();
|
||||
}
|
||||
|
||||
i.valueExpr->setName(nameSym);
|
||||
/* Keep sorted order so find can catch duplicates */
|
||||
v.attrs()->push_back(Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), i.pos));
|
||||
v.attrs()->sort(); // FIXME: inefficient
|
||||
}
|
||||
|
||||
v.attrs()->pos = pos;
|
||||
return v;
|
||||
}
|
||||
|
||||
Value ExprLet::eval(EvalState & state, Env & env)
|
||||
{
|
||||
/* Create a new environment that contains the attributes in this
|
||||
`let'. */
|
||||
Env & env2(state.ctx.mem.allocEnv(attrs.size()));
|
||||
env2.up = &env;
|
||||
|
||||
Env * inheritEnv = inheritFromExprs ? buildInheritFromEnv(state, env2) : nullptr;
|
||||
|
||||
/* The recursive attributes are evaluated in the new environment,
|
||||
while the inherited attributes are evaluated in the original
|
||||
environment. */
|
||||
Displacement displ = 0;
|
||||
for (auto & i : attrs) {
|
||||
env2.values[displ++] = i.second.e->maybeThunk(state, *i.second.chooseByKind(&env2, &env, inheritEnv));
|
||||
}
|
||||
|
||||
return body->eval(state, env2);
|
||||
}
|
||||
|
||||
Value ExprList::eval(EvalState & state, Env & env)
|
||||
{
|
||||
auto result = state.ctx.mem.newList(elems.size());
|
||||
Value v = {NewValueAs::list, result};
|
||||
for (auto && [n, v2] : enumerate(result->span())) {
|
||||
v2 = elems[n]->maybeThunk(state, env);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
Value ExprVar::eval(EvalState & state, Env & env)
|
||||
{
|
||||
Value * v2 = state.lookupVar(&env, *this, false);
|
||||
try {
|
||||
state.forceValue(*v2, pos);
|
||||
} catch (Error & e) {
|
||||
/* `name` can be invalid if we are an ExprInheritFrom */
|
||||
if (name) {
|
||||
e.addTrace(state.ctx.positions[getPos()], "while evaluating %s", state.ctx.symbols[name]);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
return *v2;
|
||||
}
|
||||
|
||||
Value ExprWith::eval(EvalState & state, Env & env)
|
||||
{
|
||||
Env & env2(state.ctx.mem.allocEnv(1));
|
||||
env2.up = &env;
|
||||
env2.values[0] = attrs->maybeThunk(state, env);
|
||||
|
||||
return body->eval(state, env2);
|
||||
}
|
||||
|
||||
Value ExprIf::eval(EvalState & state, Env & env)
|
||||
{
|
||||
Value vCond = cond->eval(state, env);
|
||||
return (state.checkBool(vCond, env, *cond) ? *then : *else_).eval(state, env);
|
||||
}
|
||||
|
||||
Value ExprAssert::eval(EvalState & state, Env & env)
|
||||
{
|
||||
Value vCond = cond->eval(state, env);
|
||||
if (!state.checkBool(vCond, env, *cond)) {
|
||||
state.ctx.errors.make<AssertionError>("assertion failed")
|
||||
.atPos(pos)
|
||||
.withFrame(env, *this)
|
||||
.debugThrow();
|
||||
}
|
||||
return body->eval(state, env);
|
||||
}
|
||||
|
||||
Value ExprOpNot::eval(EvalState & state, Env & env)
|
||||
{
|
||||
Value vInner = e->eval(state, env);
|
||||
return {NewValueAs::boolean, !state.checkBool(vInner, env, *e)};
|
||||
}
|
||||
|
||||
Value ExprOpEq::eval(EvalState & state, Env & env)
|
||||
{
|
||||
Value v1 = e1->eval(state, env);
|
||||
Value v2 = e2->eval(state, env);
|
||||
return {NewValueAs::boolean, state.eqValues(v1, v2, pos, "while testing two values for equality")};
|
||||
}
|
||||
|
||||
Value ExprOpNEq::eval(EvalState & state, Env & env)
|
||||
{
|
||||
Value v1 = e1->eval(state, env);
|
||||
Value v2 = e2->eval(state, env);
|
||||
return {NewValueAs::boolean, !state.eqValues(v1, v2, pos, "while testing two values for inequality")};
|
||||
}
|
||||
|
||||
Value ExprOpAnd::eval(EvalState & state, Env & env)
|
||||
{
|
||||
Value v1 = e1->eval(state, env);
|
||||
/* Explicitly short-circuit */
|
||||
if (!state.checkBool(v1, env, *e1)) {
|
||||
return {NewValueAs::boolean, false};
|
||||
}
|
||||
Value v2 = e2->eval(state, env);
|
||||
return {NewValueAs::boolean, state.checkBool(v2, env, *e2)};
|
||||
}
|
||||
|
||||
Value ExprOpOr::eval(EvalState & state, Env & env)
|
||||
{
|
||||
Value v1 = e1->eval(state, env);
|
||||
/* Explicitly short-circuit */
|
||||
if (state.checkBool(v1, env, *e1)) {
|
||||
return {NewValueAs::boolean, true};
|
||||
}
|
||||
Value v2 = e2->eval(state, env);
|
||||
return {NewValueAs::boolean, state.checkBool(v2, env, *e2)};
|
||||
}
|
||||
|
||||
Value ExprOpImpl::eval(EvalState & state, Env & env)
|
||||
{
|
||||
Value v1 = e1->eval(state, env);
|
||||
/* Explicitly short-circuit (ex falso quodlibet) */
|
||||
if (!state.checkBool(v1, env, *e1)) {
|
||||
return {NewValueAs::boolean, true};
|
||||
}
|
||||
Value v2 = e2->eval(state, env);
|
||||
return {NewValueAs::boolean, state.checkBool(v2, env, *e2)};
|
||||
}
|
||||
|
||||
Value ExprOpUpdate::eval(EvalState & state, Env & env)
|
||||
{
|
||||
Value v1 = e1->eval(state, env);
|
||||
state.checkAttrs(v1, env, *e1);
|
||||
Value v2 = e2->eval(state, env);
|
||||
state.checkAttrs(v2, env, *e2);
|
||||
|
||||
state.ctx.stats.nrOpUpdates++;
|
||||
|
||||
if (v1.attrs()->size() == 0) {
|
||||
return v2;
|
||||
}
|
||||
if (v2.attrs()->size() == 0) {
|
||||
return v1;
|
||||
}
|
||||
|
||||
auto attrs = state.ctx.buildBindings(v1.attrs()->size() + v2.attrs()->size());
|
||||
|
||||
/* Merge the sets, preferring values from the second set. Make
|
||||
sure to keep the resulting vector in sorted order. */
|
||||
Bindings::iterator i = v1.attrs()->begin();
|
||||
Bindings::iterator j = v2.attrs()->begin();
|
||||
|
||||
while (i != v1.attrs()->end() && j != v2.attrs()->end()) {
|
||||
if (i->name == j->name) {
|
||||
attrs.insert(*j);
|
||||
++i;
|
||||
++j;
|
||||
} else if (i->name < j->name) {
|
||||
attrs.insert(*i++);
|
||||
} else {
|
||||
attrs.insert(*j++);
|
||||
}
|
||||
}
|
||||
|
||||
while (i != v1.attrs()->end()) {
|
||||
attrs.insert(*i++);
|
||||
}
|
||||
while (j != v2.attrs()->end()) {
|
||||
attrs.insert(*j++);
|
||||
}
|
||||
|
||||
Value v = {NewValueAs::attrs, attrs.alreadySorted()};
|
||||
|
||||
state.ctx.stats.nrOpUpdateValuesCopied += v.attrs()->size();
|
||||
return v;
|
||||
}
|
||||
|
||||
Value ExprOpConcatLists::eval(EvalState & state, Env & env)
|
||||
{
|
||||
state.ctx.stats.nrListConcats++;
|
||||
|
||||
/* We don't call into `concatLists` as that loses the position information of the expressions. */
|
||||
|
||||
Value v1 = e1->eval(state, env);
|
||||
state.checkList(v1, env, *e1);
|
||||
Value v2 = e2->eval(state, env);
|
||||
state.checkList(v2, env, *e2);
|
||||
|
||||
size_t l1 = v1.listSize(), l2 = v2.listSize(), len = l1 + l2;
|
||||
|
||||
if (l1 == 0) {
|
||||
return v2;
|
||||
} else if (l2 == 0) {
|
||||
return v1;
|
||||
} else {
|
||||
auto list = state.ctx.mem.newList(len);
|
||||
auto out = list->elems;
|
||||
std::copy(v1.listElems(), v1.listElems() + l1, out);
|
||||
std::copy(v2.listElems(), v2.listElems() + l2, out + l1);
|
||||
return {NewValueAs::list, list};
|
||||
}
|
||||
}
|
||||
|
||||
Value ExprConcatStrings::eval(EvalState & state, Env & env)
|
||||
{
|
||||
NixStringContext context;
|
||||
std::vector<BackedStringView> s;
|
||||
size_t sSize = 0;
|
||||
NixInt n{0};
|
||||
NixFloat nf = 0;
|
||||
|
||||
bool first = !isInterpolation;
|
||||
ValueType firstType = nString;
|
||||
|
||||
const auto str = [&] {
|
||||
std::string result;
|
||||
result.reserve(sSize);
|
||||
for (const auto & part : s) {
|
||||
result += *part;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
/* build a gc'd value string directly instead of going through str()
|
||||
and mkString to save an allocation and copy */
|
||||
const auto gcStr = [&] {
|
||||
auto result = Value::Str::gcAlloc(sSize);
|
||||
|
||||
char * tmp = result->contents;
|
||||
for (const auto & part : s) {
|
||||
memcpy(tmp, part->data(), part->size());
|
||||
tmp += part->size();
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// List of returned strings. References to these Values must NOT be persisted.
|
||||
SmallTemporaryValueVector<conservativeStackReservation> values(es.size());
|
||||
Value * vTmpP = values.data();
|
||||
|
||||
for (auto & [i_pos, i] : es) {
|
||||
Value & vTmp = *vTmpP++;
|
||||
vTmp = i->eval(state, env);
|
||||
|
||||
/* If the first element is a path, then the result will also
|
||||
be a path, we don't copy anything (yet - that's done later,
|
||||
since paths are copied when they are used in a derivation),
|
||||
and none of the strings are allowed to have contexts. */
|
||||
if (first) {
|
||||
firstType = vTmp.type();
|
||||
}
|
||||
|
||||
if (firstType == nInt) {
|
||||
if (vTmp.type() == nInt) {
|
||||
auto newN = n + vTmp.integer();
|
||||
if (auto checked = newN.valueChecked(); checked.has_value()) {
|
||||
n = NixInt(*checked);
|
||||
} else {
|
||||
state.ctx.errors
|
||||
.make<EvalError>("integer overflow in adding %1% + %2%", n, vTmp.integer())
|
||||
.atPos(i_pos)
|
||||
.debugThrow();
|
||||
}
|
||||
} else if (vTmp.type() == nFloat) {
|
||||
// Upgrade the type from int to float;
|
||||
firstType = nFloat;
|
||||
nf = n.value;
|
||||
nf += vTmp.fpoint();
|
||||
} else {
|
||||
state.ctx.errors.make<EvalError>("cannot add %1% to an integer", showType(vTmp))
|
||||
.atPos(i_pos)
|
||||
.withFrame(env, *this)
|
||||
.debugThrow();
|
||||
}
|
||||
} else if (firstType == nFloat) {
|
||||
if (vTmp.type() == nInt) {
|
||||
nf += vTmp.integer().value;
|
||||
} else if (vTmp.type() == nFloat) {
|
||||
nf += vTmp.fpoint();
|
||||
} else {
|
||||
state.ctx.errors.make<EvalError>("cannot add %1% to a float", showType(vTmp))
|
||||
.atPos(i_pos)
|
||||
.withFrame(env, *this)
|
||||
.debugThrow();
|
||||
}
|
||||
} else {
|
||||
if (s.empty()) {
|
||||
s.reserve(es.size());
|
||||
}
|
||||
|
||||
/* If we are coercing inside of an interpolation, we may allow slightly more comfort by coercing
|
||||
* things like integers. */
|
||||
auto coercionMode = isInterpolation && featureSettings.isEnabled(Xp::CoerceIntegers)
|
||||
? StringCoercionMode::Interpolation
|
||||
: StringCoercionMode::Strict;
|
||||
|
||||
/* skip canonization of first path, which would only be not
|
||||
canonized in the first place if it's coming from a ./${foo} type
|
||||
path */
|
||||
auto part = state.coerceToString(
|
||||
i_pos,
|
||||
vTmp,
|
||||
context,
|
||||
"while evaluating a path segment",
|
||||
coercionMode,
|
||||
firstType == nString,
|
||||
!first
|
||||
);
|
||||
sSize += part->size();
|
||||
s.emplace_back(std::move(part));
|
||||
}
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
if (firstType == nInt) {
|
||||
return {NewValueAs::integer, n};
|
||||
} else if (firstType == nFloat) {
|
||||
return {NewValueAs::floating, nf};
|
||||
} else if (firstType == nPath) {
|
||||
if (!context.empty()) {
|
||||
state.ctx.errors
|
||||
.make<EvalError>("a string that refers to a store path cannot be appended to a path")
|
||||
.atPos(pos)
|
||||
.withFrame(env, *this)
|
||||
.debugThrow();
|
||||
}
|
||||
return {NewValueAs::path, CanonPath(canonPath(str()))};
|
||||
} else {
|
||||
return {NewValueAs::string, gcStr(), context};
|
||||
}
|
||||
}
|
||||
|
||||
Value ExprPos::eval(EvalState & state, Env & env)
|
||||
{
|
||||
Value v;
|
||||
state.mkPos(v, pos);
|
||||
return v;
|
||||
}
|
||||
|
||||
Value ExprBlackHole::eval(EvalState & state, Env & env)
|
||||
{
|
||||
state.ctx.errors.make<InfiniteRecursionError>("infinite recursion encountered").debugThrow();
|
||||
}
|
||||
|
||||
Value ExprDebugFrame::eval(EvalState & state, Env & env)
|
||||
{
|
||||
auto dts = makeDebugTraceStacker(state, *inner, env, state.ctx.positions[pos], message);
|
||||
return inner->eval(state, env);
|
||||
}
|
||||
|
||||
/** Returns `nullptr` if we should be using a default instead. */
|
||||
Attr const *
|
||||
ExprSelect::selectSingleAttr(EvalState & state, Env & env, AttrName const & attrName, Value & vCurrent)
|
||||
{
|
||||
Symbol const attrSym = getName(attrName, state, env);
|
||||
|
||||
try {
|
||||
state.forceValue(vCurrent, pos);
|
||||
} catch (Error & e) {
|
||||
// clang-format off
|
||||
e.addTrace(state.ctx.positions[attrName.pos], HintFmt(
|
||||
"while evaluating an expression to select '%s' on it", state.ctx.symbols[attrSym]
|
||||
));
|
||||
// clang-format on
|
||||
throw;
|
||||
}
|
||||
|
||||
if (vCurrent.type() != nAttrs) {
|
||||
// If we have an `or` provided default, then it doesn't have to be an attrset.
|
||||
// Let the caller know there's no attr value here.
|
||||
if (def != nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Otherwise, we must type error.
|
||||
// clang-format off
|
||||
state.ctx.errors.make<TypeError>(
|
||||
"expected a set but found %s: %s",
|
||||
showType(vCurrent),
|
||||
ValuePrinter(state, vCurrent, errorPrintOptions)
|
||||
).addTrace(
|
||||
attrName.pos,
|
||||
HintFmt("while selecting '%s'", state.ctx.symbols[attrSym])
|
||||
).debugThrow();
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
// Now that we know it's an attrset, we can actually look for the name.
|
||||
|
||||
auto const attrIt = vCurrent.attrs()->get(attrSym);
|
||||
if (!attrIt) {
|
||||
|
||||
// Again if we have an `or` provided default, then missing attr is not an error.
|
||||
if (def != nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Otherwise, we collect all attr names and throw an attr missing error.
|
||||
|
||||
std::set<std::string> const allAttrNames = *vCurrent.attrs()
|
||||
| std::views::transform([&state](auto const & attr) {
|
||||
return std::string{state.ctx.symbols[attr.name]};
|
||||
})
|
||||
| std::ranges::to<std::set>();
|
||||
|
||||
auto suggestions = Suggestions::bestMatches(allAttrNames, state.ctx.symbols[attrSym]);
|
||||
state.ctx.errors.make<EvalError>("attribute '%s' missing", state.ctx.symbols[attrSym])
|
||||
.atPos(attrName.pos)
|
||||
.withSuggestions(suggestions)
|
||||
.withFrame(env, *this)
|
||||
.debugThrow();
|
||||
}
|
||||
|
||||
// If we made it here, then we successfully found the attribute.
|
||||
// Return it to our caller!
|
||||
|
||||
return attrIt;
|
||||
}
|
||||
|
||||
Value ExprSelect::eval(EvalState & state, Env & env)
|
||||
{
|
||||
// Position for the current attrset Value in this select chain.
|
||||
PosIdx posCurrent;
|
||||
// Position for the current selector in this select chain.
|
||||
PosIdx posCurrentSyntax;
|
||||
|
||||
Value baseSelectee;
|
||||
try {
|
||||
// Evaluate the original thing we're selecting on.
|
||||
baseSelectee = e->eval(state, env);
|
||||
} catch (Error & e) {
|
||||
// clang-format off
|
||||
e.addTrace(state.ctx.positions[getPos()], HintFmt(
|
||||
"while evaluating an expression to select '%s' on it",
|
||||
showAttrPath(state.ctx.symbols, attrPath)
|
||||
));
|
||||
// clang-format on
|
||||
throw;
|
||||
}
|
||||
|
||||
try {
|
||||
// With the original selectee evaluated, we'll walk the selection path starting
|
||||
// with the evaluated original selectee.
|
||||
std::reference_wrapper<Value> curSelectee = std::ref(baseSelectee);
|
||||
for (AttrName const & attrName : attrPath) {
|
||||
state.ctx.stats.nrLookups++;
|
||||
|
||||
// Select `attrName` on `curSelectee`.
|
||||
auto const attr = selectSingleAttr(state, env, attrName, curSelectee.get());
|
||||
if (!attr) {
|
||||
// Use default.
|
||||
try {
|
||||
return this->def->eval(state, env);
|
||||
} catch (Error & err) {
|
||||
err.addTrace(
|
||||
state.ctx.positions[this->def->pos],
|
||||
"while evaluating fallback for missing attribute '%s'",
|
||||
state.ctx.symbols[getName(attrName, state, env)]
|
||||
);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// The selection worked. If we have another iteration, then we use `attr->value`
|
||||
// as the thing to select on. If this is the last iteration, then `attr->value`
|
||||
// is the final value this ExprSelect evaluated to.
|
||||
curSelectee = std::ref(attr->value);
|
||||
|
||||
posCurrent = attr->pos;
|
||||
posCurrentSyntax = attrName.pos;
|
||||
if (state.ctx.stats.countCalls) {
|
||||
state.ctx.stats.attrSelects[posCurrent]++;
|
||||
}
|
||||
}
|
||||
|
||||
state.forceValue(curSelectee.get(), posCurrent ? posCurrent : posCurrentSyntax);
|
||||
|
||||
return curSelectee.get();
|
||||
|
||||
} catch (Error & err) {
|
||||
auto const & lastPos = state.ctx.positions[posCurrent];
|
||||
if (lastPos && !std::get_if<Pos::Hidden>(&lastPos.origin)) {
|
||||
err.addTrace(lastPos, "while evaluating the attribute '%s'", showAttrPath(state, env, attrPath));
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
Value ExprOpHasAttr::eval(EvalState & state, Env & env)
|
||||
{
|
||||
Value vTmp = e->eval(state, env);
|
||||
Value * vAttrs = &vTmp;
|
||||
|
||||
for (auto & i : attrPath) {
|
||||
state.forceValue(*vAttrs, getPos());
|
||||
const Attr * j;
|
||||
auto name = getName(i, state, env);
|
||||
if (vAttrs->type() != nAttrs || (j = vAttrs->attrs()->get(name)) == nullptr) {
|
||||
return {NewValueAs::boolean, false};
|
||||
} else {
|
||||
vAttrs = &j->value;
|
||||
}
|
||||
}
|
||||
|
||||
return {NewValueAs::boolean, true};
|
||||
}
|
||||
|
||||
Value ExprLambda::eval(EvalState & state, Env & env)
|
||||
{
|
||||
return {NewValueAs::lambda, state.ctx.mem, env, *this};
|
||||
}
|
||||
|
||||
Value ExprCall::eval(EvalState & state, Env & env)
|
||||
{
|
||||
Value vFun = fun->eval(state, env);
|
||||
|
||||
// Empirical arity of Nixpkgs lambdas by regex e.g. ([a-zA-Z]+:(\s|(/\*.*\/)|(#.*\n))*){5}
|
||||
// 2: over 4000
|
||||
// 3: about 300
|
||||
// 4: about 60
|
||||
// 5: under 10
|
||||
// This excluded attrset lambdas (`{...}:`). Contributions of mixed lambdas appears insignificant at ~150
|
||||
// total.
|
||||
SmallValueVector<4> vArgs(args.size());
|
||||
for (size_t i = 0; i < args.size(); ++i) {
|
||||
vArgs[i] = args[i]->maybeThunk(state, env);
|
||||
}
|
||||
|
||||
return state.callFunction(vFun, vArgs, pos);
|
||||
}
|
||||
|
||||
}
|
||||
+145
-20
@@ -20,11 +20,21 @@ inline Value::Value(app_t, EvalMemory & mem, Value & lhs, Value & rhs)
|
||||
}
|
||||
|
||||
inline Value::Value(app_t, EvalMemory & mem, Value & lhs, std::span<Value> args)
|
||||
: Value(app_t{}, mem, lhs, args, {})
|
||||
{
|
||||
auto app = static_cast<Value::App *>(mem.allocBytes(sizeof(Value::App) + args.size_bytes()));
|
||||
}
|
||||
|
||||
inline Value::Value(
|
||||
app_t, EvalMemory & mem, const Value & lhs, std::span<Value> baseArgs, std::span<Value> moreArgs
|
||||
)
|
||||
{
|
||||
auto app = static_cast<Value::App *>(
|
||||
mem.allocBytes(sizeof(Value::App) + baseArgs.size_bytes() + moreArgs.size_bytes())
|
||||
);
|
||||
app->_left = lhs;
|
||||
app->_n = args.size();
|
||||
std::copy(args.begin(), args.end(), app->_args);
|
||||
app->_n = baseArgs.size() + moreArgs.size();
|
||||
std::copy(baseArgs.begin(), baseArgs.end(), app->_args);
|
||||
std::copy(moreArgs.begin(), moreArgs.end(), app->_args + baseArgs.size());
|
||||
raw = tag(tApp, app);
|
||||
}
|
||||
|
||||
@@ -100,6 +110,85 @@ Env & EvalMemory::allocEnv(size_t size)
|
||||
return *env;
|
||||
}
|
||||
|
||||
/* The overloaded versions of `checkType` exist because of non-unified error handling
|
||||
* The variant which takes an Expression is required because of debug frames (`withFrames`).
|
||||
* Ideally, at some point in the future, we'd implement debug frames that are not tied to the expression and
|
||||
* env and then unify both `checkType` functions into one. Then the argument forwarding overloading hack done
|
||||
* for the other functions below will be removable again.
|
||||
*/
|
||||
[[gnu::always_inline]]
|
||||
void EvalState::checkType(Value & v, ValueType vType, Env & env, Expr & e)
|
||||
{
|
||||
if (v.type() != vType) {
|
||||
ctx.errors
|
||||
.make<TypeError>(
|
||||
"expected %1% but found %2%: %3%",
|
||||
Uncolored(vType),
|
||||
showType(v),
|
||||
ValuePrinter(*this, v, errorPrintOptions)
|
||||
)
|
||||
.atPos(e.getPos())
|
||||
.withFrame(env, e)
|
||||
.debugThrow();
|
||||
}
|
||||
}
|
||||
|
||||
[[gnu::always_inline]]
|
||||
void EvalState::checkType(Value & v, ValueType vType)
|
||||
{
|
||||
if (v.type() != vType) {
|
||||
ctx.errors
|
||||
.make<TypeError>(
|
||||
"expected %1% but found %2%: %3%",
|
||||
Uncolored(vType),
|
||||
showType(v),
|
||||
ValuePrinter(*this, v, errorPrintOptions)
|
||||
)
|
||||
.debugThrow();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
[[gnu::always_inline]]
|
||||
bool EvalState::checkBool(Value & v, Args &&... errorArgs)
|
||||
{
|
||||
checkType(v, nBool, std::forward<Args>(errorArgs)...);
|
||||
return v.boolean();
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
[[gnu::always_inline]]
|
||||
NixInt EvalState::checkInt(Value & v, Args &&... errorArgs)
|
||||
{
|
||||
checkType(v, nInt, std::forward<Args>(errorArgs)...);
|
||||
return v.integer();
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
[[gnu::always_inline]]
|
||||
NixFloat EvalState::checkFloat(Value & v, Args &&... errorArgs)
|
||||
{
|
||||
if (v.type() == nInt) {
|
||||
return v.integer().value;
|
||||
}
|
||||
checkType(v, nFloat, std::forward<Args>(errorArgs)...);
|
||||
return v.fpoint();
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
[[gnu::always_inline]]
|
||||
void EvalState::checkList(Value & v, Args &&... errorArgs)
|
||||
{
|
||||
checkType(v, nList, std::forward<Args>(errorArgs)...);
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
[[gnu::always_inline]]
|
||||
Bindings * EvalState::checkAttrs(Value & v, Args &&... errorArgs)
|
||||
{
|
||||
checkType(v, nAttrs, std::forward<Args>(errorArgs)...);
|
||||
return v.attrs();
|
||||
}
|
||||
|
||||
[[gnu::always_inline]]
|
||||
void EvalState::forceValue(Value & v, const PosIdx pos)
|
||||
@@ -114,7 +203,7 @@ void EvalState::forceValue(Value & v, const PosIdx pos)
|
||||
Expr & expr = *thunk.expr;
|
||||
thunk = Value::blackHole;
|
||||
try {
|
||||
expr.eval(*this, *env, v);
|
||||
v = expr.eval(*this, *env);
|
||||
thunk.resolve(v);
|
||||
} catch (...) {
|
||||
thunk = backup;
|
||||
@@ -130,7 +219,7 @@ void EvalState::forceValue(Value & v, const PosIdx pos)
|
||||
auto target = app.target();
|
||||
if (!target.isPrimOp() || target.primOp()->arity <= app.totalArgs()) {
|
||||
auto tmp = v.app().left();
|
||||
callFunction(tmp, v.app().args(), v, pos);
|
||||
v = callFunction(tmp, v.app().args(), pos);
|
||||
app.resolve(v);
|
||||
}
|
||||
}
|
||||
@@ -138,15 +227,14 @@ void EvalState::forceValue(Value & v, const PosIdx pos)
|
||||
}
|
||||
|
||||
[[gnu::always_inline]]
|
||||
inline void EvalState::forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx)
|
||||
inline Bindings * EvalState::forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx)
|
||||
{
|
||||
forceValue(v, pos);
|
||||
if (v.type() != nAttrs) {
|
||||
ctx.errors.make<TypeError>(
|
||||
"expected a set but found %1%: %2%",
|
||||
showType(v),
|
||||
ValuePrinter(*this, v, errorPrintOptions)
|
||||
).withTrace(pos, errorCtx).debugThrow();
|
||||
try {
|
||||
forceValue(v, pos);
|
||||
return checkAttrs(v);
|
||||
} catch (Error & e) {
|
||||
e.addTrace(ctx.positions[pos], errorCtx);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,15 +242,52 @@ inline void EvalState::forceAttrs(Value & v, const PosIdx pos, std::string_view
|
||||
[[gnu::always_inline]]
|
||||
inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view errorCtx)
|
||||
{
|
||||
forceValue(v, pos);
|
||||
if (!v.isList()) {
|
||||
ctx.errors.make<TypeError>(
|
||||
"expected a list but found %1%: %2%",
|
||||
showType(v),
|
||||
ValuePrinter(*this, v, errorPrintOptions)
|
||||
).withTrace(pos, errorCtx).debugThrow();
|
||||
try {
|
||||
forceValue(v, pos);
|
||||
checkList(v);
|
||||
} catch (Error & e) {
|
||||
e.addTrace(ctx.positions[pos], errorCtx);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
|
||||
{
|
||||
for (auto l = var.level; l; --l, env = env->up)
|
||||
;
|
||||
|
||||
if (!var.fromWith) {
|
||||
return &env->values[var.displ];
|
||||
}
|
||||
|
||||
// This early exit defeats the `maybeThunk` optimization for variables from `with`,
|
||||
// The added complexity of handling this appears to be similarly in cost, or
|
||||
// the cases where applicable were insignificant in the first place.
|
||||
if (noEval) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto * fromWith = var.fromWith;
|
||||
while (1) {
|
||||
forceAttrs(
|
||||
env->values[0], fromWith->pos, "while evaluating the first subexpression of a with expression"
|
||||
);
|
||||
auto j = env->values[0].attrs()->get(var.name);
|
||||
if (j) {
|
||||
if (ctx.stats.countCalls) {
|
||||
ctx.stats.attrSelects[j->pos]++;
|
||||
}
|
||||
return &j->value;
|
||||
}
|
||||
if (!fromWith->parentWith) {
|
||||
ctx.errors.make<UndefinedVarError>("undefined variable '%1%'", ctx.symbols[var.name])
|
||||
.atPos(var.pos)
|
||||
.withFrame(*env, var)
|
||||
.debugThrow();
|
||||
}
|
||||
for (size_t l = fromWith->prevWith; l; --l, env = env->up)
|
||||
;
|
||||
fromWith = fromWith->parentWith;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,8 +95,6 @@ const std::string & EvalSettings::getCurrentSystem()
|
||||
|
||||
EvalSettings evalSettings;
|
||||
|
||||
static GlobalConfig::Register rEvalSettings(&evalSettings);
|
||||
|
||||
Path getNixDefExpr()
|
||||
{
|
||||
return settings.useXDGBaseDirectories
|
||||
|
||||
@@ -14,7 +14,7 @@ struct EvalSettings : Config
|
||||
|
||||
static std::string resolvePseudoUrl(std::string_view url);
|
||||
|
||||
#include "libexpr-settings.gen.inc"
|
||||
#include "lix/libexpr/libexpr-settings.gen.inc"
|
||||
|
||||
/**
|
||||
* Implements the `eval-system` vs `system` defaulting logic
|
||||
|
||||
+156
-931
File diff suppressed because it is too large
Load Diff
+49
-36
@@ -80,6 +80,9 @@ void copyContext(const Value & v, NixStringContext & context);
|
||||
std::string printValue(EvalState & state, Value & v);
|
||||
std::ostream & operator << (std::ostream & os, const ValueType t);
|
||||
|
||||
Symbol getName(const AttrName & name, EvalState & state, Env & env);
|
||||
|
||||
std::string showAttrPath(EvalState & state, Env & env, const AttrPath & attrPath);
|
||||
|
||||
/**
|
||||
* Initialise the evaluator (including Boehm GC, if applicable).
|
||||
@@ -163,20 +166,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
struct StaticSymbols
|
||||
{
|
||||
const Symbol outPath, drvPath, type, meta, name, value, system, overrides, outputs, outputName,
|
||||
ignoreNulls, file, line, column, functor, toString, right, wrong, structuredAttrs,
|
||||
allowedReferences, allowedRequisites, disallowedReferences, disallowedRequisites, maxSize,
|
||||
maxClosureSize, builder, args, contentAddressed, impure, outputHash, outputHashAlgo,
|
||||
outputHashMode, recurseForDerivations, description, self, startSet, operator_, key,
|
||||
path, prefix, outputSpecified;
|
||||
|
||||
const Expr::AstSymbols exprSymbols;
|
||||
|
||||
explicit StaticSymbols(SymbolTable & symbols);
|
||||
};
|
||||
|
||||
class EvalMemory
|
||||
{
|
||||
static constexpr size_t CACHES = 8;
|
||||
@@ -216,7 +205,7 @@ public:
|
||||
|
||||
BindingsBuilder buildBindings(SymbolTable & symbols, size_t capacity)
|
||||
{
|
||||
return BindingsBuilder(*this, symbols, allocBindings(capacity), capacity);
|
||||
return BindingsBuilder(symbols, allocBindings(capacity), capacity);
|
||||
}
|
||||
|
||||
const Statistics getStats() const { return stats; }
|
||||
@@ -491,9 +480,8 @@ class Evaluator
|
||||
EvalState * activeEval = nullptr;
|
||||
|
||||
public:
|
||||
SymbolTable symbols;
|
||||
NixSymbolTable symbols;
|
||||
PosTable positions;
|
||||
const StaticSymbols s;
|
||||
EvalMemory mem;
|
||||
EvalRuntimeCaches caches;
|
||||
EvalPaths paths;
|
||||
@@ -566,7 +554,10 @@ public:
|
||||
/**
|
||||
* Creates a thunk that will evaluate the given expression when forced.
|
||||
*/
|
||||
void evalLazily(Expr & e, Value & v);
|
||||
Value evalLazily(Expr & e);
|
||||
|
||||
/** If debugging is enabled, returns the next trace. Otherwise, std::nullopt. */
|
||||
std::optional<DebugTrace const *> nextDebugTrace() const;
|
||||
|
||||
private:
|
||||
Expr * parse(
|
||||
@@ -655,24 +646,14 @@ public:
|
||||
/**
|
||||
* Evaluate an expression read from the given file to normal form.
|
||||
*/
|
||||
void evalFile(const SourcePath & path, Value & v);
|
||||
Value evalFile(const SourcePath & path);
|
||||
|
||||
void resetFileCache();
|
||||
|
||||
/**
|
||||
* Evaluate an expression to normal form
|
||||
*
|
||||
* @param [out] v The resulting is stored here.
|
||||
*/
|
||||
void eval(Expr & e, Value & v);
|
||||
|
||||
/**
|
||||
* Evaluation the expression, then verify that it has the expected
|
||||
* type.
|
||||
*/
|
||||
inline bool evalBool(Env & env, Expr & e);
|
||||
inline void evalAttrs(Env & env, Expr & e, Value & v);
|
||||
inline void evalList(Env & env, Expr & e, Value & v);
|
||||
Value eval(Expr & e);
|
||||
|
||||
/**
|
||||
* If `v` is a thunk, enter it and overwrite `v` with the result
|
||||
@@ -697,7 +678,7 @@ public:
|
||||
NixFloat forceFloat(Value & v, const PosIdx pos, std::string_view errorCtx);
|
||||
bool forceBool(Value & v, const PosIdx pos, std::string_view errorCtx);
|
||||
|
||||
void forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx);
|
||||
inline Bindings * forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx);
|
||||
inline void forceList(Value & v, const PosIdx pos, std::string_view errorCtx);
|
||||
/**
|
||||
* @param v either lambda or primop
|
||||
@@ -707,6 +688,19 @@ public:
|
||||
std::string_view forceString(Value & v, NixStringContext & context, const PosIdx pos, std::string_view errorCtx);
|
||||
std::string_view forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx);
|
||||
|
||||
inline void checkType(Value & v, ValueType vType, Env & env, Expr & e);
|
||||
inline void checkType(Value & v, ValueType vType);
|
||||
template<typename... Args>
|
||||
bool checkBool(Value & v, Args &&... errorArgs);
|
||||
template<typename... Args>
|
||||
NixInt checkInt(Value & v, Args &&... errorArgs);
|
||||
template<typename... Args>
|
||||
NixFloat checkFloat(Value & v, Args &&... errorArgs);
|
||||
template<typename... Args>
|
||||
void checkList(Value & v, Args &&... errorArgs);
|
||||
template<typename... Args>
|
||||
Bindings * checkAttrs(Value & v, Args &&... errorArgs);
|
||||
|
||||
/**
|
||||
* Realise the given context, and return a mapping from the placeholders
|
||||
* used to construct the associated value to their final store path
|
||||
@@ -793,18 +787,18 @@ public:
|
||||
|
||||
bool isFunctor(Value & fun);
|
||||
|
||||
void callFunction(Value & fun, std::span<Value> args, Value & vRes, const PosIdx pos);
|
||||
Value callFunction(Value & fun, std::span<Value> args, const PosIdx pos);
|
||||
|
||||
void callFunction(Value & fun, Value & arg, Value & vRes, const PosIdx pos)
|
||||
Value callFunction(Value & fun, Value & arg, const PosIdx pos)
|
||||
{
|
||||
callFunction(fun, {&arg, 1}, vRes, pos);
|
||||
return callFunction(fun, {&arg, 1}, pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically call a function for which each argument has a
|
||||
* default value or has a binding in the `args` map.
|
||||
*/
|
||||
void autoCallFunction(Bindings & args, Value & fun, Value & res, PosIdx pos);
|
||||
Value autoCallFunction(Bindings & args, Value & fun, PosIdx pos);
|
||||
|
||||
void mkPos(Value & v, PosIdx pos);
|
||||
|
||||
@@ -868,7 +862,26 @@ std::string showType(const Value & v);
|
||||
|
||||
static constexpr std::string_view corepkgsPrefix{"/__corepkgs__/"};
|
||||
|
||||
|
||||
// In C++, template functions need to be defined in the header :/
|
||||
template<typename... Args>
|
||||
DebugState::TraceFrame makeDebugTraceStacker(
|
||||
EvalState & state, Expr & expr, Env & env, std::shared_ptr<Pos> && pos, const Args &... formatArgs
|
||||
)
|
||||
{
|
||||
auto trace = state.ctx.debug->addTrace(
|
||||
DebugTrace{
|
||||
.pos = std::move(pos),
|
||||
.expr = expr,
|
||||
.env = env,
|
||||
.hint = HintFmt(formatArgs...),
|
||||
.isError = false,
|
||||
}
|
||||
);
|
||||
if (state.ctx.debug->stop && state.ctx.debug->errorCallback) {
|
||||
state.ctx.debug->onEvalError(nullptr, env, expr);
|
||||
}
|
||||
return trace;
|
||||
}
|
||||
}
|
||||
|
||||
#include "lix/libexpr/eval-inline.hh" // IWYU pragma: keep
|
||||
|
||||
@@ -9,15 +9,17 @@ class EvalState;
|
||||
struct Value;
|
||||
|
||||
void prim_addDrvOutputDependencies(EvalState & state, Value * * args, Value & v);
|
||||
void prim_fetchClosure(EvalState & state, Value * * args, Value & v);
|
||||
void prim_fetchTree(EvalState & state, Value * * args, Value & v);
|
||||
void prim_fetchGit(EvalState & state, Value * * args, Value & v);
|
||||
void prim_fetchMercurial(EvalState & state, Value ** args, Value & v);
|
||||
void prim_fetchTarball(EvalState & state, Value * * args, Value & v);
|
||||
void prim_fetchurl(EvalState & state, Value * * args, Value & v);
|
||||
void prim_fromTOML(EvalState & state, Value * * args, Value & v);
|
||||
void prim_appendContext(EvalState & state, Value ** args, Value & v);
|
||||
void prim_getContext(EvalState & state, Value * * args, Value & v);
|
||||
void prim_hasContext(EvalState & state, Value * * args, Value & v);
|
||||
void prim_unsafeDiscardOutputDependency(EvalState & state, Value * * args, Value & v);
|
||||
void prim_unsafeDiscardStringContext(EvalState & state, Value ** args, Value & v);
|
||||
|
||||
namespace flake {
|
||||
|
||||
|
||||
+78
-29
@@ -1,4 +1,5 @@
|
||||
#include "lix/libexpr/flake/flake.hh"
|
||||
#include "lix/libutil/fmt.hh"
|
||||
#include "lix/libutil/logging.hh"
|
||||
#include "lix/libutil/json.hh"
|
||||
#include "lix/libutil/users.hh"
|
||||
@@ -29,19 +30,30 @@ static void writeTrustedList(const TrustedList & trustedList)
|
||||
writeFile(path, JSON(trustedList).dump());
|
||||
}
|
||||
|
||||
static bool askForSetting(
|
||||
static bool batchAskForSetting(
|
||||
bool & negativeTrustOverride,
|
||||
TrustedList & trustedList,
|
||||
const std::string & name,
|
||||
const std::string & valueS)
|
||||
std::map<std::string, std::string> & untrustedSettings)
|
||||
{
|
||||
bool trusted = false;
|
||||
std::string warning("The following settings require your decision:");
|
||||
for (const auto & [name, valueS] : untrustedSettings) {
|
||||
// FIXME: filter ANSI escapes, newlines, \r, etc.
|
||||
warning += fmt("\n- %s = %s", name, valueS);
|
||||
}
|
||||
|
||||
// FIXME: filter ANSI escapes, newlines, \r, etc.
|
||||
auto reply = logger->ask(fmt("Do you want to allow configuration setting '%s' to be set to '" ANSI_RED "%s" ANSI_NORMAL "'?\nThis may allow the flake to gain root, see the nix.conf manual page (" ANSI_BOLD "y" ANSI_NORMAL "es/" ANSI_BOLD "n" ANSI_NORMAL "o/" ANSI_BOLD "N" ANSI_NORMAL "o to all) ", name, valueS)).value_or('n');
|
||||
printWarning("%s", warning);
|
||||
|
||||
auto reply = logger
|
||||
->ask(
|
||||
fmt("Do you want to allow configuration settings to be applied?\nThis may allow the "
|
||||
"flake to gain root, see the nix.conf manual page (" ANSI_BOLD "y" ANSI_NORMAL
|
||||
"es for now/" ANSI_BOLD "A" ANSI_NORMAL "llow always/" ANSI_BOLD "n" ANSI_NORMAL
|
||||
"o/" ANSI_BOLD "N" ANSI_NORMAL "o to all) ")
|
||||
)
|
||||
.value_or('n');
|
||||
|
||||
if (reply == 'N') {
|
||||
printTaggedWarning("Rejecting all untrusted nix.conf entries");
|
||||
printWarning("Rejecting all untrusted nix.conf entries");
|
||||
printTaggedWarning(
|
||||
"you can set '%s' to '%b' to automatically reject configuration options supplied by "
|
||||
"flakes",
|
||||
@@ -49,25 +61,59 @@ static bool askForSetting(
|
||||
false
|
||||
);
|
||||
negativeTrustOverride = true;
|
||||
} else {
|
||||
if (std::tolower(reply) == 'y') {
|
||||
trusted = true;
|
||||
} else {
|
||||
printTaggedWarning(
|
||||
"you can set '%s' to '%b' to automatically reject configuration options supplied "
|
||||
"by flakes",
|
||||
"accept-flake-config",
|
||||
false
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (reply == 'y' || reply == 'A') {
|
||||
auto alwaysAllow = reply == 'A';
|
||||
for (const auto & [name, valueS] : untrustedSettings) {
|
||||
if (alwaysAllow) {
|
||||
trustedList[name][valueS] = true;
|
||||
}
|
||||
globalConfig.set(name, valueS);
|
||||
}
|
||||
|
||||
if (std::tolower(logger->ask(fmt("do you want to permanently (in %s) mark this value as %s? (y/N) ", trustedListPath(), trusted ? "trusted": "untrusted" )).value_or('n')) == 'y') {
|
||||
trustedList[name][valueS] = trusted;
|
||||
if (alwaysAllow) {
|
||||
writeTrustedList(trustedList);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
printTaggedWarning(
|
||||
"you can set '%s' to '%b' to automatically reject configuration options supplied "
|
||||
"by flakes",
|
||||
"accept-flake-config",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
auto didTrustedListChange = false;
|
||||
for (const auto & [name, valueS] : untrustedSettings) {
|
||||
auto individualReply = logger
|
||||
->ask(
|
||||
fmt("Do you want to allow setting '%s = %s'? (" ANSI_BOLD
|
||||
"y" ANSI_NORMAL "es for now/" ANSI_BOLD "A" ANSI_NORMAL
|
||||
"llow always/" ANSI_BOLD "n" ANSI_NORMAL "o for now) ",
|
||||
name,
|
||||
valueS)
|
||||
)
|
||||
.value_or('n');
|
||||
|
||||
if (individualReply == 'y' || individualReply == 'A') {
|
||||
if (individualReply == 'A') {
|
||||
trustedList[name][valueS] = true;
|
||||
didTrustedListChange = true;
|
||||
}
|
||||
|
||||
globalConfig.set(name, valueS);
|
||||
}
|
||||
}
|
||||
|
||||
return trusted;
|
||||
if (didTrustedListChange) {
|
||||
writeTrustedList(trustedList);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ConfigFile::apply()
|
||||
@@ -77,8 +123,11 @@ void ConfigFile::apply()
|
||||
// Allows to ignore all subsequent settings from this file.
|
||||
bool negativeTrustOverride = false;
|
||||
|
||||
for (auto & [name, value] : settings) {
|
||||
std::map<std::string, std::string> untrustedSettings;
|
||||
|
||||
TrustedList trustedList = readTrustedList();
|
||||
|
||||
for (auto & [name, value] : settings) {
|
||||
auto baseName = name.starts_with("extra-") ? std::string(name, 6) : name;
|
||||
|
||||
// FIXME: Move into libutil/config.cc.
|
||||
@@ -90,11 +139,12 @@ void ConfigFile::apply()
|
||||
else if (auto* b = std::get_if<Explicit<bool>>(&value))
|
||||
valueS = b->t ? "true" : "false";
|
||||
else if (auto ss = std::get_if<std::vector<std::string>>(&value))
|
||||
valueS = concatStringsSep(" ", *ss); // FIXME: evil
|
||||
valueS = concatStringsSep(" ", *ss); // FIXME: evil
|
||||
else
|
||||
assert(false);
|
||||
|
||||
bool trusted = whitelist.count(baseName);
|
||||
|
||||
if (!trusted) {
|
||||
switch (nix::fetchSettings.acceptFlakeConfig.get()) {
|
||||
case AcceptFlakeConfig::True: {
|
||||
@@ -102,21 +152,16 @@ void ConfigFile::apply()
|
||||
break;
|
||||
}
|
||||
case AcceptFlakeConfig::Ask: {
|
||||
auto trustedList = readTrustedList();
|
||||
auto tlname = get(trustedList, name);
|
||||
if (auto saved = tlname ? get(*tlname, valueS) : nullptr) {
|
||||
trusted = *saved;
|
||||
printInfo("Using saved setting for '%s = %s' from ~/.local/share/nix/trusted-settings.json.", name, valueS);
|
||||
} else {
|
||||
if (negativeTrustOverride) {
|
||||
trusted = false;
|
||||
} else {
|
||||
trusted = askForSetting(negativeTrustOverride, trustedList, name, valueS);
|
||||
}
|
||||
untrustedSettings[name] = valueS;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case nix::AcceptFlakeConfig::False: {
|
||||
case AcceptFlakeConfig::False: {
|
||||
trusted = false;
|
||||
break;
|
||||
};
|
||||
@@ -135,6 +180,10 @@ void ConfigFile::apply()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!untrustedSettings.empty()) {
|
||||
batchAskForSetting(negativeTrustOverride, trustedList, untrustedSettings);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+47
-46
@@ -331,10 +331,9 @@ static Flake getFlake(
|
||||
state.ctx.errors.make<EvalError>("file '%s' must be an attribute set", resolvedFlakeFile).debugThrow();
|
||||
}
|
||||
|
||||
Value vInfo;
|
||||
state.eval(flakeExpr, vInfo);
|
||||
Value vInfo = state.eval(flakeExpr);
|
||||
|
||||
if (auto description = vInfo.attrs()->get(state.ctx.s.description)) {
|
||||
if (auto description = vInfo.attrs()->get(state.ctx.symbols.sym_description)) {
|
||||
expectType(state, nString, description->value, description->pos);
|
||||
flake.description = description->value.str();
|
||||
}
|
||||
@@ -367,7 +366,7 @@ static Flake getFlake(
|
||||
flake.resolvedRef = resolvedRef;
|
||||
}
|
||||
|
||||
if (auto outputs = vInfo.attrs()->get(state.ctx.s.outputs)) {
|
||||
if (auto outputs = vInfo.attrs()->get(state.ctx.symbols.sym_outputs)) {
|
||||
expectType(state, nFunction, outputs->value, outputs->pos);
|
||||
|
||||
if (outputs->value.isLambda()) {
|
||||
@@ -376,19 +375,19 @@ static Flake getFlake(
|
||||
pattern)
|
||||
{
|
||||
for (auto & formal : pattern->formals) {
|
||||
if (formal.name != state.ctx.s.self)
|
||||
if (formal.name != state.ctx.symbols.sym_self) {
|
||||
flake.inputs.emplace(
|
||||
state.ctx.symbols[formal.name],
|
||||
FlakeInput{
|
||||
.ref = parseFlakeRef(std::string(state.ctx.symbols[formal.name]))
|
||||
}
|
||||
FlakeInput{.ref = parseFlakeRef(std::string(state.ctx.symbols[formal.name]))}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else
|
||||
} else {
|
||||
throw Error("flake '%s' lacks attribute 'outputs'", lockedRef);
|
||||
}
|
||||
|
||||
auto sNixConfig = state.ctx.symbols.create("nixConfig");
|
||||
|
||||
@@ -457,12 +456,16 @@ static Flake getFlake(
|
||||
}
|
||||
|
||||
for (auto & attr : *vInfo.attrs()) {
|
||||
if (attr.name != state.ctx.s.description &&
|
||||
attr.name != sInputs &&
|
||||
attr.name != state.ctx.s.outputs &&
|
||||
attr.name != sNixConfig)
|
||||
throw Error("flake '%s' has an unsupported attribute '%s', at %s",
|
||||
lockedRef, state.ctx.symbols[attr.name], state.ctx.positions[attr.pos]);
|
||||
if (attr.name != state.ctx.symbols.sym_description && attr.name != sInputs
|
||||
&& attr.name != state.ctx.symbols.sym_outputs && attr.name != sNixConfig)
|
||||
{
|
||||
throw Error(
|
||||
"flake '%s' has an unsupported attribute '%s', at %s",
|
||||
lockedRef,
|
||||
state.ctx.symbols[attr.name],
|
||||
state.ctx.positions[attr.pos]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return flake;
|
||||
@@ -937,43 +940,34 @@ LockedFlake lockFlake(
|
||||
}
|
||||
}
|
||||
|
||||
void callFlake(EvalState & state,
|
||||
const LockedFlake & lockedFlake,
|
||||
Value & vRes)
|
||||
Value callFlake(EvalState & state, const LockedFlake & lockedFlake)
|
||||
{
|
||||
Value vLocks;
|
||||
Value vRootSrc;
|
||||
Value vRootSubdir;
|
||||
Value vTmp1;
|
||||
Value vTmp2;
|
||||
|
||||
vLocks.mkString(lockedFlake.lockFile.to_string());
|
||||
vLocks = {NewValueAs::string, lockedFlake.lockFile.to_string()};
|
||||
|
||||
emitTreeAttrs(
|
||||
Value vRootSrc = emitTreeAttrs(
|
||||
state.ctx,
|
||||
*lockedFlake.flake.sourceInfo,
|
||||
lockedFlake.flake.lockedRef.input,
|
||||
vRootSrc,
|
||||
false,
|
||||
lockedFlake.flake.forceDirty
|
||||
);
|
||||
|
||||
vRootSubdir.mkString(lockedFlake.flake.lockedRef.subdir);
|
||||
vRootSubdir = {NewValueAs::string, lockedFlake.flake.lockedRef.subdir};
|
||||
|
||||
if (!state.ctx.caches.vCallFlake) {
|
||||
state.ctx.caches.vCallFlake = allocRootValue({});
|
||||
state.eval(
|
||||
state.ctx.parseExprFromString(
|
||||
*state.ctx.caches.vCallFlake = state.eval(state.ctx.parseExprFromString(
|
||||
#include "call-flake.nix.gen.hh"
|
||||
, CanonPath::root
|
||||
),
|
||||
*state.ctx.caches.vCallFlake
|
||||
);
|
||||
, CanonPath::root
|
||||
));
|
||||
}
|
||||
|
||||
state.callFunction(*state.ctx.caches.vCallFlake, vLocks, vTmp1, noPos);
|
||||
state.callFunction(vTmp1, vRootSrc, vTmp2, noPos);
|
||||
state.callFunction(vTmp2, vRootSubdir, vRes, noPos);
|
||||
Value vTmp1 = state.callFunction(*state.ctx.caches.vCallFlake, vLocks, noPos);
|
||||
Value vTmp2 = state.callFunction(vTmp1, vRootSrc, noPos);
|
||||
return state.callFunction(vTmp2, vRootSubdir, noPos);
|
||||
}
|
||||
|
||||
void prim_getFlake(EvalState & state, Value * * args, Value & v)
|
||||
@@ -983,15 +977,19 @@ void prim_getFlake(EvalState & state, Value * * args, Value & v)
|
||||
if (evalSettings.pureEval && !flakeRef.input.isLocked())
|
||||
throw Error("cannot call 'getFlake' on unlocked flake reference '%s' (use --impure to override)", flakeRefS);
|
||||
|
||||
callFlake(state,
|
||||
lockFlake(state, flakeRef,
|
||||
LockFlags {
|
||||
v = callFlake(
|
||||
state,
|
||||
lockFlake(
|
||||
state,
|
||||
flakeRef,
|
||||
LockFlags{
|
||||
.updateLockFile = false,
|
||||
.writeLockFile = false,
|
||||
.useRegistries = !evalSettings.pureEval && fetchSettings.useRegistries,
|
||||
.allowUnlocked = !evalSettings.pureEval,
|
||||
}),
|
||||
v);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
void prim_parseFlakeRef(
|
||||
@@ -1006,13 +1004,16 @@ void prim_parseFlakeRef(
|
||||
for (const auto & [key, value] : attrs) {
|
||||
auto s = state.ctx.symbols.create(key);
|
||||
auto & vv = binds.alloc(s);
|
||||
std::visit(overloaded {
|
||||
[&vv](const std::string & value) { vv.mkString(value); },
|
||||
[&vv](const uint64_t & value) { vv.mkInt(value); },
|
||||
[&vv](const Explicit<bool> & value) { vv.mkBool(value.t); }
|
||||
}, value);
|
||||
std::visit(
|
||||
overloaded{
|
||||
[&vv](const std::string & value) { vv = {NewValueAs::string, value}; },
|
||||
[&vv](const uint64_t & value) { vv = {NewValueAs::integer, NixInt::Inner(value)}; },
|
||||
[&vv](const Explicit<bool> & value) { vv = {NewValueAs::boolean, value.t}; }
|
||||
},
|
||||
value
|
||||
);
|
||||
}
|
||||
v.mkAttrs(binds);
|
||||
v = {NewValueAs::attrs, binds};
|
||||
}
|
||||
|
||||
void prim_flakeRefToString(
|
||||
@@ -1050,7 +1051,7 @@ void prim_flakeRefToString(
|
||||
}
|
||||
}
|
||||
auto flakeRef = FlakeRef::fromAttrs(attrs);
|
||||
v.mkString(flakeRef.to_string());
|
||||
v = {NewValueAs::string, flakeRef.to_string()};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -199,19 +199,14 @@ LockedFlake lockFlake(
|
||||
const FlakeRef & flakeRef,
|
||||
const LockFlags & lockFlags);
|
||||
|
||||
void callFlake(
|
||||
EvalState & state,
|
||||
const LockedFlake & lockedFlake,
|
||||
Value & v);
|
||||
|
||||
Value callFlake(EvalState & state, const LockedFlake & lockedFlake);
|
||||
}
|
||||
|
||||
void emitTreeAttrs(
|
||||
Value emitTreeAttrs(
|
||||
Evaluator & state,
|
||||
const fetchers::Tree & tree,
|
||||
const fetchers::Input & input,
|
||||
Value & v,
|
||||
bool emptyRevFallback = false,
|
||||
bool forceDirty = false);
|
||||
|
||||
bool forceDirty = false
|
||||
);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user