Compare commits
662 Commits
flake
...
bash-compl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8b2dbf272 | ||
|
|
437614b479 | ||
|
|
649c2db308 | ||
|
|
27d34ef770 | ||
|
|
259ff74bde | ||
|
|
e917332d63 | ||
|
|
da310fac62 | ||
|
|
4c3c638a05 | ||
|
|
0884f180f5 | ||
|
|
e0c19ee620 | ||
|
|
91ddee6bf0 | ||
|
|
14a3a62bfc | ||
|
|
1ad71bc62c | ||
|
|
b0e9b07e80 | ||
|
|
ff394ff206 | ||
|
|
80f4b7b6f8 | ||
|
|
2f8ee4578f | ||
|
|
6f3244ce45 | ||
|
|
941f95284a | ||
|
|
0038bbafde | ||
|
|
f459ca547f | ||
|
|
5d8504b978 | ||
|
|
9570036146 | ||
|
|
9c4e05766b | ||
|
|
70bcd6a55c | ||
|
|
5ada0831cf | ||
|
|
6521c92ce8 | ||
|
|
829dcb35d5 | ||
|
|
b51dff431c | ||
|
|
b4e23dcd9e | ||
|
|
0469795978 | ||
|
|
ef4d3fc111 | ||
|
|
c7af247bea | ||
|
|
8c75621da6 | ||
|
|
b69323f8c9 | ||
|
|
42a12f9232 | ||
|
|
539a9c1c5f | ||
|
|
0725ab2fd7 | ||
|
|
3738bcb05e | ||
|
|
69cb9f7eee | ||
|
|
aaa109565e | ||
|
|
bdb3226607 | ||
|
|
aa34c0ef51 | ||
|
|
9ea4f93f88 | ||
|
|
a6c4fd044c | ||
|
|
12b7eefbc5 | ||
|
|
7a9687ba30 | ||
|
|
3b489e8843 | ||
|
|
29043e7e9e | ||
|
|
c277231b7d | ||
|
|
f89349f07e | ||
|
|
0858738355 | ||
|
|
8f41847394 | ||
|
|
696c026006 | ||
|
|
3729df34da | ||
|
|
c0c2cb871d | ||
|
|
e5ea01c1a8 | ||
|
|
3aaceeb7e2 | ||
|
|
d103c79144 | ||
|
|
03a4a3c95c | ||
|
|
54955867a6 | ||
|
|
68b43e01dd | ||
|
|
ce3173edc1 | ||
|
|
2f494531b7 | ||
|
|
3473b1950a | ||
|
|
e35d83d1fc | ||
|
|
bf70a047a0 | ||
|
|
6e7f252ea6 | ||
|
|
9c78f7f196 | ||
|
|
bb39f2bb8a | ||
|
|
485a87f22f | ||
|
|
a6ff66b658 | ||
|
|
78ad5b3d91 | ||
|
|
e1fc9f6690 | ||
|
|
ab47868639 | ||
|
|
6d6467d376 | ||
|
|
6cf91d6fbd | ||
|
|
12f9379123 | ||
|
|
00e1400eb7 | ||
|
|
021634e3e3 | ||
|
|
ed13457dbf | ||
|
|
74024515a3 | ||
|
|
bd10a07d17 | ||
|
|
77ffaea4fa | ||
|
|
36c34c3b1f | ||
|
|
4fdec5f61d | ||
|
|
03b56e96bf | ||
|
|
d15d91cad1 | ||
|
|
3e7aab81ce | ||
|
|
2c692a3b14 | ||
|
|
e0a0ae0467 | ||
|
|
4ba4c7ff66 | ||
|
|
2287cc6486 | ||
|
|
4989c04dd2 | ||
|
|
2fccef0c59 | ||
|
|
2287e2f279 | ||
|
|
5f75d56c9b | ||
|
|
bf0b7e5423 | ||
|
|
015f8f1c13 | ||
|
|
3fa1e7dace | ||
|
|
7abe3bde8a | ||
|
|
4e67f89f38 | ||
|
|
bc5d4843a9 | ||
|
|
1537e270fb | ||
|
|
1e6e673eb7 | ||
|
|
8aa354fdfd | ||
|
|
1c127e6a82 | ||
|
|
f6ddf48882 | ||
|
|
c5ec95e2c7 | ||
|
|
1b49479836 | ||
|
|
1e7ce1d6da | ||
|
|
38e360154d | ||
|
|
d1165d8791 | ||
|
|
2a4e4f6a6e | ||
|
|
fbcb897e21 | ||
|
|
34c7645a58 | ||
|
|
7304f9f145 | ||
|
|
db34445c5e | ||
|
|
3e1abf4f05 | ||
|
|
073650db01 | ||
|
|
ea1803efdc | ||
|
|
0c2088d438 | ||
|
|
ae9119167e | ||
|
|
e188fe7c6d | ||
|
|
35f6651735 | ||
|
|
97d1c7f932 | ||
|
|
cfc38257cf | ||
|
|
73769b28e3 | ||
|
|
5a1514adb8 | ||
|
|
73b6d87e17 | ||
|
|
f443d5ca19 | ||
|
|
2672a28bb4 | ||
|
|
7cd9859e41 | ||
|
|
4a4521f462 | ||
|
|
73c9840569 | ||
|
|
d068f9ffff | ||
|
|
890df325c7 | ||
|
|
4ad5826a18 | ||
|
|
1d99c4ab25 | ||
|
|
c169ea5904 | ||
|
|
f3505a7899 | ||
|
|
30c8297ded | ||
|
|
95468e3c1e | ||
|
|
50cf77cecd | ||
|
|
dd032f624c | ||
|
|
edee6169bf | ||
|
|
1351101c28 | ||
|
|
8f9dcfc671 | ||
|
|
5921ca89f9 | ||
|
|
b5e3c04c03 | ||
|
|
6529490cc1 | ||
|
|
8a78bcf6a2 | ||
|
|
b0336e7cf7 | ||
|
|
c6e63065f3 | ||
|
|
144bb3ef7d | ||
|
|
7072b8649a | ||
|
|
eb19ff3b82 | ||
|
|
4c24263967 | ||
|
|
e375da6899 | ||
|
|
46a284263f | ||
|
|
6208d24c38 | ||
|
|
9659940659 | ||
|
|
26dacc0983 | ||
|
|
d4df99a334 | ||
|
|
442e665d6d | ||
|
|
d2032edb2f | ||
|
|
0b013a54dc | ||
|
|
84a3a5c3cd | ||
|
|
379852a152 | ||
|
|
be2580be01 | ||
|
|
022287060b | ||
|
|
750c993f00 | ||
|
|
9d7fb62db6 | ||
|
|
e2213d77a2 | ||
|
|
fa467de090 | ||
|
|
0a4e911cf4 | ||
|
|
a2628b43bb | ||
|
|
d070e1c532 | ||
|
|
44d6421160 | ||
|
|
d5334c466b | ||
|
|
f83acbbfe3 | ||
|
|
86748d3571 | ||
|
|
af35b318f3 | ||
|
|
a9ebc3ea5d | ||
|
|
90ada8e31a | ||
|
|
94a94da075 | ||
|
|
fad9faf354 | ||
|
|
7bcc9f2aaf | ||
|
|
958ec5de56 | ||
|
|
b270869466 | ||
|
|
887730aab3 | ||
|
|
b9d64f9318 | ||
|
|
8451298b35 | ||
|
|
5d70b454be | ||
|
|
9640208d00 | ||
|
|
54037f4e2d | ||
|
|
185c3c8240 | ||
|
|
8414685c0f | ||
|
|
dbefe9e6b8 | ||
|
|
e91f32f2b5 | ||
|
|
678301072f | ||
|
|
a6e2b6b360 | ||
|
|
ebfbfe9515 | ||
|
|
3c54e9ba01 | ||
|
|
b9f93e7386 | ||
|
|
b9fb372075 | ||
|
|
88b44b1e94 | ||
|
|
68e0ca608f | ||
|
|
26f895a26d | ||
|
|
f68bed7f67 | ||
|
|
e53c89a643 | ||
|
|
5bbe793abf | ||
|
|
99c8e7a48d | ||
|
|
ab41e9d543 | ||
|
|
6be04476dc | ||
|
|
1af7b94c1d | ||
|
|
c39c2503f7 | ||
|
|
5046233b5a | ||
|
|
cc22cf662b | ||
|
|
2b8ca654b0 | ||
|
|
cd973fa07f | ||
|
|
b430a81a1f | ||
|
|
9a5ca802c7 | ||
|
|
872a22fa23 | ||
|
|
b5c9dbc84f | ||
|
|
90d55ed275 | ||
|
|
32f31a8c63 | ||
|
|
543288b649 | ||
|
|
ad6e55d777 | ||
|
|
b33b94748c | ||
|
|
9f4d8c6170 | ||
|
|
1bf9eb21b7 | ||
|
|
6fadb3fc03 | ||
|
|
62f712c8ae | ||
|
|
1dc29df1d3 | ||
|
|
c7866733d7 | ||
|
|
4da1cd59ba | ||
|
|
dbb4bec003 | ||
|
|
87873d0d65 | ||
|
|
31c240ee8b | ||
|
|
ad6b738ed8 | ||
|
|
4947e0a91a | ||
|
|
b8a38fa521 | ||
|
|
a045f93396 | ||
|
|
ecb3a1afa2 | ||
|
|
ab88f4bbd4 | ||
|
|
048ef27326 | ||
|
|
90d2cf6ff9 | ||
|
|
1789c56f43 | ||
|
|
c3c23a52ee | ||
|
|
e721f99817 | ||
|
|
0456a4ec65 | ||
|
|
062012eee1 | ||
|
|
ca8caaec5e | ||
|
|
2c6dbcd5e7 | ||
|
|
fd8ee94ab2 | ||
|
|
8beedd4486 | ||
|
|
9b8cb6809b | ||
|
|
b8bddb63e6 | ||
|
|
8d2eb1ff56 | ||
|
|
2c1e05ae93 | ||
|
|
0bc0d35b6b | ||
|
|
6419f5028b | ||
|
|
75c897cf3d | ||
|
|
693e8b1286 | ||
|
|
d2438f86d5 | ||
|
|
0ba8a4e942 | ||
|
|
f730841db4 | ||
|
|
850f73045f | ||
|
|
f01304b573 | ||
|
|
e491efe9fb | ||
|
|
d5f1cc3e94 | ||
|
|
88c452d160 | ||
|
|
9ff4060d26 | ||
|
|
72748b4088 | ||
|
|
b81d9d26f5 | ||
|
|
d865085c7e | ||
|
|
e34b317bbf | ||
|
|
2d1d1e3083 | ||
|
|
e0bcacf79f | ||
|
|
9cac895406 | ||
|
|
ac9b427541 | ||
|
|
af786432c5 | ||
|
|
e30a0155d4 | ||
|
|
555ca59f2b | ||
|
|
ce27920936 | ||
|
|
91a88f3acb | ||
|
|
1e23b82a53 | ||
|
|
45b740c18b | ||
|
|
cb1a79a96a | ||
|
|
9a18f544ac | ||
|
|
b82f75464d | ||
|
|
a7aabd7cc7 | ||
|
|
a07da2fd7a | ||
|
|
4a1cd10495 | ||
|
|
8426d99b0e | ||
|
|
8e478c2341 | ||
|
|
a56036fa87 | ||
|
|
0ab64729e9 | ||
|
|
14a89aa8cd | ||
|
|
7d38060a0d | ||
|
|
0bc8f1669d | ||
|
|
5446eae949 | ||
|
|
90df25ef7e | ||
|
|
e99bb91217 | ||
|
|
d343c03edb | ||
|
|
a0bd088d84 | ||
|
|
21304c11f9 | ||
|
|
519aa479d7 | ||
|
|
33b3a0e477 | ||
|
|
d24bfe29a1 | ||
|
|
a15f9b37eb | ||
|
|
ce2c755d2a | ||
|
|
a323b7826c | ||
|
|
21e2088c1b | ||
|
|
204291f059 | ||
|
|
90d6018509 | ||
|
|
780c1a8f27 | ||
|
|
9b9de3a5e3 | ||
|
|
15b888c9a5 | ||
|
|
454e3a541a | ||
|
|
c32bba7489 | ||
|
|
382aa05ff7 | ||
|
|
893be6f5e3 | ||
|
|
14d3f45009 | ||
|
|
5a0e98d1e5 | ||
|
|
5961c94097 | ||
|
|
68e0f23edc | ||
|
|
99e8e58f2d | ||
|
|
5573365dff | ||
|
|
aeb7148afd | ||
|
|
c67407172d | ||
|
|
092ee24627 | ||
|
|
a25c022af3 | ||
|
|
55e55b34e6 | ||
|
|
1f631ac85b | ||
|
|
f0e77c8a6c | ||
|
|
f3f854dac1 | ||
|
|
4b9dee6bcc | ||
|
|
dc3f52a144 | ||
|
|
f97d3753a1 | ||
|
|
c87840ae14 | ||
|
|
2fa7f2a56a | ||
|
|
6f88fed819 | ||
|
|
4caeefaf00 | ||
|
|
e302ba0e65 | ||
|
|
2dbd69dbf4 | ||
|
|
aeb695c007 | ||
|
|
c693f80b81 | ||
|
|
61fdb16aac | ||
|
|
5ec2a1ed82 | ||
|
|
a49b6761a5 | ||
|
|
80c36d4562 | ||
|
|
30ccf4e52d | ||
|
|
0588d72286 | ||
|
|
d749f5132b | ||
|
|
2341f30ec6 | ||
|
|
89468410d5 | ||
|
|
ebc4dae517 | ||
|
|
662db921e2 | ||
|
|
ab16b3d076 | ||
|
|
1d750e0587 | ||
|
|
336afe4d5f | ||
|
|
13604318ad | ||
|
|
aa82f8b2d2 | ||
|
|
731bc65ec0 | ||
|
|
7ba928116e | ||
|
|
990b5b2dcf | ||
|
|
b45628a172 | ||
|
|
b29cec7697 | ||
|
|
bd62290c23 | ||
|
|
0802e006f2 | ||
|
|
ad42a78469 | ||
|
|
4205234f26 | ||
|
|
b0c220c02e | ||
|
|
cc218b15ba | ||
|
|
e5f881a7e4 | ||
|
|
2cc248c4fd | ||
|
|
a67cf5a358 | ||
|
|
15fa70cd1b | ||
|
|
96c6b08ed7 | ||
|
|
d4fe9daed6 | ||
|
|
aa2846198f | ||
|
|
d132d057a8 | ||
|
|
4f6a7c8621 | ||
|
|
29c2dfd0a9 | ||
|
|
29ccb2e969 | ||
|
|
d0a769cb06 | ||
|
|
e3552f2bcf | ||
|
|
e75ffbf04a | ||
|
|
59714a15e0 | ||
|
|
5d8ec94d7f | ||
|
|
a0de58f471 | ||
|
|
eb18aedccb | ||
|
|
d4a48b12fa | ||
|
|
3871131308 | ||
|
|
8a6704d826 | ||
|
|
556f33422d | ||
|
|
f2fcc163fa | ||
|
|
9d1207c02c | ||
|
|
3b2ebd029c | ||
|
|
0d69f7f3f0 | ||
|
|
2467c98375 | ||
|
|
d6c4fe55db | ||
|
|
04a5976996 | ||
|
|
615a9d031d | ||
|
|
8ea842260b | ||
|
|
415fc233e3 | ||
|
|
06010eaf19 | ||
|
|
506b6263ef | ||
|
|
e2d7569685 | ||
|
|
69b047f4ce | ||
|
|
c4d740115e | ||
|
|
c47d2dac6c | ||
|
|
6644b6099b | ||
|
|
671f16aee0 | ||
|
|
54aff8430c | ||
|
|
1b05792988 | ||
|
|
ce225615c3 | ||
|
|
087530dec4 | ||
|
|
4d31cf83f2 | ||
|
|
1e53a07712 | ||
|
|
1c5067b9a7 | ||
|
|
45b5c606ac | ||
|
|
4ec1a9ab40 | ||
|
|
5fe7be2409 | ||
|
|
9e99b5205c | ||
|
|
278114d559 | ||
|
|
6dbd5c26e6 | ||
|
|
c7c562416c | ||
|
|
653c4e439b | ||
|
|
412684f9dc | ||
|
|
d9a6a75ed2 | ||
|
|
507c150034 | ||
|
|
5fbd9fee0b | ||
|
|
fb692e5f7b | ||
|
|
a2f86ac647 | ||
|
|
95bdfaa8bd | ||
|
|
15f241775a | ||
|
|
8cb3bbd504 | ||
|
|
8abb8647a3 | ||
|
|
ccb1bad612 | ||
|
|
7adb10d29b | ||
|
|
9169046e64 | ||
|
|
b971e406de | ||
|
|
63c5c91cc0 | ||
|
|
134942f56a | ||
|
|
094539ef4a | ||
|
|
65e88694c2 | ||
|
|
49436bdbb7 | ||
|
|
3488fa7c6c | ||
|
|
b70fc8f30c | ||
|
|
a9d3524e1f | ||
|
|
0e32b32fa3 | ||
|
|
e0aaf05f4f | ||
|
|
de00ed15d3 | ||
|
|
6ae4437acb | ||
|
|
6e4a8c47f4 | ||
|
|
c356d034f3 | ||
|
|
6636808e90 | ||
|
|
315f1980ca | ||
|
|
ae7b56cd9a | ||
|
|
0f840483c7 | ||
|
|
479757dc15 | ||
|
|
444786e6d3 | ||
|
|
46294d60cd | ||
|
|
ecee759b80 | ||
|
|
e7e7a03baf | ||
|
|
fdf06ce72f | ||
|
|
25e497bf9c | ||
|
|
dda4f7167b | ||
|
|
4846304541 | ||
|
|
894e007445 | ||
|
|
6d7efcfaeb | ||
|
|
de36cf3db9 | ||
|
|
4d030a8d96 | ||
|
|
2950014535 | ||
|
|
6fb7545fa1 | ||
|
|
638c56caed | ||
|
|
981f686259 | ||
|
|
90fe1dfd2f | ||
|
|
6b77bfc28d | ||
|
|
6e984431dd | ||
|
|
013f4928c8 | ||
|
|
2f162feb0f | ||
|
|
a4ba6e5590 | ||
|
|
f0d6d67af9 | ||
|
|
df3f5a78d5 | ||
|
|
66f1d7ee95 | ||
|
|
55a0451e51 | ||
|
|
7c7105e0f8 | ||
|
|
e414bde6f9 | ||
|
|
3cecf3f39c | ||
|
|
3e8ef9eb22 | ||
|
|
70136a9bf4 | ||
|
|
bc0fb109a9 | ||
|
|
2468672e30 | ||
|
|
8e5c86befc | ||
|
|
5990b86391 | ||
|
|
20a1a65d37 | ||
|
|
818c8da5b8 | ||
|
|
ef6ae61503 | ||
|
|
98f20dee41 | ||
|
|
d9ad3723d5 | ||
|
|
b531695331 | ||
|
|
666fac2ba3 | ||
|
|
b0fc5bcee9 | ||
|
|
0f5032c5a4 | ||
|
|
5c34d66538 | ||
|
|
38b87dea62 | ||
|
|
696a98af5a | ||
|
|
10f68923c6 | ||
|
|
4c9ebd20d7 | ||
|
|
74389370c6 | ||
|
|
156e3a9daa | ||
|
|
5a80cccc70 | ||
|
|
68b17ef731 | ||
|
|
3a4b744d9c | ||
|
|
d209bdcd08 | ||
|
|
391e1f511d | ||
|
|
a746dc64d2 | ||
|
|
cead210e66 | ||
|
|
8fc1c3f413 | ||
|
|
455aa8d9ea | ||
|
|
cb5ebc5c11 | ||
|
|
2bc55aba1e | ||
|
|
a887892eb6 | ||
|
|
54e54db2e2 | ||
|
|
2fc8a29a9c | ||
|
|
77e1f9010c | ||
|
|
0d1c2e5bae | ||
|
|
9d07c3717b | ||
|
|
2d5a219688 | ||
|
|
ddd42b7e94 | ||
|
|
2a41a567e2 | ||
|
|
3c171851a8 | ||
|
|
c38c726eb5 | ||
|
|
b9c016abc1 | ||
|
|
201f92e02c | ||
|
|
f8c4742c2f | ||
|
|
2aafa6901e | ||
|
|
f662850b60 | ||
|
|
7ba0f98e64 | ||
|
|
8ec77614f6 | ||
|
|
dea18ff999 | ||
|
|
2919c496ea | ||
|
|
7dcf5b011a | ||
|
|
2b8c63f303 | ||
|
|
e0d4aa75fc | ||
|
|
5d6e8c008b | ||
|
|
a37436d792 | ||
|
|
fa88f71520 | ||
|
|
ab9e47284a | ||
|
|
43408d3cd6 | ||
|
|
00db8d4549 | ||
|
|
9b3069a88c | ||
|
|
eba85e2367 | ||
|
|
4588a6ff3c | ||
|
|
33bd10549e | ||
|
|
24b35bf9e7 | ||
|
|
35d1c95f7f | ||
|
|
e6109ec765 | ||
|
|
514117a6bb | ||
|
|
d867e1804a | ||
|
|
e9c42c06ef | ||
|
|
21d5abfc14 | ||
|
|
cc51e37ad0 | ||
|
|
4fb594a375 | ||
|
|
7ec7bad2f8 | ||
|
|
fc46f8fc5e | ||
|
|
f8a52cc598 | ||
|
|
cbfdea6857 | ||
|
|
3392f1b778 | ||
|
|
e51abb6631 | ||
|
|
50ec2bed9e | ||
|
|
bc259192b4 | ||
|
|
0cbda84f5b | ||
|
|
160ce18a0e | ||
|
|
6960ee929d | ||
|
|
46cb15df9b | ||
|
|
6e4210d8ce | ||
|
|
160b974fb0 | ||
|
|
b42ba08fc8 | ||
|
|
939bee06cd | ||
|
|
3ddb6d1833 | ||
|
|
260527a90c | ||
|
|
3c28cb1b8f | ||
|
|
54ca4b4e81 | ||
|
|
3d0e81051f | ||
|
|
8c4e759efd | ||
|
|
cfca793a20 | ||
|
|
60834492ae | ||
|
|
7b312a8762 | ||
|
|
e1d73edb10 | ||
|
|
ed9d725392 | ||
|
|
ba66455636 | ||
|
|
aecf07b1d6 | ||
|
|
035ac44354 | ||
|
|
529acfd24f | ||
|
|
d8fa2fc429 | ||
|
|
3a5493bfe8 | ||
|
|
dd935404b2 | ||
|
|
7587d62d02 | ||
|
|
b3d33b02e3 | ||
|
|
b4e367bf4a | ||
|
|
c179f668e5 | ||
|
|
f6d684b5e2 | ||
|
|
84c12dbd7c | ||
|
|
a12cd53567 | ||
|
|
be757d88d9 | ||
|
|
4bf3a8226b | ||
|
|
4ad4e48668 | ||
|
|
c64f98b883 | ||
|
|
f39670c631 | ||
|
|
3ec0c82fab | ||
|
|
641db127be | ||
|
|
18c019b616 | ||
|
|
87033f2c4e | ||
|
|
c996e04aca | ||
|
|
507da65900 | ||
|
|
47727252ff | ||
|
|
ee1254d4f5 | ||
|
|
6a4c7fb975 | ||
|
|
a9ceeeb4b0 | ||
|
|
4023ae4cdf | ||
|
|
d2875f6782 | ||
|
|
101d964a59 | ||
|
|
9b7eac332b | ||
|
|
154244adc6 | ||
|
|
6b0ca8e803 | ||
|
|
c101b29133 | ||
|
|
be7fd63595 | ||
|
|
f9c7176a87 | ||
|
|
42be60c6af | ||
|
|
edb3836696 | ||
|
|
b5565a7081 | ||
|
|
a554f523db | ||
|
|
5e4d92d267 | ||
|
|
e007f367bd | ||
|
|
d4ee8afd59 | ||
|
|
6542de98c2 | ||
|
|
9ff1a9ea65 | ||
|
|
cfb6ab80ce | ||
|
|
d342de02b9 | ||
|
|
529add316c | ||
|
|
6e9182fbc2 | ||
|
|
e38ec77de8 | ||
|
|
beab05851b | ||
|
|
272b58220d | ||
|
|
ba05f29838 | ||
|
|
91a6a47b0e | ||
|
|
0cd7f2cd8d | ||
|
|
c8a0b9d5cb | ||
|
|
52419f8db3 | ||
|
|
dcae46ab14 | ||
|
|
ef4cf4e681 | ||
|
|
7a5cf31060 | ||
|
|
f216c76c56 | ||
|
|
c02da99757 | ||
|
|
f70434b1fb | ||
|
|
15a16e5c05 | ||
|
|
aa0e2a2e70 |
7
.github/workflows/test.yml
vendored
7
.github/workflows/test.yml
vendored
@@ -6,9 +6,12 @@ jobs:
|
||||
tests:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
os: [ubuntu-18.04, macos]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: cachix/install-nix-action@v8
|
||||
- run: nix-build release.nix --arg nix '{ outPath = ./.; revCount = 123; shortRev = "abcdefgh"; }' --arg systems '[ builtins.currentSystem ]' -A installerScript -A perlBindings
|
||||
#- run: nix flake check
|
||||
- run: nix-build -A checks.$(if [[ `uname` = Linux ]]; then echo x86_64-linux; else echo x86_64-darwin; fi)
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -49,9 +49,6 @@ perl/Makefile.config
|
||||
# /src/libstore/
|
||||
*.gen.*
|
||||
|
||||
# /src/libutil/
|
||||
/src/libutil/tests/libutil-tests
|
||||
|
||||
/src/nix/nix
|
||||
|
||||
# /src/nix-env/
|
||||
|
||||
3
Makefile
3
Makefile
@@ -1,8 +1,8 @@
|
||||
makefiles = \
|
||||
mk/precompiled-headers.mk \
|
||||
local.mk \
|
||||
nix-rust/local.mk \
|
||||
src/libutil/local.mk \
|
||||
src/libutil/tests/local.mk \
|
||||
src/libstore/local.mk \
|
||||
src/libfetchers/local.mk \
|
||||
src/libmain/local.mk \
|
||||
@@ -11,6 +11,7 @@ makefiles = \
|
||||
src/resolve-system-dependencies/local.mk \
|
||||
scripts/local.mk \
|
||||
corepkgs/local.mk \
|
||||
misc/bash/local.mk \
|
||||
misc/systemd/local.mk \
|
||||
misc/launchd/local.mk \
|
||||
misc/upstart/local.mk \
|
||||
|
||||
@@ -1,38 +1,36 @@
|
||||
AR = @AR@
|
||||
BDW_GC_LIBS = @BDW_GC_LIBS@
|
||||
BOOST_LDFLAGS = @BOOST_LDFLAGS@
|
||||
BUILD_SHARED_LIBS = @BUILD_SHARED_LIBS@
|
||||
CC = @CC@
|
||||
CFLAGS = @CFLAGS@
|
||||
CXX = @CXX@
|
||||
CXXFLAGS = @CXXFLAGS@
|
||||
EDITLINE_LIBS = @EDITLINE_LIBS@
|
||||
ENABLE_S3 = @ENABLE_S3@
|
||||
GTEST_LIBS = @GTEST_LIBS@
|
||||
HAVE_SECCOMP = @HAVE_SECCOMP@
|
||||
HAVE_SODIUM = @HAVE_SODIUM@
|
||||
LDFLAGS = @LDFLAGS@
|
||||
LIBARCHIVE_LIBS = @LIBARCHIVE_LIBS@
|
||||
LIBBROTLI_LIBS = @LIBBROTLI_LIBS@
|
||||
ENABLE_S3 = @ENABLE_S3@
|
||||
HAVE_SODIUM = @HAVE_SODIUM@
|
||||
HAVE_SECCOMP = @HAVE_SECCOMP@
|
||||
BOOST_LDFLAGS = @BOOST_LDFLAGS@
|
||||
LIBCURL_LIBS = @LIBCURL_LIBS@
|
||||
LIBLZMA_LIBS = @LIBLZMA_LIBS@
|
||||
OPENSSL_LIBS = @OPENSSL_LIBS@
|
||||
PACKAGE_NAME = @PACKAGE_NAME@
|
||||
PACKAGE_VERSION = @PACKAGE_VERSION@
|
||||
SODIUM_LIBS = @SODIUM_LIBS@
|
||||
LIBLZMA_LIBS = @LIBLZMA_LIBS@
|
||||
SQLITE3_LIBS = @SQLITE3_LIBS@
|
||||
LIBBROTLI_LIBS = @LIBBROTLI_LIBS@
|
||||
LIBARCHIVE_LIBS = @LIBARCHIVE_LIBS@
|
||||
EDITLINE_LIBS = @EDITLINE_LIBS@
|
||||
bash = @bash@
|
||||
bindir = @bindir@
|
||||
lsof = @lsof@
|
||||
datadir = @datadir@
|
||||
datarootdir = @datarootdir@
|
||||
doc_generate = @doc_generate@
|
||||
docdir = @docdir@
|
||||
exec_prefix = @exec_prefix@
|
||||
includedir = @includedir@
|
||||
libdir = @libdir@
|
||||
libexecdir = @libexecdir@
|
||||
localstatedir = @localstatedir@
|
||||
lsof = @lsof@
|
||||
mandir = @mandir@
|
||||
pkglibdir = $(libdir)/$(PACKAGE_NAME)
|
||||
prefix = @prefix@
|
||||
@@ -40,5 +38,6 @@ sandbox_shell = @sandbox_shell@
|
||||
storedir = @storedir@
|
||||
sysconfdir = @sysconfdir@
|
||||
system = @system@
|
||||
doc_generate = @doc_generate@
|
||||
xmllint = @xmllint@
|
||||
xsltproc = @xsltproc@
|
||||
|
||||
@@ -123,6 +123,7 @@ AC_PATH_PROG(flex, flex, false)
|
||||
AC_PATH_PROG(bison, bison, false)
|
||||
AC_PATH_PROG(dot, dot)
|
||||
AC_PATH_PROG(lsof, lsof, lsof)
|
||||
NEED_PROG(jq, jq)
|
||||
|
||||
|
||||
AC_SUBST(coreutils, [$(dirname $(type -p cat))])
|
||||
@@ -266,10 +267,6 @@ if test "$gc" = yes; then
|
||||
fi
|
||||
|
||||
|
||||
# Look for gtest.
|
||||
PKG_CHECK_MODULES([GTEST], [gtest_main])
|
||||
|
||||
|
||||
# documentation generation switch
|
||||
AC_ARG_ENABLE(doc-gen, AC_HELP_STRING([--disable-doc-gen],
|
||||
[disable documentation generation]),
|
||||
|
||||
3
default.nix
Normal file
3
default.nix
Normal file
@@ -0,0 +1,3 @@
|
||||
(import (fetchTarball https://github.com/edolstra/flake-compat/archive/master.tar.gz) {
|
||||
src = builtins.fetchGit ./.;
|
||||
}).defaultNix
|
||||
@@ -70,7 +70,7 @@ path just built.</para>
|
||||
|
||||
<screen>
|
||||
$ nix-build ./deterministic.nix -A stable
|
||||
this derivation will be built:
|
||||
these derivations will be built:
|
||||
/nix/store/z98fasz2jqy9gs0xbvdj939p27jwda38-stable.drv
|
||||
building '/nix/store/z98fasz2jqy9gs0xbvdj939p27jwda38-stable.drv'...
|
||||
/nix/store/yyxlzw3vqaas7wfp04g0b1xg51f2czgq-stable
|
||||
@@ -85,7 +85,7 @@ checking outputs of '/nix/store/z98fasz2jqy9gs0xbvdj939p27jwda38-stable.drv'...
|
||||
|
||||
<screen>
|
||||
$ nix-build ./deterministic.nix -A unstable
|
||||
this derivation will be built:
|
||||
these derivations will be built:
|
||||
/nix/store/cgl13lbj1w368r5z8gywipl1ifli7dhk-unstable.drv
|
||||
building '/nix/store/cgl13lbj1w368r5z8gywipl1ifli7dhk-unstable.drv'...
|
||||
/nix/store/krpqk0l9ib0ibi1d2w52z293zw455cap-unstable
|
||||
@@ -193,7 +193,7 @@ repeat = 1
|
||||
An example output of this configuration:
|
||||
<screen>
|
||||
$ nix-build ./test.nix -A unstable
|
||||
this derivation will be built:
|
||||
these derivations will be built:
|
||||
/nix/store/ch6llwpr2h8c3jmnf3f2ghkhx59aa97f-unstable.drv
|
||||
building '/nix/store/ch6llwpr2h8c3jmnf3f2ghkhx59aa97f-unstable.drv' (round 1/2)...
|
||||
building '/nix/store/ch6llwpr2h8c3jmnf3f2ghkhx59aa97f-unstable.drv' (round 2/2)...
|
||||
|
||||
@@ -61,7 +61,7 @@ substituters = https://cache.nixos.org/ s3://example-nix-cache
|
||||
trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= example-nix-cache-1:1/cKDz3QCCOmwcztD2eV6Coggp6rqc9DGjWv7C0G+rM=
|
||||
</programlisting>
|
||||
|
||||
<para>We will restart the Nix daemon in a later step.</para>
|
||||
<para>we will restart the Nix daemon a later step.</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
@@ -122,7 +122,7 @@ post-build-hook = /etc/nix/upload-to-cache.sh
|
||||
|
||||
<screen>
|
||||
$ nix-build -E '(import <nixpkgs> {}).writeText "example" (builtins.toString builtins.currentTime)'
|
||||
this derivation will be built:
|
||||
these derivations will be built:
|
||||
/nix/store/s4pnfbkalzy5qz57qs6yybna8wylkig6-example.drv
|
||||
building '/nix/store/s4pnfbkalzy5qz57qs6yybna8wylkig6-example.drv'...
|
||||
running post-build-hook '/home/grahamc/projects/github.com/NixOS/nix/post-hook.sh'...
|
||||
@@ -139,7 +139,7 @@ $ nix-store --delete /nix/store/ibcyipq5gf91838ldx40mjsp0b8w9n18-example
|
||||
|
||||
<para>Now, copy the path back from the cache:</para>
|
||||
<screen>
|
||||
$ nix-store --realise /nix/store/ibcyipq5gf91838ldx40mjsp0b8w9n18-example
|
||||
$ nix store --realize /nix/store/ibcyipq5gf91838ldx40mjsp0b8w9n18-example
|
||||
copying path '/nix/store/m8bmqwrch6l3h8s0k3d673xpmipcdpsa-example from 's3://example-nix-cache'...
|
||||
warning: you did not specify '--add-root'; the result might be removed by the garbage collector
|
||||
/nix/store/m8bmqwrch6l3h8s0k3d673xpmipcdpsa-example
|
||||
|
||||
@@ -386,7 +386,7 @@ false</literal>.</para>
|
||||
|
||||
<programlisting>
|
||||
builtins.fetchurl {
|
||||
url = "https://example.org/foo-1.2.3.tar.xz";
|
||||
url = https://example.org/foo-1.2.3.tar.xz;
|
||||
sha256 = "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae";
|
||||
}
|
||||
</programlisting>
|
||||
|
||||
@@ -53,7 +53,7 @@ nixpkgs=/home/eelco/Dev/nixpkgs-branch:/etc/nixos</screen>
|
||||
<envar>NIX_PATH</envar> to
|
||||
|
||||
<screen>
|
||||
nixpkgs=https://github.com/NixOS/nixpkgs/archive/nixos-15.09.tar.gz</screen>
|
||||
nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/nixos-15.09.tar.gz</screen>
|
||||
|
||||
tells Nix to download the latest revision in the Nixpkgs/NixOS
|
||||
15.09 channel.</para>
|
||||
|
||||
@@ -516,7 +516,7 @@ source:
|
||||
$ nix-env -f '<nixpkgs>' -iA hello --dry-run
|
||||
(dry run; not doing anything)
|
||||
installing ‘hello-2.10’
|
||||
this path will be fetched (0.04 MiB download, 0.19 MiB unpacked):
|
||||
these paths will be fetched (0.04 MiB download, 0.19 MiB unpacked):
|
||||
/nix/store/wkhdf9jinag5750mqlax6z2zbwhqb76n-hello-2.10
|
||||
<replaceable>...</replaceable></screen>
|
||||
|
||||
@@ -526,10 +526,13 @@ this path will be fetched (0.04 MiB download, 0.19 MiB unpacked):
|
||||
14.12 channel:
|
||||
|
||||
<screen>
|
||||
$ nix-env -f https://github.com/NixOS/nixpkgs/archive/nixos-14.12.tar.gz -iA firefox
|
||||
$ nix-env -f https://github.com/NixOS/nixpkgs-channels/archive/nixos-14.12.tar.gz -iA firefox
|
||||
</screen>
|
||||
|
||||
</para>
|
||||
(The GitHub repository <literal>nixpkgs-channels</literal> is updated
|
||||
automatically from the main <literal>nixpkgs</literal> repository
|
||||
after certain tests have succeeded and binaries have been built and
|
||||
uploaded to the binary cache at <uri>cache.nixos.org</uri>.)</para>
|
||||
|
||||
</refsection>
|
||||
|
||||
|
||||
@@ -258,7 +258,7 @@ path. You can override it by passing <option>-I</option> or setting
|
||||
containing the Pan package from a specific revision of Nixpkgs:
|
||||
|
||||
<screen>
|
||||
$ nix-shell -p pan -I nixpkgs=https://github.com/NixOS/nixpkgs/archive/8a3eea054838b55aca962c3fbde9c83c102b8bf2.tar.gz
|
||||
$ nix-shell -p pan -I nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/8a3eea054838b55aca962c3fbde9c83c102b8bf2.tar.gz
|
||||
|
||||
[nix-shell:~]$ pan --version
|
||||
Pan 0.139
|
||||
@@ -352,7 +352,7 @@ following Haskell script uses a specific branch of Nixpkgs/NixOS (the
|
||||
<programlisting><![CDATA[
|
||||
#! /usr/bin/env nix-shell
|
||||
#! nix-shell -i runghc -p "haskellPackages.ghcWithPackages (ps: [ps.HTTP ps.tagsoup])"
|
||||
#! nix-shell -I nixpkgs=https://github.com/NixOS/nixpkgs/archive/nixos-18.03.tar.gz
|
||||
#! nix-shell -I nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/nixos-18.03.tar.gz
|
||||
|
||||
import Network.HTTP
|
||||
import Text.HTML.TagSoup
|
||||
@@ -370,7 +370,7 @@ If you want to be even more precise, you can specify a specific
|
||||
revision of Nixpkgs:
|
||||
|
||||
<programlisting>
|
||||
#! nix-shell -I nixpkgs=https://github.com/NixOS/nixpkgs/archive/0672315759b3e15e2121365f067c1c8c56bb4722.tar.gz
|
||||
#! nix-shell -I nixpkgs=https://github.com/NixOS/nixpkgs-channels/archive/0672315759b3e15e2121365f067c1c8c56bb4722.tar.gz
|
||||
</programlisting>
|
||||
|
||||
</para>
|
||||
|
||||
@@ -936,7 +936,7 @@ $ nix-store --add ./foo.c
|
||||
|
||||
<para>The operation <option>--add-fixed</option> adds the specified paths to
|
||||
the Nix store. Unlike <option>--add</option> paths are registered using the
|
||||
specified hashing algorithm, resulting in the same output path as a fixed-output
|
||||
specified hashing algorithm, resulting in the same output path as a fixed output
|
||||
derivation. This can be used for sources that are not available from a public
|
||||
url or broke since the download expression was written.
|
||||
</para>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<nop xmlns="http://docbook.org/ns/docbook">
|
||||
|
||||
|
||||
<arg><option>--help</option></arg>
|
||||
<arg><option>--version</option></arg>
|
||||
<arg rep='repeat'>
|
||||
@@ -11,10 +11,6 @@
|
||||
<arg>
|
||||
<arg choice='plain'><option>--quiet</option></arg>
|
||||
</arg>
|
||||
<arg>
|
||||
<option>--log-format</option>
|
||||
<replaceable>format</replaceable>
|
||||
</arg>
|
||||
<arg>
|
||||
<group choice='plain'>
|
||||
<arg choice='plain'><option>--no-build-output</option></arg>
|
||||
|
||||
@@ -92,37 +92,6 @@
|
||||
</varlistentry>
|
||||
|
||||
|
||||
<varlistentry xml:id="opt-log-format"><term><option>--log-format</option> <replaceable>format</replaceable></term>
|
||||
|
||||
<listitem>
|
||||
|
||||
<para>This option can be used to change the output of the log format, with
|
||||
<replaceable>format</replaceable> being one of:</para>
|
||||
|
||||
<variablelist>
|
||||
|
||||
<varlistentry><term>raw</term>
|
||||
<listitem><para>This is the raw format, as outputted by nix-build.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry><term>internal-json</term>
|
||||
<listitem><para>Outputs the logs in a structured manner. NOTE: the json schema is not guarantees to be stable between releases.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry><term>bar</term>
|
||||
<listitem><para>Only display a progress bar during the builds.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry><term>bar-with-logs</term>
|
||||
<listitem><para>Display the raw logs, with the progress bar at the bottom.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
</variablelist>
|
||||
|
||||
</listitem>
|
||||
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry><term><option>--no-build-output</option> / <option>-Q</option></term>
|
||||
|
||||
<listitem><para>By default, output written by builders to standard
|
||||
|
||||
@@ -178,7 +178,7 @@ impureEnvVars = [ "http_proxy" "https_proxy" <replaceable>...</replaceable> ];
|
||||
|
||||
<programlisting>
|
||||
fetchurl {
|
||||
url = "http://ftp.gnu.org/pub/gnu/hello/hello-2.1.1.tar.gz";
|
||||
url = http://ftp.gnu.org/pub/gnu/hello/hello-2.1.1.tar.gz;
|
||||
sha256 = "1md7jsfd8pa45z73bz1kszpp01yw6x5ljkjk2hx7wl800any6465";
|
||||
}
|
||||
</programlisting>
|
||||
@@ -189,7 +189,7 @@ fetchurl {
|
||||
|
||||
<programlisting>
|
||||
fetchurl {
|
||||
url = "ftp://ftp.nluug.nl/pub/gnu/hello/hello-2.1.1.tar.gz";
|
||||
url = ftp://ftp.nluug.nl/pub/gnu/hello/hello-2.1.1.tar.gz;
|
||||
sha256 = "1md7jsfd8pa45z73bz1kszpp01yw6x5ljkjk2hx7wl800any6465";
|
||||
}
|
||||
</programlisting>
|
||||
|
||||
@@ -324,7 +324,7 @@ if builtins ? getEnv then builtins.getEnv "PATH" else ""</programlisting>
|
||||
particular version of Nixpkgs, e.g.
|
||||
|
||||
<programlisting>
|
||||
with import (fetchTarball https://github.com/NixOS/nixpkgs/archive/nixos-14.12.tar.gz) {};
|
||||
with import (fetchTarball https://github.com/NixOS/nixpkgs-channels/archive/nixos-14.12.tar.gz) {};
|
||||
|
||||
stdenv.mkDerivation { … }
|
||||
</programlisting>
|
||||
@@ -349,7 +349,7 @@ stdenv.mkDerivation { … }
|
||||
|
||||
<programlisting>
|
||||
with import (fetchTarball {
|
||||
url = "https://github.com/NixOS/nixpkgs/archive/nixos-14.12.tar.gz";
|
||||
url = https://github.com/NixOS/nixpkgs-channels/archive/nixos-14.12.tar.gz;
|
||||
sha256 = "1jppksrfvbk5ypiqdz4cddxdl8z6zyzdb2srq8fcffr327ld5jj2";
|
||||
}) {};
|
||||
|
||||
@@ -1406,7 +1406,7 @@ stdenv.mkDerivation {
|
||||
";
|
||||
|
||||
src = fetchurl {
|
||||
url = "http://ftp.nluug.nl/pub/gnu/hello/hello-2.1.1.tar.gz";
|
||||
url = http://ftp.nluug.nl/pub/gnu/hello/hello-2.1.1.tar.gz;
|
||||
sha256 = "1md7jsfd8pa45z73bz1kszpp01yw6x5ljkjk2hx7wl800any6465";
|
||||
};
|
||||
inherit perl;
|
||||
|
||||
@@ -15,7 +15,7 @@ stdenv.mkDerivation { <co xml:id='ex-hello-nix-co-2' />
|
||||
name = "hello-2.1.1"; <co xml:id='ex-hello-nix-co-3' />
|
||||
builder = ./builder.sh; <co xml:id='ex-hello-nix-co-4' />
|
||||
src = fetchurl { <co xml:id='ex-hello-nix-co-5' />
|
||||
url = "ftp://ftp.nluug.nl/pub/gnu/hello/hello-2.1.1.tar.gz";
|
||||
url = ftp://ftp.nluug.nl/pub/gnu/hello/hello-2.1.1.tar.gz;
|
||||
sha256 = "1md7jsfd8pa45z73bz1kszpp01yw6x5ljkjk2hx7wl800any6465";
|
||||
};
|
||||
inherit perl; <co xml:id='ex-hello-nix-co-6' />
|
||||
|
||||
@@ -73,4 +73,12 @@ waiting for lock on `/nix/store/0h5b7hp8d4hqfrw8igvx97x1xawrjnac-hello-2.1.1x'</
|
||||
So it is always safe to run multiple instances of Nix in parallel
|
||||
(which isn’t the case with, say, <command>make</command>).</para>
|
||||
|
||||
<para>If you have a system with multiple CPUs, you may want to have
|
||||
Nix build different derivations in parallel (insofar as possible).
|
||||
Just pass the option <link linkend='opt-max-jobs'><option>-j
|
||||
<replaceable>N</replaceable></option></link>, where
|
||||
<replaceable>N</replaceable> is the maximum number of jobs to be run
|
||||
in parallel, or set. Typically this should be the number of
|
||||
CPUs.</para>
|
||||
|
||||
</section>
|
||||
|
||||
@@ -39,7 +39,7 @@ bundle.</para>
|
||||
<step><para>Set the environment variable and install Nix</para>
|
||||
<screen>
|
||||
$ export NIX_SSL_CERT_FILE=/etc/ssl/my-certificate-bundle.crt
|
||||
$ sh <(curl -L https://nixos.org/nix/install)
|
||||
$ sh <(curl https://nixos.org/nix/install)
|
||||
</screen></step>
|
||||
|
||||
<step><para>In the shell profile and rc files (for example,
|
||||
|
||||
@@ -6,30 +6,16 @@
|
||||
|
||||
<title>Installing a Binary Distribution</title>
|
||||
|
||||
<para>
|
||||
If you are using Linux or macOS versions up to 10.14 (Mojave), the
|
||||
easiest way to install Nix is to run the following command:
|
||||
</para>
|
||||
<para>If you are using Linux or macOS, the easiest way to install Nix
|
||||
is to run the following command:
|
||||
|
||||
<screen>
|
||||
$ sh <(curl -L https://nixos.org/nix/install)
|
||||
$ sh <(curl https://nixos.org/nix/install)
|
||||
</screen>
|
||||
|
||||
<para>
|
||||
If you're using macOS 10.15 (Catalina) or newer, consult
|
||||
<link linkend="sect-macos-installation">the macOS installation instructions</link>
|
||||
before installing.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
As of Nix 2.1.0, the Nix installer will always default to creating a
|
||||
single-user installation, however opting in to the multi-user
|
||||
installation is highly recommended.
|
||||
<!-- TODO: this explains *neither* why the default version is
|
||||
single-user, nor why we'd recommend multi-user over the default.
|
||||
True prospective users don't have much basis for evaluating this.
|
||||
What's it to me? Who should pick which? Why? What if I pick wrong?
|
||||
-->
|
||||
As of Nix 2.1.0, the Nix installer will always default to creating a
|
||||
single-user installation, however opting in to the multi-user
|
||||
installation is highly recommended.
|
||||
</para>
|
||||
|
||||
<section xml:id="sect-single-user-installation">
|
||||
@@ -39,7 +25,7 @@
|
||||
To explicitly select a single-user installation on your system:
|
||||
|
||||
<screen>
|
||||
sh <(curl -L https://nixos.org/nix/install) --no-daemon
|
||||
sh <(curl https://nixos.org/nix/install) --no-daemon
|
||||
</screen>
|
||||
</para>
|
||||
|
||||
@@ -50,7 +36,7 @@ run this under your usual user account, <emphasis>not</emphasis> as
|
||||
root. The script will invoke <command>sudo</command> to create
|
||||
<filename>/nix</filename> if it doesn’t already exist. If you don’t
|
||||
have <command>sudo</command>, you should manually create
|
||||
<filename>/nix</filename> first as root, e.g.:
|
||||
<command>/nix</command> first as root, e.g.:
|
||||
|
||||
<screen>
|
||||
$ mkdir /nix
|
||||
@@ -61,7 +47,7 @@ The install script will modify the first writable file from amongst
|
||||
<filename>.bash_profile</filename>, <filename>.bash_login</filename>
|
||||
and <filename>.profile</filename> to source
|
||||
<filename>~/.nix-profile/etc/profile.d/nix.sh</filename>. You can set
|
||||
the <envar>NIX_INSTALLER_NO_MODIFY_PROFILE</envar> environment
|
||||
the <command>NIX_INSTALLER_NO_MODIFY_PROFILE</command> environment
|
||||
variable before executing the install script to disable this
|
||||
behaviour.
|
||||
</para>
|
||||
@@ -95,9 +81,11 @@ $ rm -rf /nix
|
||||
<para>
|
||||
You can instruct the installer to perform a multi-user
|
||||
installation on your system:
|
||||
</para>
|
||||
|
||||
<screen>sh <(curl -L https://nixos.org/nix/install) --daemon</screen>
|
||||
<screen>
|
||||
sh <(curl https://nixos.org/nix/install) --daemon
|
||||
</screen>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The multi-user installation of Nix will create build users between
|
||||
@@ -148,273 +136,6 @@ sudo rm /Library/LaunchDaemons/org.nixos.nix-daemon.plist
|
||||
|
||||
</section>
|
||||
|
||||
<section xml:id="sect-macos-installation">
|
||||
<title>macOS Installation</title>
|
||||
|
||||
<para>
|
||||
Starting with macOS 10.15 (Catalina), the root filesystem is read-only.
|
||||
This means <filename>/nix</filename> can no longer live on your system
|
||||
volume, and that you'll need a workaround to install Nix.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The recommended approach, which creates an unencrypted APFS volume
|
||||
for your Nix store and a "synthetic" empty directory to mount it
|
||||
over at <filename>/nix</filename>, is least likely to impair Nix
|
||||
or your system.
|
||||
</para>
|
||||
|
||||
<note><para>
|
||||
With all separate-volume approaches, it's possible something on
|
||||
your system (particularly daemons/services and restored apps) may
|
||||
need access to your Nix store before the volume is mounted. Adding
|
||||
additional encryption makes this more likely.
|
||||
</para></note>
|
||||
|
||||
<para>
|
||||
If you're using a recent Mac with a
|
||||
<link xlink:href="https://www.apple.com/euro/mac/shared/docs/Apple_T2_Security_Chip_Overview.pdf">T2 chip</link>,
|
||||
your drive will still be encrypted at rest (in which case "unencrypted"
|
||||
is a bit of a misnomer). To use this approach, just install Nix with:
|
||||
</para>
|
||||
|
||||
<screen>$ sh <(curl -L https://nixos.org/nix/install) --darwin-use-unencrypted-nix-store-volume</screen>
|
||||
|
||||
<para>
|
||||
If you don't like the sound of this, you'll want to weigh the
|
||||
other approaches and tradeoffs detailed in this section.
|
||||
</para>
|
||||
|
||||
<note>
|
||||
<title>Eventual solutions?</title>
|
||||
<para>
|
||||
All of the known workarounds have drawbacks, but we hope
|
||||
better solutions will be available in the future. Some that
|
||||
we have our eye on are:
|
||||
</para>
|
||||
<orderedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
A true firmlink would enable the Nix store to live on the
|
||||
primary data volume without the build problems caused by
|
||||
the symlink approach. End users cannot currently
|
||||
create true firmlinks.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
If the Nix store volume shared FileVault encryption
|
||||
with the primary data volume (probably by using the same
|
||||
volume group and role), FileVault encryption could be
|
||||
easily supported by the installer without requiring
|
||||
manual setup by each user.
|
||||
</para>
|
||||
</listitem>
|
||||
</orderedlist>
|
||||
</note>
|
||||
|
||||
<section xml:id="sect-macos-installation-change-store-prefix">
|
||||
<title>Change the Nix store path prefix</title>
|
||||
<para>
|
||||
Changing the default prefix for the Nix store is a simple
|
||||
approach which enables you to leave it on your root volume,
|
||||
where it can take full advantage of FileVault encryption if
|
||||
enabled. Unfortunately, this approach also opts your device out
|
||||
of some benefits that are enabled by using the same prefix
|
||||
across systems:
|
||||
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
Your system won't be able to take advantage of the binary
|
||||
cache (unless someone is able to stand up and support
|
||||
duplicate caching infrastructure), which means you'll
|
||||
spend more time waiting for builds.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
It's harder to build and deploy packages to Linux systems.
|
||||
</para>
|
||||
</listitem>
|
||||
<!-- TODO: may be more here -->
|
||||
</itemizedlist>
|
||||
|
||||
<!-- TODO: Yes, but how?! -->
|
||||
|
||||
It would also possible (and often requested) to just apply this
|
||||
change ecosystem-wide, but it's an intrusive process that has
|
||||
side effects we want to avoid for now.
|
||||
<!-- magnificent hand-wavy gesture -->
|
||||
</para>
|
||||
<para>
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section xml:id="sect-macos-installation-encrypted-volume">
|
||||
<title>Use a separate encrypted volume</title>
|
||||
<para>
|
||||
If you like, you can also add encryption to the recommended
|
||||
approach taken by the installer. You can do this by pre-creating
|
||||
an encrypted volume before you run the installer--or you can
|
||||
run the installer and encrypt the volume it creates later.
|
||||
<!-- TODO: see later note about whether this needs both add-encryption and from-scratch directions -->
|
||||
</para>
|
||||
<para>
|
||||
In either case, adding encryption to a second volume isn't quite
|
||||
as simple as enabling FileVault for your boot volume. Before you
|
||||
dive in, there are a few things to weigh:
|
||||
</para>
|
||||
<orderedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
The additional volume won't be encrypted with your existing
|
||||
FileVault key, so you'll need another mechanism to decrypt
|
||||
the volume.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
You can store the password in Keychain to automatically
|
||||
decrypt the volume on boot--but it'll have to wait on Keychain
|
||||
and may not mount before your GUI apps restore. If any of
|
||||
your launchd agents or apps depend on Nix-installed software
|
||||
(for example, if you use a Nix-installed login shell), the
|
||||
restore may fail or break.
|
||||
</para>
|
||||
<para>
|
||||
On a case-by-case basis, you may be able to work around this
|
||||
problem by using <command>wait4path</command> to block
|
||||
execution until your executable is available.
|
||||
</para>
|
||||
<para>
|
||||
It's also possible to decrypt and mount the volume earlier
|
||||
with a login hook--but this mechanism appears to be
|
||||
deprecated and its future is unclear.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
You can hard-code the password in the clear, so that your
|
||||
store volume can be decrypted before Keychain is available.
|
||||
</para>
|
||||
</listitem>
|
||||
</orderedlist>
|
||||
<para>
|
||||
If you are comfortable navigating these tradeoffs, you can encrypt the volume with
|
||||
something along the lines of:
|
||||
<!-- TODO:
|
||||
I don't know if this also needs from-scratch instructions?
|
||||
can we just recommend use-the-installer-and-then-encrypt?
|
||||
-->
|
||||
</para>
|
||||
<!--
|
||||
TODO: it looks like this option can be encryptVolume|encrypt|enableFileVault
|
||||
|
||||
It may be more clear to use encryptVolume, here? FileVault seems
|
||||
heavily associated with the boot-volume behavior; I worry
|
||||
a little that it can mislead here, especially as it gets
|
||||
copied around minus doc context...?
|
||||
-->
|
||||
<screen>alice$ diskutil apfs enableFileVault /nix -user disk</screen>
|
||||
|
||||
<!-- TODO: and then go into detail on the mount/decrypt approaches? -->
|
||||
</section>
|
||||
|
||||
<section xml:id="sect-macos-installation-symlink">
|
||||
<!--
|
||||
Maybe a good razor is: if we'd hate having to support someone who
|
||||
installed Nix this way, it shouldn't even be detailed?
|
||||
-->
|
||||
<title>Symlink the Nix store to a custom location</title>
|
||||
<para>
|
||||
Another simple approach is using <filename>/etc/synthetic.conf</filename>
|
||||
to symlink the Nix store to the data volume. This option also
|
||||
enables your store to share any configured FileVault encryption.
|
||||
Unfortunately, builds that resolve the symlink may leak the
|
||||
canonical path or even fail.
|
||||
</para>
|
||||
<para>
|
||||
Because of these downsides, we can't recommend this approach.
|
||||
</para>
|
||||
<!-- Leaving out instructions for this one. -->
|
||||
</section>
|
||||
|
||||
<section xml:id="sect-macos-installation-recommended-notes">
|
||||
<title>Notes on the recommended approach</title>
|
||||
<para>
|
||||
This section goes into a little more detail on the recommended
|
||||
approach. You don't need to understand it to run the installer,
|
||||
but it can serve as a helpful reference if you run into trouble.
|
||||
</para>
|
||||
<orderedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
In order to compose user-writable locations into the new
|
||||
read-only system root, Apple introduced a new concept called
|
||||
<literal>firmlinks</literal>, which it describes as a
|
||||
"bi-directional wormhole" between two filesystems. You can
|
||||
see the current firmlinks in <filename>/usr/share/firmlinks</filename>.
|
||||
Unfortunately, firmlinks aren't (currently?) user-configurable.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
For special cases like NFS mount points or package manager roots,
|
||||
<link xlink:href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man5/synthetic.conf.5.html">synthetic.conf(5)</link>
|
||||
supports limited user-controlled file-creation (of symlinks,
|
||||
and synthetic empty directories) at <filename>/</filename>.
|
||||
To create a synthetic empty directory for mounting at <filename>/nix</filename>,
|
||||
add the following line to <filename>/etc/synthetic.conf</filename>
|
||||
(create it if necessary):
|
||||
</para>
|
||||
|
||||
<screen>nix</screen>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
This configuration is applied at boot time, but you can use
|
||||
<command>apfs.util</command> to trigger creation (not deletion)
|
||||
of new entries without a reboot:
|
||||
</para>
|
||||
|
||||
<screen>alice$ /System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util -B</screen>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Create the new APFS volume with diskutil:
|
||||
</para>
|
||||
|
||||
<screen>alice$ sudo diskutil apfs addVolume diskX APFS 'Nix Store' -mountpoint /nix</screen>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
Using <command>vifs</command>, add the new mount to
|
||||
<filename>/etc/fstab</filename>. If it doesn't already have
|
||||
other entries, it should look something like:
|
||||
</para>
|
||||
|
||||
<screen>
|
||||
#
|
||||
# Warning - this file should only be modified with vifs(8)
|
||||
#
|
||||
# Failure to do so is unsupported and may be destructive.
|
||||
#
|
||||
LABEL=Nix\040Store /nix apfs rw,nobrowse
|
||||
</screen>
|
||||
|
||||
<para>
|
||||
The nobrowse setting will keep Spotlight from indexing this
|
||||
volume, and keep it from showing up on your desktop.
|
||||
</para>
|
||||
</listitem>
|
||||
</orderedlist>
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
||||
<section xml:id="sect-nix-install-pinned-version-url">
|
||||
<title>Installing a pinned Nix version from a URL</title>
|
||||
|
||||
@@ -429,7 +150,7 @@ LABEL=Nix\040Store /nix apfs rw,nobrowse
|
||||
NixOS.org installation script:
|
||||
|
||||
<screen>
|
||||
sh <(curl -L https://nixos.org/nix/install)
|
||||
sh <(curl https://nixos.org/nix/install)
|
||||
</screen>
|
||||
</para>
|
||||
|
||||
|
||||
@@ -17,11 +17,6 @@
|
||||
|
||||
<para>
|
||||
Single-user installations of Nix should run this:
|
||||
<command>nix-channel --update; nix-env -iA nixpkgs.nix nixpkgs.cacert</command>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Multi-user Nix users on Linux should run this with sudo:
|
||||
<command>nix-channel --update; nix-env -iA nixpkgs.nix nixpkgs.cacert; systemctl daemon-reload; systemctl restart nix-daemon</command>
|
||||
<command>nix-channel --update; nix-env -iA nixpkgs.nix</command>
|
||||
</para>
|
||||
</chapter>
|
||||
|
||||
@@ -15,7 +15,7 @@ to subsequent chapters.</para>
|
||||
<step><para>Install single-user Nix by running the following:
|
||||
|
||||
<screen>
|
||||
$ bash <(curl -L https://nixos.org/nix/install)
|
||||
$ bash <(curl https://nixos.org/nix/install)
|
||||
</screen>
|
||||
|
||||
This will install Nix in <filename>/nix</filename>. The install script
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<para>NOTE: the hashing scheme in Nix 0.8 changed (as detailed below).
|
||||
As a result, <command>nix-pull</command> manifests and channels built
|
||||
for Nix 0.7 and below will not work anymore. However, the Nix
|
||||
for Nix 0.7 and below will now work anymore. However, the Nix
|
||||
expression language has not changed, so you can still build from
|
||||
source. Also, existing user environments continue to work. Nix 0.8
|
||||
will automatically upgrade the database schema of previous
|
||||
|
||||
28
flake.lock
generated
Normal file
28
flake.lock
generated
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"info": {
|
||||
"lastModified": 1585405475,
|
||||
"narHash": "sha256-bESW0n4KgPmZ0luxvwJ+UyATrC6iIltVCsGdLiphVeE="
|
||||
},
|
||||
"locked": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b88ff468e9850410070d4e0ccd68c7011f15b2be",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-20.03-small",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 5
|
||||
}
|
||||
510
flake.nix
Normal file
510
flake.nix
Normal file
@@ -0,0 +1,510 @@
|
||||
{
|
||||
description = "The purely functional package manager";
|
||||
|
||||
edition = 201909; # FIXME: remove
|
||||
|
||||
inputs.nixpkgs.url = "nixpkgs/nixos-20.03-small";
|
||||
|
||||
outputs = { self, nixpkgs }:
|
||||
|
||||
let
|
||||
|
||||
version = builtins.readFile ./.version + versionSuffix;
|
||||
versionSuffix =
|
||||
if officialRelease
|
||||
then ""
|
||||
else "pre${builtins.substring 0 8 (self.lastModifiedDate or self.lastModified)}_${self.shortRev or "dirty"}";
|
||||
|
||||
officialRelease = false;
|
||||
|
||||
systems = [ "x86_64-linux" "i686-linux" "x86_64-darwin" "aarch64-linux" ];
|
||||
|
||||
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system);
|
||||
|
||||
# Memoize nixpkgs for different platforms for efficiency.
|
||||
nixpkgsFor = forAllSystems (system:
|
||||
import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [ self.overlay ];
|
||||
}
|
||||
);
|
||||
|
||||
commonDeps = pkgs: with pkgs; rec {
|
||||
# Use "busybox-sandbox-shell" if present,
|
||||
# if not (legacy) fallback and hope it's sufficient.
|
||||
sh = pkgs.busybox-sandbox-shell or (busybox.override {
|
||||
useMusl = true;
|
||||
enableStatic = true;
|
||||
enableMinimal = true;
|
||||
extraConfig = ''
|
||||
CONFIG_FEATURE_FANCY_ECHO y
|
||||
CONFIG_FEATURE_SH_MATH y
|
||||
CONFIG_FEATURE_SH_MATH_64 y
|
||||
|
||||
CONFIG_ASH y
|
||||
CONFIG_ASH_OPTIMIZE_FOR_SIZE y
|
||||
|
||||
CONFIG_ASH_ALIAS y
|
||||
CONFIG_ASH_BASH_COMPAT y
|
||||
CONFIG_ASH_CMDCMD y
|
||||
CONFIG_ASH_ECHO y
|
||||
CONFIG_ASH_GETOPTS y
|
||||
CONFIG_ASH_INTERNAL_GLOB y
|
||||
CONFIG_ASH_JOB_CONTROL y
|
||||
CONFIG_ASH_PRINTF y
|
||||
CONFIG_ASH_TEST y
|
||||
'';
|
||||
});
|
||||
|
||||
configureFlags =
|
||||
lib.optionals stdenv.isLinux [
|
||||
"--with-sandbox-shell=${sh}/bin/busybox"
|
||||
];
|
||||
|
||||
buildDeps =
|
||||
[ bison
|
||||
flex
|
||||
libxml2
|
||||
libxslt
|
||||
docbook5
|
||||
docbook_xsl_ns
|
||||
autoconf-archive
|
||||
autoreconfHook
|
||||
|
||||
curl
|
||||
bzip2 xz brotli zlib editline
|
||||
openssl pkgconfig sqlite
|
||||
libarchive
|
||||
boost
|
||||
(if lib.versionAtLeast lib.version "20.03pre"
|
||||
then nlohmann_json
|
||||
else nlohmann_json.override { multipleHeaders = true; })
|
||||
nlohmann_json
|
||||
rustc cargo
|
||||
|
||||
# Tests
|
||||
git
|
||||
mercurial
|
||||
jq
|
||||
]
|
||||
++ lib.optionals stdenv.isLinux [libseccomp utillinuxMinimal]
|
||||
++ lib.optional (stdenv.isLinux || stdenv.isDarwin) libsodium
|
||||
++ lib.optional (stdenv.isLinux || stdenv.isDarwin)
|
||||
(aws-sdk-cpp.override {
|
||||
apis = ["s3" "transfer"];
|
||||
customMemoryManagement = false;
|
||||
});
|
||||
|
||||
propagatedDeps =
|
||||
[ (boehmgc.override { enableLargeConfig = true; })
|
||||
];
|
||||
|
||||
perlDeps =
|
||||
[ perl
|
||||
perlPackages.DBDSQLite
|
||||
];
|
||||
};
|
||||
|
||||
in {
|
||||
|
||||
# A Nixpkgs overlay that overrides the 'nix' and
|
||||
# 'nix.perl-bindings' packages.
|
||||
overlay = final: prev: {
|
||||
|
||||
nix = with final; with commonDeps pkgs; (stdenv.mkDerivation {
|
||||
name = "nix-${version}";
|
||||
|
||||
src = self;
|
||||
|
||||
VERSION_SUFFIX = versionSuffix;
|
||||
|
||||
outputs = [ "out" "dev" "doc" ];
|
||||
|
||||
buildInputs = buildDeps;
|
||||
|
||||
propagatedBuildInputs = propagatedDeps;
|
||||
|
||||
preConfigure =
|
||||
''
|
||||
# Copy libboost_context so we don't get all of Boost in our closure.
|
||||
# https://github.com/NixOS/nixpkgs/issues/45462
|
||||
mkdir -p $out/lib
|
||||
cp -pd ${boost}/lib/{libboost_context*,libboost_thread*,libboost_system*} $out/lib
|
||||
rm -f $out/lib/*.a
|
||||
${lib.optionalString stdenv.isLinux ''
|
||||
chmod u+w $out/lib/*.so.*
|
||||
patchelf --set-rpath $out/lib:${stdenv.cc.cc.lib}/lib $out/lib/libboost_thread.so.*
|
||||
''}
|
||||
|
||||
ln -sfn ${final.nixVendoredCrates}/vendor/ nix-rust/vendor
|
||||
'';
|
||||
|
||||
configureFlags = configureFlags ++
|
||||
[ "--sysconfdir=/etc" ];
|
||||
|
||||
enableParallelBuilding = true;
|
||||
|
||||
makeFlags = "profiledir=$(out)/etc/profile.d";
|
||||
|
||||
doCheck = true;
|
||||
|
||||
installFlags = "sysconfdir=$(out)/etc";
|
||||
|
||||
doInstallCheck = true;
|
||||
installCheckFlags = "sysconfdir=$(out)/etc";
|
||||
|
||||
separateDebugInfo = true;
|
||||
|
||||
preDist = ''
|
||||
mkdir -p $doc/nix-support
|
||||
echo "doc manual $doc/share/doc/nix/manual" >> $doc/nix-support/hydra-build-products
|
||||
'';
|
||||
}) // {
|
||||
|
||||
perl-bindings = with final; stdenv.mkDerivation {
|
||||
name = "nix-perl-${version}";
|
||||
|
||||
src = self;
|
||||
|
||||
buildInputs =
|
||||
[ autoconf-archive
|
||||
autoreconfHook
|
||||
nix
|
||||
curl
|
||||
bzip2
|
||||
xz
|
||||
pkgconfig
|
||||
pkgs.perl
|
||||
boost
|
||||
]
|
||||
++ lib.optional (stdenv.isLinux || stdenv.isDarwin) libsodium;
|
||||
|
||||
configureFlags = ''
|
||||
--with-dbi=${perlPackages.DBI}/${pkgs.perl.libPrefix}
|
||||
--with-dbd-sqlite=${perlPackages.DBDSQLite}/${pkgs.perl.libPrefix}
|
||||
'';
|
||||
|
||||
enableParallelBuilding = true;
|
||||
|
||||
postUnpack = "sourceRoot=$sourceRoot/perl";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
# Create a "vendor" directory that contains the crates listed in
|
||||
# Cargo.lock, and include it in the Nix tarball. This allows Nix
|
||||
# to be built without network access.
|
||||
nixVendoredCrates =
|
||||
let
|
||||
lockFile = builtins.fromTOML (builtins.readFile nix-rust/Cargo.lock);
|
||||
|
||||
files = map (pkg: import <nix/fetchurl.nix> {
|
||||
url = "https://crates.io/api/v1/crates/${pkg.name}/${pkg.version}/download";
|
||||
sha256 = lockFile.metadata."checksum ${pkg.name} ${pkg.version} (registry+https://github.com/rust-lang/crates.io-index)";
|
||||
}) (builtins.filter (pkg: pkg.source or "" == "registry+https://github.com/rust-lang/crates.io-index") lockFile.package);
|
||||
|
||||
in final.runCommand "cargo-vendor-dir" {}
|
||||
''
|
||||
mkdir -p $out/vendor
|
||||
|
||||
cat > $out/vendor/config <<EOF
|
||||
[source.crates-io]
|
||||
replace-with = "vendored-sources"
|
||||
|
||||
[source.vendored-sources]
|
||||
directory = "vendor"
|
||||
EOF
|
||||
|
||||
${toString (builtins.map (file: ''
|
||||
mkdir $out/vendor/tmp
|
||||
tar xvf ${file} -C $out/vendor/tmp
|
||||
dir=$(echo $out/vendor/tmp/*)
|
||||
|
||||
# Add just enough metadata to keep Cargo happy.
|
||||
printf '{"files":{},"package":"${file.outputHash}"}' > "$dir/.cargo-checksum.json"
|
||||
|
||||
# Clean up some cruft from the winapi crates. FIXME: find
|
||||
# a way to remove winapi* from our dependencies.
|
||||
if [[ $dir =~ /winapi ]]; then
|
||||
find $dir -name "*.a" -print0 | xargs -0 rm -f --
|
||||
fi
|
||||
|
||||
mv "$dir" $out/vendor/
|
||||
|
||||
rm -rf $out/vendor/tmp
|
||||
'') files)}
|
||||
'';
|
||||
|
||||
};
|
||||
|
||||
hydraJobs = {
|
||||
|
||||
vendoredCrates =
|
||||
with nixpkgsFor.x86_64-linux;
|
||||
|
||||
runCommand "vendored-crates" {}
|
||||
''
|
||||
mkdir -p $out/nix-support
|
||||
name=nix-vendored-crates-${version}
|
||||
fn=$out/$name.tar.xz
|
||||
tar cvfJ $fn -C ${nixVendoredCrates} vendor \
|
||||
--owner=0 --group=0 --mode=u+rw,uga+r \
|
||||
--transform "s,vendor,$name,"
|
||||
echo "file crates-tarball $fn" >> $out/nix-support/hydra-build-products
|
||||
'';
|
||||
|
||||
# Binary package for various platforms.
|
||||
build = nixpkgs.lib.genAttrs systems (system: nixpkgsFor.${system}.nix);
|
||||
|
||||
# Perl bindings for various platforms.
|
||||
perlBindings = nixpkgs.lib.genAttrs systems (system: nixpkgsFor.${system}.nix.perl-bindings);
|
||||
|
||||
# Binary tarball for various platforms, containing a Nix store
|
||||
# with the closure of 'nix' package, and the second half of
|
||||
# the installation script.
|
||||
binaryTarball = nixpkgs.lib.genAttrs systems (system:
|
||||
|
||||
with nixpkgsFor.${system};
|
||||
|
||||
let
|
||||
installerClosureInfo = closureInfo { rootPaths = [ nix cacert ]; };
|
||||
in
|
||||
|
||||
runCommand "nix-binary-tarball-${version}"
|
||||
{ #nativeBuildInputs = lib.optional (system != "aarch64-linux") shellcheck;
|
||||
meta.description = "Distribution-independent Nix bootstrap binaries for ${system}";
|
||||
}
|
||||
''
|
||||
cp ${installerClosureInfo}/registration $TMPDIR/reginfo
|
||||
substitute ${./scripts/install-nix-from-closure.sh} $TMPDIR/install \
|
||||
--subst-var-by nix ${nix} \
|
||||
--subst-var-by cacert ${cacert}
|
||||
|
||||
substitute ${./scripts/install-darwin-multi-user.sh} $TMPDIR/install-darwin-multi-user.sh \
|
||||
--subst-var-by nix ${nix} \
|
||||
--subst-var-by cacert ${cacert}
|
||||
substitute ${./scripts/install-systemd-multi-user.sh} $TMPDIR/install-systemd-multi-user.sh \
|
||||
--subst-var-by nix ${nix} \
|
||||
--subst-var-by cacert ${cacert}
|
||||
substitute ${./scripts/install-multi-user.sh} $TMPDIR/install-multi-user \
|
||||
--subst-var-by nix ${nix} \
|
||||
--subst-var-by cacert ${cacert}
|
||||
|
||||
if type -p shellcheck; then
|
||||
# SC1090: Don't worry about not being able to find
|
||||
# $nix/etc/profile.d/nix.sh
|
||||
shellcheck --exclude SC1090 $TMPDIR/install
|
||||
shellcheck $TMPDIR/install-darwin-multi-user.sh
|
||||
shellcheck $TMPDIR/install-systemd-multi-user.sh
|
||||
|
||||
# SC1091: Don't panic about not being able to source
|
||||
# /etc/profile
|
||||
# SC2002: Ignore "useless cat" "error", when loading
|
||||
# .reginfo, as the cat is a much cleaner
|
||||
# implementation, even though it is "useless"
|
||||
# SC2116: Allow ROOT_HOME=$(echo ~root) for resolving
|
||||
# root's home directory
|
||||
shellcheck --external-sources \
|
||||
--exclude SC1091,SC2002,SC2116 $TMPDIR/install-multi-user
|
||||
fi
|
||||
|
||||
chmod +x $TMPDIR/install
|
||||
chmod +x $TMPDIR/install-darwin-multi-user.sh
|
||||
chmod +x $TMPDIR/install-systemd-multi-user.sh
|
||||
chmod +x $TMPDIR/install-multi-user
|
||||
dir=nix-${version}-${system}
|
||||
fn=$out/$dir.tar.xz
|
||||
mkdir -p $out/nix-support
|
||||
echo "file binary-dist $fn" >> $out/nix-support/hydra-build-products
|
||||
tar cvfJ $fn \
|
||||
--owner=0 --group=0 --mode=u+rw,uga+r \
|
||||
--absolute-names \
|
||||
--hard-dereference \
|
||||
--transform "s,$TMPDIR/install,$dir/install," \
|
||||
--transform "s,$TMPDIR/reginfo,$dir/.reginfo," \
|
||||
--transform "s,$NIX_STORE,$dir/store,S" \
|
||||
$TMPDIR/install $TMPDIR/install-darwin-multi-user.sh \
|
||||
$TMPDIR/install-systemd-multi-user.sh \
|
||||
$TMPDIR/install-multi-user $TMPDIR/reginfo \
|
||||
$(cat ${installerClosureInfo}/store-paths)
|
||||
'');
|
||||
|
||||
# The first half of the installation script. This is uploaded
|
||||
# to https://nixos.org/nix/install. It downloads the binary
|
||||
# tarball for the user's system and calls the second half of the
|
||||
# installation script.
|
||||
installerScript =
|
||||
with nixpkgsFor.x86_64-linux;
|
||||
runCommand "installer-script"
|
||||
{ buildInputs = [ nix ];
|
||||
}
|
||||
''
|
||||
mkdir -p $out/nix-support
|
||||
|
||||
substitute ${./scripts/install.in} $out/install \
|
||||
${pkgs.lib.concatMapStrings
|
||||
(system: "--replace '@binaryTarball_${system}@' $(nix --experimental-features nix-command hash-file --base16 --type sha256 ${self.hydraJobs.binaryTarball.${system}}/*.tar.xz) ")
|
||||
[ "x86_64-linux" "i686-linux" "x86_64-darwin" "aarch64-linux" ]
|
||||
} \
|
||||
--replace '@nixVersion@' ${version}
|
||||
|
||||
echo "file installer $out/install" >> $out/nix-support/hydra-build-products
|
||||
'';
|
||||
|
||||
# Line coverage analysis.
|
||||
coverage =
|
||||
with nixpkgsFor.x86_64-linux;
|
||||
with commonDeps pkgs;
|
||||
|
||||
releaseTools.coverageAnalysis {
|
||||
name = "nix-coverage-${version}";
|
||||
|
||||
src = self;
|
||||
|
||||
preConfigure =
|
||||
''
|
||||
ln -sfn ${nixVendoredCrates}/vendor/ nix-rust/vendor
|
||||
'';
|
||||
|
||||
enableParallelBuilding = true;
|
||||
|
||||
buildInputs = buildDeps ++ propagatedDeps;
|
||||
|
||||
dontInstall = false;
|
||||
|
||||
doInstallCheck = true;
|
||||
|
||||
lcovFilter = [ "*/boost/*" "*-tab.*" ];
|
||||
|
||||
# We call `dot', and even though we just use it to
|
||||
# syntax-check generated dot files, it still requires some
|
||||
# fonts. So provide those.
|
||||
FONTCONFIG_FILE = texFunctions.fontsConf;
|
||||
|
||||
# To test building without precompiled headers.
|
||||
makeFlagsArray = [ "PRECOMPILE_HEADERS=0" ];
|
||||
};
|
||||
|
||||
# System tests.
|
||||
tests.remoteBuilds = import ./tests/remote-builds.nix {
|
||||
system = "x86_64-linux";
|
||||
inherit nixpkgs;
|
||||
inherit (self) overlay;
|
||||
};
|
||||
|
||||
tests.nix-copy-closure = import ./tests/nix-copy-closure.nix {
|
||||
system = "x86_64-linux";
|
||||
inherit nixpkgs;
|
||||
inherit (self) overlay;
|
||||
};
|
||||
|
||||
tests.githubFlakes = (import ./tests/github-flakes.nix rec {
|
||||
system = "x86_64-linux";
|
||||
inherit nixpkgs;
|
||||
inherit (self) overlay;
|
||||
});
|
||||
|
||||
tests.setuid = nixpkgs.lib.genAttrs
|
||||
["i686-linux" "x86_64-linux"]
|
||||
(system:
|
||||
import ./tests/setuid.nix rec {
|
||||
inherit nixpkgs system;
|
||||
inherit (self) overlay;
|
||||
});
|
||||
|
||||
# Test whether the binary tarball works in an Ubuntu system.
|
||||
tests.binaryTarball =
|
||||
with nixpkgsFor.x86_64-linux;
|
||||
vmTools.runInLinuxImage (runCommand "nix-binary-tarball-test"
|
||||
{ diskImage = vmTools.diskImages.ubuntu1204x86_64;
|
||||
}
|
||||
''
|
||||
set -x
|
||||
useradd -m alice
|
||||
su - alice -c 'tar xf ${self.hydraJobs.binaryTarball.x86_64-linux}/*.tar.*'
|
||||
mkdir /dest-nix
|
||||
mount -o bind /dest-nix /nix # Provide a writable /nix.
|
||||
chown alice /nix
|
||||
su - alice -c '_NIX_INSTALLER_TEST=1 ./nix-*/install'
|
||||
su - alice -c 'nix-store --verify'
|
||||
su - alice -c 'PAGER= nix-store -qR ${self.hydraJobs.build.x86_64-linux}'
|
||||
|
||||
# Check whether 'nix upgrade-nix' works.
|
||||
cat > /tmp/paths.nix <<EOF
|
||||
{
|
||||
x86_64-linux = "${self.hydraJobs.build.x86_64-linux}";
|
||||
}
|
||||
EOF
|
||||
su - alice -c 'nix --experimental-features nix-command upgrade-nix -vvv --nix-store-paths-url file:///tmp/paths.nix'
|
||||
(! [ -L /home/alice/.profile-1-link ])
|
||||
su - alice -c 'PAGER= nix-store -qR ${self.hydraJobs.build.x86_64-linux}'
|
||||
|
||||
mkdir -p $out/nix-support
|
||||
touch $out/nix-support/hydra-build-products
|
||||
umount /nix
|
||||
'');
|
||||
|
||||
/*
|
||||
# Check whether we can still evaluate all of Nixpkgs.
|
||||
tests.evalNixpkgs =
|
||||
import (nixpkgs + "/pkgs/top-level/make-tarball.nix") {
|
||||
# FIXME: fix pkgs/top-level/make-tarball.nix in NixOS to not require a revCount.
|
||||
inherit nixpkgs;
|
||||
pkgs = nixpkgsFor.x86_64-linux;
|
||||
officialRelease = false;
|
||||
};
|
||||
|
||||
# Check whether we can still evaluate NixOS.
|
||||
tests.evalNixOS =
|
||||
with nixpkgsFor.x86_64-linux;
|
||||
runCommand "eval-nixos" { buildInputs = [ nix ]; }
|
||||
''
|
||||
export NIX_STATE_DIR=$TMPDIR
|
||||
|
||||
nix-instantiate ${nixpkgs}/nixos/release-combined.nix -A tested --dry-run \
|
||||
--arg nixpkgs '{ outPath = ${nixpkgs}; revCount = 123; shortRev = "abcdefgh"; }'
|
||||
|
||||
touch $out
|
||||
'';
|
||||
*/
|
||||
|
||||
};
|
||||
|
||||
checks = forAllSystems (system: {
|
||||
binaryTarball = self.hydraJobs.binaryTarball.${system};
|
||||
perlBindings = self.hydraJobs.perlBindings.${system};
|
||||
});
|
||||
|
||||
packages = forAllSystems (system: {
|
||||
inherit (nixpkgsFor.${system}) nix;
|
||||
});
|
||||
|
||||
defaultPackage = forAllSystems (system: self.packages.${system}.nix);
|
||||
|
||||
devShell = forAllSystems (system:
|
||||
with nixpkgsFor.${system};
|
||||
with commonDeps pkgs;
|
||||
|
||||
stdenv.mkDerivation {
|
||||
name = "nix";
|
||||
|
||||
buildInputs = buildDeps ++ propagatedDeps ++ perlDeps ++ [ pkgs.rustfmt ];
|
||||
|
||||
inherit configureFlags;
|
||||
|
||||
enableParallelBuilding = true;
|
||||
|
||||
installFlags = "sysconfdir=$(out)/etc";
|
||||
|
||||
shellHook =
|
||||
''
|
||||
export prefix=$(pwd)/inst
|
||||
configureFlags+=" --prefix=$prefix"
|
||||
PKG_CONFIG_PATH=$prefix/lib/pkgconfig:$PKG_CONFIG_PATH
|
||||
PATH=$prefix/bin:$PATH
|
||||
unset PYTHONPATH
|
||||
'';
|
||||
});
|
||||
|
||||
};
|
||||
}
|
||||
2
local.mk
2
local.mk
@@ -8,7 +8,7 @@ clean-files += Makefile.config
|
||||
|
||||
GLOBAL_CXXFLAGS += -Wno-deprecated-declarations
|
||||
|
||||
$(foreach i, config.h $(call rwildcard, src/lib*, *.hh), \
|
||||
$(foreach i, config.h $(wildcard src/lib*/*.hh), \
|
||||
$(eval $(call install-file-in, $(i), $(includedir)/nix, 0644)))
|
||||
|
||||
$(GCH) $(PCH): src/libutil/util.hh config.h
|
||||
|
||||
@@ -142,12 +142,8 @@ $oldName =~ s/"//g;
|
||||
sub getStorePath {
|
||||
my ($jobName) = @_;
|
||||
my $buildInfo = decode_json(fetch("$evalUrl/job/$jobName", 'application/json'));
|
||||
for my $product (values %{$buildInfo->{buildproducts}}) {
|
||||
next unless $product->{type} eq "nix-build";
|
||||
next if $product->{path} =~ /[a-z]+$/;
|
||||
return $product->{path};
|
||||
}
|
||||
die;
|
||||
die unless $buildInfo->{buildproducts}->{1}->{type} eq "nix-build";
|
||||
return $buildInfo->{buildproducts}->{1}->{path};
|
||||
}
|
||||
|
||||
write_file("$nixpkgsDir/nixos/modules/installer/tools/nix-fallback-paths.nix",
|
||||
|
||||
19
misc/bash/completion.sh
Normal file
19
misc/bash/completion.sh
Normal file
@@ -0,0 +1,19 @@
|
||||
function _complete_nix {
|
||||
local -a words
|
||||
local cword cur
|
||||
_get_comp_words_by_ref -n ':=&' words cword cur
|
||||
local have_type
|
||||
while IFS= read -r line; do
|
||||
if [[ -z $have_type ]]; then
|
||||
have_type=1
|
||||
if [[ $line = filenames ]]; then
|
||||
compopt -o filenames
|
||||
fi
|
||||
else
|
||||
COMPREPLY+=("$line")
|
||||
fi
|
||||
done < <(NIX_GET_COMPLETIONS=$cword "${words[@]}")
|
||||
__ltrim_colon_completions "$cur"
|
||||
}
|
||||
|
||||
complete -F _complete_nix nix
|
||||
1
misc/bash/local.mk
Normal file
1
misc/bash/local.mk
Normal file
@@ -0,0 +1 @@
|
||||
$(eval $(call install-file-as, $(d)/completion.sh, $(datarootdir)/bash-completion/completions/_nix3, 0644))
|
||||
@@ -125,8 +125,7 @@ define build-library
|
||||
$(1)_PATH := $$(_d)/$$($(1)_NAME).a
|
||||
|
||||
$$($(1)_PATH): $$($(1)_OBJS) | $$(_d)/
|
||||
$(trace-ld) $(LD) -Ur -o $$(_d)/$$($(1)_NAME).o $$?
|
||||
$(trace-ar) $(AR) crs $$@ $$(_d)/$$($(1)_NAME).o
|
||||
$(trace-ar) $(AR) crs $$@ $$?
|
||||
|
||||
$(1)_LDFLAGS_USE += $$($(1)_PATH) $$($(1)_LDFLAGS)
|
||||
|
||||
|
||||
@@ -35,28 +35,24 @@ define build-program
|
||||
$$(trace-ld) $(CXX) -o $$@ $$(LDFLAGS) $$(GLOBAL_LDFLAGS) $$($(1)_OBJS) $$($(1)_LDFLAGS) $$(foreach lib, $$($(1)_LIBS), $$($$(lib)_LDFLAGS_USE))
|
||||
|
||||
$(1)_INSTALL_DIR ?= $$(bindir)
|
||||
$(1)_INSTALL_PATH := $$($(1)_INSTALL_DIR)/$(1)
|
||||
|
||||
ifdef $(1)_INSTALL_DIR
|
||||
$$(eval $$(call create-dir, $$($(1)_INSTALL_DIR)))
|
||||
|
||||
$(1)_INSTALL_PATH := $$($(1)_INSTALL_DIR)/$(1)
|
||||
install: $(DESTDIR)$$($(1)_INSTALL_PATH)
|
||||
|
||||
$$(eval $$(call create-dir, $$($(1)_INSTALL_DIR)))
|
||||
ifeq ($(BUILD_SHARED_LIBS), 1)
|
||||
|
||||
install: $(DESTDIR)$$($(1)_INSTALL_PATH)
|
||||
_libs_final := $$(foreach lib, $$($(1)_LIBS), $$($$(lib)_INSTALL_PATH))
|
||||
|
||||
ifeq ($(BUILD_SHARED_LIBS), 1)
|
||||
|
||||
_libs_final := $$(foreach lib, $$($(1)_LIBS), $$($$(lib)_INSTALL_PATH))
|
||||
|
||||
$(DESTDIR)$$($(1)_INSTALL_PATH): $$($(1)_OBJS) $$(_libs_final) | $(DESTDIR)$$($(1)_INSTALL_DIR)/
|
||||
$(DESTDIR)$$($(1)_INSTALL_PATH): $$($(1)_OBJS) $$(_libs_final) | $(DESTDIR)$$($(1)_INSTALL_DIR)/
|
||||
$$(trace-ld) $(CXX) -o $$@ $$(LDFLAGS) $$(GLOBAL_LDFLAGS) $$($(1)_OBJS) $$($(1)_LDFLAGS) $$(foreach lib, $$($(1)_LIBS), $$($$(lib)_LDFLAGS_USE_INSTALLED))
|
||||
|
||||
else
|
||||
else
|
||||
|
||||
$(DESTDIR)$$($(1)_INSTALL_PATH): $$($(1)_PATH) | $(DESTDIR)$$($(1)_INSTALL_DIR)/
|
||||
$(DESTDIR)$$($(1)_INSTALL_PATH): $$($(1)_PATH) | $(DESTDIR)$$($(1)_INSTALL_DIR)/
|
||||
install -t $(DESTDIR)$$($(1)_INSTALL_DIR) $$<
|
||||
|
||||
endif
|
||||
endif
|
||||
|
||||
# Propagate CFLAGS and CXXFLAGS to the individual object files.
|
||||
@@ -80,10 +76,4 @@ define build-program
|
||||
programs-list += $$($(1)_PATH)
|
||||
clean-files += $$($(1)_PATH) $$(_d)/*.o $$(_d)/.*.dep $$($(1)_DEPS) $$($(1)_OBJS)
|
||||
dist-files += $$(_srcs)
|
||||
|
||||
# Phony target to run this program (typically as a dependency of 'check').
|
||||
.PHONY: $(1)_RUN
|
||||
$(1)_RUN: $$($(1)_PATH)
|
||||
$(trace-test) $$($(1)_PATH)
|
||||
|
||||
endef
|
||||
|
||||
@@ -11,7 +11,6 @@ ifeq ($(V), 0)
|
||||
trace-javac = @echo " JAVAC " $@;
|
||||
trace-jar = @echo " JAR " $@;
|
||||
trace-mkdir = @echo " MKDIR " $@;
|
||||
trace-test = @echo " TEST " $@;
|
||||
|
||||
suppress = @
|
||||
|
||||
|
||||
@@ -41,5 +41,5 @@ ifneq ($(OS), Darwin)
|
||||
check: rust-tests
|
||||
|
||||
rust-tests:
|
||||
$(trace-test) cd nix-rust && CARGO_HOME=$$(if [[ -d vendor ]]; then echo vendor; fi) cargo test --release $$(if [[ -d vendor ]]; then echo --offline; fi)
|
||||
cd nix-rust && CARGO_HOME=$$(if [[ -d vendor ]]; then echo vendor; fi) cargo test --release $$(if [[ -d vendor ]]; then echo --offline; fi)
|
||||
endif
|
||||
|
||||
@@ -80,7 +80,7 @@ SV * queryReferences(char * path)
|
||||
SV * queryPathHash(char * path)
|
||||
PPCODE:
|
||||
try {
|
||||
auto s = store()->queryPathInfo(store()->parseStorePath(path))->narHash.to_string(Base32, true);
|
||||
auto s = store()->queryPathInfo(store()->parseStorePath(path))->narHash.to_string();
|
||||
XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0)));
|
||||
} catch (Error & e) {
|
||||
croak("%s", e.what());
|
||||
@@ -106,7 +106,7 @@ SV * queryPathInfo(char * path, int base32)
|
||||
XPUSHs(&PL_sv_undef);
|
||||
else
|
||||
XPUSHs(sv_2mortal(newSVpv(store()->printStorePath(*info->deriver).c_str(), 0)));
|
||||
auto s = info->narHash.to_string(base32 ? Base32 : Base16, true);
|
||||
auto s = info->narHash.to_string(base32 ? Base32 : Base16);
|
||||
XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0)));
|
||||
mXPUSHi(info->registrationTime);
|
||||
mXPUSHi(info->narSize);
|
||||
@@ -274,8 +274,7 @@ int checkSignature(SV * publicKey_, SV * sig_, char * msg)
|
||||
SV * addToStore(char * srcPath, int recursive, char * algo)
|
||||
PPCODE:
|
||||
try {
|
||||
auto method = recursive ? FileIngestionMethod::Recursive : FileIngestionMethod::Flat;
|
||||
auto path = store()->addToStore(std::string(baseNameOf(srcPath)), srcPath, method, parseHashType(algo));
|
||||
auto path = store()->addToStore(std::string(baseNameOf(srcPath)), srcPath, recursive, parseHashType(algo));
|
||||
XPUSHs(sv_2mortal(newSVpv(store()->printStorePath(path).c_str(), 0)));
|
||||
} catch (Error & e) {
|
||||
croak("%s", e.what());
|
||||
@@ -286,8 +285,7 @@ SV * makeFixedOutputPath(int recursive, char * algo, char * hash, char * name)
|
||||
PPCODE:
|
||||
try {
|
||||
Hash h(hash, parseHashType(algo));
|
||||
auto method = recursive ? FileIngestionMethod::Recursive : FileIngestionMethod::Flat;
|
||||
auto path = store()->makeFixedOutputPath(method, h, name);
|
||||
auto path = store()->makeFixedOutputPath(recursive, h, name);
|
||||
XPUSHs(sv_2mortal(newSVpv(store()->printStorePath(path).c_str(), 0)));
|
||||
} catch (Error & e) {
|
||||
croak("%s", e.what());
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
{ pkgs }:
|
||||
|
||||
with pkgs;
|
||||
|
||||
rec {
|
||||
# Use "busybox-sandbox-shell" if present,
|
||||
# if not (legacy) fallback and hope it's sufficient.
|
||||
sh = pkgs.busybox-sandbox-shell or (busybox.override {
|
||||
useMusl = true;
|
||||
enableStatic = true;
|
||||
enableMinimal = true;
|
||||
extraConfig = ''
|
||||
CONFIG_FEATURE_FANCY_ECHO y
|
||||
CONFIG_FEATURE_SH_MATH y
|
||||
CONFIG_FEATURE_SH_MATH_64 y
|
||||
|
||||
CONFIG_ASH y
|
||||
CONFIG_ASH_OPTIMIZE_FOR_SIZE y
|
||||
|
||||
CONFIG_ASH_ALIAS y
|
||||
CONFIG_ASH_BASH_COMPAT y
|
||||
CONFIG_ASH_CMDCMD y
|
||||
CONFIG_ASH_ECHO y
|
||||
CONFIG_ASH_GETOPTS y
|
||||
CONFIG_ASH_INTERNAL_GLOB y
|
||||
CONFIG_ASH_JOB_CONTROL y
|
||||
CONFIG_ASH_PRINTF y
|
||||
CONFIG_ASH_TEST y
|
||||
'';
|
||||
});
|
||||
|
||||
configureFlags =
|
||||
lib.optionals stdenv.isLinux [
|
||||
"--with-sandbox-shell=${sh}/bin/busybox"
|
||||
];
|
||||
|
||||
buildDeps =
|
||||
[ bison
|
||||
flex
|
||||
libxml2
|
||||
libxslt
|
||||
docbook5
|
||||
docbook_xsl_ns
|
||||
autoconf-archive
|
||||
autoreconfHook
|
||||
|
||||
curl
|
||||
bzip2 xz brotli zlib editline
|
||||
openssl pkgconfig sqlite
|
||||
libarchive
|
||||
boost
|
||||
nlohmann_json
|
||||
|
||||
# Tests
|
||||
git
|
||||
mercurial
|
||||
gmock
|
||||
]
|
||||
++ lib.optionals stdenv.isLinux [libseccomp utillinuxMinimal]
|
||||
++ lib.optional (stdenv.isLinux || stdenv.isDarwin) libsodium
|
||||
++ lib.optional (stdenv.isLinux || stdenv.isDarwin)
|
||||
((aws-sdk-cpp.override {
|
||||
apis = ["s3" "transfer"];
|
||||
customMemoryManagement = false;
|
||||
}).overrideDerivation (args: {
|
||||
/*
|
||||
patches = args.patches or [] ++ [ (fetchpatch {
|
||||
url = https://github.com/edolstra/aws-sdk-cpp/commit/3e07e1f1aae41b4c8b340735ff9e8c735f0c063f.patch;
|
||||
sha256 = "1pij0v449p166f9l29x7ppzk8j7g9k9mp15ilh5qxp29c7fnvxy2";
|
||||
}) ];
|
||||
*/
|
||||
}));
|
||||
|
||||
propagatedDeps =
|
||||
[ (boehmgc.override { enableLargeConfig = true; })
|
||||
];
|
||||
|
||||
perlDeps =
|
||||
[ perl
|
||||
perlPackages.DBDSQLite
|
||||
];
|
||||
}
|
||||
303
release.nix
303
release.nix
@@ -1,303 +0,0 @@
|
||||
{ nix ? builtins.fetchGit ./.
|
||||
, nixpkgs ? builtins.fetchTarball https://github.com/NixOS/nixpkgs/archive/nixos-20.03-small.tar.gz
|
||||
, officialRelease ? false
|
||||
, systems ? [ "x86_64-linux" "i686-linux" "x86_64-darwin" "aarch64-linux" ]
|
||||
}:
|
||||
|
||||
let
|
||||
|
||||
pkgs = import nixpkgs { system = builtins.currentSystem or "x86_64-linux"; };
|
||||
|
||||
version =
|
||||
builtins.readFile ./.version
|
||||
+ (if officialRelease then "" else "pre${toString nix.revCount}_${nix.shortRev}");
|
||||
|
||||
jobs = rec {
|
||||
|
||||
build = pkgs.lib.genAttrs systems (system:
|
||||
|
||||
let pkgs = import nixpkgs { inherit system; }; in
|
||||
|
||||
with pkgs;
|
||||
|
||||
with import ./release-common.nix { inherit pkgs; };
|
||||
|
||||
stdenv.mkDerivation {
|
||||
name = "nix-${version}";
|
||||
|
||||
src = nix;
|
||||
|
||||
outputs = [ "out" "dev" "doc" ];
|
||||
|
||||
buildInputs = buildDeps;
|
||||
|
||||
propagatedBuildInputs = propagatedDeps;
|
||||
|
||||
preConfigure =
|
||||
''
|
||||
# Copy libboost_context so we don't get all of Boost in our closure.
|
||||
# https://github.com/NixOS/nixpkgs/issues/45462
|
||||
mkdir -p $out/lib
|
||||
cp -pd ${boost}/lib/{libboost_context*,libboost_thread*,libboost_system*} $out/lib
|
||||
rm -f $out/lib/*.a
|
||||
${lib.optionalString stdenv.isLinux ''
|
||||
chmod u+w $out/lib/*.so.*
|
||||
patchelf --set-rpath $out/lib:${stdenv.cc.cc.lib}/lib $out/lib/libboost_thread.so.*
|
||||
''}
|
||||
|
||||
(cd perl; autoreconf --install --force --verbose)
|
||||
'';
|
||||
|
||||
configureFlags = configureFlags ++
|
||||
[ "--sysconfdir=/etc" ];
|
||||
|
||||
enableParallelBuilding = true;
|
||||
|
||||
makeFlags = "profiledir=$(out)/etc/profile.d";
|
||||
|
||||
installFlags = "sysconfdir=$(out)/etc";
|
||||
|
||||
postInstall = ''
|
||||
mkdir -p $doc/nix-support
|
||||
echo "doc manual $doc/share/doc/nix/manual" >> $doc/nix-support/hydra-build-products
|
||||
'';
|
||||
|
||||
doCheck = true;
|
||||
|
||||
doInstallCheck = true;
|
||||
installCheckFlags = "sysconfdir=$(out)/etc";
|
||||
|
||||
separateDebugInfo = true;
|
||||
});
|
||||
|
||||
|
||||
perlBindings = pkgs.lib.genAttrs systems (system:
|
||||
|
||||
let pkgs = import nixpkgs { inherit system; }; in with pkgs;
|
||||
|
||||
releaseTools.nixBuild {
|
||||
name = "nix-perl-${version}";
|
||||
|
||||
src = nix;
|
||||
|
||||
buildInputs =
|
||||
[ autoconf-archive
|
||||
autoreconfHook
|
||||
jobs.build.${system}
|
||||
curl
|
||||
bzip2
|
||||
xz
|
||||
pkgconfig
|
||||
pkgs.perl
|
||||
boost
|
||||
]
|
||||
++ lib.optional (stdenv.isLinux || stdenv.isDarwin) libsodium;
|
||||
|
||||
configureFlags = ''
|
||||
--with-dbi=${perlPackages.DBI}/${pkgs.perl.libPrefix}
|
||||
--with-dbd-sqlite=${perlPackages.DBDSQLite}/${pkgs.perl.libPrefix}
|
||||
'';
|
||||
|
||||
enableParallelBuilding = true;
|
||||
|
||||
postUnpack = "sourceRoot=$sourceRoot/perl";
|
||||
});
|
||||
|
||||
|
||||
binaryTarball = pkgs.lib.genAttrs systems (system:
|
||||
|
||||
with import nixpkgs { inherit system; };
|
||||
|
||||
let
|
||||
toplevel = builtins.getAttr system jobs.build;
|
||||
installerClosureInfo = closureInfo { rootPaths = [ toplevel cacert ]; };
|
||||
in
|
||||
|
||||
runCommand "nix-binary-tarball-${version}"
|
||||
{ #nativeBuildInputs = lib.optional (system != "aarch64-linux") shellcheck;
|
||||
meta.description = "Distribution-independent Nix bootstrap binaries for ${system}";
|
||||
}
|
||||
''
|
||||
cp ${installerClosureInfo}/registration $TMPDIR/reginfo
|
||||
cp ${./scripts/create-darwin-volume.sh} $TMPDIR/create-darwin-volume.sh
|
||||
substitute ${./scripts/install-nix-from-closure.sh} $TMPDIR/install \
|
||||
--subst-var-by nix ${toplevel} \
|
||||
--subst-var-by cacert ${cacert}
|
||||
substitute ${./scripts/install-darwin-multi-user.sh} $TMPDIR/install-darwin-multi-user.sh \
|
||||
--subst-var-by nix ${toplevel} \
|
||||
--subst-var-by cacert ${cacert}
|
||||
substitute ${./scripts/install-systemd-multi-user.sh} $TMPDIR/install-systemd-multi-user.sh \
|
||||
--subst-var-by nix ${toplevel} \
|
||||
--subst-var-by cacert ${cacert}
|
||||
substitute ${./scripts/install-multi-user.sh} $TMPDIR/install-multi-user \
|
||||
--subst-var-by nix ${toplevel} \
|
||||
--subst-var-by cacert ${cacert}
|
||||
|
||||
if type -p shellcheck; then
|
||||
# SC1090: Don't worry about not being able to find
|
||||
# $nix/etc/profile.d/nix.sh
|
||||
shellcheck --exclude SC1090 $TMPDIR/install
|
||||
shellcheck $TMPDIR/create-darwin-volume.sh
|
||||
shellcheck $TMPDIR/install-darwin-multi-user.sh
|
||||
shellcheck $TMPDIR/install-systemd-multi-user.sh
|
||||
|
||||
# SC1091: Don't panic about not being able to source
|
||||
# /etc/profile
|
||||
# SC2002: Ignore "useless cat" "error", when loading
|
||||
# .reginfo, as the cat is a much cleaner
|
||||
# implementation, even though it is "useless"
|
||||
# SC2116: Allow ROOT_HOME=$(echo ~root) for resolving
|
||||
# root's home directory
|
||||
shellcheck --external-sources \
|
||||
--exclude SC1091,SC2002,SC2116 $TMPDIR/install-multi-user
|
||||
fi
|
||||
|
||||
chmod +x $TMPDIR/install
|
||||
chmod +x $TMPDIR/create-darwin-volume.sh
|
||||
chmod +x $TMPDIR/install-darwin-multi-user.sh
|
||||
chmod +x $TMPDIR/install-systemd-multi-user.sh
|
||||
chmod +x $TMPDIR/install-multi-user
|
||||
dir=nix-${version}-${system}
|
||||
fn=$out/$dir.tar.xz
|
||||
mkdir -p $out/nix-support
|
||||
echo "file binary-dist $fn" >> $out/nix-support/hydra-build-products
|
||||
tar cvfJ $fn \
|
||||
--owner=0 --group=0 --mode=u+rw,uga+r \
|
||||
--absolute-names \
|
||||
--hard-dereference \
|
||||
--transform "s,$TMPDIR/install,$dir/install," \
|
||||
--transform "s,$TMPDIR/create-darwin-volume.sh,$dir/create-darwin-volume.sh," \
|
||||
--transform "s,$TMPDIR/reginfo,$dir/.reginfo," \
|
||||
--transform "s,$NIX_STORE,$dir/store,S" \
|
||||
$TMPDIR/install \
|
||||
$TMPDIR/create-darwin-volume.sh \
|
||||
$TMPDIR/install-darwin-multi-user.sh \
|
||||
$TMPDIR/install-systemd-multi-user.sh \
|
||||
$TMPDIR/install-multi-user \
|
||||
$TMPDIR/reginfo \
|
||||
$(cat ${installerClosureInfo}/store-paths)
|
||||
'');
|
||||
|
||||
|
||||
coverage =
|
||||
with pkgs;
|
||||
|
||||
with import ./release-common.nix { inherit pkgs; };
|
||||
|
||||
releaseTools.coverageAnalysis {
|
||||
name = "nix-coverage-${version}";
|
||||
|
||||
src = nix;
|
||||
|
||||
enableParallelBuilding = true;
|
||||
|
||||
buildInputs = buildDeps ++ propagatedDeps;
|
||||
|
||||
dontInstall = false;
|
||||
|
||||
doInstallCheck = true;
|
||||
|
||||
lcovFilter = [ "*/boost/*" "*-tab.*" ];
|
||||
|
||||
# We call `dot', and even though we just use it to
|
||||
# syntax-check generated dot files, it still requires some
|
||||
# fonts. So provide those.
|
||||
FONTCONFIG_FILE = texFunctions.fontsConf;
|
||||
|
||||
# To test building without precompiled headers.
|
||||
makeFlagsArray = [ "PRECOMPILE_HEADERS=0" ];
|
||||
};
|
||||
|
||||
|
||||
# System tests.
|
||||
tests.remoteBuilds = (import ./tests/remote-builds.nix rec {
|
||||
inherit nixpkgs;
|
||||
nix = build.x86_64-linux; system = "x86_64-linux";
|
||||
});
|
||||
|
||||
tests.nix-copy-closure = (import ./tests/nix-copy-closure.nix rec {
|
||||
inherit nixpkgs;
|
||||
nix = build.x86_64-linux; system = "x86_64-linux";
|
||||
});
|
||||
|
||||
tests.setuid = pkgs.lib.genAttrs
|
||||
["i686-linux" "x86_64-linux"]
|
||||
(system:
|
||||
import ./tests/setuid.nix rec {
|
||||
inherit nixpkgs;
|
||||
nix = build.${system}; inherit system;
|
||||
});
|
||||
|
||||
tests.binaryTarball =
|
||||
with import nixpkgs { system = "x86_64-linux"; };
|
||||
vmTools.runInLinuxImage (runCommand "nix-binary-tarball-test"
|
||||
{ diskImage = vmTools.diskImages.ubuntu1204x86_64;
|
||||
}
|
||||
''
|
||||
set -x
|
||||
useradd -m alice
|
||||
su - alice -c 'tar xf ${binaryTarball.x86_64-linux}/*.tar.*'
|
||||
mkdir /dest-nix
|
||||
mount -o bind /dest-nix /nix # Provide a writable /nix.
|
||||
chown alice /nix
|
||||
su - alice -c '_NIX_INSTALLER_TEST=1 ./nix-*/install'
|
||||
su - alice -c 'nix-store --verify'
|
||||
su - alice -c 'PAGER= nix-store -qR ${build.x86_64-linux}'
|
||||
|
||||
# Check whether 'nix upgrade-nix' works.
|
||||
cat > /tmp/paths.nix <<EOF
|
||||
{
|
||||
x86_64-linux = "${build.x86_64-linux}";
|
||||
}
|
||||
EOF
|
||||
su - alice -c 'nix --experimental-features nix-command upgrade-nix -vvv --nix-store-paths-url file:///tmp/paths.nix'
|
||||
(! [ -L /home/alice/.profile-1-link ])
|
||||
su - alice -c 'PAGER= nix-store -qR ${build.x86_64-linux}'
|
||||
|
||||
mkdir -p $out/nix-support
|
||||
touch $out/nix-support/hydra-build-products
|
||||
umount /nix
|
||||
''); # */
|
||||
|
||||
/*
|
||||
tests.evalNixpkgs =
|
||||
import (nixpkgs + "/pkgs/top-level/make-tarball.nix") {
|
||||
inherit nixpkgs;
|
||||
inherit pkgs;
|
||||
nix = build.x86_64-linux;
|
||||
officialRelease = false;
|
||||
};
|
||||
|
||||
tests.evalNixOS =
|
||||
pkgs.runCommand "eval-nixos" { buildInputs = [ build.x86_64-linux ]; }
|
||||
''
|
||||
export NIX_STATE_DIR=$TMPDIR
|
||||
|
||||
nix-instantiate ${nixpkgs}/nixos/release-combined.nix -A tested --dry-run \
|
||||
--arg nixpkgs '{ outPath = ${nixpkgs}; revCount = 123; shortRev = "abcdefgh"; }'
|
||||
|
||||
touch $out
|
||||
'';
|
||||
*/
|
||||
|
||||
|
||||
installerScript =
|
||||
pkgs.runCommand "installer-script"
|
||||
{ buildInputs = [ build.${builtins.currentSystem or "x86_64-linux"} ]; }
|
||||
''
|
||||
mkdir -p $out/nix-support
|
||||
|
||||
substitute ${./scripts/install.in} $out/install \
|
||||
${pkgs.lib.concatMapStrings
|
||||
(system: "--replace '@binaryTarball_${system}@' $(nix --experimental-features nix-command hash-file --base16 --type sha256 ${binaryTarball.${system}}/*.tar.xz) ")
|
||||
systems
|
||||
} \
|
||||
--replace '@nixVersion@' ${version}
|
||||
|
||||
echo "file installer $out/install" >> $out/nix-support/hydra-build-products
|
||||
'';
|
||||
|
||||
};
|
||||
|
||||
|
||||
in jobs
|
||||
@@ -1,185 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
root_disk() {
|
||||
diskutil info -plist /
|
||||
}
|
||||
|
||||
apfs_volumes_for() {
|
||||
disk=$1
|
||||
diskutil apfs list -plist "$disk"
|
||||
}
|
||||
|
||||
disk_identifier() {
|
||||
xpath "/plist/dict/key[text()='ParentWholeDisk']/following-sibling::string[1]/text()" 2>/dev/null
|
||||
}
|
||||
|
||||
volume_list_true() {
|
||||
key=$1
|
||||
xpath "/plist/dict/array/dict/key[text()='Volumes']/following-sibling::array/dict/key[text()='$key']/following-sibling::true[1]" 2> /dev/null
|
||||
}
|
||||
|
||||
volume_get_string() {
|
||||
key=$1 i=$2
|
||||
xpath "/plist/dict/array/dict/key[text()='Volumes']/following-sibling::array/dict[$i]/key[text()='$key']/following-sibling::string[1]/text()" 2> /dev/null
|
||||
}
|
||||
|
||||
find_nix_volume() {
|
||||
disk=$1
|
||||
i=1
|
||||
volumes=$(apfs_volumes_for "$disk")
|
||||
while true; do
|
||||
name=$(echo "$volumes" | volume_get_string "Name" "$i")
|
||||
if [ -z "$name" ]; then
|
||||
break
|
||||
fi
|
||||
case "$name" in
|
||||
[Nn]ix*)
|
||||
echo "$name"
|
||||
break
|
||||
;;
|
||||
esac
|
||||
i=$((i+1))
|
||||
done
|
||||
}
|
||||
|
||||
test_fstab() {
|
||||
grep -q "/nix apfs rw" /etc/fstab 2>/dev/null
|
||||
}
|
||||
|
||||
test_nix_symlink() {
|
||||
[ -L "/nix" ] || grep -q "^nix." /etc/synthetic.conf 2>/dev/null
|
||||
}
|
||||
|
||||
test_synthetic_conf() {
|
||||
grep -q "^nix$" /etc/synthetic.conf 2>/dev/null
|
||||
}
|
||||
|
||||
test_nix() {
|
||||
test -d "/nix"
|
||||
}
|
||||
|
||||
test_t2_chip_present(){
|
||||
# Use xartutil to see if system has a t2 chip.
|
||||
#
|
||||
# This isn't well-documented on its own; until it is,
|
||||
# let's keep track of knowledge/assumptions.
|
||||
#
|
||||
# Warnings:
|
||||
# - Don't search "xart" if porn will cause you trouble :)
|
||||
# - Other xartutil flags do dangerous things. Don't run them
|
||||
# naively. If you must, search "xartutil" first.
|
||||
#
|
||||
# Assumptions:
|
||||
# - the "xART session seeds recovery utility"
|
||||
# appears to interact with xartstorageremoted
|
||||
# - `sudo xartutil --list` lists xART sessions
|
||||
# and their seeds and exits 0 if successful. If
|
||||
# not, it exits 1 and prints an error such as:
|
||||
# xartutil: ERROR: No supported link to the SEP present
|
||||
# - xART sessions/seeds are present when a T2 chip is
|
||||
# (and not, otherwise)
|
||||
# - the presence of a T2 chip means a newly-created
|
||||
# volume on the primary drive will be
|
||||
# encrypted at rest
|
||||
# - all together: `sudo xartutil --list`
|
||||
# should exit 0 if a new Nix Store volume will
|
||||
# be encrypted at rest, and exit 1 if not.
|
||||
sudo xartutil --list >/dev/null 2>/dev/null
|
||||
}
|
||||
|
||||
test_filevault_in_use() {
|
||||
disk=$1
|
||||
# list vols on disk | get value of Filevault key | value is true
|
||||
apfs_volumes_for "$disk" | volume_list_true FileVault | grep -q true
|
||||
}
|
||||
|
||||
# use after error msg for conditions we don't understand
|
||||
suggest_report_error(){
|
||||
# ex "error: something sad happened :(" >&2
|
||||
echo " please report this @ https://github.com/nixos/nix/issues" >&2
|
||||
}
|
||||
|
||||
main() {
|
||||
(
|
||||
echo ""
|
||||
echo " ------------------------------------------------------------------ "
|
||||
echo " | This installer will create a volume for the nix store and |"
|
||||
echo " | configure it to mount at /nix. Follow these steps to uninstall. |"
|
||||
echo " ------------------------------------------------------------------ "
|
||||
echo ""
|
||||
echo " 1. Remove the entry from fstab using 'sudo vifs'"
|
||||
echo " 2. Destroy the data volume using 'diskutil apfs deleteVolume'"
|
||||
echo " 3. Remove the 'nix' line from /etc/synthetic.conf or the file"
|
||||
echo ""
|
||||
) >&2
|
||||
|
||||
if test_nix_symlink; then
|
||||
echo "error: /nix is a symlink, please remove it and make sure it's not in synthetic.conf (in which case a reboot is required)" >&2
|
||||
echo " /nix -> $(readlink "/nix")" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if ! test_synthetic_conf; then
|
||||
echo "Configuring /etc/synthetic.conf..." >&2
|
||||
echo nix | sudo tee -a /etc/synthetic.conf
|
||||
if ! test_synthetic_conf; then
|
||||
echo "error: failed to configure synthetic.conf;" >&2
|
||||
suggest_report_error
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! test_nix; then
|
||||
echo "Creating mountpoint for /nix..." >&2
|
||||
/System/Library/Filesystems/apfs.fs/Contents/Resources/apfs.util -B || true
|
||||
if ! test_nix; then
|
||||
sudo mkdir -p /nix 2>/dev/null || true
|
||||
fi
|
||||
if ! test_nix; then
|
||||
echo "error: failed to bootstrap /nix; if a reboot doesn't help," >&2
|
||||
suggest_report_error
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
disk=$(root_disk | disk_identifier)
|
||||
volume=$(find_nix_volume "$disk")
|
||||
if [ -z "$volume" ]; then
|
||||
echo "Creating a Nix Store volume..." >&2
|
||||
|
||||
if test_filevault_in_use "$disk"; then
|
||||
# TODO: Not sure if it's in-scope now, but `diskutil apfs list`
|
||||
# shows both filevault and encrypted at rest status, and it
|
||||
# may be the more semantic way to test for this? It'll show
|
||||
# `FileVault: No (Encrypted at rest)`
|
||||
# `FileVault: No`
|
||||
# `FileVault: Yes (Unlocked)`
|
||||
# and so on.
|
||||
if test_t2_chip_present; then
|
||||
echo "warning: boot volume is FileVault-encrypted, but the Nix store volume" >&2
|
||||
echo " is only encrypted at rest." >&2
|
||||
echo " See https://nixos.org/nix/manual/#sect-macos-installation" >&2
|
||||
else
|
||||
echo "error: refusing to create Nix store volume because the boot volume is" >&2
|
||||
echo " FileVault encrypted, but encryption-at-rest is not available." >&2
|
||||
echo " Manually create a volume for the store and re-run this script." >&2
|
||||
echo " See https://nixos.org/nix/manual/#sect-macos-installation" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
sudo diskutil apfs addVolume "$disk" APFS 'Nix Store' -mountpoint /nix
|
||||
volume="Nix Store"
|
||||
else
|
||||
echo "Using existing '$volume' volume" >&2
|
||||
fi
|
||||
|
||||
if ! test_fstab; then
|
||||
echo "Configuring /etc/fstab..." >&2
|
||||
label=$(echo "$volume" | sed 's/ /\\040/g')
|
||||
printf "\$a\nLABEL=%s /nix apfs rw,nobrowse\n.\nwq\n" "$label" | EDITOR=ed sudo vifs
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -20,18 +20,15 @@ readonly GREEN='\033[32m'
|
||||
readonly GREEN_UL='\033[4;32m'
|
||||
readonly RED='\033[31m'
|
||||
|
||||
# installer allows overriding build user count to speed up installation
|
||||
# as creating each user takes non-trivial amount of time on macos
|
||||
readonly NIX_USER_COUNT=${NIX_USER_COUNT:-32}
|
||||
readonly NIX_USER_COUNT="32"
|
||||
readonly NIX_BUILD_GROUP_ID="30000"
|
||||
readonly NIX_BUILD_GROUP_NAME="nixbld"
|
||||
readonly NIX_FIRST_BUILD_UID="30001"
|
||||
# Please don't change this. We don't support it, because the
|
||||
# default shell profile that comes with Nix doesn't support it.
|
||||
readonly NIX_ROOT="/nix"
|
||||
readonly NIX_EXTRA_CONF=${NIX_EXTRA_CONF:-}
|
||||
|
||||
readonly PROFILE_TARGETS=("/etc/bashrc" "/etc/profile.d/nix.sh" "/etc/zshenv")
|
||||
readonly PROFILE_TARGETS=("/etc/bashrc" "/etc/profile.d/nix.sh" "/etc/zshrc")
|
||||
readonly PROFILE_BACKUP_SUFFIX=".backup-before-nix"
|
||||
readonly PROFILE_NIX_FILE="$NIX_ROOT/var/nix/profiles/default/etc/profile.d/nix-daemon.sh"
|
||||
|
||||
@@ -453,11 +450,9 @@ create_directories() {
|
||||
}
|
||||
|
||||
place_channel_configuration() {
|
||||
if [ -z "${NIX_INSTALLER_NO_CHANNEL_ADD:-}" ]; then
|
||||
echo "https://nixos.org/channels/nixpkgs-unstable nixpkgs" > "$SCRATCH/.nix-channels"
|
||||
_sudo "to set up the default system channel (part 1)" \
|
||||
install -m 0664 "$SCRATCH/.nix-channels" "$ROOT_HOME/.nix-channels"
|
||||
fi
|
||||
echo "https://nixos.org/channels/nixpkgs-unstable nixpkgs" > "$SCRATCH/.nix-channels"
|
||||
_sudo "to set up the default system channel (part 1)" \
|
||||
install -m 0664 "$SCRATCH/.nix-channels" "$ROOT_HOME/.nix-channels"
|
||||
}
|
||||
|
||||
welcome_to_nix() {
|
||||
@@ -526,7 +521,7 @@ This script is going to call sudo a lot. Normally, it would show you
|
||||
exactly what commands it is running and why. However, the script is
|
||||
run in a headless fashion, like this:
|
||||
|
||||
$ curl -L https://nixos.org/nix/install | sh
|
||||
$ curl https://nixos.org/nix/install | sh
|
||||
|
||||
or maybe in a CI pipeline. Because of that, we're going to skip the
|
||||
verbose output in the interest of brevity.
|
||||
@@ -534,7 +529,7 @@ verbose output in the interest of brevity.
|
||||
If you would like to
|
||||
see the output, try like this:
|
||||
|
||||
$ curl -L -o install-nix https://nixos.org/nix/install
|
||||
$ curl -o install-nix https://nixos.org/nix/install
|
||||
$ sh ./install-nix
|
||||
|
||||
EOF
|
||||
@@ -639,20 +634,18 @@ setup_default_profile() {
|
||||
export NIX_SSL_CERT_FILE=/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt
|
||||
fi
|
||||
|
||||
if [ -z "${NIX_INSTALLER_NO_CHANNEL_ADD:-}" ]; then
|
||||
# Have to explicitly pass NIX_SSL_CERT_FILE as part of the sudo call,
|
||||
# otherwise it will be lost in environments where sudo doesn't pass
|
||||
# all the environment variables by default.
|
||||
_sudo "to update the default channel in the default profile" \
|
||||
HOME="$ROOT_HOME" NIX_SSL_CERT_FILE="$NIX_SSL_CERT_FILE" "$NIX_INSTALLED_NIX/bin/nix-channel" --update nixpkgs \
|
||||
|| channel_update_failed=1
|
||||
fi
|
||||
# Have to explicitly pass NIX_SSL_CERT_FILE as part of the sudo call,
|
||||
# otherwise it will be lost in environments where sudo doesn't pass
|
||||
# all the environment variables by default.
|
||||
_sudo "to update the default channel in the default profile" \
|
||||
HOME="$ROOT_HOME" NIX_SSL_CERT_FILE="$NIX_SSL_CERT_FILE" "$NIX_INSTALLED_NIX/bin/nix-channel" --update nixpkgs \
|
||||
|| channel_update_failed=1
|
||||
|
||||
}
|
||||
|
||||
|
||||
place_nix_configuration() {
|
||||
cat <<EOF > "$SCRATCH/nix.conf"
|
||||
$NIX_EXTRA_CONF
|
||||
build-users-group = $NIX_BUILD_GROUP_NAME
|
||||
EOF
|
||||
_sudo "to place the default nix daemon configuration (part 2)" \
|
||||
|
||||
@@ -40,85 +40,29 @@ elif [ "$(uname -s)" = "Linux" ] && [ -e /run/systemd/system ]; then
|
||||
fi
|
||||
|
||||
INSTALL_MODE=no-daemon
|
||||
CREATE_DARWIN_VOLUME=0
|
||||
# handle the command line flags
|
||||
while [ $# -gt 0 ]; do
|
||||
case $1 in
|
||||
--daemon)
|
||||
INSTALL_MODE=daemon;;
|
||||
--no-daemon)
|
||||
INSTALL_MODE=no-daemon;;
|
||||
--no-channel-add)
|
||||
export NIX_INSTALLER_NO_CHANNEL_ADD=1;;
|
||||
--daemon-user-count)
|
||||
export NIX_USER_COUNT=$2
|
||||
shift;;
|
||||
--no-modify-profile)
|
||||
NIX_INSTALLER_NO_MODIFY_PROFILE=1;;
|
||||
--darwin-use-unencrypted-nix-store-volume)
|
||||
CREATE_DARWIN_VOLUME=1;;
|
||||
--nix-extra-conf-file)
|
||||
export NIX_EXTRA_CONF="$(cat $2)"
|
||||
shift;;
|
||||
*)
|
||||
(
|
||||
echo "Nix Installer [--daemon|--no-daemon] [--daemon-user-count INT] [--no-channel-add] [--no-modify-profile] [--darwin-use-unencrypted-nix-store-volume] [--nix-extra-conf-file FILE]"
|
||||
# Trivially handle the --daemon / --no-daemon options
|
||||
if [ "x${1:-}" = "x--no-daemon" ]; then
|
||||
INSTALL_MODE=no-daemon
|
||||
elif [ "x${1:-}" = "x--daemon" ]; then
|
||||
INSTALL_MODE=daemon
|
||||
elif [ "x${1:-}" != "x" ]; then
|
||||
(
|
||||
echo "Nix Installer [--daemon|--no-daemon]"
|
||||
|
||||
echo "Choose installation method."
|
||||
echo ""
|
||||
echo " --daemon: Installs and configures a background daemon that manages the store,"
|
||||
echo " providing multi-user support and better isolation for local builds."
|
||||
echo " Both for security and reproducibility, this method is recommended if"
|
||||
echo " supported on your platform."
|
||||
echo " See https://nixos.org/nix/manual/#sect-multi-user-installation"
|
||||
echo ""
|
||||
echo " --no-daemon: Simple, single-user installation that does not require root and is"
|
||||
echo " trivial to uninstall."
|
||||
echo " (default)"
|
||||
echo ""
|
||||
echo " --no-channel-add: Don't add any channels. nixpkgs-unstable is installed by default."
|
||||
echo ""
|
||||
echo " --no-modify-profile: Skip channel installation. When not provided nixpkgs-unstable"
|
||||
echo " is installed by default."
|
||||
echo ""
|
||||
echo " --daemon-user-count: Number of build users to create. Defaults to 32."
|
||||
echo ""
|
||||
echo " --nix-extra-conf-file: Path to nix.conf to prepend when installing /etc/nix.conf"
|
||||
echo ""
|
||||
) >&2
|
||||
|
||||
# darwin and Catalina+
|
||||
if [ "$(uname -s)" = "Darwin" ] && [ "$macos_major" -gt 14 ]; then
|
||||
(
|
||||
echo " --darwin-use-unencrypted-nix-store-volume: Create an APFS volume for the Nix"
|
||||
echo " store and mount it at /nix. This is the recommended way to create"
|
||||
echo " /nix with a read-only / on macOS >=10.15."
|
||||
echo " See: https://nixos.org/nix/manual/#sect-macos-installation"
|
||||
echo ""
|
||||
) >&2
|
||||
fi
|
||||
exit;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ "$(uname -s)" = "Darwin" ]; then
|
||||
if [ "$CREATE_DARWIN_VOLUME" = 1 ]; then
|
||||
printf '\e[1;31mCreating volume and mountpoint /nix.\e[0m\n'
|
||||
"$self/create-darwin-volume.sh"
|
||||
fi
|
||||
|
||||
info=$(diskutil info -plist / | xpath "/plist/dict/key[text()='Writable']/following-sibling::true[1]" 2> /dev/null)
|
||||
if ! [ -e $dest ] && [ -n "$info" ] && [ "$macos_major" -gt 14 ]; then
|
||||
(
|
||||
echo ""
|
||||
echo "Installing on macOS >=10.15 requires relocating the store to an apfs volume."
|
||||
echo "Use sh <(curl -L https://nixos.org/nix/install) --darwin-use-unencrypted-nix-store-volume or run the preparation steps manually."
|
||||
echo "See https://nixos.org/nix/manual/#sect-macos-installation"
|
||||
echo ""
|
||||
) >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Choose installation method."
|
||||
echo ""
|
||||
echo " --daemon: Installs and configures a background daemon that manages the store,"
|
||||
echo " providing multi-user support and better isolation for local builds."
|
||||
echo " Both for security and reproducibility, this method is recommended if"
|
||||
echo " supported on your platform."
|
||||
echo " See https://nixos.org/nix/manual/#sect-multi-user-installation"
|
||||
echo ""
|
||||
echo " --no-daemon: Simple, single-user installation that does not require root and is"
|
||||
echo " trivial to uninstall."
|
||||
echo " (default)"
|
||||
echo ""
|
||||
) >&2
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ "$INSTALL_MODE" = "daemon" ]; then
|
||||
@@ -186,15 +130,13 @@ if [ -z "$NIX_SSL_CERT_FILE" ] || ! [ -f "$NIX_SSL_CERT_FILE" ]; then
|
||||
fi
|
||||
|
||||
# Subscribe the user to the Nixpkgs channel and fetch it.
|
||||
if [ -z "$NIX_INSTALLER_NO_CHANNEL_ADD" ]; then
|
||||
if ! $nix/bin/nix-channel --list | grep -q "^nixpkgs "; then
|
||||
$nix/bin/nix-channel --add https://nixos.org/channels/nixpkgs-unstable
|
||||
fi
|
||||
if [ -z "$_NIX_INSTALLER_TEST" ]; then
|
||||
if ! $nix/bin/nix-channel --update nixpkgs; then
|
||||
echo "Fetching the nixpkgs channel failed. (Are you offline?)"
|
||||
echo "To try again later, run \"nix-channel --update nixpkgs\"."
|
||||
fi
|
||||
if ! $nix/bin/nix-channel --list | grep -q "^nixpkgs "; then
|
||||
$nix/bin/nix-channel --add https://nixos.org/channels/nixpkgs-unstable
|
||||
fi
|
||||
if [ -z "$_NIX_INSTALLER_TEST" ]; then
|
||||
if ! $nix/bin/nix-channel --update nixpkgs; then
|
||||
echo "Fetching the nixpkgs channel failed. (Are you offline?)"
|
||||
echo "To try again later, run \"nix-channel --update nixpkgs\"."
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -213,17 +155,6 @@ if [ -z "$NIX_INSTALLER_NO_MODIFY_PROFILE" ]; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
for i in .zshenv .zshrc; do
|
||||
fn="$HOME/$i"
|
||||
if [ -w "$fn" ]; then
|
||||
if ! grep -q "$p" "$fn"; then
|
||||
echo "modifying $fn..." >&2
|
||||
echo "if [ -e $p ]; then . $p; fi # added by Nix installer" >> "$fn"
|
||||
fi
|
||||
added=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -z "$added" ]; then
|
||||
|
||||
@@ -36,9 +36,7 @@ tarball="$tmpDir/$(basename "$tmpDir/nix-@nixVersion@-$system.tar.xz")"
|
||||
|
||||
require_util curl "download the binary tarball"
|
||||
require_util tar "unpack the binary tarball"
|
||||
if [ "$(uname -s)" != "Darwin" ]; then
|
||||
require_util xz "unpack the binary tarball"
|
||||
fi
|
||||
require_util xz "unpack the binary tarball"
|
||||
|
||||
echo "downloading Nix @nixVersion@ binary tarball for $system from '$url' to '$tmpDir'..."
|
||||
curl -L "$url" -o "$tarball" || oops "failed to download '$url'"
|
||||
|
||||
28
shell.nix
28
shell.nix
@@ -1,25 +1,3 @@
|
||||
{ useClang ? false }:
|
||||
|
||||
with import (builtins.fetchTarball https://github.com/NixOS/nixpkgs/archive/nixos-20.03-small.tar.gz) {};
|
||||
|
||||
with import ./release-common.nix { inherit pkgs; };
|
||||
|
||||
(if useClang then clangStdenv else stdenv).mkDerivation {
|
||||
name = "nix";
|
||||
|
||||
buildInputs = buildDeps ++ propagatedDeps ++ perlDeps;
|
||||
|
||||
inherit configureFlags;
|
||||
|
||||
enableParallelBuilding = true;
|
||||
|
||||
installFlags = "sysconfdir=$(out)/etc";
|
||||
|
||||
shellHook =
|
||||
''
|
||||
export prefix=$(pwd)/inst
|
||||
configureFlags+=" --prefix=$prefix"
|
||||
PKG_CONFIG_PATH=$prefix/lib/pkgconfig:$PKG_CONFIG_PATH
|
||||
PATH=$prefix/bin:$PATH
|
||||
'';
|
||||
}
|
||||
(import (fetchTarball https://github.com/edolstra/flake-compat/archive/master.tar.gz) {
|
||||
src = builtins.fetchGit ./.;
|
||||
}).shellNix
|
||||
|
||||
@@ -200,12 +200,9 @@ static int _main(int argc, char * * argv)
|
||||
|
||||
} catch (std::exception & e) {
|
||||
auto msg = chomp(drainFD(5, false));
|
||||
logError({
|
||||
.name = "Remote build",
|
||||
.hint = hintfmt("cannot build on '%s': %s%s",
|
||||
bestMachine->storeUri, e.what(),
|
||||
(msg.empty() ? "" : ": " + msg))
|
||||
});
|
||||
printError("cannot build on '%s': %s%s",
|
||||
bestMachine->storeUri, e.what(),
|
||||
(msg.empty() ? "" : ": " + msg));
|
||||
bestMachine->enabled = false;
|
||||
continue;
|
||||
}
|
||||
@@ -244,7 +241,7 @@ connected:
|
||||
|
||||
uploadLock = -1;
|
||||
|
||||
auto drv = store->readDerivation(*drvPath);
|
||||
BasicDerivation drv(readDerivation(*store, store->realStoreDir + "/" + std::string(drvPath->to_string())));
|
||||
drv.inputSrcs = store->parseStorePathSet(inputs);
|
||||
|
||||
auto result = sshStore->buildDerivation(*drvPath, drv);
|
||||
|
||||
66
src/error-demo/error-demo.cc
Normal file
66
src/error-demo/error-demo.cc
Normal file
@@ -0,0 +1,66 @@
|
||||
#include "error.hh"
|
||||
#include "nixexpr.hh"
|
||||
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
|
||||
int main()
|
||||
{
|
||||
using namespace nix;
|
||||
|
||||
// In each program where errors occur, this has to be set.
|
||||
ErrorInfo::programName = std::optional("error-demo");
|
||||
|
||||
// Error in a program; no hint and no nix code.
|
||||
printErrorInfo(
|
||||
ErrorInfo { .level = elError,
|
||||
.name = "name",
|
||||
.description = "error description",
|
||||
});
|
||||
|
||||
// Warning with name, description, and hint.
|
||||
// The hintfmt function makes all the substituted text yellow.
|
||||
printErrorInfo(
|
||||
ErrorInfo { .level = elWarning,
|
||||
.name = "name",
|
||||
.description = "error description",
|
||||
.hint = std::optional(
|
||||
hintfmt("there was a %1%", "warning")),
|
||||
});
|
||||
|
||||
|
||||
// Warning with nix file, line number, column, and the lines of
|
||||
// code where a warning occurred.
|
||||
SymbolTable testTable;
|
||||
auto problem_file = testTable.create("myfile.nix");
|
||||
|
||||
printErrorInfo(
|
||||
ErrorInfo{
|
||||
.level = elWarning,
|
||||
.name = "warning name",
|
||||
.description = "warning description",
|
||||
.hint = hintfmt("this hint has %1% templated %2%!!", "yellow", "values"),
|
||||
.nixCode = NixCode {
|
||||
.errPos = Pos(problem_file, 40, 13),
|
||||
.prevLineOfCode = std::nullopt,
|
||||
.errLineOfCode = "this is the problem line of code",
|
||||
.nextLineOfCode = std::nullopt
|
||||
}});
|
||||
|
||||
// Error with previous and next lines of code.
|
||||
printErrorInfo(
|
||||
ErrorInfo{
|
||||
.level = elError,
|
||||
.name = "error name",
|
||||
.description = "error description",
|
||||
.hint = hintfmt("this hint has %1% templated %2%!!", "yellow", "values"),
|
||||
.nixCode = NixCode {
|
||||
.errPos = Pos(problem_file, 40, 13),
|
||||
.prevLineOfCode = std::optional("previous line of code"),
|
||||
.errLineOfCode = "this is the problem line of code",
|
||||
.nextLineOfCode = std::optional("next line of code"),
|
||||
}});
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
12
src/error-demo/local.mk
Normal file
12
src/error-demo/local.mk
Normal file
@@ -0,0 +1,12 @@
|
||||
programs += error-demo
|
||||
|
||||
error-demo_DIR := $(d)
|
||||
|
||||
error-demo_SOURCES := \
|
||||
$(wildcard $(d)/*.cc) \
|
||||
|
||||
error-demo_CXXFLAGS += -I src/libutil -I src/libexpr
|
||||
|
||||
error-demo_LIBS = libutil libexpr
|
||||
|
||||
error-demo_LDFLAGS = -pthread $(SODIUM_LIBS) $(EDITLINE_LIBS) $(BOOST_LDFLAGS) -lboost_context -lboost_thread -lboost_system
|
||||
@@ -19,7 +19,7 @@ static Strings parseAttrPath(std::string_view s)
|
||||
++i;
|
||||
while (1) {
|
||||
if (i == s.end())
|
||||
throw Error("missing closing quote in selection path '%1%'", s);
|
||||
throw Error(format("missing closing quote in selection path '%1%'") % s);
|
||||
if (*i == '"') break;
|
||||
cur.push_back(*i++);
|
||||
}
|
||||
@@ -69,11 +69,11 @@ std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const string & attr
|
||||
|
||||
if (v->type != tAttrs)
|
||||
throw TypeError(
|
||||
"the expression selected by the selection path '%1%' should be a set but is %2%",
|
||||
attrPath,
|
||||
showType(*v));
|
||||
format("the expression selected by the selection path '%1%' should be a set but is %2%")
|
||||
% attrPath % showType(*v));
|
||||
|
||||
if (attr.empty())
|
||||
throw Error("empty attribute name in selection path '%1%'", attrPath);
|
||||
throw Error(format("empty attribute name in selection path '%1%'") % attrPath);
|
||||
|
||||
Bindings::iterator a = v->attrs->find(state.symbols.create(attr));
|
||||
if (a == v->attrs->end())
|
||||
@@ -86,9 +86,9 @@ std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const string & attr
|
||||
|
||||
if (!v->isList())
|
||||
throw TypeError(
|
||||
"the expression selected by the selection path '%1%' should be a list but is %2%",
|
||||
attrPath,
|
||||
showType(*v));
|
||||
format("the expression selected by the selection path '%1%' should be a list but is %2%")
|
||||
% attrPath % showType(*v));
|
||||
|
||||
if (attrIndex >= v->listSize())
|
||||
throw AttrPathNotFound("list index %1% in selection path '%2%' is out of range", attrIndex, attrPath);
|
||||
|
||||
|
||||
@@ -76,11 +76,7 @@ public:
|
||||
{
|
||||
auto a = get(name);
|
||||
if (!a)
|
||||
throw Error({
|
||||
.hint = hintfmt("attribute '%s' missing", name),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
|
||||
throw Error("attribute '%s' missing, at %s", name, pos);
|
||||
return *a;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "util.hh"
|
||||
#include "eval.hh"
|
||||
#include "fetchers.hh"
|
||||
#include "registry.hh"
|
||||
#include "flake/flakeref.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
namespace nix {
|
||||
@@ -31,6 +33,27 @@ MixEvalArgs::MixEvalArgs()
|
||||
.labels = {"path"},
|
||||
.handler = {[&](std::string s) { searchPath.push_back(s); }}
|
||||
});
|
||||
|
||||
addFlag({
|
||||
.longName = "impure",
|
||||
.description = "allow access to mutable paths and repositories",
|
||||
.handler = {[&]() {
|
||||
evalSettings.pureEval = false;
|
||||
}},
|
||||
});
|
||||
|
||||
addFlag({
|
||||
.longName = "override-flake",
|
||||
.description = "override a flake registry value",
|
||||
.labels = {"original-ref", "resolved-ref"},
|
||||
.handler = {[&](std::string _from, std::string _to) {
|
||||
auto from = parseFlakeRef(_from, absPath("."));
|
||||
auto to = parseFlakeRef(_to, absPath("."));
|
||||
fetchers::Attrs extraAttrs;
|
||||
if (to.subdir != "") extraAttrs["dir"] = to.subdir;
|
||||
fetchers::overrideRegistry(from.input, to.input, extraAttrs);
|
||||
}}
|
||||
});
|
||||
}
|
||||
|
||||
Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
|
||||
|
||||
503
src/libexpr/eval-cache.cc
Normal file
503
src/libexpr/eval-cache.cc
Normal file
@@ -0,0 +1,503 @@
|
||||
#include "eval-cache.hh"
|
||||
#include "sqlite.hh"
|
||||
#include "eval.hh"
|
||||
#include "eval-inline.hh"
|
||||
|
||||
namespace nix::eval_cache {
|
||||
|
||||
static const char * schema = R"sql(
|
||||
create table if not exists Attributes (
|
||||
parent integer not null,
|
||||
name text,
|
||||
type integer not null,
|
||||
value text,
|
||||
primary key (parent, name)
|
||||
);
|
||||
)sql";
|
||||
|
||||
struct AttrDb
|
||||
{
|
||||
struct State
|
||||
{
|
||||
SQLite db;
|
||||
SQLiteStmt insertAttribute;
|
||||
SQLiteStmt queryAttribute;
|
||||
SQLiteStmt queryAttributes;
|
||||
std::unique_ptr<SQLiteTxn> txn;
|
||||
};
|
||||
|
||||
std::unique_ptr<Sync<State>> _state;
|
||||
|
||||
AttrDb(const Hash & fingerprint)
|
||||
: _state(std::make_unique<Sync<State>>())
|
||||
{
|
||||
auto state(_state->lock());
|
||||
|
||||
Path cacheDir = getCacheDir() + "/nix/eval-cache-v1";
|
||||
createDirs(cacheDir);
|
||||
|
||||
Path dbPath = cacheDir + "/" + fingerprint.to_string(Base16, false) + ".sqlite";
|
||||
|
||||
state->db = SQLite(dbPath);
|
||||
state->db.isCache();
|
||||
state->db.exec(schema);
|
||||
|
||||
state->insertAttribute.create(state->db,
|
||||
"insert or replace into Attributes(parent, name, type, value) values (?, ?, ?, ?)");
|
||||
|
||||
state->queryAttribute.create(state->db,
|
||||
"select rowid, type, value from Attributes where parent = ? and name = ?");
|
||||
|
||||
state->queryAttributes.create(state->db,
|
||||
"select name from Attributes where parent = ?");
|
||||
|
||||
state->txn = std::make_unique<SQLiteTxn>(state->db);
|
||||
}
|
||||
|
||||
~AttrDb()
|
||||
{
|
||||
try {
|
||||
auto state(_state->lock());
|
||||
state->txn->commit();
|
||||
state->txn.reset();
|
||||
} catch (...) {
|
||||
ignoreException();
|
||||
}
|
||||
}
|
||||
|
||||
AttrId setAttrs(
|
||||
AttrKey key,
|
||||
const std::vector<Symbol> & attrs)
|
||||
{
|
||||
auto state(_state->lock());
|
||||
|
||||
state->insertAttribute.use()
|
||||
(key.first)
|
||||
(key.second)
|
||||
(AttrType::FullAttrs)
|
||||
(0, false).exec();
|
||||
|
||||
AttrId rowId = state->db.getLastInsertedRowId();
|
||||
assert(rowId);
|
||||
|
||||
for (auto & attr : attrs)
|
||||
state->insertAttribute.use()
|
||||
(rowId)
|
||||
(attr)
|
||||
(AttrType::Placeholder)
|
||||
(0, false).exec();
|
||||
|
||||
return rowId;
|
||||
}
|
||||
|
||||
AttrId setString(
|
||||
AttrKey key,
|
||||
std::string_view s)
|
||||
{
|
||||
auto state(_state->lock());
|
||||
|
||||
state->insertAttribute.use()
|
||||
(key.first)
|
||||
(key.second)
|
||||
(AttrType::String)
|
||||
(s).exec();
|
||||
|
||||
return state->db.getLastInsertedRowId();
|
||||
}
|
||||
|
||||
AttrId setBool(
|
||||
AttrKey key,
|
||||
bool b)
|
||||
{
|
||||
auto state(_state->lock());
|
||||
|
||||
state->insertAttribute.use()
|
||||
(key.first)
|
||||
(key.second)
|
||||
(AttrType::Bool)
|
||||
(b ? 1 : 0).exec();
|
||||
|
||||
return state->db.getLastInsertedRowId();
|
||||
}
|
||||
|
||||
AttrId setPlaceholder(AttrKey key)
|
||||
{
|
||||
auto state(_state->lock());
|
||||
|
||||
state->insertAttribute.use()
|
||||
(key.first)
|
||||
(key.second)
|
||||
(AttrType::Placeholder)
|
||||
(0, false).exec();
|
||||
|
||||
return state->db.getLastInsertedRowId();
|
||||
}
|
||||
|
||||
AttrId setMissing(AttrKey key)
|
||||
{
|
||||
auto state(_state->lock());
|
||||
|
||||
state->insertAttribute.use()
|
||||
(key.first)
|
||||
(key.second)
|
||||
(AttrType::Missing)
|
||||
(0, false).exec();
|
||||
|
||||
return state->db.getLastInsertedRowId();
|
||||
}
|
||||
|
||||
AttrId setMisc(AttrKey key)
|
||||
{
|
||||
auto state(_state->lock());
|
||||
|
||||
state->insertAttribute.use()
|
||||
(key.first)
|
||||
(key.second)
|
||||
(AttrType::Misc)
|
||||
(0, false).exec();
|
||||
|
||||
return state->db.getLastInsertedRowId();
|
||||
}
|
||||
|
||||
AttrId setFailed(AttrKey key)
|
||||
{
|
||||
auto state(_state->lock());
|
||||
|
||||
state->insertAttribute.use()
|
||||
(key.first)
|
||||
(key.second)
|
||||
(AttrType::Failed)
|
||||
(0, false).exec();
|
||||
|
||||
return state->db.getLastInsertedRowId();
|
||||
}
|
||||
|
||||
std::optional<std::pair<AttrId, AttrValue>> getAttr(
|
||||
AttrKey key,
|
||||
SymbolTable & symbols)
|
||||
{
|
||||
auto state(_state->lock());
|
||||
|
||||
auto queryAttribute(state->queryAttribute.use()(key.first)(key.second));
|
||||
if (!queryAttribute.next()) return {};
|
||||
|
||||
auto rowId = (AttrType) queryAttribute.getInt(0);
|
||||
auto type = (AttrType) queryAttribute.getInt(1);
|
||||
|
||||
switch (type) {
|
||||
case AttrType::Placeholder:
|
||||
return {{rowId, placeholder_t()}};
|
||||
case AttrType::FullAttrs: {
|
||||
// FIXME: expensive, should separate this out.
|
||||
std::vector<Symbol> attrs;
|
||||
auto queryAttributes(state->queryAttributes.use()(rowId));
|
||||
while (queryAttributes.next())
|
||||
attrs.push_back(symbols.create(queryAttributes.getStr(0)));
|
||||
return {{rowId, attrs}};
|
||||
}
|
||||
case AttrType::String:
|
||||
return {{rowId, queryAttribute.getStr(2)}};
|
||||
case AttrType::Bool:
|
||||
return {{rowId, queryAttribute.getInt(2) != 0}};
|
||||
case AttrType::Missing:
|
||||
return {{rowId, missing_t()}};
|
||||
case AttrType::Misc:
|
||||
return {{rowId, misc_t()}};
|
||||
case AttrType::Failed:
|
||||
return {{rowId, failed_t()}};
|
||||
default:
|
||||
throw Error("unexpected type in evaluation cache");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
EvalCache::EvalCache(
|
||||
bool useCache,
|
||||
const Hash & fingerprint,
|
||||
EvalState & state,
|
||||
RootLoader rootLoader)
|
||||
: db(useCache ? std::make_shared<AttrDb>(fingerprint) : nullptr)
|
||||
, state(state)
|
||||
, rootLoader(rootLoader)
|
||||
{
|
||||
}
|
||||
|
||||
Value * EvalCache::getRootValue()
|
||||
{
|
||||
if (!value) {
|
||||
debug("getting root value");
|
||||
value = allocRootValue(rootLoader());
|
||||
}
|
||||
return *value;
|
||||
}
|
||||
|
||||
std::shared_ptr<AttrCursor> EvalCache::getRoot()
|
||||
{
|
||||
return std::make_shared<AttrCursor>(ref(shared_from_this()), std::nullopt);
|
||||
}
|
||||
|
||||
AttrCursor::AttrCursor(
|
||||
ref<EvalCache> root,
|
||||
Parent parent,
|
||||
Value * value,
|
||||
std::optional<std::pair<AttrId, AttrValue>> && cachedValue)
|
||||
: root(root), parent(parent), cachedValue(std::move(cachedValue))
|
||||
{
|
||||
if (value)
|
||||
_value = allocRootValue(value);
|
||||
}
|
||||
|
||||
AttrKey AttrCursor::getKey()
|
||||
{
|
||||
if (!parent)
|
||||
return {0, root->state.sEpsilon};
|
||||
if (!parent->first->cachedValue) {
|
||||
parent->first->cachedValue = root->db->getAttr(
|
||||
parent->first->getKey(), root->state.symbols);
|
||||
assert(parent->first->cachedValue);
|
||||
}
|
||||
return {parent->first->cachedValue->first, parent->second};
|
||||
}
|
||||
|
||||
Value & AttrCursor::getValue()
|
||||
{
|
||||
if (!_value) {
|
||||
if (parent) {
|
||||
auto & vParent = parent->first->getValue();
|
||||
root->state.forceAttrs(vParent);
|
||||
auto attr = vParent.attrs->get(parent->second);
|
||||
if (!attr)
|
||||
throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr());
|
||||
_value = allocRootValue(attr->value);
|
||||
} else
|
||||
_value = allocRootValue(root->getRootValue());
|
||||
}
|
||||
return **_value;
|
||||
}
|
||||
|
||||
std::vector<Symbol> AttrCursor::getAttrPath() const
|
||||
{
|
||||
if (parent) {
|
||||
auto attrPath = parent->first->getAttrPath();
|
||||
attrPath.push_back(parent->second);
|
||||
return attrPath;
|
||||
} else
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<Symbol> AttrCursor::getAttrPath(Symbol name) const
|
||||
{
|
||||
auto attrPath = getAttrPath();
|
||||
attrPath.push_back(name);
|
||||
return attrPath;
|
||||
}
|
||||
|
||||
std::string AttrCursor::getAttrPathStr() const
|
||||
{
|
||||
return concatStringsSep(".", getAttrPath());
|
||||
}
|
||||
|
||||
std::string AttrCursor::getAttrPathStr(Symbol name) const
|
||||
{
|
||||
return concatStringsSep(".", getAttrPath(name));
|
||||
}
|
||||
|
||||
Value & AttrCursor::forceValue()
|
||||
{
|
||||
debug("evaluating uncached attribute %s", getAttrPathStr());
|
||||
|
||||
auto & v = getValue();
|
||||
|
||||
try {
|
||||
root->state.forceValue(v);
|
||||
} catch (EvalError &) {
|
||||
debug("setting '%s' to failed", getAttrPathStr());
|
||||
if (root->db)
|
||||
cachedValue = {root->db->setFailed(getKey()), failed_t()};
|
||||
throw;
|
||||
}
|
||||
|
||||
if (root->db && (!cachedValue || std::get_if<placeholder_t>(&cachedValue->second))) {
|
||||
if (v.type == tString)
|
||||
cachedValue = {root->db->setString(getKey(), v.string.s), v.string.s};
|
||||
else if (v.type == tBool)
|
||||
cachedValue = {root->db->setBool(getKey(), v.boolean), v.boolean};
|
||||
else if (v.type == tAttrs)
|
||||
; // FIXME: do something?
|
||||
else
|
||||
cachedValue = {root->db->setMisc(getKey()), misc_t()};
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name)
|
||||
{
|
||||
if (root->db) {
|
||||
if (!cachedValue)
|
||||
cachedValue = root->db->getAttr(getKey(), root->state.symbols);
|
||||
|
||||
if (cachedValue) {
|
||||
if (auto attrs = std::get_if<std::vector<Symbol>>(&cachedValue->second)) {
|
||||
for (auto & attr : *attrs)
|
||||
if (attr == name)
|
||||
return std::make_shared<AttrCursor>(root, std::make_pair(shared_from_this(), name));
|
||||
return nullptr;
|
||||
} else if (std::get_if<placeholder_t>(&cachedValue->second)) {
|
||||
auto attr = root->db->getAttr({cachedValue->first, name}, root->state.symbols);
|
||||
if (attr) {
|
||||
if (std::get_if<missing_t>(&attr->second))
|
||||
return nullptr;
|
||||
else if (std::get_if<failed_t>(&attr->second))
|
||||
throw EvalError("cached failure of attribute '%s'", getAttrPathStr(name));
|
||||
else
|
||||
return std::make_shared<AttrCursor>(root,
|
||||
std::make_pair(shared_from_this(), name), nullptr, std::move(attr));
|
||||
}
|
||||
// Incomplete attrset, so need to fall thru and
|
||||
// evaluate to see whether 'name' exists
|
||||
} else
|
||||
return nullptr;
|
||||
//throw TypeError("'%s' is not an attribute set", getAttrPathStr());
|
||||
}
|
||||
}
|
||||
|
||||
auto & v = forceValue();
|
||||
|
||||
if (v.type != tAttrs)
|
||||
return nullptr;
|
||||
//throw TypeError("'%s' is not an attribute set", getAttrPathStr());
|
||||
|
||||
auto attr = v.attrs->get(name);
|
||||
|
||||
if (!attr) {
|
||||
if (root->db) {
|
||||
if (!cachedValue)
|
||||
cachedValue = {root->db->setPlaceholder(getKey()), placeholder_t()};
|
||||
root->db->setMissing({cachedValue->first, name});
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::optional<std::pair<AttrId, AttrValue>> cachedValue2;
|
||||
if (root->db) {
|
||||
if (!cachedValue)
|
||||
cachedValue = {root->db->setPlaceholder(getKey()), placeholder_t()};
|
||||
cachedValue2 = {root->db->setPlaceholder({cachedValue->first, name}), placeholder_t()};
|
||||
}
|
||||
|
||||
return std::make_shared<AttrCursor>(
|
||||
root, std::make_pair(shared_from_this(), name), attr->value, std::move(cachedValue2));
|
||||
}
|
||||
|
||||
std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(std::string_view name)
|
||||
{
|
||||
return maybeGetAttr(root->state.symbols.create(name));
|
||||
}
|
||||
|
||||
std::shared_ptr<AttrCursor> AttrCursor::getAttr(Symbol name)
|
||||
{
|
||||
auto p = maybeGetAttr(name);
|
||||
if (!p)
|
||||
throw Error("attribute '%s' does not exist", getAttrPathStr(name));
|
||||
return p;
|
||||
}
|
||||
|
||||
std::shared_ptr<AttrCursor> AttrCursor::getAttr(std::string_view name)
|
||||
{
|
||||
return getAttr(root->state.symbols.create(name));
|
||||
}
|
||||
|
||||
std::shared_ptr<AttrCursor> AttrCursor::findAlongAttrPath(const std::vector<Symbol> & attrPath)
|
||||
{
|
||||
auto res = shared_from_this();
|
||||
for (auto & attr : attrPath) {
|
||||
res = res->maybeGetAttr(attr);
|
||||
if (!res) return {};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string AttrCursor::getString()
|
||||
{
|
||||
if (root->db) {
|
||||
if (!cachedValue)
|
||||
cachedValue = root->db->getAttr(getKey(), root->state.symbols);
|
||||
if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
|
||||
if (auto s = std::get_if<std::string>(&cachedValue->second)) {
|
||||
debug("using cached string attribute '%s'", getAttrPathStr());
|
||||
return *s;
|
||||
} else
|
||||
throw TypeError("'%s' is not a string", getAttrPathStr());
|
||||
}
|
||||
}
|
||||
|
||||
auto & v = forceValue();
|
||||
|
||||
if (v.type != tString)
|
||||
throw TypeError("'%s' is not a string", getAttrPathStr());
|
||||
|
||||
return v.string.s;
|
||||
}
|
||||
|
||||
bool AttrCursor::getBool()
|
||||
{
|
||||
if (root->db) {
|
||||
if (!cachedValue)
|
||||
cachedValue = root->db->getAttr(getKey(), root->state.symbols);
|
||||
if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
|
||||
if (auto b = std::get_if<bool>(&cachedValue->second)) {
|
||||
debug("using cached Boolean attribute '%s'", getAttrPathStr());
|
||||
return *b;
|
||||
} else
|
||||
throw TypeError("'%s' is not a Boolean", getAttrPathStr());
|
||||
}
|
||||
}
|
||||
|
||||
auto & v = forceValue();
|
||||
|
||||
if (v.type != tBool)
|
||||
throw TypeError("'%s' is not a Boolean", getAttrPathStr());
|
||||
|
||||
return v.boolean;
|
||||
}
|
||||
|
||||
std::vector<Symbol> AttrCursor::getAttrs()
|
||||
{
|
||||
if (root->db) {
|
||||
if (!cachedValue)
|
||||
cachedValue = root->db->getAttr(getKey(), root->state.symbols);
|
||||
if (cachedValue && !std::get_if<placeholder_t>(&cachedValue->second)) {
|
||||
if (auto attrs = std::get_if<std::vector<Symbol>>(&cachedValue->second)) {
|
||||
debug("using cached attrset attribute '%s'", getAttrPathStr());
|
||||
return *attrs;
|
||||
} else
|
||||
throw TypeError("'%s' is not an attribute set", getAttrPathStr());
|
||||
}
|
||||
}
|
||||
|
||||
auto & v = forceValue();
|
||||
|
||||
if (v.type != tAttrs)
|
||||
throw TypeError("'%s' is not an attribute set", getAttrPathStr());
|
||||
|
||||
std::vector<Symbol> attrs;
|
||||
for (auto & attr : *getValue().attrs)
|
||||
attrs.push_back(attr.name);
|
||||
std::sort(attrs.begin(), attrs.end(), [](const Symbol & a, const Symbol & b) {
|
||||
return (const string &) a < (const string &) b;
|
||||
});
|
||||
|
||||
if (root->db)
|
||||
cachedValue = {root->db->setAttrs(getKey(), attrs), attrs};
|
||||
|
||||
return attrs;
|
||||
}
|
||||
|
||||
bool AttrCursor::isDerivation()
|
||||
{
|
||||
auto aType = maybeGetAttr("type");
|
||||
return aType && aType->getString() == "derivation";
|
||||
}
|
||||
|
||||
}
|
||||
106
src/libexpr/eval-cache.hh
Normal file
106
src/libexpr/eval-cache.hh
Normal file
@@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
|
||||
#include "sync.hh"
|
||||
#include "hash.hh"
|
||||
#include "eval.hh"
|
||||
|
||||
#include <variant>
|
||||
|
||||
namespace nix::eval_cache {
|
||||
|
||||
class AttrDb;
|
||||
class AttrCursor;
|
||||
|
||||
class EvalCache : public std::enable_shared_from_this<EvalCache>
|
||||
{
|
||||
friend class AttrCursor;
|
||||
|
||||
std::shared_ptr<AttrDb> db;
|
||||
EvalState & state;
|
||||
typedef std::function<Value *()> RootLoader;
|
||||
RootLoader rootLoader;
|
||||
RootValue value;
|
||||
|
||||
Value * getRootValue();
|
||||
|
||||
public:
|
||||
|
||||
EvalCache(
|
||||
bool useCache,
|
||||
const Hash & fingerprint,
|
||||
EvalState & state,
|
||||
RootLoader rootLoader);
|
||||
|
||||
std::shared_ptr<AttrCursor> getRoot();
|
||||
};
|
||||
|
||||
enum AttrType {
|
||||
Placeholder = 0,
|
||||
FullAttrs = 1,
|
||||
String = 2,
|
||||
Missing = 3,
|
||||
Misc = 4,
|
||||
Failed = 5,
|
||||
Bool = 6,
|
||||
};
|
||||
|
||||
struct placeholder_t {};
|
||||
struct missing_t {};
|
||||
struct misc_t {};
|
||||
struct failed_t {};
|
||||
typedef uint64_t AttrId;
|
||||
typedef std::pair<AttrId, Symbol> AttrKey;
|
||||
typedef std::variant<std::vector<Symbol>, std::string, placeholder_t, missing_t, misc_t, failed_t, bool> AttrValue;
|
||||
|
||||
class AttrCursor : public std::enable_shared_from_this<AttrCursor>
|
||||
{
|
||||
friend class EvalCache;
|
||||
|
||||
ref<EvalCache> root;
|
||||
typedef std::optional<std::pair<std::shared_ptr<AttrCursor>, Symbol>> Parent;
|
||||
Parent parent;
|
||||
RootValue _value;
|
||||
std::optional<std::pair<AttrId, AttrValue>> cachedValue;
|
||||
|
||||
AttrKey getKey();
|
||||
|
||||
Value & getValue();
|
||||
|
||||
public:
|
||||
|
||||
AttrCursor(
|
||||
ref<EvalCache> root,
|
||||
Parent parent,
|
||||
Value * value = nullptr,
|
||||
std::optional<std::pair<AttrId, AttrValue>> && cachedValue = {});
|
||||
|
||||
std::vector<Symbol> getAttrPath() const;
|
||||
|
||||
std::vector<Symbol> getAttrPath(Symbol name) const;
|
||||
|
||||
std::string getAttrPathStr() const;
|
||||
|
||||
std::string getAttrPathStr(Symbol name) const;
|
||||
|
||||
std::shared_ptr<AttrCursor> maybeGetAttr(Symbol name);
|
||||
|
||||
std::shared_ptr<AttrCursor> maybeGetAttr(std::string_view name);
|
||||
|
||||
std::shared_ptr<AttrCursor> getAttr(Symbol name);
|
||||
|
||||
std::shared_ptr<AttrCursor> getAttr(std::string_view name);
|
||||
|
||||
std::shared_ptr<AttrCursor> findAlongAttrPath(const std::vector<Symbol> & attrPath);
|
||||
|
||||
std::string getString();
|
||||
|
||||
bool getBool();
|
||||
|
||||
std::vector<Symbol> getAttrs();
|
||||
|
||||
bool isDerivation();
|
||||
|
||||
Value & forceValue();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -7,26 +7,20 @@
|
||||
|
||||
namespace nix {
|
||||
|
||||
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s))
|
||||
LocalNoInlineNoReturn(void throwEvalError(const char * s, const Pos & pos))
|
||||
{
|
||||
throw EvalError({
|
||||
.hint = hintfmt(s),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format(s) % pos);
|
||||
}
|
||||
|
||||
LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v))
|
||||
{
|
||||
throw TypeError(s, showType(v));
|
||||
throw TypeError(format(s) % showType(v));
|
||||
}
|
||||
|
||||
|
||||
LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const Value & v))
|
||||
LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v, const Pos & pos))
|
||||
{
|
||||
throw TypeError({
|
||||
.hint = hintfmt(s, showType(v)),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw TypeError(format(s) % showType(v) % pos);
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +43,7 @@ void EvalState::forceValue(Value & v, const Pos & pos)
|
||||
else if (v.type == tApp)
|
||||
callFunction(*v.app.left, *v.app.right, v, noPos);
|
||||
else if (v.type == tBlackhole)
|
||||
throwEvalError(pos, "infinite recursion encountered");
|
||||
throwEvalError("infinite recursion encountered, at %1%", pos);
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +59,7 @@ inline void EvalState::forceAttrs(Value & v, const Pos & pos)
|
||||
{
|
||||
forceValue(v, pos);
|
||||
if (v.type != tAttrs)
|
||||
throwTypeError(pos, "value is %1% while a set was expected", v);
|
||||
throwTypeError("value is %1% while a set was expected, at %2%", v, pos);
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +75,7 @@ inline void EvalState::forceList(Value & v, const Pos & pos)
|
||||
{
|
||||
forceValue(v, pos);
|
||||
if (!v.isList())
|
||||
throwTypeError(pos, "value is %1% while a list was expected", v);
|
||||
throwTypeError("value is %1% while a list was expected, at %2%", v, pos);
|
||||
}
|
||||
|
||||
/* Note: Various places expect the allocated memory to be zeroed. */
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "filetransfer.hh"
|
||||
#include "json.hh"
|
||||
#include "function-trace.hh"
|
||||
#include "flake/flake.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
@@ -199,6 +200,18 @@ string showType(const Value & v)
|
||||
}
|
||||
|
||||
|
||||
bool Value::isTrivial() const
|
||||
{
|
||||
return
|
||||
type != tApp
|
||||
&& type != tPrimOpApp
|
||||
&& (type != tThunk
|
||||
|| (dynamic_cast<ExprAttrs *>(thunk.expr)
|
||||
&& ((ExprAttrs *) thunk.expr)->dynamicAttrs.empty())
|
||||
|| dynamic_cast<ExprLambda *>(thunk.expr));
|
||||
}
|
||||
|
||||
|
||||
#if HAVE_BOEHMGC
|
||||
/* Called when the Boehm GC runs out of memory. */
|
||||
static void * oomHandler(size_t requested)
|
||||
@@ -336,6 +349,9 @@ EvalState::EvalState(const Strings & _searchPath, ref<Store> store)
|
||||
, sOutputHash(symbols.create("outputHash"))
|
||||
, sOutputHashAlgo(symbols.create("outputHashAlgo"))
|
||||
, sOutputHashMode(symbols.create("outputHashMode"))
|
||||
, sDescription(symbols.create("description"))
|
||||
, sSelf(symbols.create("self"))
|
||||
, sEpsilon(symbols.create(""))
|
||||
, sRecurseForDerivations(symbols.create("recurseForDerivations"))
|
||||
, repair(NoRepair)
|
||||
, store(store)
|
||||
@@ -522,74 +538,52 @@ Value & EvalState::getBuiltin(const string & name)
|
||||
|
||||
LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2))
|
||||
{
|
||||
throw EvalError(s, s2);
|
||||
throw EvalError(format(s) % s2);
|
||||
}
|
||||
|
||||
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const string & s2))
|
||||
LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, const Pos & pos))
|
||||
{
|
||||
throw EvalError({
|
||||
.hint = hintfmt(s, s2),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format(s) % s2 % pos);
|
||||
}
|
||||
|
||||
LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, const string & s3))
|
||||
{
|
||||
throw EvalError(s, s2, s3);
|
||||
throw EvalError(format(s) % s2 % s3);
|
||||
}
|
||||
|
||||
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const string & s2, const string & s3))
|
||||
LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, const string & s3, const Pos & pos))
|
||||
{
|
||||
throw EvalError({
|
||||
.hint = hintfmt(s, s2, s3),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format(s) % s2 % s3 % pos);
|
||||
}
|
||||
|
||||
LocalNoInlineNoReturn(void throwEvalError(const Pos & p1, const char * s, const Symbol & sym, const Pos & p2))
|
||||
LocalNoInlineNoReturn(void throwEvalError(const char * s, const Symbol & sym, const Pos & p1, const Pos & p2))
|
||||
{
|
||||
// p1 is where the error occurred; p2 is a position mentioned in the message.
|
||||
throw EvalError({
|
||||
.hint = hintfmt(s, sym, p2),
|
||||
.nixCode = NixCode { .errPos = p1 }
|
||||
});
|
||||
throw EvalError(format(s) % sym % p1 % p2);
|
||||
}
|
||||
|
||||
LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s))
|
||||
LocalNoInlineNoReturn(void throwTypeError(const char * s, const Pos & pos))
|
||||
{
|
||||
throw TypeError({
|
||||
.hint = hintfmt(s),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw TypeError(format(s) % pos);
|
||||
}
|
||||
|
||||
LocalNoInlineNoReturn(void throwTypeError(const char * s, const string & s1))
|
||||
{
|
||||
throw TypeError(s, s1);
|
||||
throw TypeError(format(s) % s1);
|
||||
}
|
||||
|
||||
LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const ExprLambda & fun, const Symbol & s2))
|
||||
LocalNoInlineNoReturn(void throwTypeError(const char * s, const ExprLambda & fun, const Symbol & s2, const Pos & pos))
|
||||
{
|
||||
throw TypeError({
|
||||
.hint = hintfmt(s, fun.showNamePos(), s2),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw TypeError(format(s) % fun.showNamePos() % s2 % pos);
|
||||
}
|
||||
|
||||
LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s, const string & s1))
|
||||
LocalNoInlineNoReturn(void throwAssertionError(const char * s, const string & s1, const Pos & pos))
|
||||
{
|
||||
throw AssertionError({
|
||||
.hint = hintfmt(s, s1),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw AssertionError(format(s) % s1 % pos);
|
||||
}
|
||||
|
||||
LocalNoInlineNoReturn(void throwUndefinedVarError(const Pos & pos, const char * s, const string & s1))
|
||||
LocalNoInlineNoReturn(void throwUndefinedVarError(const char * s, const string & s1, const Pos & pos))
|
||||
{
|
||||
throw UndefinedVarError({
|
||||
.hint = hintfmt(s, s1),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw UndefinedVarError(format(s) % s1 % pos);
|
||||
}
|
||||
|
||||
LocalNoInline(void addErrorPrefix(Error & e, const char * s, const string & s2))
|
||||
@@ -657,7 +651,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
|
||||
return j->value;
|
||||
}
|
||||
if (!env->prevWith)
|
||||
throwUndefinedVarError(var.pos, "undefined variable '%1%'", var.name);
|
||||
throwUndefinedVarError("undefined variable '%1%' at %2%", var.name, var.pos);
|
||||
for (size_t l = env->prevWith; l; --l, env = env->up) ;
|
||||
}
|
||||
}
|
||||
@@ -787,7 +781,7 @@ Value * ExprPath::maybeThunk(EvalState & state, Env & env)
|
||||
}
|
||||
|
||||
|
||||
void EvalState::evalFile(const Path & path_, Value & v)
|
||||
void EvalState::evalFile(const Path & path_, Value & v, bool mustBeTrivial)
|
||||
{
|
||||
auto path = checkSourcePath(path_);
|
||||
|
||||
@@ -816,6 +810,11 @@ void EvalState::evalFile(const Path & path_, Value & v)
|
||||
fileParseCache[path2] = e;
|
||||
|
||||
try {
|
||||
// Enforce that 'flake.nix' is a direct attrset, not a
|
||||
// computation.
|
||||
if (mustBeTrivial &&
|
||||
!(dynamic_cast<ExprAttrs *>(e)))
|
||||
throw Error("file '%s' must be an attribute set", path);
|
||||
eval(e, v);
|
||||
} catch (Error & e) {
|
||||
addErrorPrefix(e, "while evaluating the file '%1%':\n", path2);
|
||||
@@ -855,7 +854,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos)
|
||||
Value v;
|
||||
e->eval(*this, env, v);
|
||||
if (v.type != tBool)
|
||||
throwTypeError(pos, "value is %1% while a Boolean was expected", v);
|
||||
throwTypeError("value is %1% while a Boolean was expected, at %2%", v, pos);
|
||||
return v.boolean;
|
||||
}
|
||||
|
||||
@@ -969,7 +968,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
|
||||
Symbol nameSym = state.symbols.create(nameVal.string.s);
|
||||
Bindings::iterator j = v.attrs->find(nameSym);
|
||||
if (j != v.attrs->end())
|
||||
throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, *j->pos);
|
||||
throwEvalError("dynamic attribute '%1%' at %2% already defined at %3%", nameSym, i.pos, *j->pos);
|
||||
|
||||
i.valueExpr->setName(nameSym);
|
||||
/* Keep sorted order so find can catch duplicates */
|
||||
@@ -1057,7 +1056,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
|
||||
} else {
|
||||
state.forceAttrs(*vAttrs, pos);
|
||||
if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
|
||||
throwEvalError(pos, "attribute '%1%' missing", name);
|
||||
throwEvalError("attribute '%1%' missing, at %2%", name, pos);
|
||||
}
|
||||
vAttrs = j->value;
|
||||
pos2 = j->pos;
|
||||
@@ -1183,7 +1182,7 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & po
|
||||
}
|
||||
|
||||
if (fun.type != tLambda)
|
||||
throwTypeError(pos, "attempt to call something which is not a function but %1%", fun);
|
||||
throwTypeError("attempt to call something which is not a function but %1%, at %2%", fun, pos);
|
||||
|
||||
ExprLambda & lambda(*fun.lambda.fun);
|
||||
|
||||
@@ -1211,8 +1210,8 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & po
|
||||
for (auto & i : lambda.formals->formals) {
|
||||
Bindings::iterator j = arg.attrs->find(i.name);
|
||||
if (j == arg.attrs->end()) {
|
||||
if (!i.def) throwTypeError(pos, "%1% called without required argument '%2%'",
|
||||
lambda, i.name);
|
||||
if (!i.def) throwTypeError("%1% called without required argument '%2%', at %3%",
|
||||
lambda, i.name, pos);
|
||||
env2.values[displ++] = i.def->maybeThunk(*this, env2);
|
||||
} else {
|
||||
attrsUsed++;
|
||||
@@ -1227,7 +1226,7 @@ void EvalState::callFunction(Value & fun, Value & arg, Value & v, const Pos & po
|
||||
user. */
|
||||
for (auto & i : *arg.attrs)
|
||||
if (lambda.formals->argNames.find(i.name) == lambda.formals->argNames.end())
|
||||
throwTypeError(pos, "%1% called with unexpected argument '%2%'", lambda, i.name);
|
||||
throwTypeError("%1% called with unexpected argument '%2%', at %3%", lambda, i.name, pos);
|
||||
abort(); // can't happen
|
||||
}
|
||||
}
|
||||
@@ -1316,7 +1315,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v)
|
||||
if (!state.evalBool(env, cond, pos)) {
|
||||
std::ostringstream out;
|
||||
cond->show(out);
|
||||
throwAssertionError(pos, "assertion '%1%' failed at %2%", out.str());
|
||||
throwAssertionError("assertion '%1%' failed at %2%", out.str(), pos);
|
||||
}
|
||||
body->eval(state, env, v);
|
||||
}
|
||||
@@ -1468,14 +1467,14 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
||||
nf = n;
|
||||
nf += vTmp.fpoint;
|
||||
} else
|
||||
throwEvalError(pos, "cannot add %1% to an integer", showType(vTmp));
|
||||
throwEvalError("cannot add %1% to an integer, at %2%", showType(vTmp), pos);
|
||||
} else if (firstType == tFloat) {
|
||||
if (vTmp.type == tInt) {
|
||||
nf += vTmp.integer;
|
||||
} else if (vTmp.type == tFloat) {
|
||||
nf += vTmp.fpoint;
|
||||
} else
|
||||
throwEvalError(pos, "cannot add %1% to a float", showType(vTmp));
|
||||
throwEvalError("cannot add %1% to a float, at %2%", showType(vTmp), pos);
|
||||
} else
|
||||
s << state.coerceToString(pos, vTmp, context, false, firstType == tString);
|
||||
}
|
||||
@@ -1486,7 +1485,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
||||
mkFloat(v, nf);
|
||||
else if (firstType == tPath) {
|
||||
if (!context.empty())
|
||||
throwEvalError(pos, "a string that refers to a store path cannot be appended to a path");
|
||||
throwEvalError("a string that refers to a store path cannot be appended to a path, at %1%", pos);
|
||||
auto path = canonPath(s.str());
|
||||
mkPath(v, path.c_str());
|
||||
} else
|
||||
@@ -1535,7 +1534,7 @@ NixInt EvalState::forceInt(Value & v, const Pos & pos)
|
||||
{
|
||||
forceValue(v, pos);
|
||||
if (v.type != tInt)
|
||||
throwTypeError(pos, "value is %1% while an integer was expected", v);
|
||||
throwTypeError("value is %1% while an integer was expected, at %2%", v, pos);
|
||||
return v.integer;
|
||||
}
|
||||
|
||||
@@ -1546,7 +1545,7 @@ NixFloat EvalState::forceFloat(Value & v, const Pos & pos)
|
||||
if (v.type == tInt)
|
||||
return v.integer;
|
||||
else if (v.type != tFloat)
|
||||
throwTypeError(pos, "value is %1% while a float was expected", v);
|
||||
throwTypeError("value is %1% while a float was expected, at %2%", v, pos);
|
||||
return v.fpoint;
|
||||
}
|
||||
|
||||
@@ -1555,7 +1554,7 @@ bool EvalState::forceBool(Value & v, const Pos & pos)
|
||||
{
|
||||
forceValue(v, pos);
|
||||
if (v.type != tBool)
|
||||
throwTypeError(pos, "value is %1% while a Boolean was expected", v);
|
||||
throwTypeError("value is %1% while a Boolean was expected, at %2%", v, pos);
|
||||
return v.boolean;
|
||||
}
|
||||
|
||||
@@ -1570,7 +1569,7 @@ void EvalState::forceFunction(Value & v, const Pos & pos)
|
||||
{
|
||||
forceValue(v, pos);
|
||||
if (v.type != tLambda && v.type != tPrimOp && v.type != tPrimOpApp && !isFunctor(v))
|
||||
throwTypeError(pos, "value is %1% while a function was expected", v);
|
||||
throwTypeError("value is %1% while a function was expected, at %2%", v, pos);
|
||||
}
|
||||
|
||||
|
||||
@@ -1579,7 +1578,7 @@ string EvalState::forceString(Value & v, const Pos & pos)
|
||||
forceValue(v, pos);
|
||||
if (v.type != tString) {
|
||||
if (pos)
|
||||
throwTypeError(pos, "value is %1% while a string was expected", v);
|
||||
throwTypeError("value is %1% while a string was expected, at %2%", v, pos);
|
||||
else
|
||||
throwTypeError("value is %1% while a string was expected", v);
|
||||
}
|
||||
@@ -1608,8 +1607,8 @@ string EvalState::forceStringNoCtx(Value & v, const Pos & pos)
|
||||
string s = forceString(v, pos);
|
||||
if (v.string.context) {
|
||||
if (pos)
|
||||
throwEvalError(pos, "the string '%1%' is not allowed to refer to a store path (such as '%2%')",
|
||||
v.string.s, v.string.context[0]);
|
||||
throwEvalError("the string '%1%' is not allowed to refer to a store path (such as '%2%'), at %3%",
|
||||
v.string.s, v.string.context[0], pos);
|
||||
else
|
||||
throwEvalError("the string '%1%' is not allowed to refer to a store path (such as '%2%')",
|
||||
v.string.s, v.string.context[0]);
|
||||
@@ -1665,7 +1664,7 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context,
|
||||
return *maybeString;
|
||||
}
|
||||
auto i = v.attrs->find(sOutPath);
|
||||
if (i == v.attrs->end()) throwTypeError(pos, "cannot coerce a set to a string");
|
||||
if (i == v.attrs->end()) throwTypeError("cannot coerce a set to a string, at %1%", pos);
|
||||
return coerceToString(pos, *i->value, context, coerceMore, copyToStore);
|
||||
}
|
||||
|
||||
@@ -1696,7 +1695,7 @@ string EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context,
|
||||
}
|
||||
}
|
||||
|
||||
throwTypeError(pos, "cannot coerce %1% to a string", v);
|
||||
throwTypeError("cannot coerce %1% to a string, at %2%", v, pos);
|
||||
}
|
||||
|
||||
|
||||
@@ -1712,7 +1711,7 @@ string EvalState::copyPathToStore(PathSet & context, const Path & path)
|
||||
else {
|
||||
auto p = settings.readOnlyMode
|
||||
? store->computeStorePathForPath(std::string(baseNameOf(path)), checkSourcePath(path)).first
|
||||
: store->addToStore(std::string(baseNameOf(path)), checkSourcePath(path), FileIngestionMethod::Recursive, htSHA256, defaultPathFilter, repair);
|
||||
: store->addToStore(std::string(baseNameOf(path)), checkSourcePath(path), true, htSHA256, defaultPathFilter, repair);
|
||||
dstPath = store->printStorePath(p);
|
||||
srcToStore.insert_or_assign(path, std::move(p));
|
||||
printMsg(lvlChatty, "copied source '%1%' -> '%2%'", path, dstPath);
|
||||
@@ -1727,7 +1726,7 @@ Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context)
|
||||
{
|
||||
string path = coerceToString(pos, v, context, false, false);
|
||||
if (path == "" || path[0] != '/')
|
||||
throwEvalError(pos, "string '%1%' doesn't represent an absolute path", path);
|
||||
throwEvalError("string '%1%' doesn't represent an absolute path, at %2%", path, pos);
|
||||
return path;
|
||||
}
|
||||
|
||||
@@ -1934,10 +1933,8 @@ void EvalState::printStats()
|
||||
|
||||
string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const
|
||||
{
|
||||
throw TypeError({
|
||||
.hint = hintfmt("cannot coerce %1% to a string", showType()),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw TypeError(format("cannot coerce %1% to a string, at %2%") %
|
||||
showType() % pos);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
#include "value.hh"
|
||||
#include "nixexpr.hh"
|
||||
#include "symbol-table.hh"
|
||||
#include "hash.hh"
|
||||
#include "config.hh"
|
||||
|
||||
#include <regex>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
|
||||
|
||||
namespace nix {
|
||||
@@ -18,7 +18,7 @@ namespace nix {
|
||||
|
||||
class Store;
|
||||
class EvalState;
|
||||
class StorePath;
|
||||
struct StorePath;
|
||||
enum RepairFlag : bool;
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ public:
|
||||
sFile, sLine, sColumn, sFunctor, sToString,
|
||||
sRight, sWrong, sStructuredAttrs, sBuilder, sArgs,
|
||||
sOutputHash, sOutputHashAlgo, sOutputHashMode,
|
||||
sRecurseForDerivations;
|
||||
sDescription, sSelf, sEpsilon, sRecurseForDerivations;
|
||||
Symbol sDerivationNix;
|
||||
|
||||
/* If set, force copying files to the Nix store even if they
|
||||
@@ -90,6 +90,7 @@ public:
|
||||
|
||||
const ref<Store> store;
|
||||
|
||||
|
||||
private:
|
||||
SrcToStore srcToStore;
|
||||
|
||||
@@ -152,8 +153,9 @@ public:
|
||||
Expr * parseStdin();
|
||||
|
||||
/* Evaluate an expression read from the given file to normal
|
||||
form. */
|
||||
void evalFile(const Path & path, Value & v);
|
||||
form. Optionally enforce that the top-level expression is
|
||||
trivial (i.e. doesn't require arbitrary computation). */
|
||||
void evalFile(const Path & path, Value & v, bool mustBeTrivial = false);
|
||||
|
||||
void resetFileCache();
|
||||
|
||||
|
||||
29
src/libexpr/flake/call-flake.nix
Normal file
29
src/libexpr/flake/call-flake.nix
Normal file
@@ -0,0 +1,29 @@
|
||||
lockFileStr: rootSrc: rootSubdir:
|
||||
|
||||
let
|
||||
|
||||
lockFile = builtins.fromJSON lockFileStr;
|
||||
|
||||
allNodes =
|
||||
builtins.mapAttrs
|
||||
(key: node:
|
||||
let
|
||||
sourceInfo =
|
||||
if key == lockFile.root
|
||||
then rootSrc
|
||||
else fetchTree ({ inherit (node.info) narHash; } // removeAttrs node.locked ["dir"]);
|
||||
subdir = if key == lockFile.root then rootSubdir else node.locked.dir or "";
|
||||
flake = import (sourceInfo + (if subdir != "" then "/" else "") + subdir + "/flake.nix");
|
||||
inputs = builtins.mapAttrs (inputName: key: allNodes.${key}) (node.inputs or {});
|
||||
outputs = flake.outputs (inputs // { self = result; });
|
||||
result = outputs // sourceInfo // { inherit inputs; inherit outputs; inherit sourceInfo; };
|
||||
in
|
||||
if node.flake or true then
|
||||
assert builtins.isFunction flake.outputs;
|
||||
result
|
||||
else
|
||||
sourceInfo
|
||||
)
|
||||
lockFile.nodes;
|
||||
|
||||
in allNodes.${lockFile.root}
|
||||
660
src/libexpr/flake/flake.cc
Normal file
660
src/libexpr/flake/flake.cc
Normal file
@@ -0,0 +1,660 @@
|
||||
#include "flake.hh"
|
||||
#include "lockfile.hh"
|
||||
#include "primops.hh"
|
||||
#include "eval-inline.hh"
|
||||
#include "store-api.hh"
|
||||
#include "fetchers.hh"
|
||||
#include "finally.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
using namespace flake;
|
||||
|
||||
namespace flake {
|
||||
|
||||
/* If 'allowLookup' is true, then resolve 'flakeRef' using the
|
||||
registries. */
|
||||
static FlakeRef maybeLookupFlake(
|
||||
ref<Store> store,
|
||||
const FlakeRef & flakeRef,
|
||||
bool allowLookup)
|
||||
{
|
||||
if (!flakeRef.input->isDirect()) {
|
||||
if (allowLookup)
|
||||
return flakeRef.resolve(store);
|
||||
else
|
||||
throw Error("'%s' is an indirect flake reference, but registry lookups are not allowed", flakeRef);
|
||||
} else
|
||||
return flakeRef;
|
||||
}
|
||||
|
||||
typedef std::vector<std::pair<FlakeRef, FlakeRef>> FlakeCache;
|
||||
|
||||
static FlakeRef lookupInFlakeCache(
|
||||
const FlakeCache & flakeCache,
|
||||
const FlakeRef & flakeRef)
|
||||
{
|
||||
// FIXME: inefficient.
|
||||
for (auto & i : flakeCache) {
|
||||
if (flakeRef == i.first) {
|
||||
debug("mapping '%s' to previously seen input '%s' -> '%s",
|
||||
flakeRef, i.first, i.second);
|
||||
return i.second;
|
||||
}
|
||||
}
|
||||
|
||||
return flakeRef;
|
||||
}
|
||||
|
||||
static std::tuple<fetchers::Tree, FlakeRef, FlakeRef> fetchOrSubstituteTree(
|
||||
EvalState & state,
|
||||
const FlakeRef & originalRef,
|
||||
std::optional<TreeInfo> treeInfo,
|
||||
bool allowLookup,
|
||||
FlakeCache & flakeCache)
|
||||
{
|
||||
/* The tree may already be in the Nix store, or it could be
|
||||
substituted (which is often faster than fetching from the
|
||||
original source). So check that. */
|
||||
if (treeInfo && originalRef.input->isDirect() && originalRef.input->isImmutable()) {
|
||||
try {
|
||||
auto storePath = treeInfo->computeStorePath(*state.store);
|
||||
|
||||
state.store->ensurePath(storePath);
|
||||
|
||||
debug("using substituted/cached input '%s' in '%s'",
|
||||
originalRef, state.store->printStorePath(storePath));
|
||||
|
||||
auto actualPath = state.store->toRealPath(storePath);
|
||||
|
||||
if (state.allowedPaths)
|
||||
state.allowedPaths->insert(actualPath);
|
||||
|
||||
return {
|
||||
Tree {
|
||||
.actualPath = actualPath,
|
||||
.storePath = std::move(storePath),
|
||||
.info = *treeInfo,
|
||||
},
|
||||
originalRef,
|
||||
originalRef
|
||||
};
|
||||
} catch (Error & e) {
|
||||
debug("substitution of input '%s' failed: %s", originalRef, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
auto resolvedRef = lookupInFlakeCache(flakeCache,
|
||||
maybeLookupFlake(state.store,
|
||||
lookupInFlakeCache(flakeCache, originalRef), allowLookup));
|
||||
|
||||
auto [tree, lockedRef] = resolvedRef.fetchTree(state.store);
|
||||
|
||||
debug("got tree '%s' from '%s'",
|
||||
state.store->printStorePath(tree.storePath), lockedRef);
|
||||
|
||||
flakeCache.push_back({originalRef, lockedRef});
|
||||
flakeCache.push_back({resolvedRef, lockedRef});
|
||||
|
||||
if (state.allowedPaths)
|
||||
state.allowedPaths->insert(tree.actualPath);
|
||||
|
||||
if (treeInfo)
|
||||
assert(tree.storePath == treeInfo->computeStorePath(*state.store));
|
||||
|
||||
return {std::move(tree), resolvedRef, lockedRef};
|
||||
}
|
||||
|
||||
static void expectType(EvalState & state, ValueType type,
|
||||
Value & value, const Pos & pos)
|
||||
{
|
||||
if (value.type == tThunk && value.isTrivial())
|
||||
state.forceValue(value, pos);
|
||||
if (value.type != type)
|
||||
throw Error("expected %s but got %s at %s",
|
||||
showType(type), showType(value.type), pos);
|
||||
}
|
||||
|
||||
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
|
||||
EvalState & state, Value * value, const Pos & pos);
|
||||
|
||||
static FlakeInput parseFlakeInput(EvalState & state,
|
||||
const std::string & inputName, Value * value, const Pos & pos)
|
||||
{
|
||||
expectType(state, tAttrs, *value, pos);
|
||||
|
||||
FlakeInput input {
|
||||
.ref = FlakeRef::fromAttrs({{"type", "indirect"}, {"id", inputName}})
|
||||
};
|
||||
|
||||
auto sInputs = state.symbols.create("inputs");
|
||||
auto sUrl = state.symbols.create("url");
|
||||
auto sFlake = state.symbols.create("flake");
|
||||
auto sFollows = state.symbols.create("follows");
|
||||
|
||||
fetchers::Attrs attrs;
|
||||
std::optional<std::string> url;
|
||||
|
||||
for (nix::Attr attr : *(value->attrs)) {
|
||||
try {
|
||||
if (attr.name == sUrl) {
|
||||
expectType(state, tString, *attr.value, *attr.pos);
|
||||
url = attr.value->string.s;
|
||||
attrs.emplace("url", *url);
|
||||
} else if (attr.name == sFlake) {
|
||||
expectType(state, tBool, *attr.value, *attr.pos);
|
||||
input.isFlake = attr.value->boolean;
|
||||
} else if (attr.name == sInputs) {
|
||||
input.overrides = parseFlakeInputs(state, attr.value, *attr.pos);
|
||||
} else if (attr.name == sFollows) {
|
||||
expectType(state, tString, *attr.value, *attr.pos);
|
||||
input.follows = parseInputPath(attr.value->string.s);
|
||||
} else {
|
||||
state.forceValue(*attr.value);
|
||||
if (attr.value->type == tString)
|
||||
attrs.emplace(attr.name, attr.value->string.s);
|
||||
else
|
||||
throw TypeError("flake input attribute '%s' is %s while a string is expected",
|
||||
attr.name, showType(*attr.value));
|
||||
}
|
||||
} catch (Error & e) {
|
||||
e.addPrefix(fmt("in flake attribute '%s' at '%s':\n", attr.name, *attr.pos));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
if (attrs.count("type"))
|
||||
try {
|
||||
input.ref = FlakeRef::fromAttrs(attrs);
|
||||
} catch (Error & e) {
|
||||
e.addPrefix(fmt("in flake input at '%s':\n", pos));
|
||||
throw;
|
||||
}
|
||||
else {
|
||||
attrs.erase("url");
|
||||
if (!attrs.empty())
|
||||
throw Error("unexpected flake input attribute '%s', at %s", attrs.begin()->first, pos);
|
||||
if (url)
|
||||
input.ref = parseFlakeRef(*url, {}, true);
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
|
||||
EvalState & state, Value * value, const Pos & pos)
|
||||
{
|
||||
std::map<FlakeId, FlakeInput> inputs;
|
||||
|
||||
expectType(state, tAttrs, *value, pos);
|
||||
|
||||
for (nix::Attr & inputAttr : *(*value).attrs) {
|
||||
inputs.emplace(inputAttr.name,
|
||||
parseFlakeInput(state,
|
||||
inputAttr.name,
|
||||
inputAttr.value,
|
||||
*inputAttr.pos));
|
||||
}
|
||||
|
||||
return inputs;
|
||||
}
|
||||
|
||||
static Flake getFlake(
|
||||
EvalState & state,
|
||||
const FlakeRef & originalRef,
|
||||
std::optional<TreeInfo> treeInfo,
|
||||
bool allowLookup,
|
||||
FlakeCache & flakeCache)
|
||||
{
|
||||
auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
|
||||
state, originalRef, treeInfo, allowLookup, flakeCache);
|
||||
|
||||
// Guard against symlink attacks.
|
||||
auto flakeFile = canonPath(sourceInfo.actualPath + "/" + lockedRef.subdir + "/flake.nix");
|
||||
if (!isInDir(flakeFile, sourceInfo.actualPath))
|
||||
throw Error("'flake.nix' file of flake '%s' escapes from '%s'",
|
||||
lockedRef, state.store->printStorePath(sourceInfo.storePath));
|
||||
|
||||
Flake flake {
|
||||
.originalRef = originalRef,
|
||||
.resolvedRef = resolvedRef,
|
||||
.lockedRef = lockedRef,
|
||||
.sourceInfo = std::make_shared<fetchers::Tree>(std::move(sourceInfo))
|
||||
};
|
||||
|
||||
if (!pathExists(flakeFile))
|
||||
throw Error("source tree referenced by '%s' does not contain a '%s/flake.nix' file", lockedRef, lockedRef.subdir);
|
||||
|
||||
Value vInfo;
|
||||
state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack
|
||||
|
||||
expectType(state, tAttrs, vInfo, Pos(state.symbols.create(flakeFile), 0, 0));
|
||||
|
||||
auto sEdition = state.symbols.create("edition"); // FIXME: remove soon
|
||||
|
||||
if (vInfo.attrs->get(sEdition))
|
||||
warn("flake '%s' has deprecated attribute 'edition'", lockedRef);
|
||||
|
||||
if (auto description = vInfo.attrs->get(state.sDescription)) {
|
||||
expectType(state, tString, *description->value, *description->pos);
|
||||
flake.description = description->value->string.s;
|
||||
}
|
||||
|
||||
auto sInputs = state.symbols.create("inputs");
|
||||
|
||||
if (auto inputs = vInfo.attrs->get(sInputs))
|
||||
flake.inputs = parseFlakeInputs(state, inputs->value, *inputs->pos);
|
||||
|
||||
auto sOutputs = state.symbols.create("outputs");
|
||||
|
||||
if (auto outputs = vInfo.attrs->get(sOutputs)) {
|
||||
expectType(state, tLambda, *outputs->value, *outputs->pos);
|
||||
flake.vOutputs = allocRootValue(outputs->value);
|
||||
|
||||
if ((*flake.vOutputs)->lambda.fun->matchAttrs) {
|
||||
for (auto & formal : (*flake.vOutputs)->lambda.fun->formals->formals) {
|
||||
if (formal.name != state.sSelf)
|
||||
flake.inputs.emplace(formal.name, FlakeInput {
|
||||
.ref = parseFlakeRef(formal.name)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} else
|
||||
throw Error("flake '%s' lacks attribute 'outputs'", lockedRef);
|
||||
|
||||
for (auto & attr : *vInfo.attrs) {
|
||||
if (attr.name != sEdition &&
|
||||
attr.name != state.sDescription &&
|
||||
attr.name != sInputs &&
|
||||
attr.name != sOutputs)
|
||||
throw Error("flake '%s' has an unsupported attribute '%s', at %s",
|
||||
lockedRef, attr.name, *attr.pos);
|
||||
}
|
||||
|
||||
return flake;
|
||||
}
|
||||
|
||||
Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool allowLookup)
|
||||
{
|
||||
FlakeCache flakeCache;
|
||||
return getFlake(state, originalRef, {}, allowLookup, flakeCache);
|
||||
}
|
||||
|
||||
/* Compute an in-memory lock file for the specified top-level flake,
|
||||
and optionally write it to file, it the flake is writable. */
|
||||
LockedFlake lockFlake(
|
||||
EvalState & state,
|
||||
const FlakeRef & topRef,
|
||||
const LockFlags & lockFlags)
|
||||
{
|
||||
settings.requireExperimentalFeature("flakes");
|
||||
|
||||
FlakeCache flakeCache;
|
||||
|
||||
auto flake = getFlake(state, topRef, {}, lockFlags.useRegistries, flakeCache);
|
||||
|
||||
// FIXME: symlink attack
|
||||
auto oldLockFile = LockFile::read(
|
||||
flake.sourceInfo->actualPath + "/" + flake.lockedRef.subdir + "/flake.lock");
|
||||
|
||||
debug("old lock file: %s", oldLockFile);
|
||||
|
||||
// FIXME: check whether all overrides are used.
|
||||
std::map<InputPath, FlakeInput> overrides;
|
||||
std::set<InputPath> overridesUsed, updatesUsed;
|
||||
|
||||
for (auto & i : lockFlags.inputOverrides)
|
||||
overrides.insert_or_assign(i.first, FlakeInput { .ref = i.second });
|
||||
|
||||
LockFile newLockFile;
|
||||
|
||||
std::vector<FlakeRef> parents;
|
||||
std::map<InputPath, InputPath> follows;
|
||||
|
||||
std::function<void(
|
||||
const FlakeInputs & flakeInputs,
|
||||
std::shared_ptr<Node> node,
|
||||
const InputPath & inputPathPrefix,
|
||||
std::shared_ptr<const Node> oldNode)>
|
||||
computeLocks;
|
||||
|
||||
computeLocks = [&](
|
||||
const FlakeInputs & flakeInputs,
|
||||
std::shared_ptr<Node> node,
|
||||
const InputPath & inputPathPrefix,
|
||||
std::shared_ptr<const Node> oldNode)
|
||||
{
|
||||
debug("computing lock file node '%s'", concatStringsSep("/", inputPathPrefix));
|
||||
|
||||
/* Get the overrides (i.e. attributes of the form
|
||||
'inputs.nixops.inputs.nixpkgs.url = ...'). */
|
||||
// FIXME: check this
|
||||
for (auto & [id, input] : flake.inputs) {
|
||||
for (auto & [idOverride, inputOverride] : input.overrides) {
|
||||
auto inputPath(inputPathPrefix);
|
||||
inputPath.push_back(id);
|
||||
inputPath.push_back(idOverride);
|
||||
overrides.insert_or_assign(inputPath, inputOverride);
|
||||
}
|
||||
}
|
||||
|
||||
/* Go over the flake inputs, resolve/fetch them if
|
||||
necessary (i.e. if they're new or the flakeref changed
|
||||
from what's in the lock file). */
|
||||
for (auto & [id, input2] : flakeInputs) {
|
||||
auto inputPath(inputPathPrefix);
|
||||
inputPath.push_back(id);
|
||||
auto inputPathS = concatStringsSep("/", inputPath);
|
||||
debug("computing input '%s'", concatStringsSep("/", inputPath));
|
||||
|
||||
/* Do we have an override for this input from one of the
|
||||
ancestors? */
|
||||
auto i = overrides.find(inputPath);
|
||||
bool hasOverride = i != overrides.end();
|
||||
if (hasOverride) overridesUsed.insert(inputPath);
|
||||
auto & input = hasOverride ? i->second : input2;
|
||||
|
||||
/* Resolve 'follows' later (since it may refer to an input
|
||||
path we haven't processed yet. */
|
||||
if (input.follows) {
|
||||
if (hasOverride)
|
||||
/* 'follows' from an override is relative to the
|
||||
root of the graph. */
|
||||
follows.insert_or_assign(inputPath, *input.follows);
|
||||
else {
|
||||
/* Otherwise, it's relative to the current flake. */
|
||||
InputPath path(inputPathPrefix);
|
||||
for (auto & i : *input.follows) path.push_back(i);
|
||||
follows.insert_or_assign(inputPath, path);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Do we have an entry in the existing lock file? And we
|
||||
don't have a --update-input flag for this input? */
|
||||
std::shared_ptr<const LockedNode> oldLock;
|
||||
|
||||
updatesUsed.insert(inputPath);
|
||||
|
||||
if (oldNode && !lockFlags.inputUpdates.count(inputPath)) {
|
||||
auto oldLockIt = oldNode->inputs.find(id);
|
||||
if (oldLockIt != oldNode->inputs.end())
|
||||
oldLock = std::dynamic_pointer_cast<const LockedNode>(oldLockIt->second);
|
||||
}
|
||||
|
||||
if (oldLock
|
||||
&& oldLock->originalRef == input.ref
|
||||
&& !hasOverride)
|
||||
{
|
||||
debug("keeping existing input '%s'", inputPathS);
|
||||
|
||||
/* Copy the input from the old lock since its flakeref
|
||||
didn't change and there is no override from a
|
||||
higher level flake. */
|
||||
auto childNode = std::make_shared<LockedNode>(
|
||||
oldLock->lockedRef, oldLock->originalRef, oldLock->info, oldLock->isFlake);
|
||||
|
||||
node->inputs.insert_or_assign(id, childNode);
|
||||
|
||||
/* If we have an --update-input flag for an input
|
||||
of this input, then we must fetch the flake to
|
||||
to update it. */
|
||||
auto lb = lockFlags.inputUpdates.lower_bound(inputPath);
|
||||
|
||||
auto hasChildUpdate =
|
||||
lb != lockFlags.inputUpdates.end()
|
||||
&& lb->size() > inputPath.size()
|
||||
&& std::equal(inputPath.begin(), inputPath.end(), lb->begin());
|
||||
|
||||
if (hasChildUpdate) {
|
||||
auto inputFlake = getFlake(
|
||||
state, oldLock->lockedRef, oldLock->info, false, flakeCache);
|
||||
computeLocks(inputFlake.inputs, childNode, inputPath, oldLock);
|
||||
} else {
|
||||
/* No need to fetch this flake, we can be
|
||||
lazy. However there may be new overrides on the
|
||||
inputs of this flake, so we need to check
|
||||
those. */
|
||||
FlakeInputs fakeInputs;
|
||||
|
||||
for (auto & i : oldLock->inputs) {
|
||||
auto lockedNode = std::dynamic_pointer_cast<LockedNode>(i.second);
|
||||
// Note: this node is not locked in case
|
||||
// of a circular reference back to the root.
|
||||
if (lockedNode)
|
||||
fakeInputs.emplace(i.first, FlakeInput {
|
||||
.ref = lockedNode->originalRef
|
||||
});
|
||||
else {
|
||||
InputPath path(inputPath);
|
||||
path.push_back(i.first);
|
||||
follows.insert_or_assign(path, InputPath());
|
||||
}
|
||||
}
|
||||
|
||||
computeLocks(fakeInputs, childNode, inputPath, oldLock);
|
||||
}
|
||||
|
||||
} else {
|
||||
/* We need to create a new lock file entry. So fetch
|
||||
this input. */
|
||||
|
||||
if (!lockFlags.allowMutable && !input.ref.input->isImmutable())
|
||||
throw Error("cannot update flake input '%s' in pure mode", inputPathS);
|
||||
|
||||
if (input.isFlake) {
|
||||
auto inputFlake = getFlake(state, input.ref, {}, lockFlags.useRegistries, flakeCache);
|
||||
|
||||
/* Note: in case of an --override-input, we use
|
||||
the *original* ref (input2.ref) for the
|
||||
"original" field, rather than the
|
||||
override. This ensures that the override isn't
|
||||
nuked the next time we update the lock
|
||||
file. That is, overrides are sticky unless you
|
||||
use --no-write-lock-file. */
|
||||
auto childNode = std::make_shared<LockedNode>(
|
||||
inputFlake.lockedRef, input2.ref, inputFlake.sourceInfo->info);
|
||||
|
||||
node->inputs.insert_or_assign(id, childNode);
|
||||
|
||||
/* Guard against circular flake imports. */
|
||||
for (auto & parent : parents)
|
||||
if (parent == input.ref)
|
||||
throw Error("found circular import of flake '%s'", parent);
|
||||
parents.push_back(input.ref);
|
||||
Finally cleanup([&]() { parents.pop_back(); });
|
||||
|
||||
/* Recursively process the inputs of this
|
||||
flake. Also, unless we already have this flake
|
||||
in the top-level lock file, use this flake's
|
||||
own lock file. */
|
||||
computeLocks(
|
||||
inputFlake.inputs, childNode, inputPath,
|
||||
oldLock
|
||||
? std::dynamic_pointer_cast<const Node>(oldLock)
|
||||
: LockFile::read(
|
||||
inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root);
|
||||
}
|
||||
|
||||
else {
|
||||
auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
|
||||
state, input.ref, {}, lockFlags.useRegistries, flakeCache);
|
||||
node->inputs.insert_or_assign(id,
|
||||
std::make_shared<LockedNode>(lockedRef, input.ref, sourceInfo.info, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
computeLocks(
|
||||
flake.inputs, newLockFile.root, {},
|
||||
lockFlags.recreateLockFile ? nullptr : oldLockFile.root);
|
||||
|
||||
/* Insert edges for 'follows' overrides. */
|
||||
for (auto & [from, to] : follows) {
|
||||
debug("adding 'follows' node from '%s' to '%s'",
|
||||
concatStringsSep("/", from),
|
||||
concatStringsSep("/", to));
|
||||
|
||||
assert(!from.empty());
|
||||
|
||||
InputPath fromParent(from);
|
||||
fromParent.pop_back();
|
||||
|
||||
auto fromParentNode = newLockFile.root->findInput(fromParent);
|
||||
assert(fromParentNode);
|
||||
|
||||
auto toNode = newLockFile.root->findInput(to);
|
||||
if (!toNode)
|
||||
throw Error("flake input '%s' follows non-existent flake input '%s'",
|
||||
concatStringsSep("/", from),
|
||||
concatStringsSep("/", to));
|
||||
|
||||
fromParentNode->inputs.insert_or_assign(from.back(), toNode);
|
||||
}
|
||||
|
||||
for (auto & i : lockFlags.inputOverrides)
|
||||
if (!overridesUsed.count(i.first))
|
||||
warn("the flag '--override-input %s %s' does not match any input",
|
||||
concatStringsSep("/", i.first), i.second);
|
||||
|
||||
for (auto & i : lockFlags.inputUpdates)
|
||||
if (!updatesUsed.count(i))
|
||||
warn("the flag '--update-input %s' does not match any input", concatStringsSep("/", i));
|
||||
|
||||
debug("new lock file: %s", newLockFile);
|
||||
|
||||
/* Check whether we need to / can write the new lock file. */
|
||||
if (!(newLockFile == oldLockFile)) {
|
||||
|
||||
auto diff = diffLockFiles(oldLockFile, newLockFile);
|
||||
|
||||
if (!(oldLockFile == LockFile()))
|
||||
printInfo("inputs of flake '%s' changed:\n%s", topRef, chomp(diff));
|
||||
|
||||
if (lockFlags.writeLockFile) {
|
||||
if (auto sourcePath = topRef.input->getSourcePath()) {
|
||||
if (!newLockFile.isImmutable()) {
|
||||
if (settings.warnDirty)
|
||||
warn("will not write lock file of flake '%s' because it has a mutable input", topRef);
|
||||
} else {
|
||||
if (!lockFlags.updateLockFile)
|
||||
throw Error("flake '%s' requires lock file changes but they're not allowed due to '--no-update-lock-file'", topRef);
|
||||
|
||||
auto relPath = (topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock";
|
||||
|
||||
auto path = *sourcePath + "/" + relPath;
|
||||
|
||||
bool lockFileExists = pathExists(path);
|
||||
|
||||
if (lockFileExists)
|
||||
warn("updating lock file '%s'", path);
|
||||
else
|
||||
warn("creating lock file '%s'", path);
|
||||
|
||||
newLockFile.write(path);
|
||||
|
||||
topRef.input->markChangedFile(
|
||||
(topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock",
|
||||
lockFlags.commitLockFile
|
||||
? std::optional<std::string>(fmt("%s: %s\n\nFlake input changes:\n\n%s",
|
||||
relPath, lockFileExists ? "Update" : "Add", diff))
|
||||
: std::nullopt);
|
||||
|
||||
/* Rewriting the lockfile changed the top-level
|
||||
repo, so we should re-read it. FIXME: we could
|
||||
also just clear the 'rev' field... */
|
||||
auto prevLockedRef = flake.lockedRef;
|
||||
FlakeCache dummyCache;
|
||||
flake = getFlake(state, topRef, {}, lockFlags.useRegistries, dummyCache);
|
||||
|
||||
if (lockFlags.commitLockFile &&
|
||||
flake.lockedRef.input->getRev() &&
|
||||
prevLockedRef.input->getRev() != flake.lockedRef.input->getRev())
|
||||
warn("committed new revision '%s'", flake.lockedRef.input->getRev()->gitRev());
|
||||
|
||||
/* Make sure that we picked up the change,
|
||||
i.e. the tree should usually be dirty
|
||||
now. Corner case: we could have reverted from a
|
||||
dirty to a clean tree! */
|
||||
if (flake.lockedRef.input == prevLockedRef.input
|
||||
&& !flake.lockedRef.input->isImmutable())
|
||||
throw Error("'%s' did not change after I updated its 'flake.lock' file; is 'flake.lock' under version control?", flake.originalRef);
|
||||
}
|
||||
} else
|
||||
throw Error("cannot write modified lock file of flake '%s' (use '--no-write-lock-file' to ignore)", topRef);
|
||||
} else
|
||||
warn("not writing modified lock file of flake '%s'", topRef);
|
||||
}
|
||||
|
||||
return LockedFlake { .flake = std::move(flake), .lockFile = std::move(newLockFile) };
|
||||
}
|
||||
|
||||
void callFlake(EvalState & state,
|
||||
const LockedFlake & lockedFlake,
|
||||
Value & vRes)
|
||||
{
|
||||
auto vLocks = state.allocValue();
|
||||
auto vRootSrc = state.allocValue();
|
||||
auto vRootSubdir = state.allocValue();
|
||||
auto vTmp1 = state.allocValue();
|
||||
auto vTmp2 = state.allocValue();
|
||||
|
||||
mkString(*vLocks, lockedFlake.lockFile.to_string());
|
||||
|
||||
emitTreeAttrs(state, *lockedFlake.flake.sourceInfo, lockedFlake.flake.lockedRef.input, *vRootSrc);
|
||||
|
||||
mkString(*vRootSubdir, lockedFlake.flake.lockedRef.subdir);
|
||||
|
||||
static RootValue vCallFlake = nullptr;
|
||||
|
||||
if (!vCallFlake) {
|
||||
vCallFlake = allocRootValue(state.allocValue());
|
||||
state.eval(state.parseExprFromString(
|
||||
#include "call-flake.nix.gen.hh"
|
||||
, "/"), **vCallFlake);
|
||||
}
|
||||
|
||||
state.callFunction(**vCallFlake, *vLocks, *vTmp1, noPos);
|
||||
state.callFunction(*vTmp1, *vRootSrc, *vTmp2, noPos);
|
||||
state.callFunction(*vTmp2, *vRootSubdir, vRes, noPos);
|
||||
}
|
||||
|
||||
static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||
{
|
||||
auto flakeRefS = state.forceStringNoCtx(*args[0], pos);
|
||||
auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
|
||||
if (evalSettings.pureEval && !flakeRef.input->isImmutable())
|
||||
throw Error("cannot call 'getFlake' on mutable flake reference '%s', at %s (use --impure to override)", flakeRefS, pos);
|
||||
|
||||
callFlake(state,
|
||||
lockFlake(state, flakeRef,
|
||||
LockFlags {
|
||||
.updateLockFile = false,
|
||||
.useRegistries = !evalSettings.pureEval,
|
||||
.allowMutable = !evalSettings.pureEval,
|
||||
}),
|
||||
v);
|
||||
}
|
||||
|
||||
static RegisterPrimOp r2("getFlake", 1, prim_getFlake);
|
||||
|
||||
}
|
||||
|
||||
Fingerprint LockedFlake::getFingerprint() const
|
||||
{
|
||||
// FIXME: as an optimization, if the flake contains a lock file
|
||||
// and we haven't changed it, then it's sufficient to use
|
||||
// flake.sourceInfo.storePath for the fingerprint.
|
||||
return hashString(htSHA256,
|
||||
fmt("%s;%d;%d;%s",
|
||||
flake.sourceInfo->storePath.to_string(),
|
||||
flake.sourceInfo->info.revCount.value_or(0),
|
||||
flake.sourceInfo->info.lastModified.value_or(0),
|
||||
lockFile));
|
||||
}
|
||||
|
||||
Flake::~Flake() { }
|
||||
|
||||
}
|
||||
110
src/libexpr/flake/flake.hh
Normal file
110
src/libexpr/flake/flake.hh
Normal file
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.hh"
|
||||
#include "flakeref.hh"
|
||||
#include "lockfile.hh"
|
||||
#include "value.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
class EvalState;
|
||||
|
||||
namespace fetchers { struct Tree; }
|
||||
|
||||
namespace flake {
|
||||
|
||||
struct FlakeInput;
|
||||
|
||||
typedef std::map<FlakeId, FlakeInput> FlakeInputs;
|
||||
|
||||
struct FlakeInput
|
||||
{
|
||||
FlakeRef ref;
|
||||
bool isFlake = true;
|
||||
std::optional<InputPath> follows;
|
||||
FlakeInputs overrides;
|
||||
};
|
||||
|
||||
struct Flake
|
||||
{
|
||||
FlakeRef originalRef;
|
||||
FlakeRef resolvedRef;
|
||||
FlakeRef lockedRef;
|
||||
std::optional<std::string> description;
|
||||
std::shared_ptr<const fetchers::Tree> sourceInfo;
|
||||
FlakeInputs inputs;
|
||||
RootValue vOutputs;
|
||||
~Flake();
|
||||
};
|
||||
|
||||
Flake getFlake(EvalState & state, const FlakeRef & flakeRef, bool allowLookup);
|
||||
|
||||
/* Fingerprint of a locked flake; used as a cache key. */
|
||||
typedef Hash Fingerprint;
|
||||
|
||||
struct LockedFlake
|
||||
{
|
||||
Flake flake;
|
||||
LockFile lockFile;
|
||||
|
||||
Fingerprint getFingerprint() const;
|
||||
};
|
||||
|
||||
struct LockFlags
|
||||
{
|
||||
/* Whether to ignore the existing lock file, creating a new one
|
||||
from scratch. */
|
||||
bool recreateLockFile = false;
|
||||
|
||||
/* Whether to update the lock file at all. If set to false, if any
|
||||
change to the lock file is needed (e.g. when an input has been
|
||||
added to flake.nix), you get a fatal error. */
|
||||
bool updateLockFile = true;
|
||||
|
||||
/* Whether to write the lock file to disk. If set to true, if the
|
||||
any changes to the lock file are needed and the flake is not
|
||||
writable (i.e. is not a local Git working tree or similar), you
|
||||
get a fatal error. If set to false, Nix will use the modified
|
||||
lock file in memory only, without writing it to disk. */
|
||||
bool writeLockFile = true;
|
||||
|
||||
/* Whether to use the registries to lookup indirect flake
|
||||
references like 'nixpkgs'. */
|
||||
bool useRegistries = true;
|
||||
|
||||
/* Whether mutable flake references (i.e. those without a Git
|
||||
revision or similar) without a corresponding lock are
|
||||
allowed. Mutable flake references with a lock are always
|
||||
allowed. */
|
||||
bool allowMutable = true;
|
||||
|
||||
/* Whether to commit changes to flake.lock. */
|
||||
bool commitLockFile = false;
|
||||
|
||||
/* Flake inputs to be overriden. */
|
||||
std::map<InputPath, FlakeRef> inputOverrides;
|
||||
|
||||
/* Flake inputs to be updated. This means that any existing lock
|
||||
for those inputs will be ignored. */
|
||||
std::set<InputPath> inputUpdates;
|
||||
};
|
||||
|
||||
LockedFlake lockFlake(
|
||||
EvalState & state,
|
||||
const FlakeRef & flakeRef,
|
||||
const LockFlags & lockFlags);
|
||||
|
||||
void callFlake(
|
||||
EvalState & state,
|
||||
const LockedFlake & lockedFlake,
|
||||
Value & v);
|
||||
|
||||
}
|
||||
|
||||
void emitTreeAttrs(
|
||||
EvalState & state,
|
||||
const fetchers::Tree & tree,
|
||||
std::shared_ptr<const fetchers::Input> input,
|
||||
Value & v);
|
||||
|
||||
}
|
||||
196
src/libexpr/flake/flakeref.cc
Normal file
196
src/libexpr/flake/flakeref.cc
Normal file
@@ -0,0 +1,196 @@
|
||||
#include "flakeref.hh"
|
||||
#include "store-api.hh"
|
||||
#include "url.hh"
|
||||
#include "fetchers.hh"
|
||||
#include "registry.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
#if 0
|
||||
// 'dir' path elements cannot start with a '.'. We also reject
|
||||
// potentially dangerous characters like ';'.
|
||||
const static std::string subDirElemRegex = "(?:[a-zA-Z0-9_-]+[a-zA-Z0-9._-]*)";
|
||||
const static std::string subDirRegex = subDirElemRegex + "(?:/" + subDirElemRegex + ")*";
|
||||
#endif
|
||||
|
||||
std::string FlakeRef::to_string() const
|
||||
{
|
||||
auto url = input->toURL();
|
||||
if (subdir != "")
|
||||
url.query.insert_or_assign("dir", subdir);
|
||||
return url.to_string();
|
||||
}
|
||||
|
||||
fetchers::Attrs FlakeRef::toAttrs() const
|
||||
{
|
||||
auto attrs = input->toAttrs();
|
||||
if (subdir != "")
|
||||
attrs.emplace("dir", subdir);
|
||||
return attrs;
|
||||
}
|
||||
|
||||
std::ostream & operator << (std::ostream & str, const FlakeRef & flakeRef)
|
||||
{
|
||||
str << flakeRef.to_string();
|
||||
return str;
|
||||
}
|
||||
|
||||
bool FlakeRef::operator ==(const FlakeRef & other) const
|
||||
{
|
||||
return *input == *other.input && subdir == other.subdir;
|
||||
}
|
||||
|
||||
FlakeRef FlakeRef::resolve(ref<Store> store) const
|
||||
{
|
||||
auto [input2, extraAttrs] = lookupInRegistries(store, input);
|
||||
return FlakeRef(input2, fetchers::maybeGetStrAttr(extraAttrs, "dir").value_or(subdir));
|
||||
}
|
||||
|
||||
FlakeRef parseFlakeRef(
|
||||
const std::string & url, const std::optional<Path> & baseDir, bool allowMissing)
|
||||
{
|
||||
auto [flakeRef, fragment] = parseFlakeRefWithFragment(url, baseDir, allowMissing);
|
||||
if (fragment != "")
|
||||
throw Error("unexpected fragment '%s' in flake reference '%s'", fragment, url);
|
||||
return flakeRef;
|
||||
}
|
||||
|
||||
std::optional<FlakeRef> maybeParseFlakeRef(
|
||||
const std::string & url, const std::optional<Path> & baseDir)
|
||||
{
|
||||
try {
|
||||
return parseFlakeRef(url, baseDir);
|
||||
} catch (Error &) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
||||
const std::string & url, const std::optional<Path> & baseDir, bool allowMissing)
|
||||
{
|
||||
using namespace fetchers;
|
||||
|
||||
static std::string fnRegex = "[0-9a-zA-Z-._~!$&'\"()*+,;=]+";
|
||||
|
||||
static std::regex pathUrlRegex(
|
||||
"(/?" + fnRegex + "(?:/" + fnRegex + ")*/?)"
|
||||
+ "(?:\\?(" + queryRegex + "))?"
|
||||
+ "(?:#(" + queryRegex + "))?",
|
||||
std::regex::ECMAScript);
|
||||
|
||||
static std::regex flakeRegex(
|
||||
"((" + flakeIdRegexS + ")(?:/(?:" + refAndOrRevRegex + "))?)"
|
||||
+ "(?:#(" + queryRegex + "))?",
|
||||
std::regex::ECMAScript);
|
||||
|
||||
std::smatch match;
|
||||
|
||||
/* Check if 'url' is a flake ID. This is an abbreviated syntax for
|
||||
'flake:<flake-id>?ref=<ref>&rev=<rev>'. */
|
||||
|
||||
if (std::regex_match(url, match, flakeRegex)) {
|
||||
auto parsedURL = ParsedURL{
|
||||
.url = url,
|
||||
.base = "flake:" + std::string(match[1]),
|
||||
.scheme = "flake",
|
||||
.authority = "",
|
||||
.path = match[1],
|
||||
};
|
||||
|
||||
return std::make_pair(
|
||||
FlakeRef(inputFromURL(parsedURL), ""),
|
||||
percentDecode(std::string(match[6])));
|
||||
}
|
||||
|
||||
/* Check if 'url' is a path (either absolute or relative to
|
||||
'baseDir'). If so, search upward to the root of the repo
|
||||
(i.e. the directory containing .git). */
|
||||
|
||||
else if (std::regex_match(url, match, pathUrlRegex)) {
|
||||
std::string path = match[1];
|
||||
if (!baseDir && !hasPrefix(path, "/"))
|
||||
throw BadURL("flake reference '%s' is not an absolute path", url);
|
||||
path = absPath(path, baseDir, true);
|
||||
|
||||
if (!S_ISDIR(lstat(path).st_mode))
|
||||
throw BadURL("path '%s' is not a flake (because it's not a directory)", path);
|
||||
|
||||
if (!allowMissing && !pathExists(path + "/flake.nix"))
|
||||
throw BadURL("path '%s' is not a flake (because it doesn't contain a 'flake.nix' file)", path);
|
||||
|
||||
auto fragment = percentDecode(std::string(match[3]));
|
||||
|
||||
auto flakeRoot = path;
|
||||
std::string subdir;
|
||||
|
||||
while (flakeRoot != "/") {
|
||||
if (pathExists(flakeRoot + "/.git")) {
|
||||
auto base = std::string("git+file://") + flakeRoot;
|
||||
|
||||
auto parsedURL = ParsedURL{
|
||||
.url = base, // FIXME
|
||||
.base = base,
|
||||
.scheme = "git+file",
|
||||
.authority = "",
|
||||
.path = flakeRoot,
|
||||
.query = decodeQuery(match[2]),
|
||||
};
|
||||
|
||||
if (subdir != "") {
|
||||
if (parsedURL.query.count("dir"))
|
||||
throw Error("flake URL '%s' has an inconsistent 'dir' parameter", url);
|
||||
parsedURL.query.insert_or_assign("dir", subdir);
|
||||
}
|
||||
|
||||
return std::make_pair(
|
||||
FlakeRef(inputFromURL(parsedURL), get(parsedURL.query, "dir").value_or("")),
|
||||
fragment);
|
||||
}
|
||||
|
||||
subdir = std::string(baseNameOf(flakeRoot)) + (subdir.empty() ? "" : "/" + subdir);
|
||||
flakeRoot = dirOf(flakeRoot);
|
||||
}
|
||||
|
||||
fetchers::Attrs attrs;
|
||||
attrs.insert_or_assign("type", "path");
|
||||
attrs.insert_or_assign("path", path);
|
||||
|
||||
return std::make_pair(FlakeRef(inputFromAttrs(attrs), ""), fragment);
|
||||
}
|
||||
|
||||
else {
|
||||
auto parsedURL = parseURL(url);
|
||||
std::string fragment;
|
||||
std::swap(fragment, parsedURL.fragment);
|
||||
return std::make_pair(
|
||||
FlakeRef(inputFromURL(parsedURL), get(parsedURL.query, "dir").value_or("")),
|
||||
fragment);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::pair<FlakeRef, std::string>> maybeParseFlakeRefWithFragment(
|
||||
const std::string & url, const std::optional<Path> & baseDir)
|
||||
{
|
||||
try {
|
||||
return parseFlakeRefWithFragment(url, baseDir);
|
||||
} catch (Error & e) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
FlakeRef FlakeRef::fromAttrs(const fetchers::Attrs & attrs)
|
||||
{
|
||||
auto attrs2(attrs);
|
||||
attrs2.erase("dir");
|
||||
return FlakeRef(
|
||||
fetchers::inputFromAttrs(attrs2),
|
||||
fetchers::maybeGetStrAttr(attrs, "dir").value_or(""));
|
||||
}
|
||||
|
||||
std::pair<fetchers::Tree, FlakeRef> FlakeRef::fetchTree(ref<Store> store) const
|
||||
{
|
||||
auto [tree, lockedInput] = input->fetchTree(store);
|
||||
return {std::move(tree), FlakeRef(lockedInput, subdir)};
|
||||
}
|
||||
|
||||
}
|
||||
55
src/libexpr/flake/flakeref.hh
Normal file
55
src/libexpr/flake/flakeref.hh
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.hh"
|
||||
#include "hash.hh"
|
||||
#include "fetchers.hh"
|
||||
|
||||
#include <variant>
|
||||
|
||||
namespace nix {
|
||||
|
||||
class Store;
|
||||
|
||||
typedef std::string FlakeId;
|
||||
|
||||
struct FlakeRef
|
||||
{
|
||||
std::shared_ptr<const fetchers::Input> input;
|
||||
|
||||
Path subdir;
|
||||
|
||||
bool operator==(const FlakeRef & other) const;
|
||||
|
||||
FlakeRef(const std::shared_ptr<const fetchers::Input> & input, const Path & subdir)
|
||||
: input(input), subdir(subdir)
|
||||
{
|
||||
assert(input);
|
||||
}
|
||||
|
||||
// FIXME: change to operator <<.
|
||||
std::string to_string() const;
|
||||
|
||||
fetchers::Attrs toAttrs() const;
|
||||
|
||||
FlakeRef resolve(ref<Store> store) const;
|
||||
|
||||
static FlakeRef fromAttrs(const fetchers::Attrs & attrs);
|
||||
|
||||
std::pair<fetchers::Tree, FlakeRef> fetchTree(ref<Store> store) const;
|
||||
};
|
||||
|
||||
std::ostream & operator << (std::ostream & str, const FlakeRef & flakeRef);
|
||||
|
||||
FlakeRef parseFlakeRef(
|
||||
const std::string & url, const std::optional<Path> & baseDir = {}, bool allowMissing = false);
|
||||
|
||||
std::optional<FlakeRef> maybeParseFlake(
|
||||
const std::string & url, const std::optional<Path> & baseDir = {});
|
||||
|
||||
std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
||||
const std::string & url, const std::optional<Path> & baseDir = {}, bool allowMissing = false);
|
||||
|
||||
std::optional<std::pair<FlakeRef, std::string>> maybeParseFlakeRefWithFragment(
|
||||
const std::string & url, const std::optional<Path> & baseDir = {});
|
||||
|
||||
}
|
||||
256
src/libexpr/flake/lockfile.cc
Normal file
256
src/libexpr/flake/lockfile.cc
Normal file
@@ -0,0 +1,256 @@
|
||||
#include "lockfile.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace nix::flake {
|
||||
|
||||
FlakeRef flakeRefFromJson(const nlohmann::json & json)
|
||||
{
|
||||
return FlakeRef::fromAttrs(jsonToAttrs(json));
|
||||
}
|
||||
|
||||
FlakeRef getFlakeRef(
|
||||
const nlohmann::json & json,
|
||||
const char * attr)
|
||||
{
|
||||
auto i = json.find(attr);
|
||||
if (i != json.end())
|
||||
return flakeRefFromJson(*i);
|
||||
|
||||
throw Error("attribute '%s' missing in lock file", attr);
|
||||
}
|
||||
|
||||
LockedNode::LockedNode(const nlohmann::json & json)
|
||||
: lockedRef(getFlakeRef(json, "locked"))
|
||||
, originalRef(getFlakeRef(json, "original"))
|
||||
, info(TreeInfo::fromJson(json))
|
||||
, isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true)
|
||||
{
|
||||
if (!lockedRef.input->isImmutable())
|
||||
throw Error("lockfile contains mutable flakeref '%s'", lockedRef);
|
||||
}
|
||||
|
||||
StorePath LockedNode::computeStorePath(Store & store) const
|
||||
{
|
||||
return info.computeStorePath(store);
|
||||
}
|
||||
|
||||
std::shared_ptr<Node> Node::findInput(const InputPath & path)
|
||||
{
|
||||
auto pos = shared_from_this();
|
||||
|
||||
for (auto & elem : path) {
|
||||
auto i = pos->inputs.find(elem);
|
||||
if (i == pos->inputs.end())
|
||||
return {};
|
||||
pos = i->second;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
LockFile::LockFile(const nlohmann::json & json, const Path & path)
|
||||
{
|
||||
auto version = json.value("version", 0);
|
||||
if (version != 5)
|
||||
throw Error("lock file '%s' has unsupported version %d", path, version);
|
||||
|
||||
std::unordered_map<std::string, std::shared_ptr<Node>> nodeMap;
|
||||
|
||||
std::function<void(Node & node, const nlohmann::json & jsonNode)> getInputs;
|
||||
|
||||
getInputs = [&](Node & node, const nlohmann::json & jsonNode)
|
||||
{
|
||||
if (jsonNode.find("inputs") == jsonNode.end()) return;
|
||||
for (auto & i : jsonNode["inputs"].items()) {
|
||||
std::string inputKey = i.value();
|
||||
auto k = nodeMap.find(inputKey);
|
||||
if (k == nodeMap.end()) {
|
||||
auto jsonNode2 = json["nodes"][inputKey];
|
||||
auto input = std::make_shared<LockedNode>(jsonNode2);
|
||||
k = nodeMap.insert_or_assign(inputKey, input).first;
|
||||
getInputs(*input, jsonNode2);
|
||||
}
|
||||
node.inputs.insert_or_assign(i.key(), k->second);
|
||||
}
|
||||
};
|
||||
|
||||
std::string rootKey = json["root"];
|
||||
nodeMap.insert_or_assign(rootKey, root);
|
||||
getInputs(*root, json["nodes"][rootKey]);
|
||||
}
|
||||
|
||||
nlohmann::json LockFile::toJson() const
|
||||
{
|
||||
nlohmann::json nodes;
|
||||
std::unordered_map<std::shared_ptr<const Node>, std::string> nodeKeys;
|
||||
std::unordered_set<std::string> keys;
|
||||
|
||||
std::function<std::string(const std::string & key, std::shared_ptr<const Node> node)> dumpNode;
|
||||
|
||||
dumpNode = [&](std::string key, std::shared_ptr<const Node> node) -> std::string
|
||||
{
|
||||
auto k = nodeKeys.find(node);
|
||||
if (k != nodeKeys.end())
|
||||
return k->second;
|
||||
|
||||
if (!keys.insert(key).second) {
|
||||
for (int n = 2; ; ++n) {
|
||||
auto k = fmt("%s_%d", key, n);
|
||||
if (keys.insert(k).second) {
|
||||
key = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodeKeys.insert_or_assign(node, key);
|
||||
|
||||
auto n = nlohmann::json::object();
|
||||
|
||||
if (!node->inputs.empty()) {
|
||||
auto inputs = nlohmann::json::object();
|
||||
for (auto & i : node->inputs)
|
||||
inputs[i.first] = dumpNode(i.first, i.second);
|
||||
n["inputs"] = std::move(inputs);
|
||||
}
|
||||
|
||||
if (auto lockedNode = std::dynamic_pointer_cast<const LockedNode>(node)) {
|
||||
n["original"] = fetchers::attrsToJson(lockedNode->originalRef.toAttrs());
|
||||
n["locked"] = fetchers::attrsToJson(lockedNode->lockedRef.toAttrs());
|
||||
n["info"] = lockedNode->info.toJson();
|
||||
if (!lockedNode->isFlake) n["flake"] = false;
|
||||
}
|
||||
|
||||
nodes[key] = std::move(n);
|
||||
|
||||
return key;
|
||||
};
|
||||
|
||||
nlohmann::json json;
|
||||
json["version"] = 5;
|
||||
json["root"] = dumpNode("root", root);
|
||||
json["nodes"] = std::move(nodes);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
std::string LockFile::to_string() const
|
||||
{
|
||||
return toJson().dump(2);
|
||||
}
|
||||
|
||||
LockFile LockFile::read(const Path & path)
|
||||
{
|
||||
if (!pathExists(path)) return LockFile();
|
||||
return LockFile(nlohmann::json::parse(readFile(path)), path);
|
||||
}
|
||||
|
||||
std::ostream & operator <<(std::ostream & stream, const LockFile & lockFile)
|
||||
{
|
||||
stream << lockFile.toJson().dump(2);
|
||||
return stream;
|
||||
}
|
||||
|
||||
void LockFile::write(const Path & path) const
|
||||
{
|
||||
createDirs(dirOf(path));
|
||||
writeFile(path, fmt("%s\n", *this));
|
||||
}
|
||||
|
||||
bool LockFile::isImmutable() const
|
||||
{
|
||||
std::unordered_set<std::shared_ptr<const Node>> nodes;
|
||||
|
||||
std::function<void(std::shared_ptr<const Node> node)> visit;
|
||||
|
||||
visit = [&](std::shared_ptr<const Node> node)
|
||||
{
|
||||
if (!nodes.insert(node).second) return;
|
||||
for (auto & i : node->inputs) visit(i.second);
|
||||
};
|
||||
|
||||
visit(root);
|
||||
|
||||
for (auto & i : nodes) {
|
||||
if (i == root) continue;
|
||||
auto lockedNode = std::dynamic_pointer_cast<const LockedNode>(i);
|
||||
if (lockedNode && !lockedNode->lockedRef.input->isImmutable()) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LockFile::operator ==(const LockFile & other) const
|
||||
{
|
||||
// FIXME: slow
|
||||
return toJson() == other.toJson();
|
||||
}
|
||||
|
||||
InputPath parseInputPath(std::string_view s)
|
||||
{
|
||||
InputPath path;
|
||||
|
||||
for (auto & elem : tokenizeString<std::vector<std::string>>(s, "/")) {
|
||||
if (!std::regex_match(elem, flakeIdRegex))
|
||||
throw Error("invalid flake input path element '%s'", elem);
|
||||
path.push_back(elem);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
static void flattenLockFile(
|
||||
std::shared_ptr<const Node> node,
|
||||
const InputPath & prefix,
|
||||
std::unordered_set<std::shared_ptr<const Node>> & done,
|
||||
std::map<InputPath, std::shared_ptr<const LockedNode>> & res)
|
||||
{
|
||||
if (!done.insert(node).second) return;
|
||||
|
||||
for (auto &[id, input] : node->inputs) {
|
||||
auto inputPath(prefix);
|
||||
inputPath.push_back(id);
|
||||
if (auto lockedInput = std::dynamic_pointer_cast<const LockedNode>(input))
|
||||
res.emplace(inputPath, lockedInput);
|
||||
flattenLockFile(input, inputPath, done, res);
|
||||
}
|
||||
}
|
||||
|
||||
std::string diffLockFiles(const LockFile & oldLocks, const LockFile & newLocks)
|
||||
{
|
||||
std::unordered_set<std::shared_ptr<const Node>> done;
|
||||
std::map<InputPath, std::shared_ptr<const LockedNode>> oldFlat, newFlat;
|
||||
flattenLockFile(oldLocks.root, {}, done, oldFlat);
|
||||
done.clear();
|
||||
flattenLockFile(newLocks.root, {}, done, newFlat);
|
||||
|
||||
auto i = oldFlat.begin();
|
||||
auto j = newFlat.begin();
|
||||
std::string res;
|
||||
|
||||
while (i != oldFlat.end() || j != newFlat.end()) {
|
||||
if (j != newFlat.end() && (i == oldFlat.end() || i->first > j->first)) {
|
||||
res += fmt("* Added '%s': '%s'\n", concatStringsSep("/", j->first), j->second->lockedRef);
|
||||
++j;
|
||||
} else if (i != oldFlat.end() && (j == newFlat.end() || i->first < j->first)) {
|
||||
res += fmt("* Removed '%s'\n", concatStringsSep("/", i->first));
|
||||
++i;
|
||||
} else {
|
||||
if (!(i->second->lockedRef == j->second->lockedRef)) {
|
||||
assert(i->second->lockedRef.to_string() != j->second->lockedRef.to_string());
|
||||
res += fmt("* Updated '%s': '%s' -> '%s'\n",
|
||||
concatStringsSep("/", i->first),
|
||||
i->second->lockedRef,
|
||||
j->second->lockedRef);
|
||||
}
|
||||
++i;
|
||||
++j;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
77
src/libexpr/flake/lockfile.hh
Normal file
77
src/libexpr/flake/lockfile.hh
Normal file
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include "flakeref.hh"
|
||||
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
|
||||
namespace nix {
|
||||
class Store;
|
||||
struct StorePath;
|
||||
}
|
||||
|
||||
namespace nix::flake {
|
||||
|
||||
using namespace fetchers;
|
||||
|
||||
typedef std::vector<FlakeId> InputPath;
|
||||
|
||||
/* A node in the lock file. It has outgoing edges to other nodes (its
|
||||
inputs). Only the root node has this type; all other nodes have
|
||||
type LockedNode. */
|
||||
struct Node : std::enable_shared_from_this<Node>
|
||||
{
|
||||
std::map<FlakeId, std::shared_ptr<Node>> inputs;
|
||||
|
||||
virtual ~Node() { }
|
||||
|
||||
std::shared_ptr<Node> findInput(const InputPath & path);
|
||||
};
|
||||
|
||||
/* A non-root node in the lock file. */
|
||||
struct LockedNode : Node
|
||||
{
|
||||
FlakeRef lockedRef, originalRef;
|
||||
TreeInfo info;
|
||||
bool isFlake = true;
|
||||
|
||||
LockedNode(
|
||||
const FlakeRef & lockedRef,
|
||||
const FlakeRef & originalRef,
|
||||
const TreeInfo & info,
|
||||
bool isFlake = true)
|
||||
: lockedRef(lockedRef), originalRef(originalRef), info(info), isFlake(isFlake)
|
||||
{ }
|
||||
|
||||
LockedNode(const nlohmann::json & json);
|
||||
|
||||
StorePath computeStorePath(Store & store) const;
|
||||
};
|
||||
|
||||
struct LockFile
|
||||
{
|
||||
std::shared_ptr<Node> root = std::make_shared<Node>();
|
||||
|
||||
LockFile() {};
|
||||
LockFile(const nlohmann::json & json, const Path & path);
|
||||
|
||||
nlohmann::json toJson() const;
|
||||
|
||||
std::string to_string() const;
|
||||
|
||||
static LockFile read(const Path & path);
|
||||
|
||||
void write(const Path & path) const;
|
||||
|
||||
bool isImmutable() const;
|
||||
|
||||
bool operator ==(const LockFile & other) const;
|
||||
};
|
||||
|
||||
std::ostream & operator <<(std::ostream & stream, const LockFile & lockFile);
|
||||
|
||||
InputPath parseInputPath(std::string_view s);
|
||||
|
||||
std::string diffLockFiles(const LockFile & oldLocks, const LockFile & newLocks);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "get-drvs.hh"
|
||||
#include "util.hh"
|
||||
#include "eval-inline.hh"
|
||||
#include "store-api.hh"
|
||||
#include "derivations.hh"
|
||||
|
||||
#include <cstring>
|
||||
#include <regex>
|
||||
|
||||
@@ -127,14 +127,14 @@ or { return OR_KW; }
|
||||
try {
|
||||
yylval->n = boost::lexical_cast<int64_t>(yytext);
|
||||
} catch (const boost::bad_lexical_cast &) {
|
||||
throw ParseError("invalid integer '%1%'", yytext);
|
||||
throw ParseError(format("invalid integer '%1%'") % yytext);
|
||||
}
|
||||
return INT;
|
||||
}
|
||||
{FLOAT} { errno = 0;
|
||||
yylval->nf = strtod(yytext, 0);
|
||||
if (errno != 0)
|
||||
throw ParseError("invalid float '%1%'", yytext);
|
||||
throw ParseError(format("invalid float '%1%'") % yytext);
|
||||
return FLOAT;
|
||||
}
|
||||
|
||||
@@ -219,3 +219,4 @@ or { return OR_KW; }
|
||||
}
|
||||
|
||||
%%
|
||||
|
||||
|
||||
@@ -4,11 +4,16 @@ libexpr_NAME = libnixexpr
|
||||
|
||||
libexpr_DIR := $(d)
|
||||
|
||||
libexpr_SOURCES := $(wildcard $(d)/*.cc) $(wildcard $(d)/primops/*.cc) $(d)/lexer-tab.cc $(d)/parser-tab.cc
|
||||
libexpr_SOURCES := \
|
||||
$(wildcard $(d)/*.cc) \
|
||||
$(wildcard $(d)/primops/*.cc) \
|
||||
$(wildcard $(d)/flake/*.cc) \
|
||||
$(d)/lexer-tab.cc \
|
||||
$(d)/parser-tab.cc
|
||||
|
||||
libexpr_CXXFLAGS += -I src/libutil -I src/libstore -I src/libfetchers -I src/libmain -I src/libexpr
|
||||
|
||||
libexpr_LIBS = libutil libstore libfetchers
|
||||
libexpr_LIBS = libutil libstore libfetchers libnixrust
|
||||
|
||||
libexpr_LDFLAGS =
|
||||
ifneq ($(OS), FreeBSD)
|
||||
@@ -34,4 +39,9 @@ dist-files += $(d)/parser-tab.cc $(d)/parser-tab.hh $(d)/lexer-tab.cc $(d)/lexer
|
||||
|
||||
$(eval $(call install-file-in, $(d)/nix-expr.pc, $(prefix)/lib/pkgconfig, 0644))
|
||||
|
||||
$(foreach i, $(wildcard src/libexpr/flake/*.hh), \
|
||||
$(eval $(call install-file-in, $(i), $(includedir)/nix/flake, 0644)))
|
||||
|
||||
$(d)/primops.cc: $(d)/imported-drv-to-derivation.nix.gen.hh
|
||||
|
||||
$(d)/flake/flake.cc: $(d)/flake/call-flake.nix.gen.hh
|
||||
|
||||
@@ -267,11 +267,8 @@ void ExprVar::bindVars(const StaticEnv & env)
|
||||
/* Otherwise, the variable must be obtained from the nearest
|
||||
enclosing `with'. If there is no `with', then we can issue an
|
||||
"undefined variable" error now. */
|
||||
if (withLevel == -1)
|
||||
throw UndefinedVarError({
|
||||
.hint = hintfmt("undefined variable '%1%'", name),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
if (withLevel == -1) throw UndefinedVarError(format("undefined variable '%1%' at %2%") % name % pos);
|
||||
|
||||
fromWith = true;
|
||||
this->level = withLevel;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "value.hh"
|
||||
#include "symbol-table.hh"
|
||||
#include "error.hh"
|
||||
|
||||
#include <map>
|
||||
|
||||
@@ -236,10 +235,8 @@ struct ExprLambda : Expr
|
||||
: pos(pos), arg(arg), matchAttrs(matchAttrs), formals(formals), body(body)
|
||||
{
|
||||
if (!arg.empty() && formals && formals->argNames.find(arg) != formals->argNames.end())
|
||||
throw ParseError({
|
||||
.hint = hintfmt("duplicate formal function argument '%1%'", arg),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw ParseError(format("duplicate formal function argument '%1%' at %2%")
|
||||
% arg % pos);
|
||||
};
|
||||
void setName(Symbol & name);
|
||||
string showNamePos() const;
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace nix {
|
||||
Expr * result;
|
||||
Path basePath;
|
||||
Symbol path;
|
||||
ErrorInfo error;
|
||||
string error;
|
||||
Symbol sLetBody;
|
||||
ParseData(EvalState & state)
|
||||
: state(state)
|
||||
@@ -64,20 +64,15 @@ namespace nix {
|
||||
|
||||
static void dupAttr(const AttrPath & attrPath, const Pos & pos, const Pos & prevPos)
|
||||
{
|
||||
throw ParseError({
|
||||
.hint = hintfmt("attribute '%1%' already defined at %2%",
|
||||
showAttrPath(attrPath), prevPos),
|
||||
.nixCode = NixCode { .errPos = pos },
|
||||
});
|
||||
throw ParseError(format("attribute '%1%' at %2% already defined at %3%")
|
||||
% showAttrPath(attrPath) % pos % prevPos);
|
||||
}
|
||||
|
||||
|
||||
static void dupAttr(Symbol attr, const Pos & pos, const Pos & prevPos)
|
||||
{
|
||||
throw ParseError({
|
||||
.hint = hintfmt("attribute '%1%' already defined at %2%", attr, prevPos),
|
||||
.nixCode = NixCode { .errPos = pos },
|
||||
});
|
||||
throw ParseError(format("attribute '%1%' at %2% already defined at %3%")
|
||||
% attr % pos % prevPos);
|
||||
}
|
||||
|
||||
|
||||
@@ -145,11 +140,8 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath,
|
||||
static void addFormal(const Pos & pos, Formals * formals, const Formal & formal)
|
||||
{
|
||||
if (!formals->argNames.insert(formal.name).second)
|
||||
throw ParseError({
|
||||
.hint = hintfmt("duplicate formal function argument '%1%'",
|
||||
formal.name),
|
||||
.nixCode = NixCode { .errPos = pos },
|
||||
});
|
||||
throw ParseError(format("duplicate formal function argument '%1%' at %2%")
|
||||
% formal.name % pos);
|
||||
formals->formals.push_front(formal);
|
||||
}
|
||||
|
||||
@@ -257,10 +249,8 @@ static inline Pos makeCurPos(const YYLTYPE & loc, ParseData * data)
|
||||
|
||||
void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * error)
|
||||
{
|
||||
data->error = {
|
||||
.hint = hintfmt(error),
|
||||
.nixCode = NixCode { .errPos = makeCurPos(*loc, data) }
|
||||
};
|
||||
data->error = (format("%1%, at %2%")
|
||||
% error % makeCurPos(*loc, data)).str();
|
||||
}
|
||||
|
||||
|
||||
@@ -337,10 +327,8 @@ expr_function
|
||||
{ $$ = new ExprWith(CUR_POS, $2, $4); }
|
||||
| LET binds IN expr_function
|
||||
{ if (!$2->dynamicAttrs.empty())
|
||||
throw ParseError({
|
||||
.hint = hintfmt("dynamic attributes not allowed in let"),
|
||||
.nixCode = NixCode { .errPos = CUR_POS },
|
||||
});
|
||||
throw ParseError(format("dynamic attributes not allowed in let at %1%")
|
||||
% CUR_POS);
|
||||
$$ = new ExprLet($2, $4);
|
||||
}
|
||||
| expr_if
|
||||
@@ -417,10 +405,7 @@ expr_simple
|
||||
| URI {
|
||||
static bool noURLLiterals = settings.isExperimentalFeatureEnabled("no-url-literals");
|
||||
if (noURLLiterals)
|
||||
throw ParseError({
|
||||
.hint = hintfmt("URL literals are disabled"),
|
||||
.nixCode = NixCode { .errPos = CUR_POS }
|
||||
});
|
||||
throw ParseError("URL literals are disabled, at %s", CUR_POS);
|
||||
$$ = new ExprString(data->symbols.create($1));
|
||||
}
|
||||
| '(' expr ')' { $$ = $2; }
|
||||
@@ -490,10 +475,8 @@ attrs
|
||||
$$->push_back(AttrName(str->s));
|
||||
delete str;
|
||||
} else
|
||||
throw ParseError({
|
||||
.hint = hintfmt("dynamic attributes not allowed in inherit"),
|
||||
.nixCode = NixCode { .errPos = makeCurPos(@2, data) },
|
||||
});
|
||||
throw ParseError(format("dynamic attributes not allowed in inherit at %1%")
|
||||
% makeCurPos(@2, data));
|
||||
}
|
||||
| { $$ = new AttrPath; }
|
||||
;
|
||||
@@ -688,13 +671,11 @@ Path EvalState::findFile(SearchPath & searchPath, const string & path, const Pos
|
||||
Path res = r.second + suffix;
|
||||
if (pathExists(res)) return canonPath(res);
|
||||
}
|
||||
throw ThrownError({
|
||||
.hint = hintfmt(evalSettings.pureEval
|
||||
? "cannot look up '<%s>' in pure evaluation mode (use '--impure' to override)"
|
||||
: "file '%s' was not found in the Nix search path (add it using $NIX_PATH or -I)",
|
||||
path),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
format f = format(
|
||||
"file '%1%' was not found in the Nix search path (add it using $NIX_PATH or -I)"
|
||||
+ string(pos ? ", at %2%" : ""));
|
||||
f.exceptions(boost::io::all_error_bits ^ boost::io::too_many_args_bit);
|
||||
throw ThrownError(f % path % pos);
|
||||
}
|
||||
|
||||
|
||||
@@ -710,10 +691,7 @@ std::pair<bool, std::string> EvalState::resolveSearchPathElem(const SearchPathEl
|
||||
res = { true, store->toRealPath(fetchers::downloadTarball(
|
||||
store, resolveUri(elem.second), "source", false).storePath) };
|
||||
} catch (FileTransferError & e) {
|
||||
logWarning({
|
||||
.name = "Entry download",
|
||||
.hint = hintfmt("Nix search path entry '%1%' cannot be downloaded, ignoring", elem.second)
|
||||
});
|
||||
printError(format("warning: Nix search path entry '%1%' cannot be downloaded, ignoring") % elem.second);
|
||||
res = { false, "" };
|
||||
}
|
||||
} else {
|
||||
@@ -721,10 +699,7 @@ std::pair<bool, std::string> EvalState::resolveSearchPathElem(const SearchPathEl
|
||||
if (pathExists(path))
|
||||
res = { true, path };
|
||||
else {
|
||||
logWarning({
|
||||
.name = "Entry not found",
|
||||
.hint = hintfmt("warning: Nix search path entry '%1%' does not exist, ignoring", elem.second)
|
||||
});
|
||||
printError(format("warning: Nix search path entry '%1%' does not exist, ignoring") % elem.second);
|
||||
res = { false, "" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ void EvalState::realiseContext(const PathSet & context)
|
||||
if (!store->isValidPath(ctx))
|
||||
throw InvalidPathError(store->printStorePath(ctx));
|
||||
if (!outputName.empty() && ctx.isDerivation()) {
|
||||
drvs.push_back(StorePathWithOutputs{ctx, {outputName}});
|
||||
drvs.push_back(StorePathWithOutputs{ctx.clone(), {outputName}});
|
||||
|
||||
/* Add the output of this derivation to the allowed
|
||||
paths. */
|
||||
@@ -94,10 +94,8 @@ static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args
|
||||
try {
|
||||
state.realiseContext(context);
|
||||
} catch (InvalidPathError & e) {
|
||||
throw EvalError({
|
||||
.hint = hintfmt("cannot import '%1%', since path '%2%' is not valid", path, e.path),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format("cannot import '%1%', since path '%2%' is not valid, at %3%")
|
||||
% path % e.path % pos);
|
||||
}
|
||||
|
||||
Path realPath = state.checkSourcePath(state.toRealPath(path, context));
|
||||
@@ -173,12 +171,8 @@ void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value
|
||||
try {
|
||||
state.realiseContext(context);
|
||||
} catch (InvalidPathError & e) {
|
||||
throw EvalError({
|
||||
.hint = hintfmt(
|
||||
"cannot import '%1%', since path '%2%' is not valid",
|
||||
path, e.path),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format("cannot import '%1%', since path '%2%' is not valid, at %3%")
|
||||
% path % e.path % pos);
|
||||
}
|
||||
|
||||
path = state.checkSourcePath(path);
|
||||
@@ -187,17 +181,17 @@ void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value
|
||||
|
||||
void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
||||
if (!handle)
|
||||
throw EvalError("could not open '%1%': %2%", path, dlerror());
|
||||
throw EvalError(format("could not open '%1%': %2%") % path % dlerror());
|
||||
|
||||
dlerror();
|
||||
ValueInitializer func = (ValueInitializer) dlsym(handle, sym.c_str());
|
||||
if(!func) {
|
||||
char *message = dlerror();
|
||||
if (message)
|
||||
throw EvalError("could not load symbol '%1%' from '%2%': %3%", sym, path, message);
|
||||
throw EvalError(format("could not load symbol '%1%' from '%2%': %3%") % sym % path % message);
|
||||
else
|
||||
throw EvalError("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected",
|
||||
sym, path);
|
||||
throw EvalError(format("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected")
|
||||
% sym % path);
|
||||
}
|
||||
|
||||
(func)(state, v);
|
||||
@@ -213,10 +207,7 @@ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||
auto elems = args[0]->listElems();
|
||||
auto count = args[0]->listSize();
|
||||
if (count == 0) {
|
||||
throw EvalError({
|
||||
.hint = hintfmt("at least one argument to 'exec' required"),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format("at least one argument to 'exec' required, at %1%") % pos);
|
||||
}
|
||||
PathSet context;
|
||||
auto program = state.coerceToString(pos, *elems[0], context, false, false);
|
||||
@@ -227,11 +218,8 @@ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||
try {
|
||||
state.realiseContext(context);
|
||||
} catch (InvalidPathError & e) {
|
||||
throw EvalError({
|
||||
.hint = hintfmt("cannot execute '%1%', since path '%2%' is not valid",
|
||||
program, e.path),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format("cannot execute '%1%', since path '%2%' is not valid, at %3%")
|
||||
% program % e.path % pos);
|
||||
}
|
||||
|
||||
auto output = runProgram(program, true, commandArgs);
|
||||
@@ -239,13 +227,13 @@ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||
try {
|
||||
parsed = state.parseExprFromString(output, pos.file);
|
||||
} catch (Error & e) {
|
||||
e.addPrefix(fmt("While parsing the output from '%1%', at %2%\n", program, pos));
|
||||
e.addPrefix(format("While parsing the output from '%1%', at %2%\n") % program % pos);
|
||||
throw;
|
||||
}
|
||||
try {
|
||||
state.eval(parsed, v);
|
||||
} catch (Error & e) {
|
||||
e.addPrefix(fmt("While evaluating the output from '%1%', at %2%\n", program, pos));
|
||||
e.addPrefix(format("While evaluating the output from '%1%', at %2%\n") % program % pos);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -351,7 +339,7 @@ struct CompareValues
|
||||
if (v1->type == tInt && v2->type == tFloat)
|
||||
return v1->integer < v2->fpoint;
|
||||
if (v1->type != v2->type)
|
||||
throw EvalError("cannot compare %1% with %2%", showType(*v1), showType(*v2));
|
||||
throw EvalError(format("cannot compare %1% with %2%") % showType(*v1) % showType(*v2));
|
||||
switch (v1->type) {
|
||||
case tInt:
|
||||
return v1->integer < v2->integer;
|
||||
@@ -362,7 +350,7 @@ struct CompareValues
|
||||
case tPath:
|
||||
return strcmp(v1->path, v2->path) < 0;
|
||||
default:
|
||||
throw EvalError("cannot compare %1% with %2%", showType(*v1), showType(*v2));
|
||||
throw EvalError(format("cannot compare %1% with %2%") % showType(*v1) % showType(*v2));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -383,10 +371,7 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar
|
||||
Bindings::iterator startSet =
|
||||
args[0]->attrs->find(state.symbols.create("startSet"));
|
||||
if (startSet == args[0]->attrs->end())
|
||||
throw EvalError({
|
||||
.hint = hintfmt("attribute 'startSet' required"),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format("attribute 'startSet' required, at %1%") % pos);
|
||||
state.forceList(*startSet->value, pos);
|
||||
|
||||
ValueList workSet;
|
||||
@@ -397,10 +382,7 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar
|
||||
Bindings::iterator op =
|
||||
args[0]->attrs->find(state.symbols.create("operator"));
|
||||
if (op == args[0]->attrs->end())
|
||||
throw EvalError({
|
||||
.hint = hintfmt("attribute 'operator' required"),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format("attribute 'operator' required, at %1%") % pos);
|
||||
state.forceValue(*op->value, pos);
|
||||
|
||||
/* Construct the closure by applying the operator to element of
|
||||
@@ -419,10 +401,7 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar
|
||||
Bindings::iterator key =
|
||||
e->attrs->find(state.symbols.create("key"));
|
||||
if (key == e->attrs->end())
|
||||
throw EvalError({
|
||||
.hint = hintfmt("attribute 'key' required"),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format("attribute 'key' required, at %1%") % pos);
|
||||
state.forceValue(*key->value, pos);
|
||||
|
||||
if (!doneKeys.insert(key->value).second) continue;
|
||||
@@ -452,7 +431,7 @@ static void prim_abort(EvalState & state, const Pos & pos, Value * * args, Value
|
||||
{
|
||||
PathSet context;
|
||||
string s = state.coerceToString(pos, *args[0], context);
|
||||
throw Abort("evaluation aborted with the following error message: '%1%'", s);
|
||||
throw Abort(format("evaluation aborted with the following error message: '%1%'") % s);
|
||||
}
|
||||
|
||||
|
||||
@@ -527,9 +506,9 @@ static void prim_trace(EvalState & state, const Pos & pos, Value * * args, Value
|
||||
{
|
||||
state.forceValue(*args[0], pos);
|
||||
if (args[0]->type == tString)
|
||||
printError("trace: %1%", args[0]->string.s);
|
||||
printError(format("trace: %1%") % args[0]->string.s);
|
||||
else
|
||||
printError("trace: %1%", *args[0]);
|
||||
printError(format("trace: %1%") % *args[0]);
|
||||
state.forceValue(*args[1], pos);
|
||||
v = *args[1];
|
||||
}
|
||||
@@ -554,16 +533,13 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
|
||||
/* Figure out the name first (for stack backtraces). */
|
||||
Bindings::iterator attr = args[0]->attrs->find(state.sName);
|
||||
if (attr == args[0]->attrs->end())
|
||||
throw EvalError({
|
||||
.hint = hintfmt("required attribute 'name' missing"),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format("required attribute 'name' missing, at %1%") % pos);
|
||||
string drvName;
|
||||
Pos & posDrvName(*attr->pos);
|
||||
try {
|
||||
drvName = state.forceStringNoCtx(*attr->value, pos);
|
||||
} catch (Error & e) {
|
||||
e.addPrefix(fmt("while evaluating the derivation attribute 'name' at %1%:\n", posDrvName));
|
||||
e.addPrefix(format("while evaluating the derivation attribute 'name' at %1%:\n") % posDrvName);
|
||||
throw;
|
||||
}
|
||||
|
||||
@@ -587,7 +563,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
|
||||
|
||||
std::optional<std::string> outputHash;
|
||||
std::string outputHashAlgo;
|
||||
auto ingestionMethod = FileIngestionMethod::Flat;
|
||||
bool outputHashRecursive = false;
|
||||
|
||||
StringSet outputs;
|
||||
outputs.insert("out");
|
||||
@@ -598,40 +574,27 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
|
||||
vomit("processing attribute '%1%'", key);
|
||||
|
||||
auto handleHashMode = [&](const std::string & s) {
|
||||
if (s == "recursive") ingestionMethod = FileIngestionMethod::Recursive;
|
||||
else if (s == "flat") ingestionMethod = FileIngestionMethod::Flat;
|
||||
else
|
||||
throw EvalError({
|
||||
.hint = hintfmt("invalid value '%s' for 'outputHashMode' attribute", s),
|
||||
.nixCode = NixCode { .errPos = posDrvName }
|
||||
});
|
||||
if (s == "recursive") outputHashRecursive = true;
|
||||
else if (s == "flat") outputHashRecursive = false;
|
||||
else throw EvalError("invalid value '%s' for 'outputHashMode' attribute, at %s", s, posDrvName);
|
||||
};
|
||||
|
||||
auto handleOutputs = [&](const Strings & ss) {
|
||||
outputs.clear();
|
||||
for (auto & j : ss) {
|
||||
if (outputs.find(j) != outputs.end())
|
||||
throw EvalError({
|
||||
.hint = hintfmt("duplicate derivation output '%1%'", j),
|
||||
.nixCode = NixCode { .errPos = posDrvName }
|
||||
});
|
||||
throw EvalError(format("duplicate derivation output '%1%', at %2%") % j % posDrvName);
|
||||
/* !!! Check whether j is a valid attribute
|
||||
name. */
|
||||
/* Derivations cannot be named ‘drv’, because
|
||||
then we'd have an attribute ‘drvPath’ in
|
||||
the resulting set. */
|
||||
if (j == "drv")
|
||||
throw EvalError({
|
||||
.hint = hintfmt("invalid derivation output name 'drv'" ),
|
||||
.nixCode = NixCode { .errPos = posDrvName }
|
||||
});
|
||||
throw EvalError(format("invalid derivation output name 'drv', at %1%") % posDrvName);
|
||||
outputs.insert(j);
|
||||
}
|
||||
if (outputs.empty())
|
||||
throw EvalError({
|
||||
.hint = hintfmt("derivation cannot have an empty set of outputs"),
|
||||
.nixCode = NixCode { .errPos = posDrvName }
|
||||
});
|
||||
throw EvalError(format("derivation cannot have an empty set of outputs, at %1%") % posDrvName);
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -724,9 +687,9 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
|
||||
StorePathSet refs;
|
||||
state.store->computeFSClosure(state.store->parseStorePath(std::string_view(path).substr(1)), refs);
|
||||
for (auto & j : refs) {
|
||||
drv.inputSrcs.insert(j);
|
||||
drv.inputSrcs.insert(j.clone());
|
||||
if (j.isDerivation())
|
||||
drv.inputDrvs[j] = state.store->readDerivation(j).outputNames();
|
||||
drv.inputDrvs[j.clone()] = state.store->queryDerivationOutputNames(j);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -743,44 +706,27 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
|
||||
|
||||
/* Do we have all required attributes? */
|
||||
if (drv.builder == "")
|
||||
throw EvalError({
|
||||
.hint = hintfmt("required attribute 'builder' missing"),
|
||||
.nixCode = NixCode { .errPos = posDrvName }
|
||||
});
|
||||
|
||||
throw EvalError(format("required attribute 'builder' missing, at %1%") % posDrvName);
|
||||
if (drv.platform == "")
|
||||
throw EvalError({
|
||||
.hint = hintfmt("required attribute 'system' missing"),
|
||||
.nixCode = NixCode { .errPos = posDrvName }
|
||||
});
|
||||
throw EvalError(format("required attribute 'system' missing, at %1%") % posDrvName);
|
||||
|
||||
/* Check whether the derivation name is valid. */
|
||||
if (isDerivation(drvName))
|
||||
throw EvalError({
|
||||
.hint = hintfmt("derivation names are not allowed to end in '%s'", drvExtension),
|
||||
.nixCode = NixCode { .errPos = posDrvName }
|
||||
});
|
||||
throw EvalError("derivation names are not allowed to end in '%s', at %s", drvExtension, posDrvName);
|
||||
|
||||
if (outputHash) {
|
||||
/* Handle fixed-output derivations. */
|
||||
if (outputs.size() != 1 || *(outputs.begin()) != "out")
|
||||
throw Error({
|
||||
.hint = hintfmt("multiple outputs are not supported in fixed-output derivations"),
|
||||
.nixCode = NixCode { .errPos = posDrvName }
|
||||
});
|
||||
throw Error(format("multiple outputs are not supported in fixed-output derivations, at %1%") % posDrvName);
|
||||
|
||||
std::optional<HashType> ht = parseHashTypeOpt(outputHashAlgo);
|
||||
Hash h = newHashAllowEmpty(*outputHash, ht);
|
||||
HashType ht = outputHashAlgo.empty() ? htUnknown : parseHashType(outputHashAlgo);
|
||||
Hash h(*outputHash, ht);
|
||||
|
||||
auto outPath = state.store->makeFixedOutputPath(ingestionMethod, h, drvName);
|
||||
auto outPath = state.store->makeFixedOutputPath(outputHashRecursive, h, drvName);
|
||||
if (!jsonObject) drv.env["out"] = state.store->printStorePath(outPath);
|
||||
drv.outputs.insert_or_assign("out", DerivationOutput {
|
||||
.path = std::move(outPath),
|
||||
.hash = FixedOutputHash {
|
||||
.method = ingestionMethod,
|
||||
.hash = std::move(h),
|
||||
},
|
||||
});
|
||||
drv.outputs.insert_or_assign("out", DerivationOutput(std::move(outPath),
|
||||
(outputHashRecursive ? "r:" : "") + printHashType(h.type),
|
||||
h.to_string(Base16, false)));
|
||||
}
|
||||
|
||||
else {
|
||||
@@ -793,10 +739,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
|
||||
for (auto & i : outputs) {
|
||||
if (!jsonObject) drv.env[i] = "";
|
||||
drv.outputs.insert_or_assign(i,
|
||||
DerivationOutput {
|
||||
.path = StorePath::dummy,
|
||||
.hash = std::optional<FixedOutputHash> {},
|
||||
});
|
||||
DerivationOutput(StorePath::dummy.clone(), "", ""));
|
||||
}
|
||||
|
||||
Hash h = hashDerivationModulo(*state.store, Derivation(drv), true);
|
||||
@@ -805,10 +748,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
|
||||
auto outPath = state.store->makeOutputPath(i, h, drvName);
|
||||
if (!jsonObject) drv.env[i] = state.store->printStorePath(outPath);
|
||||
drv.outputs.insert_or_assign(i,
|
||||
DerivationOutput {
|
||||
.path = std::move(outPath),
|
||||
.hash = std::optional<FixedOutputHash>(),
|
||||
});
|
||||
DerivationOutput(std::move(outPath), "", ""));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -821,7 +761,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
|
||||
/* Optimisation, but required in read-only mode! because in that
|
||||
case we don't actually write store derivations, so we can't
|
||||
read them later. */
|
||||
drvHashes.insert_or_assign(drvPath,
|
||||
drvHashes.insert_or_assign(drvPath.clone(),
|
||||
hashDerivationModulo(*state.store, Derivation(drv), false));
|
||||
|
||||
state.mkAttrs(v, 1 + drv.outputs.size());
|
||||
@@ -878,10 +818,7 @@ static void prim_storePath(EvalState & state, const Pos & pos, Value * * args, V
|
||||
e.g. nix-push does the right thing. */
|
||||
if (!state.store->isStorePath(path)) path = canonPath(path, true);
|
||||
if (!state.store->isInStore(path))
|
||||
throw EvalError({
|
||||
.hint = hintfmt("path '%1%' is not in the Nix store", path),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format("path '%1%' is not in the Nix store, at %2%") % path % pos);
|
||||
Path path2 = state.store->toStorePath(path);
|
||||
if (!settings.readOnlyMode)
|
||||
state.store->ensurePath(state.store->parseStorePath(path2));
|
||||
@@ -897,12 +834,9 @@ static void prim_pathExists(EvalState & state, const Pos & pos, Value * * args,
|
||||
try {
|
||||
state.realiseContext(context);
|
||||
} catch (InvalidPathError & e) {
|
||||
throw EvalError({
|
||||
.hint = hintfmt(
|
||||
"cannot check the existence of '%1%', since path '%2%' is not valid",
|
||||
path, e.path),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format(
|
||||
"cannot check the existence of '%1%', since path '%2%' is not valid, at %3%")
|
||||
% path % e.path % pos);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -945,14 +879,12 @@ static void prim_readFile(EvalState & state, const Pos & pos, Value * * args, Va
|
||||
try {
|
||||
state.realiseContext(context);
|
||||
} catch (InvalidPathError & e) {
|
||||
throw EvalError({
|
||||
.hint = hintfmt("cannot read '%1%', since path '%2%' is not valid", path, e.path),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format("cannot read '%1%', since path '%2%' is not valid, at %3%")
|
||||
% path % e.path % pos);
|
||||
}
|
||||
string s = readFile(state.checkSourcePath(state.toRealPath(path, context)));
|
||||
if (s.find((char) 0) != string::npos)
|
||||
throw Error("the contents of the file '%1%' cannot be represented as a Nix string", path);
|
||||
throw Error(format("the contents of the file '%1%' cannot be represented as a Nix string") % path);
|
||||
mkString(v, s.c_str());
|
||||
}
|
||||
|
||||
@@ -976,10 +908,7 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va
|
||||
|
||||
i = v2.attrs->find(state.symbols.create("path"));
|
||||
if (i == v2.attrs->end())
|
||||
throw EvalError({
|
||||
.hint = hintfmt("attribute 'path' missing"),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format("attribute 'path' missing, at %1%") % pos);
|
||||
|
||||
PathSet context;
|
||||
string path = state.coerceToString(pos, *i->value, context, false, false);
|
||||
@@ -987,10 +916,8 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va
|
||||
try {
|
||||
state.realiseContext(context);
|
||||
} catch (InvalidPathError & e) {
|
||||
throw EvalError({
|
||||
.hint = hintfmt("cannot find '%1%', since path '%2%' is not valid", path, e.path),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format("cannot find '%1%', since path '%2%' is not valid, at %3%")
|
||||
% path % e.path % pos);
|
||||
}
|
||||
|
||||
searchPath.emplace_back(prefix, path);
|
||||
@@ -1005,17 +932,14 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va
|
||||
static void prim_hashFile(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||
{
|
||||
string type = state.forceStringNoCtx(*args[0], pos);
|
||||
std::optional<HashType> ht = parseHashType(type);
|
||||
if (!ht)
|
||||
throw Error({
|
||||
.hint = hintfmt("unknown hash type '%1%'", type),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
HashType ht = parseHashType(type);
|
||||
if (ht == htUnknown)
|
||||
throw Error(format("unknown hash type '%1%', at %2%") % type % pos);
|
||||
|
||||
PathSet context; // discarded
|
||||
Path p = state.coerceToPath(pos, *args[1], context);
|
||||
|
||||
mkString(v, hashFile(*ht, state.checkSourcePath(p)).to_string(Base16, false), context);
|
||||
mkString(v, hashFile(ht, state.checkSourcePath(p)).to_string(Base16, false), context);
|
||||
}
|
||||
|
||||
/* Read a directory (without . or ..) */
|
||||
@@ -1026,10 +950,8 @@ static void prim_readDir(EvalState & state, const Pos & pos, Value * * args, Val
|
||||
try {
|
||||
state.realiseContext(ctx);
|
||||
} catch (InvalidPathError & e) {
|
||||
throw EvalError({
|
||||
.hint = hintfmt("cannot read '%1%', since path '%2%' is not valid", path, e.path),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format("cannot read '%1%', since path '%2%' is not valid, at %3%")
|
||||
% path % e.path % pos);
|
||||
}
|
||||
|
||||
DirEntries entries = readDirectory(state.checkSourcePath(path));
|
||||
@@ -1099,13 +1021,9 @@ static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Valu
|
||||
|
||||
for (auto path : context) {
|
||||
if (path.at(0) != '/')
|
||||
throw EvalError( {
|
||||
.hint = hintfmt(
|
||||
"in 'toFile': the file named '%1%' must not contain a reference "
|
||||
"to a derivation but contains (%2%)",
|
||||
name, path),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format(
|
||||
"in 'toFile': the file named '%1%' must not contain a reference "
|
||||
"to a derivation but contains (%2%), at %3%") % name % path % pos);
|
||||
refs.insert(state.store->parseStorePath(path));
|
||||
}
|
||||
|
||||
@@ -1122,7 +1040,7 @@ static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Valu
|
||||
|
||||
|
||||
static void addPath(EvalState & state, const Pos & pos, const string & name, const Path & path_,
|
||||
Value * filterFun, FileIngestionMethod method, const Hash & expectedHash, Value & v)
|
||||
Value * filterFun, bool recursive, const Hash & expectedHash, Value & v)
|
||||
{
|
||||
const auto path = evalSettings.pureEval && expectedHash ?
|
||||
path_ :
|
||||
@@ -1153,12 +1071,12 @@ static void addPath(EvalState & state, const Pos & pos, const string & name, con
|
||||
|
||||
std::optional<StorePath> expectedStorePath;
|
||||
if (expectedHash)
|
||||
expectedStorePath = state.store->makeFixedOutputPath(method, expectedHash, name);
|
||||
expectedStorePath = state.store->makeFixedOutputPath(recursive, expectedHash, name);
|
||||
Path dstPath;
|
||||
if (!expectedHash || !state.store->isValidPath(*expectedStorePath)) {
|
||||
dstPath = state.store->printStorePath(settings.readOnlyMode
|
||||
? state.store->computeStorePathForPath(name, path, method, htSHA256, filter).first
|
||||
: state.store->addToStore(name, path, method, htSHA256, filter, state.repair));
|
||||
? state.store->computeStorePathForPath(name, path, recursive, htSHA256, filter).first
|
||||
: state.store->addToStore(name, path, recursive, htSHA256, filter, state.repair));
|
||||
if (expectedHash && expectedStorePath != state.store->parseStorePath(dstPath))
|
||||
throw Error("store path mismatch in (possibly filtered) path added from '%s'", path);
|
||||
} else
|
||||
@@ -1173,21 +1091,13 @@ static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args
|
||||
PathSet context;
|
||||
Path path = state.coerceToPath(pos, *args[1], context);
|
||||
if (!context.empty())
|
||||
throw EvalError({
|
||||
.hint = hintfmt("string '%1%' cannot refer to other paths", path),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format("string '%1%' cannot refer to other paths, at %2%") % path % pos);
|
||||
|
||||
state.forceValue(*args[0], pos);
|
||||
if (args[0]->type != tLambda)
|
||||
throw TypeError({
|
||||
.hint = hintfmt(
|
||||
"first argument in call to 'filterSource' is not a function but %1%",
|
||||
showType(*args[0])),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw TypeError(format("first argument in call to 'filterSource' is not a function but %1%, at %2%") % showType(*args[0]) % pos);
|
||||
|
||||
addPath(state, pos, std::string(baseNameOf(path)), path, args[0], FileIngestionMethod::Recursive, Hash(), v);
|
||||
addPath(state, pos, std::string(baseNameOf(path)), path, args[0], true, Hash(), v);
|
||||
}
|
||||
|
||||
static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||
@@ -1196,7 +1106,7 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value
|
||||
Path path;
|
||||
string name;
|
||||
Value * filterFun = nullptr;
|
||||
auto method = FileIngestionMethod::Recursive;
|
||||
auto recursive = true;
|
||||
Hash expectedHash;
|
||||
|
||||
for (auto & attr : *args[0]->attrs) {
|
||||
@@ -1205,34 +1115,25 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value
|
||||
PathSet context;
|
||||
path = state.coerceToPath(*attr.pos, *attr.value, context);
|
||||
if (!context.empty())
|
||||
throw EvalError({
|
||||
.hint = hintfmt("string '%1%' cannot refer to other paths", path),
|
||||
.nixCode = NixCode { .errPos = *attr.pos }
|
||||
});
|
||||
throw EvalError(format("string '%1%' cannot refer to other paths, at %2%") % path % *attr.pos);
|
||||
} else if (attr.name == state.sName)
|
||||
name = state.forceStringNoCtx(*attr.value, *attr.pos);
|
||||
else if (n == "filter") {
|
||||
state.forceValue(*attr.value, pos);
|
||||
filterFun = attr.value;
|
||||
} else if (n == "recursive")
|
||||
method = FileIngestionMethod { state.forceBool(*attr.value, *attr.pos) };
|
||||
recursive = state.forceBool(*attr.value, *attr.pos);
|
||||
else if (n == "sha256")
|
||||
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256);
|
||||
expectedHash = Hash(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256);
|
||||
else
|
||||
throw EvalError({
|
||||
.hint = hintfmt("unsupported argument '%1%' to 'addPath'", attr.name),
|
||||
.nixCode = NixCode { .errPos = *attr.pos }
|
||||
});
|
||||
throw EvalError(format("unsupported argument '%1%' to 'addPath', at %2%") % attr.name % *attr.pos);
|
||||
}
|
||||
if (path.empty())
|
||||
throw EvalError({
|
||||
.hint = hintfmt("'path' required"),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format("'path' required, at %1%") % pos);
|
||||
if (name.empty())
|
||||
name = baseNameOf(path);
|
||||
|
||||
addPath(state, pos, name, path, filterFun, method, expectedHash, v);
|
||||
addPath(state, pos, name, path, filterFun, recursive, expectedHash, v);
|
||||
}
|
||||
|
||||
|
||||
@@ -1286,10 +1187,7 @@ void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||
// !!! Should we create a symbol here or just do a lookup?
|
||||
Bindings::iterator i = args[1]->attrs->find(state.symbols.create(attr));
|
||||
if (i == args[1]->attrs->end())
|
||||
throw EvalError({
|
||||
.hint = hintfmt("attribute '%1%' missing", attr),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format("attribute '%1%' missing, at %2%") % attr % pos);
|
||||
// !!! add to stack trace?
|
||||
if (state.countCalls && i->pos) state.attrSelects[*i->pos]++;
|
||||
state.forceValue(*i->value, pos);
|
||||
@@ -1369,20 +1267,15 @@ static void prim_listToAttrs(EvalState & state, const Pos & pos, Value * * args,
|
||||
|
||||
Bindings::iterator j = v2.attrs->find(state.sName);
|
||||
if (j == v2.attrs->end())
|
||||
throw TypeError({
|
||||
.hint = hintfmt("'name' attribute missing in a call to 'listToAttrs'"),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw TypeError(format("'name' attribute missing in a call to 'listToAttrs', at %1%") % pos);
|
||||
string name = state.forceStringNoCtx(*j->value, pos);
|
||||
|
||||
Symbol sym = state.symbols.create(name);
|
||||
if (seen.insert(sym).second) {
|
||||
Bindings::iterator j2 = v2.attrs->find(state.symbols.create(state.sValue));
|
||||
if (j2 == v2.attrs->end())
|
||||
throw TypeError({
|
||||
.hint = hintfmt("'value' attribute missing in a call to 'listToAttrs'"),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw TypeError(format("'value' attribute missing in a call to 'listToAttrs', at %1%") % pos);
|
||||
|
||||
v.attrs->push_back(Attr(sym, j2->value, j2->pos));
|
||||
}
|
||||
}
|
||||
@@ -1455,10 +1348,7 @@ static void prim_functionArgs(EvalState & state, const Pos & pos, Value * * args
|
||||
{
|
||||
state.forceValue(*args[0], pos);
|
||||
if (args[0]->type != tLambda)
|
||||
throw TypeError({
|
||||
.hint = hintfmt("'functionArgs' requires a function"),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw TypeError(format("'functionArgs' requires a function, at %1%") % pos);
|
||||
|
||||
if (!args[0]->lambda.fun->matchAttrs) {
|
||||
state.mkAttrs(v, 0);
|
||||
@@ -1511,10 +1401,7 @@ static void elemAt(EvalState & state, const Pos & pos, Value & list, int n, Valu
|
||||
{
|
||||
state.forceList(list, pos);
|
||||
if (n < 0 || (unsigned int) n >= list.listSize())
|
||||
throw Error({
|
||||
.hint = hintfmt("list index %1% is out of bounds", n),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw Error(format("list index %1% is out of bounds, at %2%") % n % pos);
|
||||
state.forceValue(*list.listElems()[n], pos);
|
||||
v = *list.listElems()[n];
|
||||
}
|
||||
@@ -1541,11 +1428,7 @@ static void prim_tail(EvalState & state, const Pos & pos, Value * * args, Value
|
||||
{
|
||||
state.forceList(*args[0], pos);
|
||||
if (args[0]->listSize() == 0)
|
||||
throw Error({
|
||||
.hint = hintfmt("'tail' called on an empty list"),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
|
||||
throw Error(format("'tail' called on an empty list, at %1%") % pos);
|
||||
state.mkList(v, args[0]->listSize() - 1);
|
||||
for (unsigned int n = 0; n < v.listSize(); ++n)
|
||||
v.listElems()[n] = args[0]->listElems()[n + 1];
|
||||
@@ -1686,10 +1569,7 @@ static void prim_genList(EvalState & state, const Pos & pos, Value * * args, Val
|
||||
auto len = state.forceInt(*args[1], pos);
|
||||
|
||||
if (len < 0)
|
||||
throw EvalError({
|
||||
.hint = hintfmt("cannot create list of size %1%", len),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format("cannot create list of size %1%, at %2%") % len % pos);
|
||||
|
||||
state.mkList(v, len);
|
||||
|
||||
@@ -1847,11 +1727,7 @@ static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value &
|
||||
state.forceValue(*args[1], pos);
|
||||
|
||||
NixFloat f2 = state.forceFloat(*args[1], pos);
|
||||
if (f2 == 0)
|
||||
throw EvalError({
|
||||
.hint = hintfmt("division by zero"),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
if (f2 == 0) throw EvalError(format("division by zero, at %1%") % pos);
|
||||
|
||||
if (args[0]->type == tFloat || args[1]->type == tFloat) {
|
||||
mkFloat(v, state.forceFloat(*args[0], pos) / state.forceFloat(*args[1], pos));
|
||||
@@ -1860,11 +1736,7 @@ static void prim_div(EvalState & state, const Pos & pos, Value * * args, Value &
|
||||
NixInt i2 = state.forceInt(*args[1], pos);
|
||||
/* Avoid division overflow as it might raise SIGFPE. */
|
||||
if (i1 == std::numeric_limits<NixInt>::min() && i2 == -1)
|
||||
throw EvalError({
|
||||
.hint = hintfmt("overflow in integer division"),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
|
||||
throw EvalError(format("overflow in integer division, at %1%") % pos);
|
||||
mkInt(v, i1 / i2);
|
||||
}
|
||||
}
|
||||
@@ -1920,11 +1792,7 @@ static void prim_substring(EvalState & state, const Pos & pos, Value * * args, V
|
||||
PathSet context;
|
||||
string s = state.coerceToString(pos, *args[2], context);
|
||||
|
||||
if (start < 0)
|
||||
throw EvalError({
|
||||
.hint = hintfmt("negative start position in 'substring'"),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
if (start < 0) throw EvalError(format("negative start position in 'substring', at %1%") % pos);
|
||||
|
||||
mkString(v, (unsigned int) start >= s.size() ? "" : string(s, start, len), context);
|
||||
}
|
||||
@@ -1942,17 +1810,14 @@ static void prim_stringLength(EvalState & state, const Pos & pos, Value * * args
|
||||
static void prim_hashString(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||
{
|
||||
string type = state.forceStringNoCtx(*args[0], pos);
|
||||
std::optional<HashType> ht = parseHashType(type);
|
||||
if (!ht)
|
||||
throw Error({
|
||||
.hint = hintfmt("unknown hash type '%1%'", type),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
HashType ht = parseHashType(type);
|
||||
if (ht == htUnknown)
|
||||
throw Error(format("unknown hash type '%1%', at %2%") % type % pos);
|
||||
|
||||
PathSet context; // discarded
|
||||
string s = state.forceString(*args[1], context, pos);
|
||||
|
||||
mkString(v, hashString(*ht, s).to_string(Base16, false), context);
|
||||
mkString(v, hashString(ht, s).to_string(Base16, false), context);
|
||||
}
|
||||
|
||||
|
||||
@@ -1989,16 +1854,10 @@ void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||
|
||||
} catch (std::regex_error &e) {
|
||||
if (e.code() == std::regex_constants::error_space) {
|
||||
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
|
||||
throw EvalError({
|
||||
.hint = hintfmt("memory limit exceeded by regular expression '%s'", re),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
|
||||
throw EvalError("memory limit exceeded by regular expression '%s', at %s", re, pos);
|
||||
} else {
|
||||
throw EvalError({
|
||||
.hint = hintfmt("invalid regular expression '%s'", re),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError("invalid regular expression '%s', at %s", re, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2062,16 +1921,10 @@ static void prim_split(EvalState & state, const Pos & pos, Value * * args, Value
|
||||
|
||||
} catch (std::regex_error &e) {
|
||||
if (e.code() == std::regex_constants::error_space) {
|
||||
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
|
||||
throw EvalError({
|
||||
.hint = hintfmt("memory limit exceeded by regular expression '%s'", re),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
|
||||
throw EvalError("memory limit exceeded by regular expression '%s', at %s", re, pos);
|
||||
} else {
|
||||
throw EvalError({
|
||||
.hint = hintfmt("invalid regular expression '%s'", re),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError("invalid regular expression '%s', at %s", re, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2102,10 +1955,7 @@ static void prim_replaceStrings(EvalState & state, const Pos & pos, Value * * ar
|
||||
state.forceList(*args[0], pos);
|
||||
state.forceList(*args[1], pos);
|
||||
if (args[0]->listSize() != args[1]->listSize())
|
||||
throw EvalError({
|
||||
.hint = hintfmt("'from' and 'to' arguments to 'replaceStrings' have different lengths"),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format("'from' and 'to' arguments to 'replaceStrings' have different lengths, at %1%") % pos);
|
||||
|
||||
vector<string> from;
|
||||
from.reserve(args[0]->listSize());
|
||||
@@ -2208,11 +2058,10 @@ static void prim_splitVersion(EvalState & state, const Pos & pos, Value * * args
|
||||
RegisterPrimOp::PrimOps * RegisterPrimOp::primOps;
|
||||
|
||||
|
||||
RegisterPrimOp::RegisterPrimOp(std::string name, size_t arity, PrimOpFun fun,
|
||||
std::optional<std::string> requiredFeature)
|
||||
RegisterPrimOp::RegisterPrimOp(std::string name, size_t arity, PrimOpFun fun)
|
||||
{
|
||||
if (!primOps) primOps = new PrimOps;
|
||||
primOps->push_back({name, arity, fun, requiredFeature});
|
||||
primOps->emplace_back(name, arity, fun);
|
||||
}
|
||||
|
||||
|
||||
@@ -2404,8 +2253,7 @@ void EvalState::createBaseEnv()
|
||||
|
||||
if (RegisterPrimOp::primOps)
|
||||
for (auto & primOp : *RegisterPrimOp::primOps)
|
||||
if (!primOp.requiredFeature || settings.isExperimentalFeatureEnabled(*primOp.requiredFeature))
|
||||
addPrimOp(primOp.name, primOp.arity, primOp.primOp);
|
||||
addPrimOp(std::get<0>(primOp), std::get<1>(primOp), std::get<2>(primOp));
|
||||
|
||||
/* Now that we've added all primops, sort the `builtins' set,
|
||||
because attribute lookups expect it to be sorted. */
|
||||
|
||||
@@ -7,25 +7,12 @@ namespace nix {
|
||||
|
||||
struct RegisterPrimOp
|
||||
{
|
||||
struct Info
|
||||
{
|
||||
std::string name;
|
||||
size_t arity;
|
||||
PrimOpFun primOp;
|
||||
std::optional<std::string> requiredFeature;
|
||||
};
|
||||
|
||||
typedef std::vector<Info> PrimOps;
|
||||
typedef std::vector<std::tuple<std::string, size_t, PrimOpFun>> PrimOps;
|
||||
static PrimOps * primOps;
|
||||
|
||||
/* You can register a constant by passing an arity of 0. fun
|
||||
will get called during EvalState initialization, so there
|
||||
may be primops not yet added and builtins is not yet sorted. */
|
||||
RegisterPrimOp(
|
||||
std::string name,
|
||||
size_t arity,
|
||||
PrimOpFun fun,
|
||||
std::optional<std::string> requiredFeature = {});
|
||||
RegisterPrimOp(std::string name, size_t arity, PrimOpFun fun);
|
||||
};
|
||||
|
||||
/* These primops are disabled without enableNativeCode, but plugins
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "primops.hh"
|
||||
#include "eval-inline.hh"
|
||||
#include "store-api.hh"
|
||||
#include "derivations.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -146,10 +146,7 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg
|
||||
auto sAllOutputs = state.symbols.create("allOutputs");
|
||||
for (auto & i : *args[1]->attrs) {
|
||||
if (!state.store->isStorePath(i.name))
|
||||
throw EvalError({
|
||||
.hint = hintfmt("Context key '%s' is not a store path", i.name),
|
||||
.nixCode = NixCode { .errPos = *i.pos }
|
||||
});
|
||||
throw EvalError("Context key '%s' is not a store path, at %s", i.name, i.pos);
|
||||
if (!settings.readOnlyMode)
|
||||
state.store->ensurePath(state.store->parseStorePath(i.name));
|
||||
state.forceAttrs(*i.value, *i.pos);
|
||||
@@ -163,10 +160,7 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg
|
||||
if (iter != i.value->attrs->end()) {
|
||||
if (state.forceBool(*iter->value, *iter->pos)) {
|
||||
if (!isDerivation(i.name)) {
|
||||
throw EvalError({
|
||||
.hint = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", i.name),
|
||||
.nixCode = NixCode { .errPos = *i.pos }
|
||||
});
|
||||
throw EvalError("Tried to add all-outputs context of %s, which is not a derivation, to a string, at %s", i.name, i.pos);
|
||||
}
|
||||
context.insert("=" + string(i.name));
|
||||
}
|
||||
@@ -176,10 +170,7 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg
|
||||
if (iter != i.value->attrs->end()) {
|
||||
state.forceList(*iter->value, *iter->pos);
|
||||
if (iter->value->listSize() && !isDerivation(i.name)) {
|
||||
throw EvalError({
|
||||
.hint = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", i.name),
|
||||
.nixCode = NixCode { .errPos = *i.pos }
|
||||
});
|
||||
throw EvalError("Tried to add derivation output context of %s, which is not a derivation, to a string, at %s", i.name, i.pos);
|
||||
}
|
||||
for (unsigned int n = 0; n < iter->value->listSize(); ++n) {
|
||||
auto name = state.forceStringNoCtx(*iter->value->listElems()[n], *iter->pos);
|
||||
|
||||
@@ -35,17 +35,11 @@ static void prim_fetchGit(EvalState & state, const Pos & pos, Value * * args, Va
|
||||
else if (n == "submodules")
|
||||
fetchSubmodules = state.forceBool(*attr.value, *attr.pos);
|
||||
else
|
||||
throw EvalError({
|
||||
.hint = hintfmt("unsupported argument '%s' to 'fetchGit'", attr.name),
|
||||
.nixCode = NixCode { .errPos = *attr.pos }
|
||||
});
|
||||
throw EvalError("unsupported argument '%s' to 'fetchGit', at %s", attr.name, *attr.pos);
|
||||
}
|
||||
|
||||
if (url.empty())
|
||||
throw EvalError({
|
||||
.hint = hintfmt("'url' argument required"),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format("'url' argument required, at %1%") % pos);
|
||||
|
||||
} else
|
||||
url = state.coerceToString(pos, *args[0], context, false, false);
|
||||
|
||||
@@ -38,17 +38,11 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar
|
||||
else if (n == "name")
|
||||
name = state.forceStringNoCtx(*attr.value, *attr.pos);
|
||||
else
|
||||
throw EvalError({
|
||||
.hint = hintfmt("unsupported argument '%s' to 'fetchMercurial'", attr.name),
|
||||
.nixCode = NixCode { .errPos = *attr.pos }
|
||||
});
|
||||
throw EvalError("unsupported argument '%s' to 'fetchMercurial', at %s", attr.name, *attr.pos);
|
||||
}
|
||||
|
||||
if (url.empty())
|
||||
throw EvalError({
|
||||
.hint = hintfmt("'url' argument required"),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError(format("'url' argument required, at %1%") % pos);
|
||||
|
||||
} else
|
||||
url = state.coerceToString(pos, *args[0], context, false, false);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "store-api.hh"
|
||||
#include "fetchers.hh"
|
||||
#include "filetransfer.hh"
|
||||
#include "registry.hh"
|
||||
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
@@ -23,7 +24,7 @@ void emitTreeAttrs(
|
||||
|
||||
assert(tree.info.narHash);
|
||||
mkString(*state.allocAttr(v, state.symbols.create("narHash")),
|
||||
tree.info.narHash.to_string(SRI, true));
|
||||
tree.info.narHash.to_string(SRI));
|
||||
|
||||
if (input->getRev()) {
|
||||
mkString(*state.allocAttr(v, state.symbols.create("rev")), input->getRev()->gitRev());
|
||||
@@ -33,9 +34,11 @@ void emitTreeAttrs(
|
||||
if (tree.info.revCount)
|
||||
mkInt(*state.allocAttr(v, state.symbols.create("revCount")), *tree.info.revCount);
|
||||
|
||||
if (tree.info.lastModified)
|
||||
mkString(*state.allocAttr(v, state.symbols.create("lastModified")),
|
||||
if (tree.info.lastModified) {
|
||||
mkInt(*state.allocAttr(v, state.symbols.create("lastModified")), *tree.info.lastModified);
|
||||
mkString(*state.allocAttr(v, state.symbols.create("lastModifiedDate")),
|
||||
fmt("%s", std::put_time(std::gmtime(&*tree.info.lastModified), "%Y%m%d%H%M%S")));
|
||||
}
|
||||
|
||||
v.attrs->sort();
|
||||
}
|
||||
@@ -60,23 +63,25 @@ static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, V
|
||||
attrs.emplace(attr.name, attr.value->string.s);
|
||||
else if (attr.value->type == tBool)
|
||||
attrs.emplace(attr.name, attr.value->boolean);
|
||||
else if (attr.value->type == tInt)
|
||||
attrs.emplace(attr.name, attr.value->integer);
|
||||
else
|
||||
throw TypeError("fetchTree argument '%s' is %s while a string or Boolean is expected",
|
||||
throw TypeError("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
|
||||
attr.name, showType(*attr.value));
|
||||
}
|
||||
|
||||
if (!attrs.count("type"))
|
||||
throw Error({
|
||||
.hint = hintfmt("attribute 'type' is missing in call to 'fetchTree'"),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw Error("attribute 'type' is missing in call to 'fetchTree', at %s", pos);
|
||||
|
||||
input = fetchers::inputFromAttrs(attrs);
|
||||
} else
|
||||
input = fetchers::inputFromURL(state.coerceToString(pos, *args[0], context, false, false));
|
||||
|
||||
if (!evalSettings.pureEval && !input->isDirect())
|
||||
input = lookupInRegistries(state.store, input).first;
|
||||
|
||||
if (evalSettings.pureEval && !input->isImmutable())
|
||||
throw Error("in pure evaluation mode, 'fetchTree' requires an immutable input");
|
||||
throw Error("in pure evaluation mode, 'fetchTree' requires an immutable input, at %s", pos);
|
||||
|
||||
// FIXME: use fetchOrSubstituteTree
|
||||
auto [tree, input2] = input->fetchTree(state.store);
|
||||
@@ -106,21 +111,17 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
|
||||
if (n == "url")
|
||||
url = state.forceStringNoCtx(*attr.value, *attr.pos);
|
||||
else if (n == "sha256")
|
||||
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256);
|
||||
expectedHash = Hash(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256);
|
||||
else if (n == "name")
|
||||
name = state.forceStringNoCtx(*attr.value, *attr.pos);
|
||||
else
|
||||
throw EvalError({
|
||||
.hint = hintfmt("unsupported argument '%s' to '%s'", attr.name, who),
|
||||
.nixCode = NixCode { .errPos = *attr.pos }
|
||||
});
|
||||
}
|
||||
throw EvalError("unsupported argument '%s' to '%s', at %s",
|
||||
attr.name, who, *attr.pos);
|
||||
}
|
||||
|
||||
if (!url)
|
||||
throw EvalError({
|
||||
.hint = hintfmt("'url' argument required"),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError("'url' argument required, at %s", pos);
|
||||
|
||||
} else
|
||||
url = state.forceStringNoCtx(*args[0], pos);
|
||||
|
||||
@@ -147,7 +148,7 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
|
||||
: hashFile(htSHA256, path);
|
||||
if (hash != *expectedHash)
|
||||
throw Error((unsigned int) 102, "hash mismatch in file downloaded from '%s':\n wanted: %s\n got: %s",
|
||||
*url, expectedHash->to_string(Base32, true), hash.to_string(Base32, true));
|
||||
*url, expectedHash->to_string(), hash.to_string());
|
||||
}
|
||||
|
||||
if (state.allowedPaths)
|
||||
|
||||
@@ -81,10 +81,7 @@ static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Va
|
||||
try {
|
||||
visit(v, parser(tomlStream).parse());
|
||||
} catch (std::runtime_error & e) {
|
||||
throw EvalError({
|
||||
.hint = hintfmt("while parsing a TOML string: %s", e.what()),
|
||||
.nixCode = NixCode { .errPos = pos }
|
||||
});
|
||||
throw EvalError("while parsing a TOML string at %s: %s", pos, e.what());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,12 @@ public:
|
||||
return s == s2.s;
|
||||
}
|
||||
|
||||
// FIXME: remove
|
||||
bool operator == (std::string_view s2) const
|
||||
{
|
||||
return s->compare(s2) == 0;
|
||||
}
|
||||
|
||||
bool operator != (const Symbol & s2) const
|
||||
{
|
||||
return s != s2.s;
|
||||
@@ -68,9 +74,10 @@ private:
|
||||
Symbols symbols;
|
||||
|
||||
public:
|
||||
Symbol create(const string & s)
|
||||
Symbol create(std::string_view s)
|
||||
{
|
||||
std::pair<Symbols::iterator, bool> res = symbols.insert(s);
|
||||
// FIXME: avoid allocation if 's' already exists in the symbol table.
|
||||
std::pair<Symbols::iterator, bool> res = symbols.emplace(std::string(s));
|
||||
return Symbol(&*res.first);
|
||||
}
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ void printValueAsJSON(EvalState & state, bool strict,
|
||||
break;
|
||||
|
||||
default:
|
||||
throw TypeError("cannot convert %1% to JSON", showType(v));
|
||||
throw TypeError(format("cannot convert %1% to JSON") % showType(v));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ void printValueAsJSON(EvalState & state, bool strict,
|
||||
void ExternalValueBase::printValueAsJSON(EvalState & state, bool strict,
|
||||
JSONPlaceholder & out, PathSet & context) const
|
||||
{
|
||||
throw TypeError("cannot convert %1% to JSON", showType());
|
||||
throw TypeError(format("cannot convert %1% to JSON") % showType());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -166,6 +166,11 @@ struct Value
|
||||
{
|
||||
return type == tList1 ? 1 : type == tList2 ? 2 : bigList.size;
|
||||
}
|
||||
|
||||
/* Check whether forcing this value requires a trivial amount of
|
||||
computation. In particular, function applications are
|
||||
non-trivial. */
|
||||
bool isTrivial() const;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ std::unique_ptr<Input> inputFromAttrs(const Attrs & attrs)
|
||||
if (res) {
|
||||
if (auto narHash = maybeGetStrAttr(attrs, "narHash"))
|
||||
// FIXME: require SRI hash.
|
||||
res->narHash = newHashAllowEmpty(*narHash, {});
|
||||
res->narHash = Hash(*narHash);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ Attrs Input::toAttrs() const
|
||||
{
|
||||
auto attrs = toAttrsInternal();
|
||||
if (narHash)
|
||||
attrs.emplace("narHash", narHash->to_string(SRI, true));
|
||||
attrs.emplace("narHash", narHash->to_string(SRI));
|
||||
attrs.emplace("type", type());
|
||||
return attrs;
|
||||
}
|
||||
@@ -67,9 +67,20 @@ std::pair<Tree, std::shared_ptr<const Input>> Input::fetchTree(ref<Store> store)
|
||||
|
||||
if (narHash && narHash != input->narHash)
|
||||
throw Error("NAR hash mismatch in input '%s' (%s), expected '%s', got '%s'",
|
||||
to_string(), tree.actualPath, narHash->to_string(SRI, true), input->narHash->to_string(SRI, true));
|
||||
to_string(), tree.actualPath, narHash->to_string(SRI), input->narHash->to_string(SRI));
|
||||
|
||||
return {std::move(tree), input};
|
||||
}
|
||||
|
||||
std::shared_ptr<const Input> Input::applyOverrides(
|
||||
std::optional<std::string> ref,
|
||||
std::optional<Hash> rev) const
|
||||
{
|
||||
if (ref)
|
||||
throw Error("don't know how to apply '%s' to '%s'", *ref, to_string());
|
||||
if (rev)
|
||||
throw Error("don't know how to apply '%s' to '%s'", rev->to_string(Base16, false), to_string());
|
||||
return shared_from_this();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -57,6 +57,22 @@ struct Input : std::enable_shared_from_this<Input>
|
||||
|
||||
std::pair<Tree, std::shared_ptr<const Input>> fetchTree(ref<Store> store) const;
|
||||
|
||||
virtual std::shared_ptr<const Input> applyOverrides(
|
||||
std::optional<std::string> ref,
|
||||
std::optional<Hash> rev) const;
|
||||
|
||||
virtual std::optional<Path> getSourcePath() const { return {}; }
|
||||
|
||||
virtual void markChangedFile(
|
||||
std::string_view file,
|
||||
std::optional<std::string> commitMsg) const
|
||||
{ assert(false); }
|
||||
|
||||
virtual void clone(const Path & destDir) const
|
||||
{
|
||||
throw Error("do not know how to clone input '%s'", to_string());
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
virtual std::pair<Tree, std::shared_ptr<const Input>> fetchTreeInternal(ref<Store> store) const = 0;
|
||||
|
||||
@@ -79,6 +79,63 @@ struct GitInput : Input
|
||||
return attrs;
|
||||
}
|
||||
|
||||
void clone(const Path & destDir) const override
|
||||
{
|
||||
auto [isLocal, actualUrl] = getActualUrl();
|
||||
|
||||
Strings args = {"clone"};
|
||||
|
||||
args.push_back(actualUrl);
|
||||
|
||||
if (ref) {
|
||||
args.push_back("--branch");
|
||||
args.push_back(*ref);
|
||||
}
|
||||
|
||||
if (rev) throw Error("cloning a specific revision is not implemented");
|
||||
|
||||
args.push_back(destDir);
|
||||
|
||||
runProgram("git", true, args);
|
||||
}
|
||||
|
||||
std::shared_ptr<const Input> applyOverrides(
|
||||
std::optional<std::string> ref,
|
||||
std::optional<Hash> rev) const override
|
||||
{
|
||||
if (!ref && !rev) return shared_from_this();
|
||||
|
||||
auto res = std::make_shared<GitInput>(*this);
|
||||
|
||||
if (ref) res->ref = ref;
|
||||
if (rev) res->rev = rev;
|
||||
|
||||
if (!res->ref && res->rev)
|
||||
throw Error("Git input '%s' has a commit hash but no branch/tag name", res->to_string());
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::optional<Path> getSourcePath() const override
|
||||
{
|
||||
if (url.scheme == "file" && !ref && !rev)
|
||||
return url.path;
|
||||
return {};
|
||||
}
|
||||
|
||||
void markChangedFile(std::string_view file, std::optional<std::string> commitMsg) const override
|
||||
{
|
||||
auto sourcePath = getSourcePath();
|
||||
assert(sourcePath);
|
||||
|
||||
runProgram("git", true,
|
||||
{ "-C", *sourcePath, "add", "--force", "--intent-to-add", std::string(file) });
|
||||
|
||||
if (commitMsg)
|
||||
runProgram("git", true,
|
||||
{ "-C", *sourcePath, "commit", std::string(file), "-m", *commitMsg });
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> getActualUrl() const
|
||||
{
|
||||
// Don't clone file:// URIs (but otherwise treat them the
|
||||
@@ -195,7 +252,7 @@ struct GitInput : Input
|
||||
return files.count(file);
|
||||
};
|
||||
|
||||
auto storePath = store->addToStore("source", actualUrl, FileIngestionMethod::Recursive, htSHA256, filter);
|
||||
auto storePath = store->addToStore("source", actualUrl, true, htSHA256, filter);
|
||||
|
||||
auto tree = Tree {
|
||||
.actualPath = store->printStorePath(storePath),
|
||||
@@ -282,10 +339,7 @@ struct GitInput : Input
|
||||
// FIXME: git stderr messes up our progress indicator, so
|
||||
// we're using --quiet for now. Should process its stderr.
|
||||
try {
|
||||
auto fetchRef = input->ref->compare(0, 5, "refs/") == 0
|
||||
? *input->ref
|
||||
: "refs/heads/" + *input->ref;
|
||||
runProgram("git", true, { "-C", repoDir, "fetch", "--quiet", "--force", "--", actualUrl, fmt("%s:%s", fetchRef, fetchRef) });
|
||||
runProgram("git", true, { "-C", repoDir, "fetch", "--quiet", "--force", "--", actualUrl, fmt("%s:%s", *input->ref, *input->ref) });
|
||||
} catch (Error & e) {
|
||||
if (!pathExists(localRefFile)) throw;
|
||||
warn("could not update local clone of Git repository '%s'; continuing with the most recent version", actualUrl);
|
||||
@@ -350,7 +404,7 @@ struct GitInput : Input
|
||||
unpackTarfile(*source, tmpDir);
|
||||
}
|
||||
|
||||
auto storePath = store->addToStore(name, tmpDir, FileIngestionMethod::Recursive, htSHA256, filter);
|
||||
auto storePath = store->addToStore(name, tmpDir, true, htSHA256, filter);
|
||||
|
||||
auto lastModified = std::stoull(runProgram("git", true, { "-C", repoDir, "log", "-1", "--format=%ct", input->rev->gitRev() }));
|
||||
|
||||
@@ -421,7 +475,7 @@ struct GitInputScheme : InputScheme
|
||||
|
||||
auto input = std::make_unique<GitInput>(parseURL(getStrAttr(attrs, "url")));
|
||||
if (auto ref = maybeGetStrAttr(attrs, "ref")) {
|
||||
if (std::regex_search(*ref, badGitRefRegex))
|
||||
if (!std::regex_match(*ref, refRegex))
|
||||
throw BadURL("invalid Git branch/tag name '%s'", *ref);
|
||||
input->ref = *ref;
|
||||
}
|
||||
|
||||
@@ -64,6 +64,13 @@ struct GitHubInput : Input
|
||||
return attrs;
|
||||
}
|
||||
|
||||
void clone(const Path & destDir) const override
|
||||
{
|
||||
std::shared_ptr<const Input> input = inputFromURL(fmt("git+ssh://git@github.com/%s/%s.git", owner, repo));
|
||||
input = input->applyOverrides(ref.value_or("master"), rev);
|
||||
input->clone(destDir);
|
||||
}
|
||||
|
||||
std::pair<Tree, std::shared_ptr<const Input>> fetchTreeInternal(nix::ref<Store> store) const override
|
||||
{
|
||||
auto rev = this->rev;
|
||||
@@ -76,7 +83,7 @@ struct GitHubInput : Input
|
||||
readFile(
|
||||
store->toRealPath(
|
||||
downloadFile(store, url, "source", false).storePath)));
|
||||
rev = Hash(std::string { json["sha"] }, htSHA1);
|
||||
rev = Hash(json["sha"], htSHA1);
|
||||
debug("HEAD revision for '%s' is %s", url, rev->gitRev());
|
||||
}
|
||||
|
||||
@@ -126,6 +133,20 @@ struct GitHubInput : Input
|
||||
|
||||
return {std::move(tree), input};
|
||||
}
|
||||
|
||||
std::shared_ptr<const Input> applyOverrides(
|
||||
std::optional<std::string> ref,
|
||||
std::optional<Hash> rev) const override
|
||||
{
|
||||
if (!ref && !rev) return shared_from_this();
|
||||
|
||||
auto res = std::make_shared<GitHubInput>(*this);
|
||||
|
||||
if (ref) res->ref = ref;
|
||||
if (rev) res->rev = rev;
|
||||
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
struct GitHubInputScheme : InputScheme
|
||||
|
||||
140
src/libfetchers/indirect.cc
Normal file
140
src/libfetchers/indirect.cc
Normal file
@@ -0,0 +1,140 @@
|
||||
#include "fetchers.hh"
|
||||
|
||||
namespace nix::fetchers {
|
||||
|
||||
std::regex flakeRegex("[a-zA-Z][a-zA-Z0-9_-]*", std::regex::ECMAScript);
|
||||
|
||||
struct IndirectInput : Input
|
||||
{
|
||||
std::string id;
|
||||
std::optional<Hash> rev;
|
||||
std::optional<std::string> ref;
|
||||
|
||||
std::string type() const override { return "indirect"; }
|
||||
|
||||
bool operator ==(const Input & other) const override
|
||||
{
|
||||
auto other2 = dynamic_cast<const IndirectInput *>(&other);
|
||||
return
|
||||
other2
|
||||
&& id == other2->id
|
||||
&& rev == other2->rev
|
||||
&& ref == other2->ref;
|
||||
}
|
||||
|
||||
bool isDirect() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<std::string> getRef() const override { return ref; }
|
||||
|
||||
std::optional<Hash> getRev() const override { return rev; }
|
||||
|
||||
bool contains(const Input & other) const override
|
||||
{
|
||||
auto other2 = dynamic_cast<const IndirectInput *>(&other);
|
||||
return
|
||||
other2
|
||||
&& id == other2->id
|
||||
&& (!ref || ref == other2->ref)
|
||||
&& (!rev || rev == other2->rev);
|
||||
}
|
||||
|
||||
ParsedURL toURL() const override
|
||||
{
|
||||
ParsedURL url;
|
||||
url.scheme = "flake";
|
||||
url.path = id;
|
||||
if (ref) { url.path += '/'; url.path += *ref; };
|
||||
if (rev) { url.path += '/'; url.path += rev->gitRev(); };
|
||||
return url;
|
||||
}
|
||||
|
||||
Attrs toAttrsInternal() const override
|
||||
{
|
||||
Attrs attrs;
|
||||
attrs.emplace("id", id);
|
||||
if (ref)
|
||||
attrs.emplace("ref", *ref);
|
||||
if (rev)
|
||||
attrs.emplace("rev", rev->gitRev());
|
||||
return attrs;
|
||||
}
|
||||
|
||||
std::shared_ptr<const Input> applyOverrides(
|
||||
std::optional<std::string> ref,
|
||||
std::optional<Hash> rev) const override
|
||||
{
|
||||
if (!ref && !rev) return shared_from_this();
|
||||
|
||||
auto res = std::make_shared<IndirectInput>(*this);
|
||||
|
||||
if (ref) res->ref = ref;
|
||||
if (rev) res->rev = rev;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::pair<Tree, std::shared_ptr<const Input>> fetchTreeInternal(nix::ref<Store> store) const override
|
||||
{
|
||||
throw Error("indirect input '%s' cannot be fetched directly", to_string());
|
||||
}
|
||||
};
|
||||
|
||||
struct IndirectInputScheme : InputScheme
|
||||
{
|
||||
std::unique_ptr<Input> inputFromURL(const ParsedURL & url) override
|
||||
{
|
||||
if (url.scheme != "flake") return nullptr;
|
||||
|
||||
auto path = tokenizeString<std::vector<std::string>>(url.path, "/");
|
||||
auto input = std::make_unique<IndirectInput>();
|
||||
|
||||
if (path.size() == 1) {
|
||||
} else if (path.size() == 2) {
|
||||
if (std::regex_match(path[1], revRegex))
|
||||
input->rev = Hash(path[1], htSHA1);
|
||||
else if (std::regex_match(path[1], refRegex))
|
||||
input->ref = path[1];
|
||||
else
|
||||
throw BadURL("in flake URL '%s', '%s' is not a commit hash or branch/tag name", url.url, path[1]);
|
||||
} else if (path.size() == 3) {
|
||||
if (!std::regex_match(path[1], refRegex))
|
||||
throw BadURL("in flake URL '%s', '%s' is not a branch/tag name", url.url, path[1]);
|
||||
input->ref = path[1];
|
||||
if (!std::regex_match(path[2], revRegex))
|
||||
throw BadURL("in flake URL '%s', '%s' is not a commit hash", url.url, path[2]);
|
||||
input->rev = Hash(path[2], htSHA1);
|
||||
} else
|
||||
throw BadURL("GitHub URL '%s' is invalid", url.url);
|
||||
|
||||
// FIXME: forbid query params?
|
||||
|
||||
input->id = path[0];
|
||||
if (!std::regex_match(input->id, flakeRegex))
|
||||
throw BadURL("'%s' is not a valid flake ID", input->id);
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
std::unique_ptr<Input> inputFromAttrs(const Attrs & attrs) override
|
||||
{
|
||||
if (maybeGetStrAttr(attrs, "type") != "indirect") return {};
|
||||
|
||||
for (auto & [name, value] : attrs)
|
||||
if (name != "type" && name != "id" && name != "ref" && name != "rev")
|
||||
throw Error("unsupported indirect input attribute '%s'", name);
|
||||
|
||||
auto input = std::make_unique<IndirectInput>();
|
||||
input->id = getStrAttr(attrs, "id");
|
||||
input->ref = maybeGetStrAttr(attrs, "ref");
|
||||
if (auto rev = maybeGetStrAttr(attrs, "rev"))
|
||||
input->rev = Hash(*rev, htSHA1);
|
||||
return input;
|
||||
}
|
||||
};
|
||||
|
||||
static auto r1 = OnStartup([] { registerInputScheme(std::make_unique<IndirectInputScheme>()); });
|
||||
|
||||
}
|
||||
@@ -8,4 +8,4 @@ libfetchers_SOURCES := $(wildcard $(d)/*.cc)
|
||||
|
||||
libfetchers_CXXFLAGS += -I src/libutil -I src/libstore
|
||||
|
||||
libfetchers_LIBS = libutil libstore
|
||||
libfetchers_LIBS = libutil libstore libnixrust
|
||||
|
||||
@@ -60,6 +60,41 @@ struct MercurialInput : Input
|
||||
return attrs;
|
||||
}
|
||||
|
||||
std::shared_ptr<const Input> applyOverrides(
|
||||
std::optional<std::string> ref,
|
||||
std::optional<Hash> rev) const override
|
||||
{
|
||||
if (!ref && !rev) return shared_from_this();
|
||||
|
||||
auto res = std::make_shared<MercurialInput>(*this);
|
||||
|
||||
if (ref) res->ref = ref;
|
||||
if (rev) res->rev = rev;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::optional<Path> getSourcePath() const
|
||||
{
|
||||
if (url.scheme == "file" && !ref && !rev)
|
||||
return url.path;
|
||||
return {};
|
||||
}
|
||||
|
||||
void markChangedFile(std::string_view file, std::optional<std::string> commitMsg) const override
|
||||
{
|
||||
auto sourcePath = getSourcePath();
|
||||
assert(sourcePath);
|
||||
|
||||
// FIXME: shut up if file is already tracked.
|
||||
runProgram("hg", true,
|
||||
{ "add", *sourcePath + "/" + std::string(file) });
|
||||
|
||||
if (commitMsg)
|
||||
runProgram("hg", true,
|
||||
{ "commit", *sourcePath + "/" + std::string(file), "-m", *commitMsg });
|
||||
}
|
||||
|
||||
std::pair<bool, std::string> getActualUrl() const
|
||||
{
|
||||
bool isLocal = url.scheme == "file";
|
||||
@@ -114,7 +149,7 @@ struct MercurialInput : Input
|
||||
return files.count(file);
|
||||
};
|
||||
|
||||
auto storePath = store->addToStore("source", actualUrl, FileIngestionMethod::Recursive, htSHA256, filter);
|
||||
auto storePath = store->addToStore("source", actualUrl, true, htSHA256, filter);
|
||||
|
||||
return {Tree {
|
||||
.actualPath = store->printStorePath(storePath),
|
||||
|
||||
@@ -32,7 +32,7 @@ struct PathInput : Input
|
||||
|
||||
bool isImmutable() const override
|
||||
{
|
||||
return (bool) narHash;
|
||||
return narHash || rev;
|
||||
}
|
||||
|
||||
ParsedURL toURL() const override
|
||||
@@ -56,9 +56,20 @@ struct PathInput : Input
|
||||
attrs.emplace("revCount", *revCount);
|
||||
if (lastModified)
|
||||
attrs.emplace("lastModified", *lastModified);
|
||||
if (!rev && narHash)
|
||||
attrs.emplace("narHash", narHash->to_string(SRI));
|
||||
return attrs;
|
||||
}
|
||||
|
||||
std::optional<Path> getSourcePath() const override
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
void markChangedFile(std::string_view file, std::optional<std::string> commitMsg) const override
|
||||
{
|
||||
}
|
||||
|
||||
std::pair<Tree, std::shared_ptr<const Input>> fetchTreeInternal(nix::ref<Store> store) const override
|
||||
{
|
||||
auto input = std::make_shared<PathInput>(*this);
|
||||
@@ -74,6 +85,8 @@ struct PathInput : Input
|
||||
// FIXME: try to substitute storePath.
|
||||
storePath = store->addToStore("source", path);
|
||||
|
||||
input->narHash = store->queryPathInfo(*storePath)->narHash;
|
||||
|
||||
return
|
||||
{
|
||||
Tree {
|
||||
@@ -99,6 +112,9 @@ struct PathInputScheme : InputScheme
|
||||
auto input = std::make_unique<PathInput>();
|
||||
input->path = url.path;
|
||||
|
||||
if (url.authority && *url.authority != "")
|
||||
throw Error("path URL '%s' should not have an authority ('%s')", url.url, *url.authority);
|
||||
|
||||
for (auto & [name, value] : url.query)
|
||||
if (name == "rev")
|
||||
input->rev = Hash(value, htSHA1);
|
||||
@@ -114,6 +130,9 @@ struct PathInputScheme : InputScheme
|
||||
throw Error("path URL '%s' has invalid parameter '%s'", url.to_string(), name);
|
||||
input->lastModified = lastModified;
|
||||
}
|
||||
else if (name == "narHash")
|
||||
// FIXME: require SRI hash.
|
||||
input->narHash = Hash(value);
|
||||
else
|
||||
throw Error("path URL '%s' has unsupported parameter '%s'", url.to_string(), name);
|
||||
|
||||
@@ -134,6 +153,9 @@ struct PathInputScheme : InputScheme
|
||||
input->revCount = getIntAttr(attrs, "revCount");
|
||||
else if (name == "lastModified")
|
||||
input->lastModified = getIntAttr(attrs, "lastModified");
|
||||
else if (name == "narHash")
|
||||
// FIXME: require SRI hash.
|
||||
input->narHash = Hash(getStrAttr(attrs, "narHash"));
|
||||
else if (name == "type" || name == "path")
|
||||
;
|
||||
else
|
||||
|
||||
222
src/libfetchers/registry.cc
Normal file
222
src/libfetchers/registry.cc
Normal file
@@ -0,0 +1,222 @@
|
||||
#include "registry.hh"
|
||||
#include "fetchers.hh"
|
||||
#include "util.hh"
|
||||
#include "globals.hh"
|
||||
#include "store-api.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace nix::fetchers {
|
||||
|
||||
std::shared_ptr<Registry> Registry::read(
|
||||
const Path & path, RegistryType type)
|
||||
{
|
||||
auto registry = std::make_shared<Registry>(type);
|
||||
|
||||
if (!pathExists(path))
|
||||
return std::make_shared<Registry>(type);
|
||||
|
||||
try {
|
||||
|
||||
auto json = nlohmann::json::parse(readFile(path));
|
||||
|
||||
auto version = json.value("version", 0);
|
||||
|
||||
// FIXME: remove soon
|
||||
if (version == 1) {
|
||||
auto flakes = json["flakes"];
|
||||
for (auto i = flakes.begin(); i != flakes.end(); ++i) {
|
||||
auto url = i->value("url", i->value("uri", ""));
|
||||
if (url.empty())
|
||||
throw Error("flake registry '%s' lacks a 'url' attribute for entry '%s'",
|
||||
path, i.key());
|
||||
registry->entries.push_back(
|
||||
{inputFromURL(i.key()), inputFromURL(url), {}});
|
||||
}
|
||||
}
|
||||
|
||||
else if (version == 2) {
|
||||
for (auto & i : json["flakes"]) {
|
||||
auto toAttrs = jsonToAttrs(i["to"]);
|
||||
Attrs extraAttrs;
|
||||
auto j = toAttrs.find("dir");
|
||||
if (j != toAttrs.end()) {
|
||||
extraAttrs.insert(*j);
|
||||
toAttrs.erase(j);
|
||||
}
|
||||
auto exact = i.find("exact");
|
||||
registry->entries.push_back(
|
||||
Entry {
|
||||
.from = inputFromAttrs(jsonToAttrs(i["from"])),
|
||||
.to = inputFromAttrs(toAttrs),
|
||||
.extraAttrs = extraAttrs,
|
||||
.exact = exact != i.end() && exact.value()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
throw Error("flake registry '%s' has unsupported version %d", path, version);
|
||||
|
||||
} catch (nlohmann::json::exception & e) {
|
||||
warn("cannot parse flake registry '%s': %s", path, e.what());
|
||||
} catch (Error & e) {
|
||||
warn("cannot read flake registry '%s': %s", path, e.what());
|
||||
}
|
||||
|
||||
return registry;
|
||||
}
|
||||
|
||||
void Registry::write(const Path & path)
|
||||
{
|
||||
nlohmann::json arr;
|
||||
for (auto & entry : entries) {
|
||||
nlohmann::json obj;
|
||||
obj["from"] = attrsToJson(entry.from->toAttrs());
|
||||
obj["to"] = attrsToJson(entry.to->toAttrs());
|
||||
if (!entry.extraAttrs.empty())
|
||||
obj["to"].update(attrsToJson(entry.extraAttrs));
|
||||
if (entry.exact)
|
||||
obj["exact"] = true;
|
||||
arr.emplace_back(std::move(obj));
|
||||
}
|
||||
|
||||
nlohmann::json json;
|
||||
json["version"] = 2;
|
||||
json["flakes"] = std::move(arr);
|
||||
|
||||
createDirs(dirOf(path));
|
||||
writeFile(path, json.dump(2));
|
||||
}
|
||||
|
||||
void Registry::add(
|
||||
const std::shared_ptr<const Input> & from,
|
||||
const std::shared_ptr<const Input> & to,
|
||||
const Attrs & extraAttrs)
|
||||
{
|
||||
entries.emplace_back(
|
||||
Entry {
|
||||
.from = from,
|
||||
.to = to,
|
||||
.extraAttrs = extraAttrs
|
||||
});
|
||||
}
|
||||
|
||||
void Registry::remove(const std::shared_ptr<const Input> & input)
|
||||
{
|
||||
// FIXME: use C++20 std::erase.
|
||||
for (auto i = entries.begin(); i != entries.end(); )
|
||||
if (*i->from == *input)
|
||||
i = entries.erase(i);
|
||||
else
|
||||
++i;
|
||||
}
|
||||
|
||||
static Path getSystemRegistryPath()
|
||||
{
|
||||
return settings.nixConfDir + "/registry.json";
|
||||
}
|
||||
|
||||
static std::shared_ptr<Registry> getSystemRegistry()
|
||||
{
|
||||
static auto systemRegistry =
|
||||
Registry::read(getSystemRegistryPath(), Registry::System);
|
||||
return systemRegistry;
|
||||
}
|
||||
|
||||
Path getUserRegistryPath()
|
||||
{
|
||||
return getHome() + "/.config/nix/registry.json";
|
||||
}
|
||||
|
||||
std::shared_ptr<Registry> getUserRegistry()
|
||||
{
|
||||
static auto userRegistry =
|
||||
Registry::read(getUserRegistryPath(), Registry::User);
|
||||
return userRegistry;
|
||||
}
|
||||
|
||||
static std::shared_ptr<Registry> flagRegistry =
|
||||
std::make_shared<Registry>(Registry::Flag);
|
||||
|
||||
std::shared_ptr<Registry> getFlagRegistry()
|
||||
{
|
||||
return flagRegistry;
|
||||
}
|
||||
|
||||
void overrideRegistry(
|
||||
const std::shared_ptr<const Input> & from,
|
||||
const std::shared_ptr<const Input> & to,
|
||||
const Attrs & extraAttrs)
|
||||
{
|
||||
flagRegistry->add(from, to, extraAttrs);
|
||||
}
|
||||
|
||||
static std::shared_ptr<Registry> getGlobalRegistry(ref<Store> store)
|
||||
{
|
||||
static auto reg = [&]() {
|
||||
auto path = settings.flakeRegistry.get();
|
||||
|
||||
if (!hasPrefix(path, "/")) {
|
||||
auto storePath = downloadFile(store, path, "flake-registry.json", false).storePath;
|
||||
if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>())
|
||||
store2->addPermRoot(storePath, getCacheDir() + "/nix/flake-registry.json", true);
|
||||
path = store->toRealPath(storePath);
|
||||
}
|
||||
|
||||
return Registry::read(path, Registry::Global);
|
||||
}();
|
||||
|
||||
return reg;
|
||||
}
|
||||
|
||||
Registries getRegistries(ref<Store> store)
|
||||
{
|
||||
Registries registries;
|
||||
registries.push_back(getFlagRegistry());
|
||||
registries.push_back(getUserRegistry());
|
||||
registries.push_back(getSystemRegistry());
|
||||
registries.push_back(getGlobalRegistry(store));
|
||||
return registries;
|
||||
}
|
||||
|
||||
std::pair<std::shared_ptr<const Input>, Attrs> lookupInRegistries(
|
||||
ref<Store> store,
|
||||
std::shared_ptr<const Input> input)
|
||||
{
|
||||
Attrs extraAttrs;
|
||||
int n = 0;
|
||||
|
||||
restart:
|
||||
|
||||
n++;
|
||||
if (n > 100) throw Error("cycle detected in flake registr for '%s'", input);
|
||||
|
||||
for (auto & registry : getRegistries(store)) {
|
||||
// FIXME: O(n)
|
||||
for (auto & entry : registry->entries) {
|
||||
if (entry.exact) {
|
||||
if (*entry.from == *input) {
|
||||
input = entry.to;
|
||||
extraAttrs = entry.extraAttrs;
|
||||
goto restart;
|
||||
}
|
||||
} else {
|
||||
if (entry.from->contains(*input)) {
|
||||
input = entry.to->applyOverrides(
|
||||
!entry.from->getRef() && input->getRef() ? input->getRef() : std::optional<std::string>(),
|
||||
!entry.from->getRev() && input->getRev() ? input->getRev() : std::optional<Hash>());
|
||||
extraAttrs = entry.extraAttrs;
|
||||
goto restart;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!input->isDirect())
|
||||
throw Error("cannot find flake '%s' in the flake registries", input->to_string());
|
||||
|
||||
return {input, extraAttrs};
|
||||
}
|
||||
|
||||
}
|
||||
65
src/libfetchers/registry.hh
Normal file
65
src/libfetchers/registry.hh
Normal file
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.hh"
|
||||
#include "fetchers.hh"
|
||||
|
||||
namespace nix { class Store; }
|
||||
|
||||
namespace nix::fetchers {
|
||||
|
||||
struct Registry
|
||||
{
|
||||
enum RegistryType {
|
||||
Flag = 0,
|
||||
User = 1,
|
||||
System = 2,
|
||||
Global = 3,
|
||||
};
|
||||
|
||||
RegistryType type;
|
||||
|
||||
struct Entry
|
||||
{
|
||||
std::shared_ptr<const Input> from;
|
||||
std::shared_ptr<const Input> to;
|
||||
Attrs extraAttrs;
|
||||
bool exact = false;
|
||||
};
|
||||
|
||||
std::vector<Entry> entries;
|
||||
|
||||
Registry(RegistryType type)
|
||||
: type(type)
|
||||
{ }
|
||||
|
||||
static std::shared_ptr<Registry> read(
|
||||
const Path & path, RegistryType type);
|
||||
|
||||
void write(const Path & path);
|
||||
|
||||
void add(
|
||||
const std::shared_ptr<const Input> & from,
|
||||
const std::shared_ptr<const Input> & to,
|
||||
const Attrs & extraAttrs);
|
||||
|
||||
void remove(const std::shared_ptr<const Input> & input);
|
||||
};
|
||||
|
||||
typedef std::vector<std::shared_ptr<Registry>> Registries;
|
||||
|
||||
std::shared_ptr<Registry> getUserRegistry();
|
||||
|
||||
Path getUserRegistryPath();
|
||||
|
||||
Registries getRegistries(ref<Store> store);
|
||||
|
||||
void overrideRegistry(
|
||||
const std::shared_ptr<const Input> & from,
|
||||
const std::shared_ptr<const Input> & to,
|
||||
const Attrs & extraAttrs);
|
||||
|
||||
std::pair<std::shared_ptr<const Input>, Attrs> lookupInRegistries(
|
||||
ref<Store> store,
|
||||
std::shared_ptr<const Input> input);
|
||||
|
||||
}
|
||||
@@ -67,15 +67,11 @@ DownloadFileResult downloadFile(
|
||||
StringSink sink;
|
||||
dumpString(*res.data, sink);
|
||||
auto hash = hashString(htSHA256, *res.data);
|
||||
ValidPathInfo info(store->makeFixedOutputPath(FileIngestionMethod::Flat, hash, name));
|
||||
ValidPathInfo info(store->makeFixedOutputPath(false, hash, name));
|
||||
info.narHash = hashString(htSHA256, *sink.s);
|
||||
info.narSize = sink.s->size();
|
||||
info.ca = FixedOutputHash {
|
||||
.method = FileIngestionMethod::Flat,
|
||||
.hash = hash,
|
||||
};
|
||||
auto source = StringSource { *sink.s };
|
||||
store->addToStore(info, source, NoRepair, NoCheckSigs);
|
||||
info.ca = makeFixedOutputCA(false, hash);
|
||||
store->addToStore(info, sink.s, NoRepair, NoCheckSigs);
|
||||
storePath = std::move(info.path);
|
||||
}
|
||||
|
||||
@@ -145,7 +141,7 @@ Tree downloadTarball(
|
||||
throw nix::Error("tarball '%s' contains an unexpected number of top-level files", url);
|
||||
auto topDir = tmpDir + "/" + members.begin()->name;
|
||||
lastModified = lstat(topDir).st_mtime;
|
||||
unpackedStorePath = store->addToStore(name, topDir, FileIngestionMethod::Recursive, htSHA256, defaultPathFilter, NoRepair);
|
||||
unpackedStorePath = store->addToStore(name, topDir, true, htSHA256, defaultPathFilter, NoRepair);
|
||||
}
|
||||
|
||||
Attrs infoAttrs({
|
||||
@@ -199,9 +195,9 @@ struct TarballInput : Input
|
||||
// NAR hashes are preferred over file hashes since tar/zip files
|
||||
// don't have a canonical representation.
|
||||
if (narHash)
|
||||
url2.query.insert_or_assign("narHash", narHash->to_string(SRI, true));
|
||||
url2.query.insert_or_assign("narHash", narHash->to_string(SRI));
|
||||
else if (hash)
|
||||
url2.query.insert_or_assign("hash", hash->to_string(SRI, true));
|
||||
url2.query.insert_or_assign("hash", hash->to_string(SRI));
|
||||
return url2;
|
||||
}
|
||||
|
||||
@@ -210,7 +206,7 @@ struct TarballInput : Input
|
||||
Attrs attrs;
|
||||
attrs.emplace("url", url.to_string());
|
||||
if (hash)
|
||||
attrs.emplace("hash", hash->to_string(SRI, true));
|
||||
attrs.emplace("hash", hash->to_string(SRI));
|
||||
return attrs;
|
||||
}
|
||||
|
||||
@@ -267,7 +263,8 @@ struct TarballInputScheme : InputScheme
|
||||
|
||||
auto input = std::make_unique<TarballInput>(parseURL(getStrAttr(attrs, "url")));
|
||||
if (auto hash = maybeGetStrAttr(attrs, "hash"))
|
||||
input->hash = newHashAllowEmpty(*hash, {});
|
||||
// FIXME: require SRI hash.
|
||||
input->hash = Hash(*hash);
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,53 @@ namespace nix::fetchers {
|
||||
StorePath TreeInfo::computeStorePath(Store & store) const
|
||||
{
|
||||
assert(narHash);
|
||||
return store.makeFixedOutputPath(FileIngestionMethod::Recursive, narHash, "source");
|
||||
return store.makeFixedOutputPath(true, narHash, "source");
|
||||
}
|
||||
|
||||
TreeInfo TreeInfo::fromJson(const nlohmann::json & json)
|
||||
{
|
||||
TreeInfo info;
|
||||
|
||||
auto i = json.find("info");
|
||||
if (i != json.end()) {
|
||||
const nlohmann::json & i2(*i);
|
||||
|
||||
auto j = i2.find("narHash");
|
||||
if (j != i2.end())
|
||||
info.narHash = Hash((std::string) *j);
|
||||
else
|
||||
throw Error("attribute 'narHash' missing in lock file");
|
||||
|
||||
j = i2.find("revCount");
|
||||
if (j != i2.end())
|
||||
info.revCount = *j;
|
||||
|
||||
j = i2.find("lastModified");
|
||||
if (j != i2.end())
|
||||
info.lastModified = *j;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
i = json.find("narHash");
|
||||
if (i != json.end()) {
|
||||
info.narHash = Hash((std::string) *i);
|
||||
return info;
|
||||
}
|
||||
|
||||
throw Error("attribute 'info' missing in lock file");
|
||||
}
|
||||
|
||||
nlohmann::json TreeInfo::toJson() const
|
||||
{
|
||||
nlohmann::json json;
|
||||
assert(narHash);
|
||||
json["narHash"] = narHash.to_string(SRI);
|
||||
if (revCount)
|
||||
json["revCount"] = *revCount;
|
||||
if (lastModified)
|
||||
json["lastModified"] = *lastModified;
|
||||
return json;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@ struct TreeInfo
|
||||
}
|
||||
|
||||
StorePath computeStorePath(Store & store) const;
|
||||
|
||||
static TreeInfo fromJson(const nlohmann::json & json);
|
||||
|
||||
nlohmann::json toJson() const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "common-args.hh"
|
||||
#include "globals.hh"
|
||||
#include "loggers.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
@@ -34,17 +33,19 @@ MixCommonArgs::MixCommonArgs(const string & programName)
|
||||
try {
|
||||
globalConfig.set(name, value);
|
||||
} catch (UsageError & e) {
|
||||
warn(e.what());
|
||||
if (!completions)
|
||||
warn(e.what());
|
||||
}
|
||||
}},
|
||||
});
|
||||
|
||||
addFlag({
|
||||
.longName = "log-format",
|
||||
.description = "format of log output; \"raw\", \"internal-json\", \"bar\" "
|
||||
"or \"bar-with-logs\"",
|
||||
.labels = {"format"},
|
||||
.handler = {[](std::string format) { setLogFormat(format); }},
|
||||
.completer = [](size_t index, std::string_view prefix) {
|
||||
if (index == 0) {
|
||||
std::map<std::string, Config::SettingInfo> settings;
|
||||
globalConfig.getSettings(settings);
|
||||
for (auto & s : settings)
|
||||
if (hasPrefix(s.first, prefix))
|
||||
completions->insert(s.first);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
addFlag({
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
#include "loggers.hh"
|
||||
#include "progress-bar.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
LogFormat defaultLogFormat = LogFormat::raw;
|
||||
|
||||
LogFormat parseLogFormat(const std::string & logFormatStr) {
|
||||
if (logFormatStr == "raw")
|
||||
return LogFormat::raw;
|
||||
else if (logFormatStr == "raw-with-logs")
|
||||
return LogFormat::rawWithLogs;
|
||||
else if (logFormatStr == "internal-json")
|
||||
return LogFormat::internalJson;
|
||||
else if (logFormatStr == "bar")
|
||||
return LogFormat::bar;
|
||||
else if (logFormatStr == "bar-with-logs")
|
||||
return LogFormat::barWithLogs;
|
||||
throw Error("option 'log-format' has an invalid value '%s'", logFormatStr);
|
||||
}
|
||||
|
||||
Logger * makeDefaultLogger() {
|
||||
switch (defaultLogFormat) {
|
||||
case LogFormat::raw:
|
||||
return makeSimpleLogger(false);
|
||||
case LogFormat::rawWithLogs:
|
||||
return makeSimpleLogger(true);
|
||||
case LogFormat::internalJson:
|
||||
return makeJSONLogger(*makeSimpleLogger());
|
||||
case LogFormat::bar:
|
||||
return makeProgressBar();
|
||||
case LogFormat::barWithLogs:
|
||||
return makeProgressBar(true);
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void setLogFormat(const std::string & logFormatStr) {
|
||||
setLogFormat(parseLogFormat(logFormatStr));
|
||||
}
|
||||
|
||||
void setLogFormat(const LogFormat & logFormat) {
|
||||
defaultLogFormat = logFormat;
|
||||
createDefaultLogger();
|
||||
}
|
||||
|
||||
void createDefaultLogger() {
|
||||
logger = makeDefaultLogger();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "types.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
enum class LogFormat {
|
||||
raw,
|
||||
rawWithLogs,
|
||||
internalJson,
|
||||
bar,
|
||||
barWithLogs,
|
||||
};
|
||||
|
||||
void setLogFormat(const std::string & logFormatStr);
|
||||
void setLogFormat(const LogFormat & logFormat);
|
||||
|
||||
void createDefaultLogger();
|
||||
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "shared.hh"
|
||||
#include "store-api.hh"
|
||||
#include "util.hh"
|
||||
#include "loggers.hh"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
@@ -48,10 +47,7 @@ void printMissing(ref<Store> store, const StorePathSet & willBuild,
|
||||
unsigned long long downloadSize, unsigned long long narSize, Verbosity lvl)
|
||||
{
|
||||
if (!willBuild.empty()) {
|
||||
if (willBuild.size() == 1)
|
||||
printMsg(lvl, fmt("this derivation will be built:"));
|
||||
else
|
||||
printMsg(lvl, fmt("these %d derivations will be built:", willBuild.size()));
|
||||
printMsg(lvl, "these derivations will be built:");
|
||||
auto sorted = store->topoSortPaths(willBuild);
|
||||
reverse(sorted.begin(), sorted.end());
|
||||
for (auto & i : sorted)
|
||||
@@ -59,18 +55,9 @@ void printMissing(ref<Store> store, const StorePathSet & willBuild,
|
||||
}
|
||||
|
||||
if (!willSubstitute.empty()) {
|
||||
const float downloadSizeMiB = downloadSize / (1024.f * 1024.f);
|
||||
const float narSizeMiB = narSize / (1024.f * 1024.f);
|
||||
if (willSubstitute.size() == 1) {
|
||||
printMsg(lvl, fmt("this path will be fetched (%.2f MiB download, %.2f MiB unpacked):",
|
||||
downloadSizeMiB,
|
||||
narSizeMiB));
|
||||
} else {
|
||||
printMsg(lvl, fmt("these %d paths will be fetched (%.2f MiB download, %.2f MiB unpacked):",
|
||||
willSubstitute.size(),
|
||||
downloadSizeMiB,
|
||||
narSizeMiB));
|
||||
}
|
||||
printMsg(lvl, fmt("these paths will be fetched (%.2f MiB download, %.2f MiB unpacked):",
|
||||
downloadSize / (1024.0 * 1024.0),
|
||||
narSize / (1024.0 * 1024.0)));
|
||||
for (auto & i : willSubstitute)
|
||||
printMsg(lvl, fmt(" %s", store->printStorePath(i)));
|
||||
}
|
||||
@@ -88,7 +75,7 @@ string getArg(const string & opt,
|
||||
Strings::iterator & i, const Strings::iterator & end)
|
||||
{
|
||||
++i;
|
||||
if (i == end) throw UsageError("'%1%' requires an argument", opt);
|
||||
if (i == end) throw UsageError(format("'%1%' requires an argument") % opt);
|
||||
return *i;
|
||||
}
|
||||
|
||||
@@ -182,7 +169,7 @@ LegacyArgs::LegacyArgs(const std::string & programName,
|
||||
.longName = "no-build-output",
|
||||
.shortName = 'Q',
|
||||
.description = "do not show build output",
|
||||
.handler = {[&]() {setLogFormat(LogFormat::raw); }},
|
||||
.handler = {&settings.verboseBuild, false},
|
||||
});
|
||||
|
||||
addFlag({
|
||||
@@ -247,7 +234,7 @@ bool LegacyArgs::processArgs(const Strings & args, bool finish)
|
||||
Strings ss(args);
|
||||
auto pos = ss.begin();
|
||||
if (!parseArg(pos, ss.end()))
|
||||
throw UsageError("unexpected argument '%1%'", args.front());
|
||||
throw UsageError(format("unexpected argument '%1%'") % args.front());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -294,7 +281,7 @@ void showManPage(const string & name)
|
||||
restoreSignals();
|
||||
setenv("MANPATH", settings.nixManDir.c_str(), 1);
|
||||
execlp("man", "man", name.c_str(), nullptr);
|
||||
throw SysError("command 'man %1%' failed", name.c_str());
|
||||
throw SysError(format("command 'man %1%' failed") % name.c_str());
|
||||
}
|
||||
|
||||
|
||||
@@ -302,8 +289,6 @@ int handleExceptions(const string & programName, std::function<void()> fun)
|
||||
{
|
||||
ReceiveInterrupts receiveInterrupts; // FIXME: need better place for this
|
||||
|
||||
ErrorInfo::programName = baseNameOf(programName);
|
||||
|
||||
string error = ANSI_RED "error:" ANSI_NORMAL " ";
|
||||
try {
|
||||
try {
|
||||
@@ -319,13 +304,12 @@ int handleExceptions(const string & programName, std::function<void()> fun)
|
||||
} catch (Exit & e) {
|
||||
return e.status;
|
||||
} catch (UsageError & e) {
|
||||
logError(e.info());
|
||||
printError("Try '%1% --help' for more information.", programName);
|
||||
printError(
|
||||
format(error + "%1%\nTry '%2% --help' for more information.")
|
||||
% e.what() % programName);
|
||||
return 1;
|
||||
} catch (BaseError & e) {
|
||||
if (settings.showTrace && e.prefix() != "")
|
||||
printError(e.prefix());
|
||||
logError(e.info());
|
||||
printError(format(error + "%1%%2%") % (settings.showTrace ? e.prefix() : "") % e.msg());
|
||||
if (e.prefix() != "" && !settings.showTrace)
|
||||
printError("(use '--show-trace' to show detailed location information)");
|
||||
return e.status;
|
||||
@@ -362,7 +346,7 @@ RunPager::RunPager()
|
||||
execlp("pager", "pager", nullptr);
|
||||
execlp("less", "less", nullptr);
|
||||
execlp("more", "more", nullptr);
|
||||
throw SysError("executing '%1%'", pager);
|
||||
throw SysError(format("executing '%1%'") % pager);
|
||||
});
|
||||
|
||||
pid.setKillSignal(SIGINT);
|
||||
|
||||
@@ -56,7 +56,7 @@ template<class N> N getIntArg(const string & opt,
|
||||
Strings::iterator & i, const Strings::iterator & end, bool allowUnit)
|
||||
{
|
||||
++i;
|
||||
if (i == end) throw UsageError("'%1%' requires an argument", opt);
|
||||
if (i == end) throw UsageError(format("'%1%' requires an argument") % opt);
|
||||
string s = *i;
|
||||
N multiplier = 1;
|
||||
if (allowUnit && !s.empty()) {
|
||||
@@ -66,13 +66,13 @@ template<class N> N getIntArg(const string & opt,
|
||||
else if (u == 'M') multiplier = 1ULL << 20;
|
||||
else if (u == 'G') multiplier = 1ULL << 30;
|
||||
else if (u == 'T') multiplier = 1ULL << 40;
|
||||
else throw UsageError("invalid unit specifier '%1%'", u);
|
||||
else throw UsageError(format("invalid unit specifier '%1%'") % u);
|
||||
s.resize(s.size() - 1);
|
||||
}
|
||||
}
|
||||
N n;
|
||||
if (!string2Int(s, n))
|
||||
throw UsageError("'%1%' requires an integer argument", opt);
|
||||
throw UsageError(format("'%1%' requires an integer argument") % opt);
|
||||
return n * multiplier;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "error.hh"
|
||||
#include "types.hh"
|
||||
|
||||
#include <cstring>
|
||||
#include <cstddef>
|
||||
|
||||
@@ -40,14 +40,14 @@ void BinaryCacheStore::init()
|
||||
upsertFile(cacheInfoFile, "StoreDir: " + storeDir + "\n", "text/x-nix-cache-info");
|
||||
} else {
|
||||
for (auto & line : tokenizeString<Strings>(*cacheInfo, "\n")) {
|
||||
size_t colon= line.find(':');
|
||||
if (colon ==std::string::npos) continue;
|
||||
size_t colon = line.find(':');
|
||||
if (colon == std::string::npos) continue;
|
||||
auto name = line.substr(0, colon);
|
||||
auto value = trim(line.substr(colon + 1, std::string::npos));
|
||||
if (name == "StoreDir") {
|
||||
if (value != storeDir)
|
||||
throw Error("binary cache '%s' is for Nix stores with prefix '%s', not '%s'",
|
||||
getUri(), value, storeDir);
|
||||
throw Error(format("binary cache '%s' is for Nix stores with prefix '%s', not '%s'")
|
||||
% getUri() % value % storeDir);
|
||||
} else if (name == "WantMassQuery") {
|
||||
wantMassQuery.setDefault(value == "1" ? "true" : "false");
|
||||
} else if (name == "Priority") {
|
||||
@@ -93,7 +93,7 @@ std::shared_ptr<std::string> BinaryCacheStore::getFile(const std::string & path)
|
||||
|
||||
std::string BinaryCacheStore::narInfoFileFor(const StorePath & storePath)
|
||||
{
|
||||
return std::string(storePath.hashPart()) + ".narinfo";
|
||||
return storePathToHash(printStorePath(storePath)) + ".narinfo";
|
||||
}
|
||||
|
||||
void BinaryCacheStore::writeNarInfo(ref<NarInfo> narInfo)
|
||||
@@ -102,7 +102,7 @@ void BinaryCacheStore::writeNarInfo(ref<NarInfo> narInfo)
|
||||
|
||||
upsertFile(narInfoFile, narInfo->to_string(*this), "text/x-nix-narinfo");
|
||||
|
||||
std::string hashPart(narInfo->path.hashPart());
|
||||
auto hashPart = storePathToHash(printStorePath(narInfo->path));
|
||||
|
||||
{
|
||||
auto state_(state.lock());
|
||||
@@ -113,12 +113,9 @@ void BinaryCacheStore::writeNarInfo(ref<NarInfo> narInfo)
|
||||
diskCache->upsertNarInfo(getUri(), hashPart, std::shared_ptr<NarInfo>(narInfo));
|
||||
}
|
||||
|
||||
void BinaryCacheStore::addToStore(const ValidPathInfo & info, Source & narSource,
|
||||
void BinaryCacheStore::addToStore(const ValidPathInfo & info, const ref<std::string> & nar,
|
||||
RepairFlag repair, CheckSigsFlag checkSigs, std::shared_ptr<FSAccessor> accessor)
|
||||
{
|
||||
// FIXME: See if we can use the original source to reduce memory usage.
|
||||
auto nar = make_ref<std::string>(narSource.drain());
|
||||
|
||||
if (!repair && isValidPath(info.path)) return;
|
||||
|
||||
/* Verify that all references are valid. This may do some .narinfo
|
||||
@@ -164,7 +161,7 @@ void BinaryCacheStore::addToStore(const ValidPathInfo & info, Source & narSource
|
||||
}
|
||||
}
|
||||
|
||||
upsertFile(std::string(info.path.to_string()) + ".ls", jsonOut.str(), "application/json");
|
||||
upsertFile(storePathToHash(printStorePath(info.path)) + ".ls", jsonOut.str(), "application/json");
|
||||
}
|
||||
|
||||
/* Compress the NAR. */
|
||||
@@ -287,7 +284,7 @@ void BinaryCacheStore::narFromPath(const StorePath & storePath, Sink & sink)
|
||||
try {
|
||||
getFile(info->url, *decompressor);
|
||||
} catch (NoSuchBinaryCacheFile & e) {
|
||||
throw SubstituteGone(e.info());
|
||||
throw SubstituteGone(e.what());
|
||||
}
|
||||
|
||||
decompressor->finish();
|
||||
@@ -330,7 +327,7 @@ void BinaryCacheStore::queryPathInfoUncached(const StorePath & storePath,
|
||||
}
|
||||
|
||||
StorePath BinaryCacheStore::addToStore(const string & name, const Path & srcPath,
|
||||
FileIngestionMethod method, HashType hashAlgo, PathFilter & filter, RepairFlag repair)
|
||||
bool recursive, HashType hashAlgo, PathFilter & filter, RepairFlag repair)
|
||||
{
|
||||
// FIXME: some cut&paste from LocalStore::addToStore().
|
||||
|
||||
@@ -339,7 +336,7 @@ StorePath BinaryCacheStore::addToStore(const string & name, const Path & srcPath
|
||||
small files. */
|
||||
StringSink sink;
|
||||
Hash h;
|
||||
if (method == FileIngestionMethod::Recursive) {
|
||||
if (recursive) {
|
||||
dumpPath(srcPath, sink, filter);
|
||||
h = hashString(hashAlgo, *sink.s);
|
||||
} else {
|
||||
@@ -348,10 +345,9 @@ StorePath BinaryCacheStore::addToStore(const string & name, const Path & srcPath
|
||||
h = hashString(hashAlgo, s);
|
||||
}
|
||||
|
||||
ValidPathInfo info(makeFixedOutputPath(method, h, name));
|
||||
ValidPathInfo info(makeFixedOutputPath(recursive, h, name));
|
||||
|
||||
auto source = StringSource { *sink.s };
|
||||
addToStore(info, source, repair, CheckSigs, nullptr);
|
||||
addToStore(info, sink.s, repair, CheckSigs, nullptr);
|
||||
|
||||
return std::move(info.path);
|
||||
}
|
||||
@@ -360,13 +356,12 @@ StorePath BinaryCacheStore::addTextToStore(const string & name, const string & s
|
||||
const StorePathSet & references, RepairFlag repair)
|
||||
{
|
||||
ValidPathInfo info(computeStorePathForText(name, s, references));
|
||||
info.references = references;
|
||||
info.references = cloneStorePathSet(references);
|
||||
|
||||
if (repair || !isValidPath(info.path)) {
|
||||
StringSink sink;
|
||||
dumpString(s, sink);
|
||||
auto source = StringSource { *sink.s };
|
||||
addToStore(info, source, repair, CheckSigs, nullptr);
|
||||
addToStore(info, sink.s, repair, CheckSigs, nullptr);
|
||||
}
|
||||
|
||||
return std::move(info.path);
|
||||
@@ -388,19 +383,21 @@ void BinaryCacheStore::addSignatures(const StorePath & storePath, const StringSe
|
||||
|
||||
narInfo->sigs.insert(sigs.begin(), sigs.end());
|
||||
|
||||
auto narInfoFile = narInfoFileFor(narInfo->path);
|
||||
|
||||
writeNarInfo(narInfo);
|
||||
}
|
||||
|
||||
std::shared_ptr<std::string> BinaryCacheStore::getBuildLog(const StorePath & path)
|
||||
{
|
||||
auto drvPath = path;
|
||||
auto drvPath = path.clone();
|
||||
|
||||
if (!path.isDerivation()) {
|
||||
try {
|
||||
auto info = queryPathInfo(path);
|
||||
// FIXME: add a "Log" field to .narinfo
|
||||
if (!info->deriver) return nullptr;
|
||||
drvPath = *info->deriver;
|
||||
drvPath = info->deriver->clone();
|
||||
} catch (InvalidPath &) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -74,12 +74,12 @@ public:
|
||||
std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override
|
||||
{ unsupported("queryPathFromHashPart"); }
|
||||
|
||||
void addToStore(const ValidPathInfo & info, Source & narSource,
|
||||
void addToStore(const ValidPathInfo & info, const ref<std::string> & nar,
|
||||
RepairFlag repair, CheckSigsFlag checkSigs,
|
||||
std::shared_ptr<FSAccessor> accessor) override;
|
||||
|
||||
StorePath addToStore(const string & name, const Path & srcPath,
|
||||
FileIngestionMethod method, HashType hashAlgo,
|
||||
bool recursive, HashType hashAlgo,
|
||||
PathFilter & filter, RepairFlag repair) override;
|
||||
|
||||
StorePath addTextToStore(const string & name, const string & s,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user