Compare commits
204
Commits
sb/jade/coverage
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e48ac50ca | ||
|
|
3dd53ecdef | ||
|
|
289fab6a68 | ||
|
|
f5b9f105e2 | ||
|
|
57fb4f534d | ||
|
|
553ae61ffd | ||
|
|
33d0984e27 | ||
|
|
93ca179023 | ||
|
|
ece46d54c7 | ||
|
|
4c4d8b8ccd | ||
|
|
f91bdc9367 | ||
|
|
25d47498bd | ||
|
|
99c6ad45b5 | ||
|
|
0e180a479f | ||
|
|
7307475218 | ||
|
|
66090810f1 | ||
|
|
9bd9947f41 | ||
|
|
25f53b755c | ||
|
|
f51148a75b | ||
|
|
e6ddca402b | ||
|
|
c739701077 | ||
|
|
734cfd1d48 | ||
|
|
fca58aa044 | ||
|
|
782d967342 | ||
|
|
08f18285ef | ||
|
|
c6d22874d6 | ||
|
|
1657d0eb47 | ||
|
|
527ecba696 | ||
|
|
72f8000b8f | ||
|
|
8294860ccf | ||
|
|
3b286bbd62 | ||
|
|
28fc206387 | ||
|
|
64c486e22c | ||
|
|
1a2853bf7b | ||
|
|
81084c7fc9 | ||
|
|
41bad096e3 | ||
|
|
dd8f1843cc | ||
|
|
9302845601 | ||
|
|
523df65fba | ||
|
|
de67d1424d | ||
|
|
405ff2eb03 | ||
|
|
c1370bc81c | ||
|
|
12f10fca7e | ||
|
|
b62b37296c | ||
|
|
abeefa0e4f | ||
|
|
4126e671cc | ||
|
|
ad199c4723 | ||
|
|
e9a01a5be6 | ||
|
|
48f7747deb | ||
|
|
2c45e056e9 | ||
|
|
d4e445f251 | ||
|
|
43c5aa2076 | ||
|
|
dbf1ef3d42 | ||
|
|
325364fc26 | ||
|
|
cde71e45a1 | ||
|
|
1705515e79 | ||
|
|
d26a73bfe6 | ||
|
|
cf1e565a0a | ||
|
|
a92e4b5c42 | ||
|
|
418918b599 | ||
|
|
6e19a940b1 | ||
|
|
57c00a20c5 | ||
|
|
d0deb1a150 | ||
|
|
22e2ecdc43 | ||
|
|
5434ddf4cd | ||
|
|
7ef274600d | ||
|
|
12c1132f07 | ||
|
|
d5044f0016 | ||
|
|
2605d3eab1 | ||
|
|
567348a90c | ||
|
|
1c2b5d4b5a | ||
|
|
c5bcb9a312 | ||
|
|
91663cca2d | ||
|
|
d9ecb5301e | ||
|
|
9e846fbaea | ||
|
|
84fb93a009 | ||
|
|
7868f52c2a | ||
|
|
fc6772a7dd | ||
|
|
fce63f6dbf | ||
|
|
de68de1d11 | ||
|
|
071b810f95 | ||
|
|
63fb295ad8 | ||
|
|
2ea4b5d9e1 | ||
|
|
8902b78442 | ||
|
|
30ca2e38c4 | ||
|
|
3f7bc8ffb8 | ||
|
|
20453629ab | ||
|
|
0cddd2d38a | ||
|
|
10845bfe63 | ||
|
|
05e9ca3e26 | ||
|
|
ff920956a9 | ||
|
|
cf0ce785b7 | ||
|
|
d748b4e509 | ||
|
|
0d6b372c19 | ||
|
|
7bf0edcc0d | ||
|
|
4b0371bff3 | ||
|
|
c019a776e9 | ||
|
|
93c872ab25 | ||
|
|
2ea51a6c71 | ||
|
|
fe79d62b73 | ||
|
|
9614f12908 | ||
|
|
bc8a95457f | ||
|
|
ba3a026f40 | ||
|
|
12baca7bea | ||
|
|
abfe9d0c3e | ||
|
|
64b434b87b | ||
|
|
da9f0c494e | ||
|
|
a50b85b7d6 | ||
|
|
cb4703019b | ||
|
|
ab8cdcc9b8 | ||
|
|
f7ce20d879 | ||
|
|
ab149a6cd8 | ||
|
|
99175779c4 | ||
|
|
a4634bca67 | ||
|
|
c6fdf7d843 | ||
|
|
5d91fcba5f | ||
|
|
28a8c5c42a | ||
|
|
868ec73e16 | ||
|
|
1a1234cf68 | ||
|
|
daa2bc82bc | ||
|
|
0cad504c8a | ||
|
|
1396012a03 | ||
|
|
267a8e7c9b | ||
|
|
2698fe8611 | ||
|
|
d5cf115a3c | ||
|
|
4f0214b4bc | ||
|
|
6cd8fcd9d8 | ||
|
|
e108432cb7 | ||
|
|
c13ca9adcf | ||
|
|
82d23e8f1b | ||
|
|
1c70995b25 | ||
|
|
4931a5f10c | ||
|
|
9b2a19530c | ||
|
|
9fe22f4e5e | ||
|
|
52449f3f8e | ||
|
|
eb4997592d | ||
|
|
8d581f42d9 | ||
|
|
79d6056d26 | ||
|
|
13bcd07385 | ||
|
|
1b9570162e | ||
|
|
003946f062 | ||
|
|
c64fbcbdb2 | ||
|
|
eef57410d7 | ||
|
|
8edb857248 | ||
|
|
b113be5f13 | ||
|
|
720306b693 | ||
|
|
f6cfd6b3ea | ||
|
|
839ae964fb | ||
|
|
e5ca862ea9 | ||
|
|
1f100371d7 | ||
|
|
b118785864 | ||
|
|
1c81bd926d | ||
|
|
b34f4d7b17 | ||
|
|
1971824580 | ||
|
|
7831c98a4d | ||
|
|
cc271148b0 | ||
|
|
2b409ad322 | ||
|
|
7cdda9c5fa | ||
|
|
481fc30ae7 | ||
|
|
d0a4b55a0e | ||
|
|
b2c95d41c9 | ||
|
|
010d062926 | ||
|
|
af4d064be7 | ||
|
|
7edb421e1b | ||
|
|
de3a2451eb | ||
|
|
4da9a53b66 | ||
|
|
8ab7547a74 | ||
|
|
2f7d7444f1 | ||
|
|
c595477002 | ||
|
|
c71d2ebace | ||
|
|
65346101a2 | ||
|
|
0d2c48a797 | ||
|
|
078bbe6171 | ||
|
|
cd573beb0a | ||
|
|
d94bb49cf4 | ||
|
|
076d1aa4cd | ||
|
|
8782c7533f | ||
|
|
7017def185 | ||
|
|
e70ae36b3f | ||
|
|
89f5974125 | ||
|
|
faaa14a303 | ||
|
|
f8b13b254b | ||
|
|
68834b7841 | ||
|
|
0c63036c7d | ||
|
|
de212be82b | ||
|
|
bb8bc31e58 | ||
|
|
be34bc0481 | ||
|
|
eeac529d5e | ||
|
|
47c9f73d36 | ||
|
|
84347f94da | ||
|
|
cda9e01053 | ||
|
|
c40afdea73 | ||
|
|
dda519baff | ||
|
|
af99ede1fd | ||
|
|
0a09782cc8 | ||
|
|
e3ee75633e | ||
|
|
d505a477a4 | ||
|
|
f006b41a3c | ||
|
|
a5205265c0 | ||
|
|
23b2797321 | ||
|
|
0e971cddee | ||
|
|
5afda956a7 | ||
|
|
ae4b3d585d | ||
|
|
62b728e766 |
@@ -0,0 +1,14 @@
|
|||||||
|
[target.'cfg(true)']
|
||||||
|
rustflags = [
|
||||||
|
# rustc will pass `-nodefaultlibs` without this, but we need the C++ standard library.
|
||||||
|
'-Cdefault-linker-libraries=yes',
|
||||||
|
]
|
||||||
|
|
||||||
|
[target.'cfg(target_env = "musl")']
|
||||||
|
rustflags = [
|
||||||
|
'-Cdefault-linker-libraries=yes',
|
||||||
|
# musl, at least in Nixpkgs, is not compiled with -fPIE.
|
||||||
|
# XXX: nevermind? as of Nixpkgs 26.05??
|
||||||
|
# Oh gods do we need to gate this??
|
||||||
|
#'-Crelocation-model=static',
|
||||||
|
]
|
||||||
+2
-1
@@ -1,4 +1,5 @@
|
|||||||
outputs/
|
/build
|
||||||
|
/outputs
|
||||||
|
|
||||||
# GNU Global
|
# GNU Global
|
||||||
GPATH
|
GPATH
|
||||||
|
|||||||
Generated
+878
-4
@@ -2,18 +2,320 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "allocator-api2"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"is_terminal_polyfill",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"once_cell_polyfill",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ar_archive_writer"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4087686b4b0a3427190bae57a1d9a478dbb2d40c5dc1bd6e2b6d797913bdd348"
|
||||||
|
dependencies = [
|
||||||
|
"object",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ariadne"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72fe02fc62033df9ba41cba57ee19acf5e742511a140c7dbc3a873e19a19a1bd"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-width 0.1.14",
|
||||||
|
"yansi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "askama"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28"
|
||||||
|
dependencies = [
|
||||||
|
"askama_derive",
|
||||||
|
"askama_escape",
|
||||||
|
"humansize",
|
||||||
|
"num-traits",
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "askama_derive"
|
||||||
|
version = "0.12.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83"
|
||||||
|
dependencies = [
|
||||||
|
"askama_parser",
|
||||||
|
"basic-toml",
|
||||||
|
"mime",
|
||||||
|
"mime_guess",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"serde",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "askama_escape"
|
||||||
|
version = "0.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "askama_parser"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0"
|
||||||
|
dependencies = [
|
||||||
|
"nom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "basic-toml"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.10.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.2.65"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96"
|
||||||
|
dependencies = [
|
||||||
|
"find-msvc-tools",
|
||||||
|
"shlex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg_aliases"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chumsky"
|
||||||
|
version = "1.0.0-alpha.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0e82d74e6c83060ec269fe9e0d408d6de4a1645d525f9a0bbbb841ba4efd91ac"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.15.5",
|
||||||
|
"regex-automata 0.3.9",
|
||||||
|
"serde",
|
||||||
|
"stacker",
|
||||||
|
"unicode-ident",
|
||||||
|
"unicode-segmentation",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clipboard-win"
|
||||||
|
version = "5.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4"
|
||||||
|
dependencies = [
|
||||||
|
"error-code",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "countme"
|
name = "countme"
|
||||||
version = "3.0.1"
|
version = "3.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
|
checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpufeatures"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crypto-common"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
"typenum",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.10.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer",
|
||||||
|
"crypto-common",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dissimilar"
|
name = "dissimilar"
|
||||||
version = "1.0.9"
|
version = "1.0.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "59f8e79d1fbf76bdfbde321e902714bf6c49df88a7dda6fc682fc2979226962d"
|
checksum = "59f8e79d1fbf76bdfbde321e902714bf6c49df88a7dda6fc682fc2979226962d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "endian-type"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "869b0adbda23651a9c5c0c3d270aac9fcb52e8622a8f2b17e57802d7791962f2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "error-code"
|
||||||
|
version = "3.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "expect-test"
|
name = "expect-test"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -24,12 +326,139 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "find-msvc-tools"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foldhash"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generic-array"
|
||||||
|
version = "0.14.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.14.5"
|
version = "0.14.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.15.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||||
|
dependencies = [
|
||||||
|
"allocator-api2",
|
||||||
|
"equivalent",
|
||||||
|
"foldhash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.17.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hex"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "home"
|
||||||
|
version = "0.5.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "humansize"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
|
||||||
|
dependencies = [
|
||||||
|
"libm",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "2.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||||
|
dependencies = [
|
||||||
|
"equivalent",
|
||||||
|
"hashbrown 0.17.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_terminal_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.186"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libm"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "licxxbridge"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"zngur",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lix"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"lix-doc",
|
||||||
|
"pkg-config",
|
||||||
|
"regex",
|
||||||
|
"rootcause",
|
||||||
|
"rustyline",
|
||||||
|
"rustyline-derive",
|
||||||
|
"zngur",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lix-doc"
|
name = "lix-doc"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
@@ -40,8 +469,87 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lixutil-rs"
|
name = "log"
|
||||||
version = "0.0.0"
|
version = "0.4.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime_guess"
|
||||||
|
version = "2.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
|
||||||
|
dependencies = [
|
||||||
|
"mime",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minimal-lexical"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nibble_vec"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
|
||||||
|
dependencies = [
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.31.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cfg-if",
|
||||||
|
"cfg_aliases",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "7.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"minimal-lexical",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "object"
|
||||||
|
version = "0.37.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
@@ -49,6 +557,108 @@ version = "1.19.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell_polyfill"
|
||||||
|
version = "1.70.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "percent-encoding"
|
||||||
|
version = "2.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.106"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "psm"
|
||||||
|
version = "0.1.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "645dbe486e346d9b5de3ef16ede18c26e6c70ad97418f4874b8b1889d6e761ea"
|
||||||
|
dependencies = [
|
||||||
|
"ar_archive_writer",
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.45"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "radix_trie"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b4431027dcd37fc2a73ef740b5f233aa805897935b8bce0195e41bbf9a3289a"
|
||||||
|
dependencies = [
|
||||||
|
"endian-type",
|
||||||
|
"nibble_vec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.12.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata 0.4.14",
|
||||||
|
"regex-syntax 0.8.11",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax 0.7.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax 0.8.11",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rnix"
|
name = "rnix"
|
||||||
version = "0.12.0"
|
version = "0.12.0"
|
||||||
@@ -58,6 +668,28 @@ dependencies = [
|
|||||||
"rowan",
|
"rowan",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rootcause"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b660d9968fae12f4e691f2b2be5d9a3a6de875300c682e8d2cb89a618dd60875"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.17.1",
|
||||||
|
"indexmap",
|
||||||
|
"rootcause-internals",
|
||||||
|
"rustc-hash 2.1.3",
|
||||||
|
"triomphe",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rootcause-internals"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0184f6fcff3b58b7c963aee6e3cc915c04331aa6eef974f79d7d44d21e246c24"
|
||||||
|
dependencies = [
|
||||||
|
"triomphe",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rowan"
|
name = "rowan"
|
||||||
version = "0.15.16"
|
version = "0.15.16"
|
||||||
@@ -65,8 +697,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "0a542b0253fa46e632d27a1dc5cf7b930de4df8659dc6e720b647fc72147ae3d"
|
checksum = "0a542b0253fa46e632d27a1dc5cf7b930de4df8659dc6e720b647fc72147ae3d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"countme",
|
"countme",
|
||||||
"hashbrown",
|
"hashbrown 0.14.5",
|
||||||
"rustc-hash",
|
"rustc-hash 1.1.0",
|
||||||
"text-size",
|
"text-size",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -76,8 +708,250 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-hash"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustyline"
|
||||||
|
version = "18.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53f6a737db68eb1a8ccff86b584b2fc13eca6a7bb6f78ebc7c529547e3ab9684"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cfg-if",
|
||||||
|
"clipboard-win",
|
||||||
|
"home",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"memchr",
|
||||||
|
"nix",
|
||||||
|
"radix_trie",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"unicode-width 0.2.2",
|
||||||
|
"utf8parse",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustyline-derive"
|
||||||
|
version = "0.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "64e5587417a3c4e16a4415e8d7d07f80998ed835ade621d19dfbe9fbe3205b0f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.228"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_core"
|
||||||
|
version = "1.0.228"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.228"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha2"
|
||||||
|
version = "0.10.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.15.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stacker"
|
||||||
|
version = "0.1.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "640c8cdd92b6b12f5bcb1803ca3bbf5ab96e5e6b6b96b9ab77dabe9e880b3190"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"psm",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.118"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "text-size"
|
name = "text-size"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233"
|
checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "triomphe"
|
||||||
|
version = "0.1.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b40688ea6389c8171614b25491f71d4a27946e0c7ce2da1c6de27e25abf1a0ae"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typenum"
|
||||||
|
version = "1.20.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicase"
|
||||||
|
version = "2.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.13.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.61.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yansi"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zngur"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7fc912d12934b4d04aabc52c14db6fc88ae8aa5a47902f27e8759c2de254723f"
|
||||||
|
dependencies = [
|
||||||
|
"zngur-generator",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zngur-def"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f27e49a62db537cea43a6c122ded7eda99c4ac0ca1d7cfd74f2bf6a6c88712d"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"itertools",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zngur-generator"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ffad8c6994c477023aba7613bdf17c470003bbd191846d3096e84ffecf86d4c"
|
||||||
|
dependencies = [
|
||||||
|
"askama",
|
||||||
|
"hex",
|
||||||
|
"indexmap",
|
||||||
|
"itertools",
|
||||||
|
"sha2",
|
||||||
|
"zngur-def",
|
||||||
|
"zngur-parser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zngur-parser"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d66c1b85ca6eab9576df5de31758cb1f9046b4ce47b25f45dabcb0fc7ef8789"
|
||||||
|
dependencies = [
|
||||||
|
"ariadne",
|
||||||
|
"chumsky",
|
||||||
|
"itertools",
|
||||||
|
"zngur-def",
|
||||||
|
]
|
||||||
|
|||||||
+23
-1
@@ -1,6 +1,28 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = ["lix/lix-doc", "lix/libutil"]
|
members = [
|
||||||
|
"lix/lix-doc",
|
||||||
|
"lix/lix-rs",
|
||||||
|
"tools/licxxbridge",
|
||||||
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
clap = "4"
|
||||||
|
regex = "1.12.4"
|
||||||
|
rootcause = "0.13.0"
|
||||||
|
rustyline = "18"
|
||||||
|
rustyline-derive = "0.12"
|
||||||
|
syn = "2.0"
|
||||||
|
zngur = "0.10"
|
||||||
|
pkg-config = "0.3.33"
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
opt-level = 1
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
debug = "full"
|
||||||
|
debug-assertions = true
|
||||||
|
overflow-checks = true
|
||||||
|
|||||||
+2
-1
@@ -10,6 +10,7 @@ import platform
|
|||||||
import shlex
|
import shlex
|
||||||
import textwrap
|
import textwrap
|
||||||
import dataclasses
|
import dataclasses
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
flake_args = ["--extra-experimental-features", "nix-command flakes"]
|
flake_args = ["--extra-experimental-features", "nix-command flakes"]
|
||||||
cases = {
|
cases = {
|
||||||
@@ -18,7 +19,7 @@ cases = {
|
|||||||
*flake_args,
|
*flake_args,
|
||||||
"search",
|
"search",
|
||||||
"--no-eval-cache",
|
"--no-eval-cache",
|
||||||
"github:nixos/nixpkgs/e1fa12d4f6c6fe19ccb59cac54b5b3f25e160870",
|
f"path:{Path('./bench/nixpkgs/').readlink()}",
|
||||||
"hello",
|
"hello",
|
||||||
],
|
],
|
||||||
"rebuild": lambda build: [
|
"rebuild": lambda build: [
|
||||||
|
|||||||
@@ -1,19 +1,13 @@
|
|||||||
# Darwin: don't link liblix* into plugins (host process provides them at runtime).
|
|
||||||
# Explicitly link curl so it binds to Nix-store libcurl, not /usr/lib/libcurl.
|
|
||||||
if is_darwin
|
|
||||||
plugin_deps = [
|
|
||||||
liblix.partial_dependency(includes : true, compile_args : true),
|
|
||||||
curl,
|
|
||||||
]
|
|
||||||
else
|
|
||||||
plugin_deps = [liblix, curl]
|
|
||||||
endif
|
|
||||||
|
|
||||||
plugin_mtls_store = shared_module(
|
plugin_mtls_store = shared_module(
|
||||||
'plugin_mtls_store',
|
'plugin_mtls_store',
|
||||||
'plugin_mtls_store.cc',
|
'plugin_mtls_store.cc',
|
||||||
dependencies : plugin_deps,
|
# don't link liblix* into plugins (host process provides them at runtime).
|
||||||
|
# Explicitly link curl so it binds to Nix-store libcurl, not /usr/lib/libcurl.
|
||||||
|
dependencies : [
|
||||||
|
liblix.partial_dependency(includes : true, compile_args : true),
|
||||||
|
curl,
|
||||||
|
],
|
||||||
install : false,
|
install : false,
|
||||||
build_by_default : true,
|
build_by_default : true,
|
||||||
link_args : is_darwin ? shared_module_link_args : strict_shared_module_link_args,
|
link_args : plugin_link_args,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ def map_contents_recursively(transformer):
|
|||||||
def process_command:
|
def process_command:
|
||||||
.[0] as $context |
|
.[0] as $context |
|
||||||
.[1] as $body |
|
.[1] as $body |
|
||||||
# XXX FUTURE: drop sections once mdBook is at 0.5.0 or above in nixpkgs
|
$body | .items |= map(map_contents_recursively(if $context.renderer == "html" then transform_anchors_html else transform_anchors_strip end))
|
||||||
$body | (.items? // .sections) |= map(map_contents_recursively(if $context.renderer == "html" then transform_anchors_html else transform_anchors_strip end))
|
|
||||||
;
|
;
|
||||||
|
|
||||||
process_command
|
process_command
|
||||||
|
|||||||
@@ -31,12 +31,11 @@ command = "jq --from-file anchors.jq"
|
|||||||
|
|
||||||
[output.markdown]
|
[output.markdown]
|
||||||
|
|
||||||
# XXX FUTURE: may be reenabled once mdBook 0.5.0 or above and matching mdbook-linkchecker are in nixpkgs
|
[output.linkcheck2]
|
||||||
#[output.linkcheck]
|
|
||||||
# no Internet during the build (in the sandbox)
|
# no Internet during the build (in the sandbox)
|
||||||
#follow-web-links = false
|
follow-web-links = false
|
||||||
|
|
||||||
# mdbook-linkcheck does not understand [foo]{#bar} style links, resulting in
|
# mdbook-linkcheck does not understand [foo]{#bar} style links, resulting in
|
||||||
# excessive "Potential incomplete link" warnings. No other kind of warning was
|
# excessive "Potential incomplete link" warnings. No other kind of warning was
|
||||||
# produced at the time of writing.
|
# produced at the time of writing.
|
||||||
#warning-policy = "ignore"
|
warning-policy = "ignore"
|
||||||
|
|||||||
@@ -48,6 +48,11 @@ artemist:
|
|||||||
display_name: Artemis Tosini
|
display_name: Artemis Tosini
|
||||||
forgejo: artemist
|
forgejo: artemist
|
||||||
|
|
||||||
|
astreaprtcl:
|
||||||
|
display_name: Astreaprtcl
|
||||||
|
forgejo: astreaprtcl
|
||||||
|
github: astreaprtcl
|
||||||
|
|
||||||
bb010g:
|
bb010g:
|
||||||
display_name: Dusk Banks
|
display_name: Dusk Banks
|
||||||
forgejo: bb010g
|
forgejo: bb010g
|
||||||
@@ -77,6 +82,9 @@ detroyejr:
|
|||||||
display_name: Jonathan De Troye
|
display_name: Jonathan De Troye
|
||||||
github: detroyejr
|
github: detroyejr
|
||||||
|
|
||||||
|
edef:
|
||||||
|
github: edef1c
|
||||||
|
|
||||||
edolstra:
|
edolstra:
|
||||||
display_name: Eelco Dolstra
|
display_name: Eelco Dolstra
|
||||||
github: edolstra
|
github: edolstra
|
||||||
@@ -142,6 +150,9 @@ kasimeka:
|
|||||||
forgejo: janw4ld
|
forgejo: janw4ld
|
||||||
github: kasimeka
|
github: kasimeka
|
||||||
|
|
||||||
|
keysmashes:
|
||||||
|
github: keysmashes
|
||||||
|
|
||||||
kfears:
|
kfears:
|
||||||
display_name: KFears
|
display_name: KFears
|
||||||
forgejo: kfearsoff
|
forgejo: kfearsoff
|
||||||
@@ -245,6 +256,11 @@ raito:
|
|||||||
forgejo: raito
|
forgejo: raito
|
||||||
github: RaitoBezarius
|
github: RaitoBezarius
|
||||||
|
|
||||||
|
rkjnsn:
|
||||||
|
display_name: Erik Jensen
|
||||||
|
forgejo: rkjnsn
|
||||||
|
github: rkjnsn
|
||||||
|
|
||||||
roberth:
|
roberth:
|
||||||
display_name: Robert Hensing
|
display_name: Robert Hensing
|
||||||
github: roberth
|
github: roberth
|
||||||
@@ -253,6 +269,9 @@ rootile:
|
|||||||
display_name: rootile (Rutile)
|
display_name: rootile (Rutile)
|
||||||
forgejo: rootile
|
forgejo: rootile
|
||||||
|
|
||||||
|
sandydoo:
|
||||||
|
github: sandydoo
|
||||||
|
|
||||||
seppel3210:
|
seppel3210:
|
||||||
github: Seppel3210
|
github: Seppel3210
|
||||||
|
|
||||||
@@ -317,5 +336,8 @@ yorickvp:
|
|||||||
yshui:
|
yshui:
|
||||||
github: yshui
|
github: yshui
|
||||||
|
|
||||||
|
ysndr:
|
||||||
|
github: ysndr
|
||||||
|
|
||||||
zimbatm:
|
zimbatm:
|
||||||
github: zimbatm
|
github: zimbatm
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
synopsis: "check for missing ca-file or netrc-file if one is specified"
|
||||||
|
cls: [5646]
|
||||||
|
category: "Improvements"
|
||||||
|
credits: [astreaprtcl]
|
||||||
|
issues: [fj#1106]
|
||||||
|
---
|
||||||
|
|
||||||
|
If the settings `ssl-cert-file` or `netrc-file` have been set by the user, check if those files actually exist and fail if they are missing.
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
synopsis: "don't treat tarball fetches with empty or zero hash as locked"
|
||||||
|
cls: []
|
||||||
|
category: "Fixes"
|
||||||
|
credits: [horrors]
|
||||||
|
issues: [fj#1233]
|
||||||
|
---
|
||||||
|
|
||||||
|
Lix no longer treats tarball fetches with empty or zero hashes as locked.
|
||||||
|
All such fetches are now also affected by `tarball-ttl` as a consequence.
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
synopsis: builtins.floor/builtins.ceil handle out-of-range inputs correctly
|
||||||
|
issues: [nix#12899]
|
||||||
|
cls: [3923]
|
||||||
|
prs: [nix#13013]
|
||||||
|
category: "Breaking Changes"
|
||||||
|
credits: [jade, nan-git, rootile]
|
||||||
|
---
|
||||||
|
Previously, `builtins.floor` and `builtins.ceil` always cast the input into a floating point value before running the operation and casting the floating point result back into an integer.
|
||||||
|
No checks were made for precision loss in either coercing integer inputs or converting the output to an integer (and in fact in the latter case, invoked undefined behaviour).
|
||||||
|
|
||||||
|
Now, Lix checks for precision loss on integer input (to avoid a silent eval semantics change if we were to simply pass it through as-is) and on integer output.
|
||||||
|
If your code fails to evaluate after this change, use `--extra-deprecated-features floor-ceil-corrupt-integers`.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
synopsis: "Use a lock when fetching inputs"
|
||||||
|
issues: [1122]
|
||||||
|
cls: [5438]
|
||||||
|
category: "Fixes"
|
||||||
|
credits: [lheckemann]
|
||||||
|
---
|
||||||
|
|
||||||
|
Up to now, attempting to fetch the same git input from multiple processes
|
||||||
|
concurrently when the input is not yet cached presented multiple issues:
|
||||||
|
|
||||||
|
- If the input was not already present, it would unnecessarily be fetched
|
||||||
|
multiple times;
|
||||||
|
|
||||||
|
- Access to the fetcher cache database was contentious, and could lead to
|
||||||
|
evaluation or flake locking failing unnecessary because the fetcher cache
|
||||||
|
was locked.
|
||||||
|
|
||||||
|
We now acquire a lock on a path based on a hash of the input specification
|
||||||
|
before accessing the fetcher db, reducing contention significantly, and
|
||||||
|
preventing more than one process from fetching the same path at the same time.
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
synopsis: "Use mimalloc for faster evaluation"
|
||||||
|
cls: [5645]
|
||||||
|
category: Features
|
||||||
|
credits: [getchoo, lovesegfault]
|
||||||
|
---
|
||||||
|
|
||||||
|
Lix now links with [mimalloc](https://github.com/microsoft/mimalloc),
|
||||||
|
replacing the system's default `malloc()` for all non-GC allocations.
|
||||||
|
|
||||||
|
This yields a **5–12% wall-clock improvement** on evaluation workloads,
|
||||||
|
ranging from `nix-instantiate hello` to `nix-env -qa` and full NixOS
|
||||||
|
configurations.
|
||||||
|
|
||||||
|
The allocator can be disabled at build time with `-Dmimalloc=disabled`,
|
||||||
|
or by passing the `useMimalloc = false` override to the `lix` package.
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
synopsis: "nix-eval-jobs support `--apply` flag"
|
||||||
|
cls: [5748]
|
||||||
|
category: "Features"
|
||||||
|
credits: [isabelroses,mic92,ysndr]
|
||||||
|
issues: [fj#1214]
|
||||||
|
---
|
||||||
|
|
||||||
|
`nix-eval-jobs` now supports the `--apply` flag. With this you can apply the
|
||||||
|
provided function to the each derivation, the result of this function will then
|
||||||
|
be serialized as a JSON value and stored inside `"extraValue"` key of the json
|
||||||
|
line output.
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
synopsis: "Fix `nix-copy-closure --include-outputs`"
|
||||||
|
issues: [gh#5105]
|
||||||
|
cls: [5588]
|
||||||
|
category: "Fixes"
|
||||||
|
credits: [rkjnsn]
|
||||||
|
---
|
||||||
|
|
||||||
|
The `--include-outputs` flag for `nix-copy-closure` now works as intended.
|
||||||
|
Previously, the option was accepted but silently ignored.
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
synopsis: "Improve nix doctor"
|
||||||
|
cls: [5316, 5317, 5318, 5319, 5320, 5768, 5829]
|
||||||
|
category: Features
|
||||||
|
credits: [rootile, raito]
|
||||||
|
---
|
||||||
|
The `nix doctor` diagnosics interface now provides a lot more useful information including, but not limited to:
|
||||||
|
- General system information (OS, Hardware etc)
|
||||||
|
- Nix Information like Sandbox, Version, Store, State and other directories
|
||||||
|
- Flake registry
|
||||||
|
- Search path Information
|
||||||
|
- Nixpkgs provenance
|
||||||
|
- Remote builder configuration (including remote connection)
|
||||||
|
- fix crash when having relative Paths in PATH
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
synopsis: "Remove `max-connections` store parameters for `ssh://` and `ssh-ng://` stores"
|
||||||
|
cls: []
|
||||||
|
category: Miscellany
|
||||||
|
credits: [horrors]
|
||||||
|
---
|
||||||
|
|
||||||
|
The `max-connections` parameter was undocumented, untested, and (in the case of `ssh`) even ignored
|
||||||
|
entirely for remote builds. During a survey of public nixos configurations we have found *two* uses
|
||||||
|
of `max-connections` for `ssh-ng`, and none at all for `ssh`. Since it is so rarely used but brings
|
||||||
|
significant internal complexity that hinders improvements we have decided to remove these features.
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
synopsis: "Print REPL backtraces in more convenient order"
|
||||||
|
issues: []
|
||||||
|
cls: [5491]
|
||||||
|
category: "Improvements"
|
||||||
|
credits: [blokyk]
|
||||||
|
---
|
||||||
|
|
||||||
|
When using the debugger, stack traces printed with the `:bt` command were
|
||||||
|
previously printed in reverse order compared to most other situations where they
|
||||||
|
appeared: the current stack frame would be printed at the very top, with the
|
||||||
|
most outer frame at the bottom, meaning that you'd have to scroll up to get a
|
||||||
|
sense of where you are.
|
||||||
|
|
||||||
|
With this change, the stack frames are printed such that the most relevant ones
|
||||||
|
are immediatly visible at the bottom, just like other traces in lix (e.g.
|
||||||
|
ones caused by errors).
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
synopsis: "REPL now uses rustyline"
|
||||||
|
cls: [5703]
|
||||||
|
category: "Improvements"
|
||||||
|
credits: [horrors]
|
||||||
|
issues: []
|
||||||
|
---
|
||||||
|
|
||||||
|
The REPL now uses [rustyline](https://github.com/kkawakam/rustyline) for input processing instead
|
||||||
|
of editline. This comes with some improvements to REPL behavior: wrapping lines no longer confuse
|
||||||
|
the line editor, unicode is fully supported, pasting multiline expressions is noew possible, even
|
||||||
|
undo commands are now available! We plan to improve the REPL further using these newfound powers.
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
synopsis: "Hash mismatch diagnostics now work with `structuredAttrs`"
|
||||||
|
issues: [fj#1175]
|
||||||
|
cls: [5441]
|
||||||
|
category: Fixes
|
||||||
|
credits: [keysmashes]
|
||||||
|
---
|
||||||
|
|
||||||
|
Nixpkgs fetchers like `fetchurl` now use `structuredAttrs`, which broke the
|
||||||
|
hash mismatch diagnostics added in Lix 2.91. This has been fixed and the likely
|
||||||
|
URL is now shown again.
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
synopsis: "Changes to `flake.nix` validation"
|
||||||
|
cls: [5523]
|
||||||
|
category: "Breaking Changes"
|
||||||
|
credits: [piegames, Qyriad, horrors]
|
||||||
|
issues: [gh#4945]
|
||||||
|
---
|
||||||
|
|
||||||
|
Flakes try to keep their inputs and metadata "simple", to make sure no unbounded computation may happen when calling e.g. `nix flake show`.
|
||||||
|
Those checks were haphazard, a maintenance burden, and also easily circumventable.
|
||||||
|
|
||||||
|
Lix has now replaced all the old checks by a simple rule: **No function calls outside of `outputs`.**
|
||||||
|
This is easier to reason about than the previous set of inconsistent rules, and crucially now also allows syntax features that users felt like they *should* have worked in the past, like let bindings.
|
||||||
|
However, some warts still remain for now: Some syntax constructs like `-1` internally desugar to `__sub 0 1`, which is a function call and thus remains forbidden.
|
||||||
|
This will be rectified as soon as the deprecation period of the respective anti-features has been completed.
|
||||||
|
|
||||||
|
This change is **breaking** in the sense that flakes which are written with the newly allowed language features will not evaluate with an older Lix version which still uses the old, more restrictive checks.
|
||||||
|
Crucially, this also affects **all transitive dependants** of such Flakes.
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
synopsis: "Fix unsigned overflow leading to out-of-band write in the NAR parser"
|
||||||
|
cls: [5554]
|
||||||
|
category: "Fixes"
|
||||||
|
credits: [horrors, raito, edef, sandydoo]
|
||||||
|
issues: []
|
||||||
|
---
|
||||||
|
|
||||||
|
The NAR parser contained an unsigned integer overflow that could be used by an
|
||||||
|
attacker to write arbitrary data to an unknown memory location and possibly
|
||||||
|
achieve code execution. A successful attack on the system-wide Lix daemon
|
||||||
|
could lead to privilege escalation to root. Any process that involves NAR
|
||||||
|
serialization could trigger this issue, including (but not limited to)
|
||||||
|
|
||||||
|
- local user interaction, whether the users are trusted or untrusted
|
||||||
|
- malicious substituters sending malformed NARs
|
||||||
|
- remote builders sending malformed build results
|
||||||
|
- remote daemons sending malformed inputs when requesting remote builds
|
||||||
|
|
||||||
|
Successful attacks using this bug require ASLR weakening of some sort, whether
|
||||||
|
by architecture constraints (e.g. on 32 bit systems, where little randomization
|
||||||
|
is possible) or system configuration (e.g. low ASLR entropy when loading
|
||||||
|
libraries), and millions of attempts. Local attacks can be mounted in less than
|
||||||
|
an hour. Remote builds typically require a fresh SSH connection for each build
|
||||||
|
and are thus less susceptible. Only one attempt can be made by substituters for
|
||||||
|
every build using substituters, they are thus not a likely vector for attacks.
|
||||||
|
|
||||||
|
At the time of writing, MITRE has not assigned this a CVE yet.
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
synopsis: "Always print frames from `addErrorContext` in error traces"
|
||||||
|
cls: [5847]
|
||||||
|
category: "Improvements"
|
||||||
|
credits: [blokyk]
|
||||||
|
issues: []
|
||||||
|
---
|
||||||
|
|
||||||
|
The [`builtins.addErrorContext`](@docroot@/language/builtins.md#builtins-addErrorContext)
|
||||||
|
function allows an author to add artificial stack frames with custom messages to
|
||||||
|
help end-users understand the context of an error and the path the code took to
|
||||||
|
get there, without having to read and understand the original source code. A
|
||||||
|
particularly notable user of this is the Nixpkgs module system, which adds
|
||||||
|
custom frames detailing what option it's evaluating or which definition it's
|
||||||
|
looking at.
|
||||||
|
|
||||||
|
However, previously, these frames would end up treated just as any other,
|
||||||
|
meaning they would most often not be visible without `--show-trace`; yet, using
|
||||||
|
`--show-trace`, they would be drowned out in the noise of the hundreds of other
|
||||||
|
frames, rendering them just as unusable.
|
||||||
|
|
||||||
|
With this change, these frames are now unconditionally shown, even without
|
||||||
|
`--show-trace`, which makes basic error traces much more informative.
|
||||||
@@ -51,7 +51,7 @@ $ nix-shell -A native-clangStdenvPackages
|
|||||||
|
|
||||||
### Building from the development shell
|
### Building from the development shell
|
||||||
|
|
||||||
We have a [justfile](just.systems) for extra convenient building.
|
We have a [justfile](https://just.systems/) for extra convenient building.
|
||||||
It defaults to using `./build` as the build directory, and `$out` (`./outputs/out`) as the install directory.
|
It defaults to using `./build` as the build directory, and `$out` (`./outputs/out`) as the install directory.
|
||||||
For most cases, you can clean-build, install, and run the tests with:
|
For most cases, you can clean-build, install, and run the tests with:
|
||||||
|
|
||||||
@@ -183,13 +183,13 @@ The development shell on Linux uses LLD by default for faster link times.
|
|||||||
This is set using `mesonFlags`, so to override it, you can simplify re-specify the linker to Meson:
|
This is set using `mesonFlags`, so to override it, you can simplify re-specify the linker to Meson:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ just setup-custom -Dc_link_args=-fuse-ld=ld -Dcpp_link_args=-fuse-ld=ld
|
$ just setup -Dc_link_args=-fuse-ld=ld -Dcpp_link_args=-fuse-ld=ld
|
||||||
```
|
```
|
||||||
|
|
||||||
While using LLD, you may find it helpful to use ThinLTO for even further improvements to link times for incremental builds:
|
While using LLD, you may find it helpful to use ThinLTO for even further improvements to link times for incremental builds:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ just setup-custom -Db_lto=true -Db_lto_mode=thin -Db_thinlto_cache=true
|
$ just setup -Db_lto=true -Db_lto_mode=thin -Db_thinlto_cache=true
|
||||||
```
|
```
|
||||||
|
|
||||||
## Sending changes to Gerrit for review {#sending-to-gerrit}
|
## Sending changes to Gerrit for review {#sending-to-gerrit}
|
||||||
@@ -200,7 +200,7 @@ Our instance is at <https://gerrit.lix.systems>.
|
|||||||
There's much more information about how to use Gerrit in the [wiki section on Gerrit][wiki-gerrit] including how to use Jujutsu, how to use the UI and more.
|
There's much more information about how to use Gerrit in the [wiki section on Gerrit][wiki-gerrit] including how to use Jujutsu, how to use the UI and more.
|
||||||
The Snix project also has some Gerrit information [in their contributing docs][snix-gerrit].
|
The Snix project also has some Gerrit information [in their contributing docs][snix-gerrit].
|
||||||
|
|
||||||
[wiki-gerrit]: https://wiki.lix.systems/books/lix-contributors/chapter/gerrit
|
[wiki-gerrit]: https://wiki.lix.systems/books/contributing/chapter/intro-to-gerrit
|
||||||
[snix-gerrit]: https://snix.dev/docs/guides/contributing/
|
[snix-gerrit]: https://snix.dev/docs/guides/contributing/
|
||||||
|
|
||||||
The gist is that once you have your SSH key and git remote set up, you can send commits for review with:
|
The gist is that once you have your SSH key and git remote set up, you can send commits for review with:
|
||||||
@@ -226,7 +226,7 @@ The `Code-Review+2` from before will stick around through trivial rebases so no
|
|||||||
We use Buildkite for our CI, usually you will not have to interact directly with it other than reviewing any errors it produces, which are linked from Gerrit.
|
We use Buildkite for our CI, usually you will not have to interact directly with it other than reviewing any errors it produces, which are linked from Gerrit.
|
||||||
|
|
||||||
However in certain cases a CI run will fail due to transient issues not related to your code and you will need to rerun it by hand.
|
However in certain cases a CI run will fail due to transient issues not related to your code and you will need to rerun it by hand.
|
||||||
You can log in to the CI via [SSO](https://buildkite.com/sso/lix-project). On your job you can then hit the "Retry failed" button to rerun it, normally you will not have a repeat of the transient issue.
|
You can log in to the CI via [SSO](https://buildkite.com/sso/afnix). On your job you can then hit the "Retry failed" button to rerun it, normally you will not have a repeat of the transient issue.
|
||||||
If the build still fails on CI issues or all builds are failing this should be reported via [Zulip on #T-infra](https://zulip.lix.systems/#narrow/channel/7-T-infra) or [Matrix on #dev](https://matrix.to/#/%23dev%3Alix.systems?via=lix.systems).
|
If the build still fails on CI issues or all builds are failing this should be reported via [Zulip on #T-infra](https://zulip.lix.systems/#narrow/channel/7-T-infra) or [Matrix on #dev](https://matrix.to/#/%23dev%3Alix.systems?via=lix.systems).
|
||||||
|
|
||||||
## Building Lix with `nix`
|
## Building Lix with `nix`
|
||||||
@@ -566,7 +566,7 @@ Then add the new file there, and don't forget to register it in the appropriate
|
|||||||
The following metadata properties are supported for builtin functions:
|
The following metadata properties are supported for builtin functions:
|
||||||
* `name` (required): the language-facing name (as a member of the `builtins` attribute set) of the function.
|
* `name` (required): the language-facing name (as a member of the `builtins` attribute set) of the function.
|
||||||
* `implementation` (optional): a C++ expression specifying the implementation of the builtin.
|
* `implementation` (optional): a C++ expression specifying the implementation of the builtin.
|
||||||
It must be a function of signature `void(EvalState &, PosIdx, Value * *, Value &)`.
|
It must be a function of signature `Value(EvalState &, PosIdx, Value * *)`.
|
||||||
If not specified, defaults to `prim_${name}`.
|
If not specified, defaults to `prim_${name}`.
|
||||||
* `renameInGlobalScope` (optional): whether the definition should be "hidden" in the global scope by prefixing its name with two underscores.
|
* `renameInGlobalScope` (optional): whether the definition should be "hidden" in the global scope by prefixing its name with two underscores.
|
||||||
If not specified, defaults to `true`.
|
If not specified, defaults to `true`.
|
||||||
|
|||||||
@@ -50,10 +50,6 @@ The most current alternative to this section is to read `package.nix` and see wh
|
|||||||
- The `boost` library of version 1.66.0 or higher. It can be obtained
|
- The `boost` library of version 1.66.0 or higher. It can be obtained
|
||||||
from the official web site <https://www.boost.org/>.
|
from the official web site <https://www.boost.org/>.
|
||||||
|
|
||||||
- The `editline` library of version 1.14.0 or higher. It can be
|
|
||||||
obtained from the its repository
|
|
||||||
<https://github.com/troglobit/editline>.
|
|
||||||
|
|
||||||
- Recent versions of Bison and Flex to build the parser. (This is
|
- Recent versions of Bison and Flex to build the parser. (This is
|
||||||
because Nix needs GLR support in Bison and reentrancy support in
|
because Nix needs GLR support in Bison and reentrancy support in
|
||||||
Flex.) For Bison, you need version 2.6, which can be obtained from
|
Flex.) For Bison, you need version 2.6, which can be obtained from
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ Note that lists are only lazy in values, and they are strict in length.
|
|||||||
|
|
||||||
An attribute set is a collection of name-value-pairs (called *attributes*) enclosed in curly brackets (`{ }`).
|
An attribute set is a collection of name-value-pairs (called *attributes*) enclosed in curly brackets (`{ }`).
|
||||||
|
|
||||||
An attribute name can be an identifier or a [string](#type-string).
|
An attribute name can be an identifier or a [double-quoted string](#type-string).
|
||||||
An identifier must start with a letter (`a-z`, `A-Z`) or underscore (`_`), and can otherwise contain letters (`a-z`, `A-Z`), numbers (`0-9`), underscores (`_`), apostrophes (`'`), or dashes (`-`).
|
An identifier must start with a letter (`a-z`, `A-Z`) or underscore (`_`), and can otherwise contain letters (`a-z`, `A-Z`), numbers (`0-9`), underscores (`_`), apostrophes (`'`), or dashes (`-`).
|
||||||
|
|
||||||
> *name* = *identifier* | *string* \
|
> *name* = *identifier* | *string* \
|
||||||
|
|||||||
@@ -70,11 +70,6 @@ def do_include(content: str, relative_md_path: Path, source_root: Path, search_p
|
|||||||
|
|
||||||
def recursive_replace(data, book_root, search_path):
|
def recursive_replace(data, book_root, search_path):
|
||||||
match data:
|
match data:
|
||||||
# XXX FUTURE: drop sections once mdBook is at 0.5.0 or above in nixpkgs
|
|
||||||
case {'sections': sections}:
|
|
||||||
return data | dict(
|
|
||||||
sections = [recursive_replace(section, book_root, search_path) for section in sections],
|
|
||||||
)
|
|
||||||
case {'items': items}:
|
case {'items': items}:
|
||||||
return data | dict(
|
return data | dict(
|
||||||
items = [recursive_replace(item, book_root, search_path) for item in items],
|
items = [recursive_replace(item, book_root, search_path) for item in items],
|
||||||
|
|||||||
Generated
+4
-4
@@ -106,16 +106,16 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1773082486,
|
"lastModified": 1783770249,
|
||||||
"narHash": "sha256-TKUDrM0nKUo5s/b8jhjXa2prcu5KU5Cck3HBTRLDjfo=",
|
"narHash": "sha256-K8pGvFito5dp9T0+clr60q+bJPGEskK75aAJ39w7HBM=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "7f8b8875bdb38a70c7b5ceb9ba6a6a8d69859e16",
|
"rev": "62463162b3ce92919f19ada41a70a0d943a08da8",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"ref": "nixos-25.11-small",
|
"ref": "nixos-26.05-small",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
description = "Lix: A modern, delicious implementation of the Nix package manager";
|
description = "Lix: A modern, delicious implementation of the Nix package manager";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11-small";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05-small";
|
||||||
nixpkgs-regression.url = "github:NixOS/nixpkgs/215d4d0fd80ca5163643b03a33fde804a29cc1e2";
|
nixpkgs-regression.url = "github:NixOS/nixpkgs/215d4d0fd80ca5163643b03a33fde804a29cc1e2";
|
||||||
|
|
||||||
# Required because Nix 2.18 is not in Nixpkgs ≥ 25.05 anymore.
|
# Required because Nix 2.18 is not in Nixpkgs ≥ 25.05 anymore.
|
||||||
@@ -42,6 +42,7 @@
|
|||||||
|
|
||||||
let
|
let
|
||||||
inherit (nixpkgs) lib;
|
inherit (nixpkgs) lib;
|
||||||
|
lixSrc = self;
|
||||||
|
|
||||||
# This notice gets echoed as a dev shell hook, and can be turned off with
|
# This notice gets echoed as a dev shell hook, and can be turned off with
|
||||||
# `touch .nocontribmsg`
|
# `touch .nocontribmsg`
|
||||||
@@ -76,206 +77,35 @@
|
|||||||
(Run `touch .nocontribmsg` to hide this message.)
|
(Run `touch .nocontribmsg` to hide this message.)
|
||||||
'';
|
'';
|
||||||
|
|
||||||
versionJson = builtins.fromJSON (builtins.readFile ./version.json);
|
scope = import ./nix-support/build/inputs.nix {
|
||||||
officialRelease = versionJson.official_release;
|
inherit
|
||||||
|
lib
|
||||||
|
nixpkgs
|
||||||
|
nix_2_18
|
||||||
|
nix2container
|
||||||
|
lixSrc
|
||||||
|
nixpkgs-regression
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
# Set to true to build the release notes for the next release.
|
inherit (scope)
|
||||||
buildUnreleasedNotes = true;
|
crossSystems
|
||||||
|
darwinSystems
|
||||||
|
forAllStdenvs
|
||||||
|
forAllSystems
|
||||||
|
forAvailableSystems
|
||||||
|
linux64BitSystems
|
||||||
|
nixpkgsFor
|
||||||
|
overlayFor
|
||||||
|
systems
|
||||||
|
versionSuffix
|
||||||
|
;
|
||||||
|
|
||||||
versionSuffix =
|
inherit (scope.callPackage ./nix-support/build/outputs.nix { })
|
||||||
if officialRelease then
|
packages
|
||||||
""
|
ciArtifacts
|
||||||
else
|
tests
|
||||||
"pre${
|
;
|
||||||
builtins.substring 0 8 (self.lastModifiedDate or self.lastModified or "19700101")
|
|
||||||
}-dev_${self.shortRev or "dirty"}";
|
|
||||||
|
|
||||||
linux32BitSystems = [ "i686-linux" ];
|
|
||||||
linux64BitSystems = [
|
|
||||||
"x86_64-linux"
|
|
||||||
"aarch64-linux"
|
|
||||||
];
|
|
||||||
linuxSystems = linux32BitSystems ++ linux64BitSystems;
|
|
||||||
darwinSystems = [
|
|
||||||
"x86_64-darwin"
|
|
||||||
"aarch64-darwin"
|
|
||||||
];
|
|
||||||
nonDarwinSystems = linuxSystems;
|
|
||||||
systems = linuxSystems ++ darwinSystems;
|
|
||||||
|
|
||||||
# If you add something here, please update the list in doc/manual/src/contributing/hacking.md.
|
|
||||||
# Thanks~
|
|
||||||
crossSystems = [
|
|
||||||
"armv6l-linux"
|
|
||||||
"armv7l-linux"
|
|
||||||
"riscv64-linux"
|
|
||||||
"aarch64-linux"
|
|
||||||
"x86_64-freebsd"
|
|
||||||
# FIXME: broken dev shell due to python
|
|
||||||
# "x86_64-netbsd"
|
|
||||||
];
|
|
||||||
|
|
||||||
stdenvs = [
|
|
||||||
# see assertion in package.nix why these two are disabled
|
|
||||||
# "stdenv"
|
|
||||||
# "gccStdenv"
|
|
||||||
"clangStdenv"
|
|
||||||
"libcxxStdenv"
|
|
||||||
"ccacheStdenv"
|
|
||||||
];
|
|
||||||
|
|
||||||
forAllSystems = lib.genAttrs systems;
|
|
||||||
# Same as forAllSystems, but removes nulls, in case something is broken
|
|
||||||
# on that system.
|
|
||||||
forAvailableSystems =
|
|
||||||
f: lib.filterAttrs (name: value: value != null && value != { }) (forAllSystems f);
|
|
||||||
|
|
||||||
forAllCrossSystems = lib.genAttrs crossSystems;
|
|
||||||
|
|
||||||
forAllStdenvs =
|
|
||||||
f:
|
|
||||||
lib.listToAttrs (
|
|
||||||
map (stdenvName: {
|
|
||||||
name = "${stdenvName}Packages";
|
|
||||||
value = f stdenvName;
|
|
||||||
}) stdenvs
|
|
||||||
)
|
|
||||||
// {
|
|
||||||
# TODO delete this and reënable gcc stdenvs once gcc compiles kj coros correctly
|
|
||||||
stdenvPackages = f "clangStdenv";
|
|
||||||
};
|
|
||||||
|
|
||||||
# Memoize nixpkgs for different platforms for efficiency.
|
|
||||||
nixpkgsFor = forAllSystems (
|
|
||||||
system:
|
|
||||||
let
|
|
||||||
make-pkgs =
|
|
||||||
crossSystem: stdenv:
|
|
||||||
import nixpkgs {
|
|
||||||
localSystem = {
|
|
||||||
inherit system;
|
|
||||||
};
|
|
||||||
crossSystem = if crossSystem == null then null else { system = crossSystem; };
|
|
||||||
overlays = [ (overlayFor (p: p.${stdenv})) ];
|
|
||||||
};
|
|
||||||
stdenvs = forAllStdenvs (make-pkgs null);
|
|
||||||
native = stdenvs.stdenvPackages;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit stdenvs native;
|
|
||||||
static = native.pkgsStatic;
|
|
||||||
cross = forAllCrossSystems (crossSystem: make-pkgs crossSystem "clangStdenv");
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
overlayFor =
|
|
||||||
getStdenv: final: prev:
|
|
||||||
let
|
|
||||||
currentStdenv = getStdenv final;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
nixStable = prev.nix;
|
|
||||||
|
|
||||||
nixVersions = prev.nixVersions // {
|
|
||||||
# Nix 2.18 has been removed from Nixpkgs ≥ 25.05, so we need to reintroduce it ourselves for our tests.
|
|
||||||
nix_2_18 =
|
|
||||||
nix_2_18.outputs.packages.${currentStdenv.hostPlatform.system}.default.overrideAttrs
|
|
||||||
(_: {
|
|
||||||
pname = "nix";
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
check-headers = final.buildPackages.callPackage ./maintainers/check-headers.nix { };
|
|
||||||
check-syscalls = final.buildPackages.callPackage ./maintainers/check-syscalls.nix { };
|
|
||||||
|
|
||||||
default-busybox-sandbox-shell = final.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
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
nix = final.callPackage ./package.nix {
|
|
||||||
inherit versionSuffix officialRelease;
|
|
||||||
stdenv = currentStdenv;
|
|
||||||
busybox-sandbox-shell = final.busybox-sandbox-shell or final.default-busybox-sandbox-shell;
|
|
||||||
# See below
|
|
||||||
lowdown = final.lowdown_3_0;
|
|
||||||
lowdown-unsandboxed = final.lowdown_3_0.override { enableDarwinSandbox = false; };
|
|
||||||
};
|
|
||||||
|
|
||||||
lix-clang-tidy = final.callPackage ./subprojects/lix-clang-tidy { };
|
|
||||||
|
|
||||||
nix-eval-jobs = final.callPackage ./subprojects/nix-eval-jobs {
|
|
||||||
stdenv = currentStdenv;
|
|
||||||
srcDir = ./subprojects/nix-eval-jobs;
|
|
||||||
};
|
|
||||||
|
|
||||||
# HACK: We need nix-prefetch-git for fetchCargoVendor for Rust stuff,
|
|
||||||
# so it can't use Lix, or we infrec:
|
|
||||||
# lix -> Rust stuff -> fetchCargoVendor -> nix-prefetch-git -> nix (lix)
|
|
||||||
# This will eventually become a problem upstream, but until then,
|
|
||||||
# apply some duct tape and pray.
|
|
||||||
nix-prefetch-git =
|
|
||||||
if (lib.functionArgs prev.nix-prefetch-git.override) ? "nix" then
|
|
||||||
prev.nix-prefetch-git.override { nix = prev.nix; }
|
|
||||||
else
|
|
||||||
prev.nix-prefetch-git;
|
|
||||||
|
|
||||||
# Export the patched version of boehmgc that Lix uses into the overlay
|
|
||||||
# for consumers of this flake.
|
|
||||||
boehmgc-nix = final.nix.passthru.boehmgc-nix;
|
|
||||||
# And same thing for our build-release-notes package.
|
|
||||||
build-release-notes = final.nix.passthru.build-release-notes;
|
|
||||||
|
|
||||||
# As soon as Nixpkgs updates to >= 3.0.0, change to lowdown_2_0!
|
|
||||||
# We don't change the default version in order to not change the hash
|
|
||||||
# of Nix/Lix from upstream Nixpkgs.
|
|
||||||
lowdown_3_0 =
|
|
||||||
if (lib.versions.major prev.lowdown.version == "3") then
|
|
||||||
prev.lowdown
|
|
||||||
else
|
|
||||||
prev.lowdown.overrideAttrs (
|
|
||||||
finalAttrs: _prevAttrs: {
|
|
||||||
version = "3.0.0";
|
|
||||||
|
|
||||||
src = final.fetchurl {
|
|
||||||
url = "https://kristaps.bsd.lv/lowdown/snapshots/lowdown-${finalAttrs.version}.tar.gz";
|
|
||||||
sha512 = "94e97234d598382c3c3dc27f9bfdb3a3a2fcf7dbb6a8df3c85ee09f27f792449034a41d49d9cfd3d8450d2de01b8562c20c3d120e65c81af4d7d6c9454119e93";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
capnproto = prev.capnproto.overrideAttrs (old: {
|
|
||||||
patches =
|
|
||||||
old.patches or [ ]
|
|
||||||
++ [
|
|
||||||
# backport of https://github.com/capnproto/capnproto/pull/1810
|
|
||||||
./misc/capnproto-promise-nodiscard.patch
|
|
||||||
]
|
|
||||||
++ lib.optionals (lib.versionOlder old.version "1.2.0") [
|
|
||||||
# backport of https://github.com/capnproto/capnproto/pull/2296
|
|
||||||
./misc/capnproto-monotonic-clocks-are-a-lie.patch
|
|
||||||
];
|
|
||||||
});
|
|
||||||
};
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
# for repl debugging
|
# for repl debugging
|
||||||
@@ -285,202 +115,13 @@
|
|||||||
# 'nix.perl-bindings' packages.
|
# 'nix.perl-bindings' packages.
|
||||||
overlays.default = overlayFor (p: p.clangStdenv);
|
overlays.default = overlayFor (p: p.clangStdenv);
|
||||||
|
|
||||||
hydraJobs = {
|
hydraJobs = ciArtifacts // {
|
||||||
# Aggregate job that is finished in Hydra _after_ all constituent jobs (here: grouped by system)
|
|
||||||
# succeed.
|
|
||||||
# This is used to run CD scripts once all builds are finished on Hydra.
|
|
||||||
release = forAllSystems (
|
|
||||||
system:
|
|
||||||
let
|
|
||||||
pkgs = nixpkgsFor.${system}.native;
|
|
||||||
in
|
|
||||||
pkgs.runCommand "release"
|
|
||||||
{
|
|
||||||
_hydraAggregate = true;
|
|
||||||
constituents = lib.filter (x: x != null) (
|
|
||||||
lib.mapAttrsToListRecursiveCond
|
|
||||||
(_: val: !(lib.isDerivation val || builtins.any (system': val ? ${system'}) systems))
|
|
||||||
(
|
|
||||||
path: drv:
|
|
||||||
if drv ? ${system} then
|
|
||||||
lib.concatStringsSep "." (path ++ [ system ])
|
|
||||||
else if drv.system or null == system then
|
|
||||||
lib.concatStringsSep "." path
|
|
||||||
else
|
|
||||||
null
|
|
||||||
)
|
|
||||||
(
|
|
||||||
removeAttrs self.hydraJobs [
|
|
||||||
"devShell"
|
|
||||||
"release"
|
|
||||||
"rl-next"
|
|
||||||
]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
''
|
|
||||||
touch $out
|
|
||||||
''
|
|
||||||
);
|
|
||||||
|
|
||||||
# Binary package for various platforms.
|
|
||||||
build = forAllSystems (system: self.packages.${system}.nix);
|
|
||||||
|
|
||||||
# Ensure support for lowdown < 3.0 doesn't regress for NixOS 25.11
|
|
||||||
build-lowdown_2_0 = lib.genAttrs [ "aarch64-linux" ] (
|
|
||||||
system:
|
|
||||||
assert lib.versionOlder nixpkgsFor.${system}.native.lowdown.version "3.0.0";
|
|
||||||
self.packages.${system}.nix.override {
|
|
||||||
lowdown = nixpkgsFor.${system}.native.lowdown;
|
|
||||||
lowdown-unsandboxed = nixpkgsFor.${system}.native.lowdown-unsandboxed;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
buildStatic = lib.genAttrs linux64BitSystems (system: self.packages.${system}.nix-static);
|
|
||||||
|
|
||||||
devShell = forAllSystems (system: {
|
devShell = forAllSystems (system: {
|
||||||
default = self.devShells.${system}.default;
|
default = self.devShells.${system}.default;
|
||||||
clang = self.devShells.${system}.native-clangStdenvPackages;
|
clang = self.devShells.${system}.native-clangStdenvPackages;
|
||||||
});
|
});
|
||||||
|
|
||||||
rl-next = forAllSystems (
|
inherit tests;
|
||||||
system:
|
|
||||||
let
|
|
||||||
rl-next-check =
|
|
||||||
name: dir:
|
|
||||||
let
|
|
||||||
pkgs = nixpkgsFor.${system}.native;
|
|
||||||
in
|
|
||||||
pkgs.buildPackages.runCommand "test-${name}-release-notes" { } ''
|
|
||||||
LANG=C.UTF-8 ${lib.getExe pkgs.build-release-notes} --change-authors ${./doc/manual/change-authors.yml} ${dir} >$out
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
{
|
|
||||||
user = rl-next-check "rl-next" ./doc/manual/rl-next;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
# Completion tests for the Nix REPL.
|
|
||||||
repl-completion = forAllSystems (
|
|
||||||
system: nixpkgsFor.${system}.native.callPackage ./tests/repl-completion.nix { }
|
|
||||||
);
|
|
||||||
|
|
||||||
# Perl bindings for various platforms.
|
|
||||||
perlBindings = forAllSystems (system: nixpkgsFor.${system}.native.nix.passthru.perl-bindings);
|
|
||||||
|
|
||||||
# nix-eval-jobs can be built against this Lix.
|
|
||||||
nix-eval-jobs = forAllSystems (system: nixpkgsFor.${system}.native.nix-eval-jobs);
|
|
||||||
|
|
||||||
# Binary tarball for various platforms, containing a Nix store
|
|
||||||
# with the closure of 'nix' package.
|
|
||||||
binaryTarball = forAllSystems (system: nixpkgsFor.${system}.native.nix.passthru.binaryTarball);
|
|
||||||
|
|
||||||
# docker image with Lix inside
|
|
||||||
dockerImage = lib.genAttrs linux64BitSystems (system: self.packages.${system}.dockerImage);
|
|
||||||
|
|
||||||
# API docs for Nix's unstable internal C++ interfaces.
|
|
||||||
internal-api-docs =
|
|
||||||
let
|
|
||||||
nixpkgs = nixpkgsFor.x86_64-linux.native;
|
|
||||||
inherit (nixpkgs) pkgs;
|
|
||||||
|
|
||||||
nix = pkgs.callPackage ./package.nix {
|
|
||||||
inherit versionSuffix officialRelease buildUnreleasedNotes;
|
|
||||||
inherit (pkgs) build-release-notes;
|
|
||||||
# Required since we don't support gcc stdenv
|
|
||||||
stdenv = pkgs.clangStdenv;
|
|
||||||
internalApiDocs = true;
|
|
||||||
busybox-sandbox-shell = pkgs.busybox-sandbox-shell;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
nix.overrideAttrs (prev: {
|
|
||||||
# This Hydra job is just for the internal API docs.
|
|
||||||
# We don't need the build artifacts here.
|
|
||||||
dontBuild = true;
|
|
||||||
doCheck = false;
|
|
||||||
doInstallCheck = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
# System tests.
|
|
||||||
tests =
|
|
||||||
import ./tests/nixos {
|
|
||||||
inherit
|
|
||||||
self
|
|
||||||
lib
|
|
||||||
nixpkgs
|
|
||||||
nixpkgsFor
|
|
||||||
;
|
|
||||||
}
|
|
||||||
// {
|
|
||||||
# the n-e-j test suite is unusably slow in darwin ci. disbled until anywho fixes this.
|
|
||||||
nix-eval-jobs = (lib.genAttrs nonDarwinSystems) (
|
|
||||||
system: self.packages.${system}.nix-eval-jobs.tests.nix-eval-jobs
|
|
||||||
);
|
|
||||||
|
|
||||||
# This is x86_64-linux only, just because we have significantly
|
|
||||||
# cheaper x86_64-linux compute in CI.
|
|
||||||
# It is clangStdenv because clang's sanitizers are nicer.
|
|
||||||
asanBuild = self.packages.x86_64-linux.nix-clangStdenv.override {
|
|
||||||
# Improve caching of non-code changes by not changing the
|
|
||||||
# derivation name every single time, since this will never be seen
|
|
||||||
# by users anyway.
|
|
||||||
versionSuffix = "";
|
|
||||||
sanitize = [
|
|
||||||
"address"
|
|
||||||
"undefined"
|
|
||||||
];
|
|
||||||
# it is very hard to make *every* CI build use this option such
|
|
||||||
# that we don't wind up building Lix twice, so we do it here where
|
|
||||||
# we are already doing so.
|
|
||||||
werror = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
# Although this might be nicer to do with pre-commit, that would
|
|
||||||
# require adding 12MB of nodejs to the dev shell, whereas building it
|
|
||||||
# in CI with Nix avoids that at a cost of slower feedback on rarely
|
|
||||||
# touched files.
|
|
||||||
jsSyntaxCheck =
|
|
||||||
let
|
|
||||||
nixpkgs = nixpkgsFor.x86_64-linux.native;
|
|
||||||
inherit (nixpkgs) pkgs;
|
|
||||||
docSources = lib.fileset.toSource {
|
|
||||||
root = ./doc;
|
|
||||||
fileset = lib.fileset.fileFilter (f: f.hasExt "js") ./doc;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
pkgs.runCommand "js-syntax-check" { } ''
|
|
||||||
find ${docSources} -type f -print -exec ${pkgs.nodejs-slim}/bin/node --check '{}' ';'
|
|
||||||
touch $out
|
|
||||||
'';
|
|
||||||
|
|
||||||
# clang-tidy run against the Lix codebase using the Lix clang-tidy plugin
|
|
||||||
clang-tidy = forAllSystems (
|
|
||||||
system:
|
|
||||||
let
|
|
||||||
pkgs = nixpkgsFor.${system}.native;
|
|
||||||
in
|
|
||||||
pkgs.callPackage ./package.nix {
|
|
||||||
# Required since we don't support gcc stdenv
|
|
||||||
stdenv = pkgs.clangStdenv;
|
|
||||||
versionSuffix = "";
|
|
||||||
lintInsteadOfBuild = true;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
# Make sure that nix-env still produces the exact same result
|
|
||||||
# on a particular version of Nixpkgs.
|
|
||||||
evalNixpkgs = nixpkgsFor.x86_64-linux.native.callPackage ./tests/nixpkgs/eval.nix {
|
|
||||||
inherit nixpkgs-regression;
|
|
||||||
};
|
|
||||||
|
|
||||||
nixpkgsLibTests = forAllSystems (
|
|
||||||
system:
|
|
||||||
nixpkgsFor.${system}.native.callPackage ./tests/nixpkgs/lib.nix {
|
|
||||||
inherit nixpkgs system;
|
|
||||||
inherit (self.packages.${system}) nix;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
pre-commit = forAvailableSystems (
|
pre-commit = forAvailableSystems (
|
||||||
system:
|
system:
|
||||||
@@ -540,45 +181,7 @@
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
packages = forAllSystems (
|
inherit packages;
|
||||||
system:
|
|
||||||
rec {
|
|
||||||
inherit (nixpkgsFor.${system}.native) nix;
|
|
||||||
default = nix;
|
|
||||||
|
|
||||||
inherit (nixpkgsFor.${system}.native) lix-clang-tidy nix-eval-jobs;
|
|
||||||
}
|
|
||||||
// (
|
|
||||||
lib.optionalAttrs (builtins.elem system linux64BitSystems) {
|
|
||||||
# python doesn't work in static builds as of 2025-06-27
|
|
||||||
nix-static = nixpkgsFor.${system}.static.nix.overrideAttrs (_: {
|
|
||||||
doCheck = false;
|
|
||||||
});
|
|
||||||
dockerImage =
|
|
||||||
let
|
|
||||||
pkgs = nixpkgsFor.${system}.native;
|
|
||||||
nix2container' = import nix2container { inherit pkgs; };
|
|
||||||
in
|
|
||||||
import ./docker.nix {
|
|
||||||
inherit pkgs;
|
|
||||||
nix2container = nix2container'.nix2container;
|
|
||||||
tag = pkgs.nix.version;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// builtins.listToAttrs (
|
|
||||||
map (crossSystem: {
|
|
||||||
name = "nix-${crossSystem}";
|
|
||||||
value = nixpkgsFor.${system}.cross.${crossSystem}.nix;
|
|
||||||
}) crossSystems
|
|
||||||
)
|
|
||||||
// builtins.listToAttrs (
|
|
||||||
map (stdenvName: {
|
|
||||||
name = "nix-${stdenvName}";
|
|
||||||
value = nixpkgsFor.${system}.stdenvs."${stdenvName}Packages".nix;
|
|
||||||
}) stdenvs
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
devShells =
|
devShells =
|
||||||
let
|
let
|
||||||
@@ -611,12 +214,30 @@
|
|||||||
in
|
in
|
||||||
(makeShells "native" nixpkgsFor.${system}.native)
|
(makeShells "native" nixpkgsFor.${system}.native)
|
||||||
// (makeShells "static" nixpkgsFor.${system}.static)
|
// (makeShells "static" nixpkgsFor.${system}.static)
|
||||||
// (forAllCrossSystems (
|
// (lib.listToAttrs (
|
||||||
crossSystem:
|
# Provide e.g., both '.#native-aarch64-linux` and `.#static-aarch64-linux`,
|
||||||
let
|
# for each cross-system.
|
||||||
pkgs = nixpkgsFor.${system}.cross.${crossSystem};
|
# "native" feels like a misnomer here since it's literally cross compiling,
|
||||||
in
|
# but at least it's consistent with the native/static dichotomy we've set up.
|
||||||
makeShell pkgs pkgs.clangStdenv
|
lib.concatMap (
|
||||||
|
crossSystem:
|
||||||
|
let
|
||||||
|
pkgs = nixpkgsFor.${system}.cross.${crossSystem};
|
||||||
|
inherit (pkgs) pkgsStatic;
|
||||||
|
native = makeShell pkgs pkgs.clangStdenv;
|
||||||
|
static = makeShell pkgsStatic pkgsStatic.clangStdenv;
|
||||||
|
in
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name = "native-${crossSystem}";
|
||||||
|
value = native;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "static-${crossSystem}";
|
||||||
|
value = static;
|
||||||
|
}
|
||||||
|
]
|
||||||
|
) crossSystems
|
||||||
))
|
))
|
||||||
// {
|
// {
|
||||||
default = self.devShells.${system}.native-clangStdenvPackages;
|
default = self.devShells.${system}.native-clangStdenvPackages;
|
||||||
|
|||||||
@@ -3,6 +3,14 @@
|
|||||||
# Take a look at ./doc/manual/src/contributing/hacking.md for a detailed
|
# Take a look at ./doc/manual/src/contributing/hacking.md for a detailed
|
||||||
# explanation on how to use this file!
|
# explanation on how to use this file!
|
||||||
|
|
||||||
|
# Pin the shell to bash (anything sufficiently POSIX-y would do)
|
||||||
|
# HACK: We use https://github.com/casey/just#positional-arguments
|
||||||
|
# and `"@$"` to forward arguments to the inner commands.
|
||||||
|
# The reason we require this is that `{{ OPTIONS }}` does not escape any values,
|
||||||
|
# and thus requires one additional level of escaping when running `just` commands with e.g. spaces in them.
|
||||||
|
# just provides no good solution to this problem, so we have to rely on its forwarding of arguments and shell semantics.
|
||||||
|
set shell := ["bash", "-uc"]
|
||||||
|
|
||||||
outdir := x"${out:-$PWD/outputs/out}"
|
outdir := x"${out:-$PWD/outputs/out}"
|
||||||
builddir := "build"
|
builddir := "build"
|
||||||
|
|
||||||
@@ -10,28 +18,33 @@ builddir := "build"
|
|||||||
list:
|
list:
|
||||||
just --list
|
just --list
|
||||||
|
|
||||||
# Clean build artifacts
|
# Clean build artifacts and outputs.
|
||||||
clean:
|
clean:
|
||||||
rm -rf {{ builddir }}
|
rm -rf {{ quote(builddir) }}/* {{ quote(builddir) }}/.* {{ quote(outdir) }}/* {{ quote(outdir) }}/.*
|
||||||
|
cargo clean
|
||||||
|
|
||||||
# Prepare meson for building.
|
# Prepare meson for building.
|
||||||
|
[positional-arguments]
|
||||||
setup *OPTIONS:
|
setup *OPTIONS:
|
||||||
meson setup {{ builddir }} --reconfigure --prefix="{{outdir}}" $mesonFlags {{ OPTIONS }}
|
meson setup {{ builddir }} --reconfigure --prefix="{{outdir}}" $mesonFlags "$@"
|
||||||
|
|
||||||
# Build lix with extra options
|
# Build lix with extra options
|
||||||
|
[positional-arguments]
|
||||||
build *OPTIONS:
|
build *OPTIONS:
|
||||||
meson compile -C {{ builddir }} {{ OPTIONS }}
|
meson compile -C {{ builddir }} "$@"
|
||||||
|
|
||||||
alias compile := build
|
alias compile := build
|
||||||
|
|
||||||
# `meson install` will automatically build anything that needs to be built to install it.
|
# `meson install` will automatically build anything that needs to be built to install it.
|
||||||
[doc("Install Lix for local development")]
|
[doc("Install Lix for local development")]
|
||||||
|
[positional-arguments]
|
||||||
install *OPTIONS:
|
install *OPTIONS:
|
||||||
meson install --quiet -C {{ builddir }} {{ OPTIONS }}
|
meson install --quiet -C {{ builddir }} "$@"
|
||||||
|
|
||||||
# Run all tests tests (installs first).
|
# Run all tests tests (installs first).
|
||||||
|
[positional-arguments]
|
||||||
test *OPTIONS: (install)
|
test *OPTIONS: (install)
|
||||||
meson test -C {{ builddir }} --print-errorlogs --max-lines 10000 {{ OPTIONS }}
|
meson test -C {{ builddir }} --print-errorlogs --max-lines 10000 "$@"
|
||||||
|
|
||||||
# Run unit tests only
|
# Run unit tests only
|
||||||
test-unit *OPTIONS: (test "--suite" "check")
|
test-unit *OPTIONS: (test "--suite" "check")
|
||||||
@@ -40,10 +53,15 @@ test-unit *OPTIONS: (test "--suite" "check")
|
|||||||
test-integration *OPTIONS: (test "--suite" "installcheck" OPTIONS)
|
test-integration *OPTIONS: (test "--suite" "installcheck" OPTIONS)
|
||||||
|
|
||||||
# Run functional2 tests using pytest directly, allowing for additional arguments to be passed to pytest e.g. for more granular test selection
|
# Run functional2 tests using pytest directly, allowing for additional arguments to be passed to pytest e.g. for more granular test selection
|
||||||
|
[positional-arguments]
|
||||||
test-functional2 *OPTIONS:
|
test-functional2 *OPTIONS:
|
||||||
cd tests/functional2 && python -m pytest -v {{ OPTIONS }}
|
cd tests/functional2 && python -m pytest -v "$@"
|
||||||
|
|
||||||
alias clang-tidy := lint
|
# special target for cargo because meson cannot be convinced to not mangle cargo test output,
|
||||||
|
# and getting properly colored test output any other way also doesn't look all that possible.
|
||||||
|
[positional-arguments]
|
||||||
|
test-rs *OPTIONS:
|
||||||
|
meson test -C {{ builddir }} --interactive lix-rs-tests "$@"
|
||||||
|
|
||||||
# Lint with `clang-tidy`
|
# Lint with `clang-tidy`
|
||||||
lint:
|
lint:
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class ExtraFeature:
|
|||||||
{
|
{
|
||||||
"\n ".join(
|
"\n ".join(
|
||||||
[
|
[
|
||||||
f"- {event.date}, {event.release}: {event.message} [{", ".join([f'[CL {cl}](https://git.lix.systems/c/lix/+/{cl})' for cl in event.cls])}]"
|
f"- {event.date}, {event.release}: {event.message} [{", ".join([f'[CL {cl}](https://gerrit.lix.systems/c/lix/+/{cl})' for cl in event.cls])}]"
|
||||||
for event in self.timeline
|
for event in self.timeline
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
+32
-112
@@ -5,6 +5,7 @@
|
|||||||
#include "lix/libutil/file-descriptor.hh"
|
#include "lix/libutil/file-descriptor.hh"
|
||||||
#include "lix/libutil/logging-rpc.hh"
|
#include "lix/libutil/logging-rpc.hh"
|
||||||
#include "lix/libutil/logging.hh"
|
#include "lix/libutil/logging.hh"
|
||||||
|
#include "lix/libutil/result.hh"
|
||||||
#include "lix/libutil/rpc.hh"
|
#include "lix/libutil/rpc.hh"
|
||||||
#include "lix/libutil/types-rpc.hh" // IWYU pragma: keep
|
#include "lix/libutil/types-rpc.hh" // IWYU pragma: keep
|
||||||
#include "lix/libutil/types.hh"
|
#include "lix/libutil/types.hh"
|
||||||
@@ -50,7 +51,7 @@ struct Instance final : rpc::build_remote::HookInstance::Server
|
|||||||
|
|
||||||
kj::Promise<void> init(InitContext context) override;
|
kj::Promise<void> init(InitContext context) override;
|
||||||
|
|
||||||
kj::Promise<void> buildImpl(BuildContext context);
|
kj::Promise<Result<void>> buildImpl(BuildContext context);
|
||||||
kj::Promise<void> build(BuildContext context) override;
|
kj::Promise<void> build(BuildContext context) override;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -96,7 +97,7 @@ static std::tuple<bool, Machine *, AutoCloseFD> selectBestMachine(
|
|||||||
uint64_t bestLoad = 0;
|
uint64_t bestLoad = 0;
|
||||||
|
|
||||||
for (auto & m : machines) {
|
for (auto & m : machines) {
|
||||||
debug("considering building on remote machine '%s'", m.storeUri);
|
debug("considering building on remote machine '%s'", m.name);
|
||||||
|
|
||||||
if (m.enabled && m.systemSupported(neededSystem) && m.allSupported(requiredFeatures)
|
if (m.enabled && m.systemSupported(neededSystem) && m.allSupported(requiredFeatures)
|
||||||
&& m.mandatoryMet(requiredFeatures))
|
&& m.mandatoryMet(requiredFeatures))
|
||||||
@@ -184,67 +185,6 @@ struct BuilderConnection
|
|||||||
AutoCloseFD slotLock;
|
AutoCloseFD slotLock;
|
||||||
std::shared_ptr<Store> sshStore;
|
std::shared_ptr<Store> sshStore;
|
||||||
std::string storeUri;
|
std::string storeUri;
|
||||||
Pipe logPipe;
|
|
||||||
|
|
||||||
// start the thread that reads ssh stderr and turns it into log items.
|
|
||||||
// this future *must* outlive sshStore, otherwise it will never finish
|
|
||||||
kj::Promise<Result<void>> startLogThread(std::string buildDescription, std::string drvPath)
|
|
||||||
try {
|
|
||||||
if (!logPipe.readSide) {
|
|
||||||
co_return result::success();
|
|
||||||
}
|
|
||||||
|
|
||||||
logPipe.writeSide.close();
|
|
||||||
|
|
||||||
// NOTE this is very similar to handleBuilderOutput in DerivationGoal, but unlike
|
|
||||||
// the derivation goal we do not need to handle EIO from a pty here. we also have
|
|
||||||
// no timeouts or limits to keep track of, which makes deduplication less useful.
|
|
||||||
auto act = logger->startActivity(
|
|
||||||
lvlInfo, actBuild, buildDescription, Logger::Fields{drvPath, storeUri, 1, 1}
|
|
||||||
);
|
|
||||||
|
|
||||||
std::map<ActivityId, Activity> activities;
|
|
||||||
|
|
||||||
auto reader = AIO().lowLevelProvider.wrapInputFd(logPipe.readSide.get());
|
|
||||||
|
|
||||||
LogLineSplitter splitter;
|
|
||||||
|
|
||||||
auto flushLine = [&](const std::string & line) {
|
|
||||||
if (const auto state =
|
|
||||||
handleJSONLogMessage(line, act, activities, "the derivation builder"))
|
|
||||||
{
|
|
||||||
return *state;
|
|
||||||
} else {
|
|
||||||
return act.result(resBuildLogLine, line);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
auto buf = kj::heapArray<char>(4096);
|
|
||||||
while (true) {
|
|
||||||
const auto got = co_await reader->tryRead(buf.begin(), 1, buf.size());
|
|
||||||
if (got == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string_view data{buf.begin(), got};
|
|
||||||
while (!data.empty()) {
|
|
||||||
if (auto line = splitter.feed(data)) {
|
|
||||||
if (flushLine(*line) == Logger::BufferState::NeedsFlush) {
|
|
||||||
TRY_AWAIT(act.getLogger().flush());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto line = splitter.finish(); !line.empty()) {
|
|
||||||
(void) flushLine(line);
|
|
||||||
TRY_AWAIT(act.getLogger().flush());
|
|
||||||
}
|
|
||||||
|
|
||||||
co_return result::success();
|
|
||||||
} catch (...) {
|
|
||||||
co_return result::current_exception();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AcceptedBuild final : rpc::build_remote::HookInstance::AcceptedBuild::Server
|
struct AcceptedBuild final : rpc::build_remote::HookInstance::AcceptedBuild::Server
|
||||||
@@ -261,7 +201,7 @@ struct AcceptedBuild final : rpc::build_remote::HookInstance::AcceptedBuild::Ser
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
kj::Promise<void> runImpl(RunContext context);
|
kj::Promise<Result<void>> runImpl(RunContext context);
|
||||||
kj::Promise<void> run(RunContext context) override;
|
kj::Promise<void> run(RunContext context) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -326,26 +266,15 @@ try {
|
|||||||
lock.reset();
|
lock.reset();
|
||||||
|
|
||||||
std::shared_ptr<Store> sshStore;
|
std::shared_ptr<Store> sshStore;
|
||||||
Pipe logPipe;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto act = logger->startActivity(
|
auto act =
|
||||||
lvlTalkative, actUnknown, fmt("connecting to '%s'", bestMachine->storeUri)
|
logger->startActivity(lvlTalkative, actUnknown, fmt("connecting to '%s'", bestMachine->name));
|
||||||
);
|
|
||||||
|
|
||||||
std::tie(sshStore, logPipe) = TRY_AWAIT(bestMachine->openStore());
|
sshStore = TRY_AWAIT(bestMachine->openStore());
|
||||||
TRY_AWAIT(sshStore->connect());
|
co_return BuilderConnection{std::move(bestSlotLock), sshStore, bestMachine->storeUri};
|
||||||
co_return BuilderConnection{
|
|
||||||
std::move(bestSlotLock), sshStore, bestMachine->storeUri, std::move(logPipe)
|
|
||||||
};
|
|
||||||
} catch (std::exception & e) { // NOLINT(lix-foreign-exceptions)
|
} catch (std::exception & e) { // NOLINT(lix-foreign-exceptions)
|
||||||
std::string msg = logPipe.readSide ? chomp(drainFD(logPipe.readSide.get(), false)) : "";
|
printError("cannot build on '%s': %s", bestMachine->name, e.what());
|
||||||
printError(
|
|
||||||
"cannot build on '%s': %s%s",
|
|
||||||
bestMachine->storeUri,
|
|
||||||
e.what(),
|
|
||||||
msg.empty() ? "" : ": " + msg
|
|
||||||
);
|
|
||||||
bestMachine->enabled = false;
|
bestMachine->enabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -366,7 +295,7 @@ static int main_build_remote(AsyncIoRoot & aio, std::string programName, Strings
|
|||||||
if (argv.size() != 1)
|
if (argv.size() != 1)
|
||||||
throw UsageError("called without required arguments");
|
throw UsageError("called without required arguments");
|
||||||
|
|
||||||
verbosity = (Verbosity) std::stoll(argv.front());
|
setVerbosity((Verbosity) std::stoll(argv.front()));
|
||||||
|
|
||||||
auto conn = aio.kj.lowLevelProvider->wrapUnixSocketFd(1);
|
auto conn = aio.kj.lowLevelProvider->wrapUnixSocketFd(1);
|
||||||
capnp::TwoPartyServer srv(kj::heap<Instance>());
|
capnp::TwoPartyServer srv(kj::heap<Instance>());
|
||||||
@@ -395,16 +324,14 @@ kj::Promise<void> Instance::init(InitContext context)
|
|||||||
initPlugins();
|
initPlugins();
|
||||||
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
|
|
||||||
context.getResults().initResult().setGood();
|
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
RPC_FILL(context.getResults(), initResult, std::current_exception());
|
rpc::rethrow_as_rpc_error();
|
||||||
}
|
}
|
||||||
|
|
||||||
return kj::READY_NOW;
|
return kj::READY_NOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
kj::Promise<void> Instance::buildImpl(BuildContext context)
|
kj::Promise<Result<void>> Instance::buildImpl(BuildContext context)
|
||||||
try {
|
try {
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
throw Error("build hook not fully initialized");
|
throw Error("build hook not fully initialized");
|
||||||
@@ -431,8 +358,8 @@ try {
|
|||||||
debug("got %d remote builders", machines.size());
|
debug("got %d remote builders", machines.size());
|
||||||
|
|
||||||
if (machines.empty()) {
|
if (machines.empty()) {
|
||||||
context.getResults().initResult().initGood().setDeclinePermanently();
|
context.getResults().initResult().setDeclinePermanently();
|
||||||
co_return;
|
co_return result::success();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto amWilling = context.getParams().getAmWilling();
|
auto amWilling = context.getParams().getAmWilling();
|
||||||
@@ -448,21 +375,23 @@ try {
|
|||||||
if (auto immediateResponse = std::get_if<BuildRejected>(&result)) {
|
if (auto immediateResponse = std::get_if<BuildRejected>(&result)) {
|
||||||
switch (*immediateResponse) {
|
switch (*immediateResponse) {
|
||||||
case BuildRejected::Temporarily:
|
case BuildRejected::Temporarily:
|
||||||
context.getResults().initResult().initGood().setPostpone();
|
context.getResults().initResult().setPostpone();
|
||||||
co_return;
|
co_return result::success();
|
||||||
case BuildRejected::Permanently:
|
case BuildRejected::Permanently:
|
||||||
context.getResults().initResult().initGood().setDecline();
|
context.getResults().initResult().setDecline();
|
||||||
co_return;
|
co_return result::success();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto builder = std::get_if<BuilderConnection>(&result);
|
auto builder = std::get_if<BuilderConnection>(&result);
|
||||||
assert(builder);
|
assert(builder);
|
||||||
|
|
||||||
auto ac = context.getResults().initResult().initGood().initAccept();
|
auto ac = context.getResults().initResult().initAccept();
|
||||||
ac.setMachine(kj::heap<AcceptedBuild>(store, drvPath, std::move(*builder)));
|
ac.setMachine(kj::heap<AcceptedBuild>(store, drvPath, std::move(*builder)));
|
||||||
|
|
||||||
|
co_return result::success();
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
RPC_FILL(context.getResults(), initResult, std::current_exception());
|
co_return result::current_exception();
|
||||||
}
|
}
|
||||||
|
|
||||||
kj::Promise<void> Instance::build(BuildContext context)
|
kj::Promise<void> Instance::build(BuildContext context)
|
||||||
@@ -471,11 +400,12 @@ try {
|
|||||||
throw Error("build hooks can only accept a single job");
|
throw Error("build hooks can only accept a single job");
|
||||||
}
|
}
|
||||||
used = true; // lock out other rpc calls during processing
|
used = true; // lock out other rpc calls during processing
|
||||||
co_await buildImpl(context);
|
auto result = co_await buildImpl(context);
|
||||||
TRY_AWAIT(logger->flush());
|
TRY_AWAIT(logger->flush());
|
||||||
used = context.getResults().getResult().getGood().isAccept();
|
used = result.has_value() && context.getResults().getResult().isAccept();
|
||||||
|
result.value();
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
RPC_FILL(context.getResults(), getResult, std::current_exception());
|
rpc::rethrow_as_rpc_error();
|
||||||
}
|
}
|
||||||
|
|
||||||
kj::Promise<void> AcceptedBuild::run(RunContext context)
|
kj::Promise<void> AcceptedBuild::run(RunContext context)
|
||||||
@@ -493,23 +423,17 @@ kj::Promise<void> AcceptedBuild::run(RunContext context)
|
|||||||
throw Error("build hooks builds are single-use items");
|
throw Error("build hooks builds are single-use items");
|
||||||
}
|
}
|
||||||
used = true;
|
used = true;
|
||||||
co_await runImpl(context);
|
auto result = co_await runImpl(context);
|
||||||
TRY_AWAIT(logger->flush());
|
TRY_AWAIT(logger->flush());
|
||||||
|
result.value();
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
RPC_FILL(context.getResults(), getResult, std::current_exception());
|
rpc::rethrow_as_rpc_error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kj::Promise<void> AcceptedBuild::runImpl(RunContext context)
|
kj::Promise<Result<void>> AcceptedBuild::runImpl(RunContext context)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
auto logHandler = builder.startLogThread(
|
|
||||||
fmt("%s on '%s'",
|
|
||||||
rpc::to<std::string_view>(context.getParams().getDescription()),
|
|
||||||
builder.storeUri),
|
|
||||||
store->printStorePath(drvPath)
|
|
||||||
);
|
|
||||||
|
|
||||||
auto & sshStore = builder.sshStore;
|
auto & sshStore = builder.sshStore;
|
||||||
auto & storeUri = builder.storeUri;
|
auto & storeUri = builder.storeUri;
|
||||||
|
|
||||||
@@ -619,13 +543,9 @@ kj::Promise<void> AcceptedBuild::runImpl(RunContext context)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// drop store connection, let log handler process any remaining input
|
co_return result::success();
|
||||||
builder.sshStore = nullptr;
|
|
||||||
TRY_AWAIT(logHandler);
|
|
||||||
|
|
||||||
context.getResults().initResult().setGood();
|
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
RPC_FILL(context.getResults(), initResult, std::current_exception());
|
co_return result::current_exception();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ static int main_builtin_builder(AsyncIoRoot & aio, std::string programName, Stri
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (auto val = string2Int<int>(getArg("verbosity"))) {
|
if (auto val = string2Int<int>(getArg("verbosity"))) {
|
||||||
verbosity = verbosityFromIntClamped(*val);
|
setVerbosity(verbosityFromIntClamped(*val));
|
||||||
} else {
|
} else {
|
||||||
throw Error("expected a verbosity argument");
|
throw Error("expected a verbosity argument");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ static int main_nix_copy_closure(AsyncIoRoot & aio, std::string programName, Str
|
|||||||
for (auto & path : storePaths)
|
for (auto & path : storePaths)
|
||||||
storePaths2.insert(from->followLinksToStorePath(path));
|
storePaths2.insert(from->followLinksToStorePath(path));
|
||||||
|
|
||||||
aio.blockOn(copyClosure(*from, *to, storePaths2, NoRepair, NoCheckSigs, useSubstitutes));
|
aio.blockOn(copyClosure(*from, *to, storePaths2, NoRepair, NoCheckSigs, useSubstitutes, includeOutputs));
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -948,7 +948,7 @@ opServe(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
|
|||||||
auto getBuildSettings = [&]() {
|
auto getBuildSettings = [&]() {
|
||||||
// FIXME: changing options here doesn't work if we're
|
// FIXME: changing options here doesn't work if we're
|
||||||
// building through the daemon.
|
// building through the daemon.
|
||||||
verbosity = lvlError;
|
setVerbosity(lvlError);
|
||||||
settings.keepLog.override(false);
|
settings.keepLog.override(false);
|
||||||
settings.useSubstitutes.override(false);
|
settings.useSubstitutes.override(false);
|
||||||
settings.maxSilentTime.override(readNum<unsigned>(in));
|
settings.maxSilentTime.override(readNum<unsigned>(in));
|
||||||
|
|||||||
+60
-215
@@ -1,251 +1,101 @@
|
|||||||
#include "lix/libutil/c-calls.hh"
|
#include "libutil/fmt.hh"
|
||||||
|
#include "libutil/terminal.hh"
|
||||||
#include "lix/libutil/error.hh"
|
#include "lix/libutil/error.hh"
|
||||||
#include "lix/libutil/file-system.hh"
|
#include "lix/libutil/file-system.hh"
|
||||||
#include "lix/libutil/logging.hh"
|
#include "lix/libutil/logging.hh"
|
||||||
#include <csignal>
|
#include "lix/lix-rs/main.gen.hh"
|
||||||
#include <cstdio>
|
#include "lix/lix-rs/utils.hh"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
|
||||||
#include <cerrno>
|
|
||||||
|
|
||||||
// editline < 1.15.2 don't wrap their API for C++ usage
|
|
||||||
// (added in https://github.com/troglobit/editline/commit/91398ceb3427b730995357e9d120539fb9bb7461).
|
|
||||||
// This results in linker errors due to to name-mangling of editline C symbols.
|
|
||||||
// For compatibility with these versions, we wrap the API here
|
|
||||||
// (wrapping multiple times on newer versions is no problem).
|
|
||||||
extern "C" {
|
|
||||||
#include <editline.h>
|
|
||||||
}
|
|
||||||
|
|
||||||
#include "lix/libutil/finally.hh"
|
|
||||||
#include "lix/libutil/strings.hh"
|
|
||||||
#include "lix/libcmd/repl-interacter.hh"
|
#include "lix/libcmd/repl-interacter.hh"
|
||||||
|
|
||||||
|
namespace rust {
|
||||||
|
Vec<String> Impl<lix::repl::CxxCompleter, lix::repl::ReplCompleter>::complete(
|
||||||
|
Ref<lix::repl::CxxCompleter> self, Ref<Str> input
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
auto s = to_std_string(input);
|
||||||
|
|
||||||
|
auto result = std::vec::Vec<std::string::String>::new_();
|
||||||
|
for (auto & possible : self.cpp().completePrefix(s)) {
|
||||||
|
result.push(rust::to_string(possible));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (...) {
|
||||||
|
// the completer should have logged anything interesting.
|
||||||
|
return std::vec::Vec<std::string::String>::new_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
namespace {
|
ReadlineLikeInteracter::ReadlineLikeInteracter(std::string historyFile) : historyFile(historyFile) {}
|
||||||
// Used to communicate to NixRepl::getLine whether a signal occurred in ::readline.
|
|
||||||
volatile sig_atomic_t g_signal_received = 0;
|
|
||||||
|
|
||||||
void sigintHandler(int signo)
|
void ReadlineLikeInteracter::init(detail::ReplCompleterMixin * repl)
|
||||||
{
|
{
|
||||||
g_signal_received = signo;
|
|
||||||
}
|
|
||||||
|
|
||||||
static detail::ReplCompleterMixin * curRepl; // ugly
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return a null-terminated list of completions as expected by `el_print_columns`
|
|
||||||
*/
|
|
||||||
char ** copyCompletions(const StringSet& possible)
|
|
||||||
{
|
|
||||||
int ac = 0;
|
|
||||||
char ** vp = nullptr;
|
|
||||||
|
|
||||||
auto check = [&](auto * p) {
|
|
||||||
if (!p) {
|
|
||||||
if (vp) {
|
|
||||||
while (--ac >= 0)
|
|
||||||
free(vp[ac]);
|
|
||||||
free(static_cast<void *>(vp));
|
|
||||||
}
|
|
||||||
throw Error("allocation failure");
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
};
|
|
||||||
vp = check(static_cast<char **>(malloc(possible.size() * sizeof(char *))));
|
|
||||||
for (auto & p : possible)
|
|
||||||
vp[ac++] = check(strdup(p.c_str()));
|
|
||||||
|
|
||||||
return vp;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instead of using the readline-provided prefix, do our own tokenization
|
|
||||||
// to avoid the default behavior of treating dots/quotes as word boundaries.
|
|
||||||
// See the definition of SEPS for what it treats as a boundary:
|
|
||||||
// https://github.com/troglobit/editline/blob/caf4b3c0ce3b0785791198b11de6f3134e9f05d8/src/editline.c
|
|
||||||
std::string getLastTokenBeforeCursor()
|
|
||||||
{
|
|
||||||
std::string_view line{rl_line_buffer, static_cast<size_t>(rl_point)};
|
|
||||||
|
|
||||||
auto tokens = tokenizeString<std::vector<std::string>>(
|
|
||||||
line,
|
|
||||||
// Same as editline's SEPS, except for double and single quotes:
|
|
||||||
"#$&()*:;<=>?[\\]^`{|}~\n\t "
|
|
||||||
);
|
|
||||||
|
|
||||||
if (tokens.empty()) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return tokens.back();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sometimes inserting text or listing possible completions has a side effect
|
|
||||||
// of hiding the text after the cursor (even though it remains in the buffer).
|
|
||||||
// This helper just refreshes the display while keeping the cursor in place.
|
|
||||||
//
|
|
||||||
// Inserting text also sometimes moves the whole buffer down one line, usually
|
|
||||||
// if the cursor is inside a quoted attr name. I'm not sure why (vs unquoted)
|
|
||||||
// but it still seems to work pretty well and is just a visual artifact.
|
|
||||||
el_status_t redisplay()
|
|
||||||
{
|
|
||||||
int cursorPos = rl_point;
|
|
||||||
rl_refresh_line(0, 0);
|
|
||||||
rl_point = cursorPos;
|
|
||||||
return (rl_point == rl_end) ? CSstay : CSmove;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
static el_status_t doCompletion() {
|
|
||||||
auto s = getLastTokenBeforeCursor();
|
|
||||||
auto possible = curRepl->completePrefix(s);
|
|
||||||
|
|
||||||
if (possible.empty()) {
|
|
||||||
return el_ring_bell();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (possible.size() == 1) {
|
|
||||||
const auto completion = *possible.cbegin();
|
|
||||||
if (completion.size() > s.size()) {
|
|
||||||
rl_insert_text(requireCString(completion.substr(s.size())));
|
|
||||||
return redisplay();
|
|
||||||
}
|
|
||||||
|
|
||||||
return el_ring_bell();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto checkAllHaveSameAt = [&](size_t pos) {
|
|
||||||
auto & first = *possible.begin();
|
|
||||||
for (auto & p : possible) {
|
|
||||||
if (p.size() <= pos || p[pos] != first[pos]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
size_t start = s.size();
|
|
||||||
size_t len = 0;
|
|
||||||
while (checkAllHaveSameAt(start + len)) {
|
|
||||||
++len;
|
|
||||||
}
|
|
||||||
if (len > 0) {
|
|
||||||
auto commonPrefix = possible.begin()->substr(start, len);
|
|
||||||
rl_insert_text(requireCString(commonPrefix));
|
|
||||||
el_ring_bell();
|
|
||||||
return redisplay();
|
|
||||||
}
|
|
||||||
|
|
||||||
char** columns = copyCompletions(possible);
|
|
||||||
el_print_columns(possible.size(), columns);
|
|
||||||
return redisplay();
|
|
||||||
}
|
|
||||||
|
|
||||||
ReadlineLikeInteracter::Guard ReadlineLikeInteracter::init(detail::ReplCompleterMixin * repl)
|
|
||||||
{
|
|
||||||
// Allow nix-repl specific settings in .inputrc
|
|
||||||
rl_readline_name = "nix-repl";
|
|
||||||
try {
|
try {
|
||||||
createDirs(dirOf(historyFile));
|
createDirs(dirOf(historyFile));
|
||||||
} catch (SysError & e) {
|
} catch (SysError & e) {
|
||||||
logWarning(e.info());
|
logWarning(e.info());
|
||||||
}
|
}
|
||||||
el_hist_size = 1000;
|
|
||||||
read_history(requireCString(historyFile));
|
auto rl = repl::Rustyline::new_(rust::to_string(historyFile).as_str(), *repl);
|
||||||
auto oldRepl = curRepl;
|
match_result(
|
||||||
curRepl = repl;
|
std::move(rl),
|
||||||
Guard restoreRepl([oldRepl] { curRepl = oldRepl; });
|
[&](repl::Rustyline ok) { this->rl = std::make_unique<repl::Rustyline>(std::move(ok)); },
|
||||||
// editline does its own escaping of completions, so we rebind tab
|
[&](rust::Box<rust::Dyn<rust::std::error::Error>> err) {
|
||||||
// to our own completion function to skip that and do nix escaping
|
throw Error("%s", Uncolored(to_std_string(err.to_string())));
|
||||||
// instead of shell escaping.
|
}
|
||||||
el_bind_key(CTL('I'), doCompletion);
|
);
|
||||||
return restoreRepl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr const char * promptForType(ReplPromptType promptType)
|
static rust::Ref<rust::Str> promptForType(ReplPromptType promptType)
|
||||||
{
|
{
|
||||||
switch (promptType) {
|
switch (promptType) {
|
||||||
case ReplPromptType::ReplPrompt:
|
case ReplPromptType::ReplPrompt:
|
||||||
return "nix-repl> ";
|
return "nix-repl> "_rs;
|
||||||
case ReplPromptType::ContinuationPrompt:
|
case ReplPromptType::ContinuationPrompt:
|
||||||
return " ";
|
return " "_rs;
|
||||||
}
|
}
|
||||||
assert(false);
|
assert(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ReadlineLikeInteracter::getLine(std::string & input, ReplPromptType promptType)
|
bool ReadlineLikeInteracter::getLine(std::string & input, ReplPromptType promptType)
|
||||||
{
|
{
|
||||||
struct sigaction act, old;
|
auto s = rl->ask(promptForType(promptType));
|
||||||
sigset_t savedSignalMask, set;
|
|
||||||
|
|
||||||
auto setupSignals = [&]() {
|
// rustyline temporarily sets a SIGWINCH handler
|
||||||
act.sa_handler = sigintHandler;
|
KJ_DEFER(invalidateWindowSize());
|
||||||
sigfillset(&act.sa_mask);
|
|
||||||
act.sa_flags = 0;
|
|
||||||
if (sigaction(SIGINT, &act, &old))
|
|
||||||
throw SysError("installing handler for SIGINT");
|
|
||||||
|
|
||||||
sigemptyset(&set);
|
return match_result(
|
||||||
sigaddset(&set, SIGINT);
|
std::move(s),
|
||||||
if (sigprocmask(SIG_UNBLOCK, &set, &savedSignalMask))
|
[&](rust::String ok) {
|
||||||
throw SysError("unblocking SIGINT");
|
input += to_std_string(ok);
|
||||||
};
|
input += '\n';
|
||||||
auto restoreSignals = [&]() {
|
return true;
|
||||||
if (sigprocmask(SIG_SETMASK, &savedSignalMask, nullptr))
|
},
|
||||||
throw SysError("restoring signals");
|
[&](rust::rustyline::error::ReadlineError err) {
|
||||||
|
if (err.matches_Interrupted()) {
|
||||||
|
input.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (sigaction(SIGINT, &old, 0))
|
if (err.matches_Eof()) {
|
||||||
throw SysError("restoring handler for SIGINT");
|
return false;
|
||||||
};
|
}
|
||||||
|
|
||||||
setupSignals();
|
throw Error("%s", Uncolored(to_std_string(err.into().to_string())));
|
||||||
char * s = readline(promptForType(promptType)); // NOLINT(lix-unsafe-c-calls)
|
}
|
||||||
Finally doFree([&]() { free(s); });
|
);
|
||||||
restoreSignals();
|
|
||||||
|
|
||||||
if (g_signal_received) {
|
|
||||||
g_signal_received = 0;
|
|
||||||
input.clear();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!s)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
this->writeHistory();
|
|
||||||
input += s;
|
|
||||||
input += '\n';
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReadlineLikeInteracter::writeHistory()
|
void ReadlineLikeInteracter::writeHistory()
|
||||||
{
|
{
|
||||||
int ret = write_history(requireCString(historyFile));
|
if (rl) {
|
||||||
int writeHistErr = errno;
|
rl->write_history();
|
||||||
|
|
||||||
if (ret == 0) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the open fails, editline returns EOF. If the close fails, editline
|
|
||||||
// forwards the return value of fclose(), which is EOF on error.
|
|
||||||
// readline however, returns the errno.
|
|
||||||
// So if we didn't get exactly EOF, then consider the return value the error
|
|
||||||
// code; otherwise use the errno we saved above.
|
|
||||||
// https://github.com/troglobit/editline/issues/66
|
|
||||||
if (ret != EOF) {
|
|
||||||
writeHistErr = ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// In any of these cases, we should explicitly ignore the error, but log
|
|
||||||
// them so the user isn't confused why their history is getting eaten.
|
|
||||||
|
|
||||||
std::string_view const errMsg(std::strerror(writeHistErr));
|
|
||||||
printTaggedWarning("ignoring error writing repl history to %s: %s", this->historyFile, errMsg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ReadlineLikeInteracter::~ReadlineLikeInteracter()
|
ReadlineLikeInteracter::~ReadlineLikeInteracter()
|
||||||
@@ -253,11 +103,6 @@ ReadlineLikeInteracter::~ReadlineLikeInteracter()
|
|||||||
this->writeHistory();
|
this->writeHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
AutomationInteracter::Guard AutomationInteracter::init(detail::ReplCompleterMixin *)
|
|
||||||
{
|
|
||||||
return Guard([] {});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ASCII ENQ character
|
// ASCII ENQ character
|
||||||
constexpr const char * automationPrompt = "\x05";
|
constexpr const char * automationPrompt = "\x05";
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
/// @file
|
/// @file
|
||||||
|
|
||||||
#include "lix/libutil/finally.hh"
|
|
||||||
#include "lix/libutil/types.hh"
|
#include "lix/libutil/types.hh"
|
||||||
#include <functional>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
namespace rust::lix::repl {
|
||||||
|
struct Rustyline;
|
||||||
|
}
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
@@ -24,9 +27,7 @@ enum class ReplPromptType {
|
|||||||
class ReplInteracter
|
class ReplInteracter
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using Guard = Finally<std::function<void()>>;
|
virtual void init(detail::ReplCompleterMixin * repl) {}
|
||||||
|
|
||||||
virtual Guard init(detail::ReplCompleterMixin * repl) = 0;
|
|
||||||
/** Returns a boolean of whether the interacter got EOF */
|
/** Returns a boolean of whether the interacter got EOF */
|
||||||
virtual bool getLine(std::string & input, ReplPromptType promptType) = 0;
|
virtual bool getLine(std::string & input, ReplPromptType promptType) = 0;
|
||||||
virtual ~ReplInteracter(){};
|
virtual ~ReplInteracter(){};
|
||||||
@@ -35,12 +36,12 @@ public:
|
|||||||
class ReadlineLikeInteracter final : public ReplInteracter
|
class ReadlineLikeInteracter final : public ReplInteracter
|
||||||
{
|
{
|
||||||
std::string historyFile;
|
std::string historyFile;
|
||||||
|
std::unique_ptr<rust::lix::repl::Rustyline> rl;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ReadlineLikeInteracter(std::string historyFile)
|
ReadlineLikeInteracter(std::string historyFile);
|
||||||
: historyFile(historyFile)
|
|
||||||
{
|
virtual void init(detail::ReplCompleterMixin * repl) override;
|
||||||
}
|
|
||||||
virtual Guard init(detail::ReplCompleterMixin * repl) override;
|
|
||||||
virtual bool getLine(std::string & input, ReplPromptType promptType) override;
|
virtual bool getLine(std::string & input, ReplPromptType promptType) override;
|
||||||
/** Writes the current history to the history file.
|
/** Writes the current history to the history file.
|
||||||
*
|
*
|
||||||
@@ -54,7 +55,6 @@ class AutomationInteracter final : public ReplInteracter
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
AutomationInteracter() = default;
|
AutomationInteracter() = default;
|
||||||
virtual Guard init(detail::ReplCompleterMixin * repl) override;
|
|
||||||
virtual bool getLine(std::string & input, ReplPromptType promptType) override;
|
virtual bool getLine(std::string & input, ReplPromptType promptType) override;
|
||||||
virtual ~AutomationInteracter() override = default;
|
virtual ~AutomationInteracter() override = default;
|
||||||
};
|
};
|
||||||
|
|||||||
+22
-19
@@ -1,6 +1,5 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <editline.h>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -380,7 +379,7 @@ ReplExitStatus NixRepl::mainLoop()
|
|||||||
std::list<ReplLoadable> loadables = std::exchange(loaded, {});
|
std::list<ReplLoadable> loadables = std::exchange(loaded, {});
|
||||||
loadFiles(loadables);
|
loadFiles(loadables);
|
||||||
|
|
||||||
auto _guard = interacter->init(static_cast<detail::ReplCompleterMixin *>(this));
|
interacter->init(static_cast<detail::ReplCompleterMixin *>(this));
|
||||||
|
|
||||||
/* Stop the progress bar because it interferes with the display of
|
/* Stop the progress bar because it interferes with the display of
|
||||||
the repl. */
|
the repl. */
|
||||||
@@ -441,15 +440,10 @@ StringSet NixRepl::completePrefix(const std::string &prefix)
|
|||||||
{
|
{
|
||||||
StringSet completions;
|
StringSet completions;
|
||||||
|
|
||||||
// We should only complete colon commands if there's a colon at the beginning,
|
if (prefix.starts_with(':')) {
|
||||||
// but editline (for... whatever reason) doesn't *give* us the colon in the
|
|
||||||
// completion callback. If the user types :rel<TAB>, `prefix` will only be `rel`.
|
|
||||||
// Luckily, editline provides a global variable for its current buffer, so we can
|
|
||||||
// check for the presence of a colon there.
|
|
||||||
if (rl_line_buffer != nullptr && rl_line_buffer[0] == ':') {
|
|
||||||
for (auto const & [colonCmd, cmd] : registeredCommands) {
|
for (auto const & [colonCmd, cmd] : registeredCommands) {
|
||||||
if ((!cmd->attributes.debugModeOnly || inDebugger()) && colonCmd.starts_with(prefix)) {
|
if ((!cmd->attributes.debugModeOnly || inDebugger()) && colonCmd.starts_with(prefix.substr(1))) {
|
||||||
completions.insert(std::string(colonCmd));
|
completions.insert(":" + colonCmd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -699,17 +693,26 @@ void NixRepl::initDebugBuiltinCommands()
|
|||||||
addCommand(
|
addCommand(
|
||||||
"backtrace",
|
"backtrace",
|
||||||
[](NixRepl & repl, const std::string & _arg) {
|
[](NixRepl & repl, const std::string & _arg) {
|
||||||
auto traces = repl.evaluator.debug->traces();
|
auto tracesGenerator = repl.evaluator.debug->traces();
|
||||||
for (const auto & [idx, i] : enumerate(traces)) {
|
|
||||||
std::cout << "\n" << ANSI_BLUE << idx << ANSI_NORMAL << ": ";
|
// since we want to print the stack trace in reverse order,
|
||||||
showDebugTrace(std::cout, repl.evaluator.positions, *i);
|
// we have to first traverse all frames and accumulate them
|
||||||
|
// in a list (in which we store each new trace at the /beginning/)
|
||||||
|
std::list<std::pair<size_t, const DebugTrace *>> reversedTraces;
|
||||||
|
for (const auto trace : tracesGenerator) {
|
||||||
|
// because the original traces are indexed from 0 upto N,
|
||||||
|
// this gives us their original index
|
||||||
|
auto idx = reversedTraces.size();
|
||||||
|
reversedTraces.push_front({idx, trace});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto & [traceIdx, trace] : reversedTraces) {
|
||||||
|
std::cout << "\n" << ANSI_BLUE << traceIdx << ANSI_NORMAL << ": ";
|
||||||
|
showDebugTrace(std::cout, repl.evaluator.positions, *trace);
|
||||||
}
|
}
|
||||||
return ProcessLineResult::PromptAgain;
|
return ProcessLineResult::PromptAgain;
|
||||||
},
|
},
|
||||||
{.aliases = {"bt"},
|
{.aliases = {"bt"}, .debugModeOnly = true, .help = "Show trace stack", .section = "Debug mode"}
|
||||||
.debugModeOnly = true,
|
|
||||||
.help = "Show trace stack",
|
|
||||||
.section = "Debug mode"}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
addCommand(
|
addCommand(
|
||||||
@@ -817,7 +820,7 @@ void NixRepl::initDebugBuiltinCommands()
|
|||||||
{.aliases = {"st"},
|
{.aliases = {"st"},
|
||||||
.debugModeOnly = true,
|
.debugModeOnly = true,
|
||||||
.help = "Show current trace. If an integer is provided, this switches to that stack "
|
.help = "Show current trace. If an integer is provided, this switches to that stack "
|
||||||
"beforehand. If the integer has an explicit + or - sign, it is treated as"
|
"beforehand. If the integer has an explicit + or - sign, it is treated as "
|
||||||
"relative to the current stack index.",
|
"relative to the current stack index.",
|
||||||
.section = "Debug mode",
|
.section = "Debug mode",
|
||||||
.positionalArgsSpecifiers = {{.placeholderText = "integer index", .optional = true}}}
|
.positionalArgsSpecifiers = {{.placeholderText = "integer index", .optional = true}}}
|
||||||
|
|||||||
@@ -36,9 +36,12 @@ int helperMain(const char * name, std::span<char *> args) noexcept
|
|||||||
other things, determines if kill(-1, signo) affects the
|
other things, determines if kill(-1, signo) affects the
|
||||||
calling process. In the OSX libc, it's set to true,
|
calling process. In the OSX libc, it's set to true,
|
||||||
which means "follow POSIX", which we don't want here */
|
which means "follow POSIX", which we don't want here */
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||||
if (syscall(SYS_kill, -1, SIGKILL, false) == 0) {
|
if (syscall(SYS_kill, -1, SIGKILL, false) == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
#pragma clang diagnostic pop
|
||||||
#else
|
#else
|
||||||
if (kill(-1, SIGKILL) == 0) {
|
if (kill(-1, SIGKILL) == 0) {
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ static void setPersonality(std::string_view system)
|
|||||||
|
|
||||||
bool pathExists(const fs::path & path)
|
bool pathExists(const fs::path & path)
|
||||||
{
|
{
|
||||||
return fs::exists(fs::symlink_status(path));
|
return fs::exists(fs::symlink_status(path)); // NOLINT(lix-foreign-exceptions): caught by main
|
||||||
}
|
}
|
||||||
|
|
||||||
void bindPath(const fs::path & source, const fs::path & target, bool optional = false)
|
void bindPath(const fs::path & source, const fs::path & target, bool optional = false)
|
||||||
@@ -72,7 +72,7 @@ void bindPath(const fs::path & source, const fs::path & target, bool optional =
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
auto st = fs::symlink_status(source);
|
auto st = fs::symlink_status(source); // NOLINT(lix-foreign-exceptions): caught by main
|
||||||
if (st.type() == fs::file_type::not_found) {
|
if (st.type() == fs::file_type::not_found) {
|
||||||
if (optional) {
|
if (optional) {
|
||||||
return;
|
return;
|
||||||
@@ -82,14 +82,14 @@ void bindPath(const fs::path & source, const fs::path & target, bool optional =
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (st.type() == fs::file_type::directory) {
|
if (st.type() == fs::file_type::directory) {
|
||||||
fs::create_directories(target);
|
fs::create_directories(target); // NOLINT(lix-foreign-exceptions): caught by main
|
||||||
bindMount();
|
bindMount();
|
||||||
} else if (st.type() == fs::file_type::symlink) {
|
} else if (st.type() == fs::file_type::symlink) {
|
||||||
// Symlinks can (apparently) not be bind-mounted, so just copy it
|
// Symlinks can (apparently) not be bind-mounted, so just copy it
|
||||||
fs::create_directories(target.parent_path());
|
fs::create_directories(target.parent_path()); // NOLINT(lix-foreign-exceptions): caught by main
|
||||||
fs::copy_symlink(source, target);
|
fs::copy_symlink(source, target); // NOLINT(lix-foreign-exceptions): caught by main
|
||||||
} else {
|
} else {
|
||||||
fs::create_directories(target.parent_path());
|
fs::create_directories(target.parent_path()); // NOLINT(lix-foreign-exceptions): caught by main
|
||||||
if (kj::AutoCloseFd file{open(target.c_str(), O_RDWR | O_CREAT, 0644)}; file == nullptr) {
|
if (kj::AutoCloseFd file{open(target.c_str(), O_RDWR | O_CREAT, 0644)}; file == nullptr) {
|
||||||
throw SysError("could not create %s", target);
|
throw SysError("could not create %s", target);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,17 @@
|
|||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/un.h>
|
#include <sys/un.h>
|
||||||
|
|
||||||
LIBEXEC_HELPER(4)
|
LIBEXEC_HELPER(5)
|
||||||
|
|
||||||
|
static int resultFd = -1;
|
||||||
|
|
||||||
|
static void sendResult(int result)
|
||||||
|
{
|
||||||
|
// NOTE posix says pipe writes smaller than PIPE_BUF must be atomic, so this either
|
||||||
|
// succeeds or fails (pipe bufs of four bytes make no sense at all for our systems)
|
||||||
|
int error = result;
|
||||||
|
DIE_UNLESS_SYS("writing result", ::write(resultFd, &error, sizeof(error)));
|
||||||
|
}
|
||||||
|
|
||||||
int helperMain(const char *, std::span<char *> args) noexcept
|
int helperMain(const char *, std::span<char *> args) noexcept
|
||||||
{
|
{
|
||||||
@@ -10,8 +20,12 @@ int helperMain(const char *, std::span<char *> args) noexcept
|
|||||||
std::string_view method = args[1];
|
std::string_view method = args[1];
|
||||||
const auto dir = args[2];
|
const auto dir = args[2];
|
||||||
const auto name = args[3];
|
const auto name = args[3];
|
||||||
|
resultFd = argToInt<int>("result-fd", args[4]);
|
||||||
|
|
||||||
DIE_UNLESS_SYS("chdir", chdir(dir));
|
if (chdir(dir)) {
|
||||||
|
sendResult(errno);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
struct sockaddr_un addr;
|
struct sockaddr_un addr;
|
||||||
addr.sun_family = AF_UNIX;
|
addr.sun_family = AF_UNIX;
|
||||||
@@ -23,9 +37,11 @@ int helperMain(const char *, std::span<char *> args) noexcept
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (method == "bind") {
|
if (method == "bind") {
|
||||||
DIE_UNLESS_SYS("bind", bind(socket, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)));
|
auto result = bind(socket, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) ? errno : 0;
|
||||||
|
sendResult(result);
|
||||||
} else if (method == "connect") {
|
} else if (method == "connect") {
|
||||||
DIE_UNLESS_SYS("connect", connect(socket, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)));
|
auto result = connect(socket, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) ? errno : 0;
|
||||||
|
sendResult(result);
|
||||||
} else {
|
} else {
|
||||||
die(std::format("invalid method %s", method));
|
die(std::format("invalid method %s", method));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,70 +23,14 @@ countDown 2
|
|||||||
Then, evaluating the file will give the following stack trace:
|
Then, evaluating the file will give the following stack trace:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ nix-instantiate --show-trace err.nix
|
$ nix-instantiate err.nix
|
||||||
error:
|
error:
|
||||||
… from call site
|
|
||||||
at /home/plop/git.lix.systems/lix-project/lix/err.nix:9:1:
|
|
||||||
8| in
|
|
||||||
9| countDown 2
|
|
||||||
| ^
|
|
||||||
10|
|
|
||||||
|
|
||||||
… while calling 'countDown'
|
|
||||||
at /home/plop/git.lix.systems/lix-project/lix/err.nix:3:5:
|
|
||||||
2| countDown =
|
|
||||||
3| n:
|
|
||||||
| ^
|
|
||||||
4| if n == 0 then
|
|
||||||
|
|
||||||
… while calling the 'addErrorContext' builtin
|
|
||||||
at /home/plop/git.lix.systems/lix-project/lix/err.nix:7:7:
|
|
||||||
6| else
|
|
||||||
7| builtins.addErrorContext "while counting down; n = ${toString n}" ("x" + countDown (n - 1));
|
|
||||||
| ^
|
|
||||||
8| in
|
|
||||||
|
|
||||||
… while counting down; n = 2
|
… while counting down; n = 2
|
||||||
|
|
||||||
… from call site
|
|
||||||
at /home/plop/git.lix.systems/lix-project/lix/err.nix:7:80:
|
|
||||||
6| else
|
|
||||||
7| builtins.addErrorContext "while counting down; n = ${toString n}" ("x" + countDown (n - 1));
|
|
||||||
| ^
|
|
||||||
8| in
|
|
||||||
|
|
||||||
… while calling 'countDown'
|
|
||||||
at /home/plop/git.lix.systems/lix-project/lix/err.nix:3:5:
|
|
||||||
2| countDown =
|
|
||||||
3| n:
|
|
||||||
| ^
|
|
||||||
4| if n == 0 then
|
|
||||||
|
|
||||||
… while calling the 'addErrorContext' builtin
|
|
||||||
at /home/plop/git.lix.systems/lix-project/lix/err.nix:7:7:
|
|
||||||
6| else
|
|
||||||
7| builtins.addErrorContext "while counting down; n = ${toString n}" ("x" + countDown (n - 1));
|
|
||||||
| ^
|
|
||||||
8| in
|
|
||||||
|
|
||||||
… while counting down; n = 1
|
… while counting down; n = 1
|
||||||
|
|
||||||
… from call site
|
|
||||||
at /home/plop/git.lix.systems/lix-project/lix/err.nix:7:80:
|
|
||||||
6| else
|
|
||||||
7| builtins.addErrorContext "while counting down; n = ${toString n}" ("x" + countDown (n - 1));
|
|
||||||
| ^
|
|
||||||
8| in
|
|
||||||
|
|
||||||
… while calling 'countDown'
|
|
||||||
at /home/plop/git.lix.systems/lix-project/lix/err.nix:3:5:
|
|
||||||
2| countDown =
|
|
||||||
3| n:
|
|
||||||
| ^
|
|
||||||
4| if n == 0 then
|
|
||||||
|
|
||||||
… caused by explicit throw
|
… caused by explicit throw
|
||||||
at /home/plop/git.lix.systems/lix-project/lix/err.nix:5:7:
|
at err.nix:5:7:
|
||||||
4| if n == 0 then
|
4| if n == 0 then
|
||||||
5| throw "kaboom"
|
5| throw "kaboom"
|
||||||
| ^
|
| ^
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
---
|
---
|
||||||
name: ceil
|
name: ceil
|
||||||
args: [double]
|
args: [number]
|
||||||
---
|
---
|
||||||
Converts an IEEE-754 double-precision floating-point number (*double*) to
|
Returns an integer value containing the smallest integer greater than or equal to *number* (which is either a floating-point or integer value).
|
||||||
the next higher integer.
|
|
||||||
|
|
||||||
If the datatype is neither an integer nor a "float", an evaluation error will be
|
If the result is out of range for the integer type, such as NaN, infinity, or a number with magnitude out of range, Lix throws an evaluation error.
|
||||||
thrown.
|
|
||||||
|
Lix currently throws an evaluation error for some *integer* inputs between 2\*\*52 and 2\*\*63 - 1 as those previously experienced floating-point precision loss due to a Nix bug (https://github.com/NixOS/nix/issues/12899).
|
||||||
|
In a future release, such integers will be passed through.
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
---
|
---
|
||||||
name: floor
|
name: floor
|
||||||
args: [double]
|
args: [number]
|
||||||
---
|
---
|
||||||
Converts an IEEE-754 double-precision floating-point number (*double*) to
|
Returns an integer value containing the largest integer less than or equal to *number* (which is either a float or integer).
|
||||||
the next lower integer.
|
|
||||||
|
|
||||||
If the datatype is neither an integer nor a "float", an evaluation error will be
|
If the result is out of range for the integer type, such as NaN, infinity, or a number with magnitude out of range, Lix throws an evaluation error.
|
||||||
thrown.
|
|
||||||
|
Lix currently throws an evaluation error for some *integer* inputs between 2\*\*52 and 2\*\*63 - 1 as those previously experienced floating-point precision loss due to a Nix bug (https://github.com/NixOS/nix/issues/12899).
|
||||||
|
In a future release, such integers will be passed through.
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public:
|
|||||||
ref<AttrCursor> getRoot();
|
ref<AttrCursor> getRoot();
|
||||||
};
|
};
|
||||||
|
|
||||||
enum AttrType {
|
enum AttrType : uint8_t {
|
||||||
Placeholder = 0,
|
Placeholder = 0,
|
||||||
FullAttrs = 1,
|
FullAttrs = 1,
|
||||||
String = 2,
|
String = 2,
|
||||||
|
|||||||
+29
-24
@@ -1,4 +1,5 @@
|
|||||||
#include "eval.hh"
|
#include "eval.hh"
|
||||||
|
#include "libexpr/nixexpr.hh"
|
||||||
#include "primops.hh"
|
#include "primops.hh"
|
||||||
#include "gc-small-vector.hh"
|
#include "gc-small-vector.hh"
|
||||||
|
|
||||||
@@ -10,14 +11,19 @@
|
|||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
Value Expr::makeThunk(Evaluator & ctx, Env & env)
|
||||||
|
{
|
||||||
|
ctx.stats.nrThunks++;
|
||||||
|
return {NewValueAs::thunk, ctx.mem, env, *this};
|
||||||
|
}
|
||||||
|
|
||||||
/* Create a thunk for the delayed computation of the given expression
|
/* Create a thunk for the delayed computation of the given expression
|
||||||
in the given environment. But if the expression is a variable,
|
in the given environment. But if the expression is a variable,
|
||||||
then look it up right away. This significantly reduces the number
|
then look it up right away. This significantly reduces the number
|
||||||
of thunks allocated. */
|
of thunks allocated. */
|
||||||
Value Expr::maybeThunk(EvalState & state, Env & env)
|
Value Expr::maybeThunk(EvalState & state, Env & env)
|
||||||
{
|
{
|
||||||
state.ctx.stats.nrThunks++;
|
return makeThunk(state.ctx, env);
|
||||||
return {NewValueAs::thunk, state.ctx.mem, env, *this};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Value ExprVar::maybeThunk(EvalState & state, Env & env)
|
Value ExprVar::maybeThunk(EvalState & state, Env & env)
|
||||||
@@ -99,18 +105,10 @@ Value ExprSet::eval(EvalState & state, Env & env)
|
|||||||
in the original environment. */
|
in the original environment. */
|
||||||
Displacement displ = 0;
|
Displacement displ = 0;
|
||||||
for (auto & i : attrs) {
|
for (auto & i : attrs) {
|
||||||
Value vAttr;
|
Env & thunkEnv = *i.second.chooseByKind(&env2, &env, inheritEnv);
|
||||||
if (hasOverrides && i.second.kind != ExprAttrs::AttrDef::Kind::Inherited) {
|
Value vAttr = hasOverrides && i.second.kind != ExprAttrs::AttrDef::Kind::Inherited
|
||||||
vAttr = {
|
? i.second.e->makeThunk(state.ctx, thunkEnv)
|
||||||
NewValueAs::thunk,
|
: i.second.e->maybeThunk(state, thunkEnv);
|
||||||
state.ctx.mem,
|
|
||||||
*i.second.chooseByKind(&env2, &env, inheritEnv),
|
|
||||||
*i.second.e
|
|
||||||
};
|
|
||||||
state.ctx.stats.nrThunks++;
|
|
||||||
} else {
|
|
||||||
vAttr = i.second.e->maybeThunk(state, *i.second.chooseByKind(&env2, &env, inheritEnv));
|
|
||||||
}
|
|
||||||
env2.values[displ++] = vAttr;
|
env2.values[displ++] = vAttr;
|
||||||
v.attrs()->push_back(Attr(i.first, vAttr, i.second.pos));
|
v.attrs()->push_back(Attr(i.first, vAttr, i.second.pos));
|
||||||
}
|
}
|
||||||
@@ -358,12 +356,14 @@ Value ExprOpConcatLists::eval(EvalState & state, Env & env)
|
|||||||
{
|
{
|
||||||
state.ctx.stats.nrListConcats++;
|
state.ctx.stats.nrListConcats++;
|
||||||
|
|
||||||
/* We don't call into `concatLists` as that loses the position information of the expressions. */
|
/* We could simply call into `state.concatLists`, but that would add a redundant trace to our errors,
|
||||||
|
* and to fix that we would need to make the error on it and `forceList` optional, and *sigh*
|
||||||
|
*/
|
||||||
|
|
||||||
Value v1 = e1->eval(state, env);
|
Value v1 = e1->eval(state, env);
|
||||||
state.checkList(v1, env, *e1);
|
state.checkList(v1, env, *this); // Pass in `this` instead of `e1` to make the error point to the `++`
|
||||||
Value v2 = e2->eval(state, env);
|
Value v2 = e2->eval(state, env);
|
||||||
state.checkList(v2, env, *e2);
|
state.checkList(v2, env, *this); // Pass in `this` instead of `e2` to make the error point to the `++`
|
||||||
|
|
||||||
size_t l1 = v1.listSize(), l2 = v2.listSize(), len = l1 + l2;
|
size_t l1 = v1.listSize(), l2 = v2.listSize(), len = l1 + l2;
|
||||||
|
|
||||||
@@ -436,7 +436,7 @@ Value ExprConcatStrings::eval(EvalState & state, Env & env)
|
|||||||
} else {
|
} else {
|
||||||
state.ctx.errors
|
state.ctx.errors
|
||||||
.make<EvalError>("integer overflow in adding %1% + %2%", n, vTmp.integer())
|
.make<EvalError>("integer overflow in adding %1% + %2%", n, vTmp.integer())
|
||||||
.atPos(i_pos)
|
.atPos(isInterpolation ? i_pos : pos)
|
||||||
.debugThrow();
|
.debugThrow();
|
||||||
}
|
}
|
||||||
} else if (vTmp.type() == nFloat) {
|
} else if (vTmp.type() == nFloat) {
|
||||||
@@ -446,7 +446,7 @@ Value ExprConcatStrings::eval(EvalState & state, Env & env)
|
|||||||
nf += vTmp.fpoint();
|
nf += vTmp.fpoint();
|
||||||
} else {
|
} else {
|
||||||
state.ctx.errors.make<EvalError>("cannot add %1% to an integer", showType(vTmp))
|
state.ctx.errors.make<EvalError>("cannot add %1% to an integer", showType(vTmp))
|
||||||
.atPos(i_pos)
|
.atPos(isInterpolation ? i_pos : pos)
|
||||||
.withFrame(env, *this)
|
.withFrame(env, *this)
|
||||||
.debugThrow();
|
.debugThrow();
|
||||||
}
|
}
|
||||||
@@ -457,7 +457,7 @@ Value ExprConcatStrings::eval(EvalState & state, Env & env)
|
|||||||
nf += vTmp.fpoint();
|
nf += vTmp.fpoint();
|
||||||
} else {
|
} else {
|
||||||
state.ctx.errors.make<EvalError>("cannot add %1% to a float", showType(vTmp))
|
state.ctx.errors.make<EvalError>("cannot add %1% to a float", showType(vTmp))
|
||||||
.atPos(i_pos)
|
.atPos(isInterpolation ? i_pos : pos)
|
||||||
.withFrame(env, *this)
|
.withFrame(env, *this)
|
||||||
.debugThrow();
|
.debugThrow();
|
||||||
}
|
}
|
||||||
@@ -473,13 +473,18 @@ Value ExprConcatStrings::eval(EvalState & state, Env & env)
|
|||||||
: StringCoercionMode::Strict;
|
: StringCoercionMode::Strict;
|
||||||
|
|
||||||
/* skip canonization of first path, which would only be not
|
/* skip canonization of first path, which would only be not
|
||||||
canonized in the first place if it's coming from a ./${foo} type
|
canonized in the first place if it's coming from a ./${foo} type
|
||||||
path */
|
path */
|
||||||
auto part = state.coerceToString(
|
auto part = state.coerceToString(
|
||||||
i_pos,
|
isInterpolation ? i_pos : pos,
|
||||||
vTmp,
|
vTmp,
|
||||||
context,
|
context,
|
||||||
"while evaluating a path segment",
|
(isInterpolation && firstType == nPath)
|
||||||
|
? "while evaluating a path interpolation"
|
||||||
|
: (isInterpolation ? "while evaluating a string interpolation"
|
||||||
|
// TODO: to the person who eventually cleans up all of this mess,
|
||||||
|
// please turn this into "cannot concatenate $type to a $type" instead.
|
||||||
|
: "while concatenating"),
|
||||||
coercionMode,
|
coercionMode,
|
||||||
firstType == nString,
|
firstType == nString,
|
||||||
!first
|
!first
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
#include "lix/libexpr/eval-error.hh"
|
#include "lix/libexpr/eval-error.hh"
|
||||||
#include "lix/libexpr/gc-alloc.hh"
|
#include "lix/libexpr/gc-alloc.hh"
|
||||||
#include "value.hh"
|
#include "value.hh"
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|||||||
+14
-19
@@ -847,8 +847,7 @@ Value::List * EvalMemory::newList(size_t size)
|
|||||||
|
|
||||||
Value Evaluator::evalLazily(Expr & e)
|
Value Evaluator::evalLazily(Expr & e)
|
||||||
{
|
{
|
||||||
stats.nrThunks++;
|
return e.makeThunk(*this, builtins.env);
|
||||||
return {NewValueAs::thunk, mem, builtins.env, e};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Value EvalState::mkPos(PosIdx p)
|
Value EvalState::mkPos(PosIdx p)
|
||||||
@@ -908,19 +907,6 @@ std::string EvalState::mkSingleDerivedPathStringRaw(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void EvalState::mkSingleDerivedPathString(
|
|
||||||
const SingleDerivedPath & p,
|
|
||||||
Value & v)
|
|
||||||
{
|
|
||||||
v = {
|
|
||||||
NewValueAs::string,
|
|
||||||
mkSingleDerivedPathStringRaw(p),
|
|
||||||
NixStringContext{
|
|
||||||
std::visit([](auto && v) -> NixStringContextElem { return v; }, p),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CachedEvalFile
|
struct CachedEvalFile
|
||||||
{
|
{
|
||||||
Value result;
|
Value result;
|
||||||
@@ -1132,8 +1118,9 @@ Env & AttrsPattern::match(
|
|||||||
|
|
||||||
Value EvalState::callFunction(Value & fun, std::span<Value> args, const PosIdx pos)
|
Value EvalState::callFunction(Value & fun, std::span<Value> args, const PosIdx pos)
|
||||||
{
|
{
|
||||||
if (callDepth > evalSettings.maxCallDepth)
|
if (callDepth >= evalSettings.maxCallDepth) {
|
||||||
ctx.errors.make<EvalError>("stack overflow; max-call-depth exceeded").atPos(pos).debugThrow();
|
ctx.errors.make<EvalError>("stack overflow; max-call-depth exceeded").atPos(pos).debugThrow();
|
||||||
|
}
|
||||||
MaintainCount _level(callDepth);
|
MaintainCount _level(callDepth);
|
||||||
|
|
||||||
auto trace = evalSettings.traceFunctionCalls
|
auto trace = evalSettings.traceFunctionCalls
|
||||||
@@ -1208,12 +1195,18 @@ Value EvalState::callFunction(Value & fun, std::span<Value> args, const PosIdx p
|
|||||||
// was being evaluated and an explicit thrown error.
|
// was being evaluated and an explicit thrown error.
|
||||||
if (fn->name == "throw" && !e.hasTrace()) {
|
if (fn->name == "throw" && !e.hasTrace()) {
|
||||||
e.addTrace(ctx.positions[pos], "caused by explicit %s", "throw");
|
e.addTrace(ctx.positions[pos], "caused by explicit %s", "throw");
|
||||||
} else {
|
}
|
||||||
|
// otherwise, print a trace of this builtin, as long as it isn't
|
||||||
|
// a 'addErrorContext' call (which would just create noise)
|
||||||
|
else if (fn->name != "addErrorContext")
|
||||||
|
{
|
||||||
e.addTrace(ctx.positions[pos], "while calling the '%s' builtin", fn->name);
|
e.addTrace(ctx.positions[pos], "while calling the '%s' builtin", fn->name);
|
||||||
}
|
}
|
||||||
throw;
|
throw;
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace(ctx.positions[pos], "while calling the '%1%' builtin", fn->name);
|
if (fn->name != "addErrorContext") {
|
||||||
|
e.addTrace(ctx.positions[pos], "while calling the '%1%' builtin", fn->name);
|
||||||
|
}
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1259,7 +1252,9 @@ Value EvalState::callFunction(Value & fun, std::span<Value> args, const PosIdx p
|
|||||||
// so the debugger allows to inspect the wrong parameters passed to the builtin.
|
// so the debugger allows to inspect the wrong parameters passed to the builtin.
|
||||||
vCur = fn->fun(*this, vArgs.data());
|
vCur = fn->fun(*this, vArgs.data());
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace(ctx.positions[pos], "while calling the '%1%' builtin", fn->name);
|
if (fn->name != "addErrorContext") {
|
||||||
|
e.addTrace(ctx.positions[pos], "while calling the '%1%' builtin", fn->name);
|
||||||
|
}
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -817,15 +817,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
Value mkOutputString(const SingleDerivedPath::Built & b, const StorePath & staticOutputPath);
|
Value mkOutputString(const SingleDerivedPath::Built & b, const StorePath & staticOutputPath);
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a string representing a `SingleDerivedPath`.
|
|
||||||
*
|
|
||||||
* A combination of `mkStorePathString` and `mkOutputString`.
|
|
||||||
*/
|
|
||||||
void mkSingleDerivedPathString(
|
|
||||||
const SingleDerivedPath & p,
|
|
||||||
Value & v);
|
|
||||||
|
|
||||||
Value updateAttrs(const Value & v1, const Value & v2);
|
Value updateAttrs(const Value & v1, const Value & v2);
|
||||||
Value concatLists(std::span<Value> lists, const PosIdx pos, std::string_view errorCtx);
|
Value concatLists(std::span<Value> lists, const PosIdx pos, std::string_view errorCtx);
|
||||||
|
|
||||||
|
|||||||
+119
-63
@@ -4,6 +4,7 @@
|
|||||||
#include "lix/libutil/json.hh"
|
#include "lix/libutil/json.hh"
|
||||||
#include "lix/libutil/users.hh"
|
#include "lix/libutil/users.hh"
|
||||||
#include "lix/libfetchers/fetch-settings.hh"
|
#include "lix/libfetchers/fetch-settings.hh"
|
||||||
|
#include <cctype>
|
||||||
|
|
||||||
namespace nix::flake {
|
namespace nix::flake {
|
||||||
|
|
||||||
@@ -30,6 +31,64 @@ static void writeTrustedList(const TrustedList & trustedList)
|
|||||||
writeFile(path, JSON(trustedList).dump());
|
writeFile(path, JSON(trustedList).dump());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
askForEachSetting(TrustedList & trustedList, std::map<std::string, std::string> & untrustedSettings)
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
constexpr auto prompt =
|
||||||
|
"[" ANSI_BOLD "y" ANSI_NORMAL "]es for now/"
|
||||||
|
"[" ANSI_BOLD "N" ANSI_NORMAL "]o for now/"
|
||||||
|
"[" ANSI_BOLD "a" ANSI_NORMAL "]lways allow";
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
auto didTrustedListChange = false;
|
||||||
|
int acceptedCount = 0;
|
||||||
|
for (const auto & [name, valueS] : untrustedSettings) {
|
||||||
|
auto reply =
|
||||||
|
logger
|
||||||
|
->ask(
|
||||||
|
fmt("Do you want to allow setting '" ANSI_MAGENTA "%s = %s" ANSI_NORMAL "'? (%s) ",
|
||||||
|
name,
|
||||||
|
valueS,
|
||||||
|
prompt)
|
||||||
|
)
|
||||||
|
.value_or("n");
|
||||||
|
|
||||||
|
reply = toLower(reply);
|
||||||
|
|
||||||
|
static const std::string yes = "yes for now";
|
||||||
|
static const std::string no = "no for now";
|
||||||
|
static const std::string always = "always allow";
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (no.starts_with(reply)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yes.starts_with(reply) || always.starts_with(reply)) {
|
||||||
|
if (reply[0] == 'a') {
|
||||||
|
trustedList[name][valueS] = true;
|
||||||
|
didTrustedListChange = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
globalConfig.set(name, valueS);
|
||||||
|
acceptedCount++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the reply wasn't a prefix of any answer, ask the user again
|
||||||
|
reply = logger->ask(fmt("Couldn't understand reply.\n%s: ", prompt)).value_or("n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (didTrustedListChange) {
|
||||||
|
writeTrustedList(trustedList);
|
||||||
|
}
|
||||||
|
|
||||||
|
// return false if *none* of the settings were accepted
|
||||||
|
return acceptedCount > 0;
|
||||||
|
}
|
||||||
|
|
||||||
static bool batchAskForSetting(
|
static bool batchAskForSetting(
|
||||||
bool & negativeTrustOverride,
|
bool & negativeTrustOverride,
|
||||||
TrustedList & trustedList,
|
TrustedList & trustedList,
|
||||||
@@ -43,77 +102,74 @@ static bool batchAskForSetting(
|
|||||||
|
|
||||||
printWarning("%s", warning);
|
printWarning("%s", warning);
|
||||||
|
|
||||||
auto reply = logger
|
// clang-format off
|
||||||
->ask(
|
constexpr auto globalPrompt =
|
||||||
fmt("Do you want to allow configuration settings to be applied?\nThis may allow the "
|
"[" ANSI_BOLD "y" ANSI_NORMAL "]es for now/"
|
||||||
"flake to gain root, see the nix.conf manual page (" ANSI_BOLD "y" ANSI_NORMAL
|
"[" ANSI_BOLD "n" ANSI_NORMAL "]o for now/"
|
||||||
"es for now/" ANSI_BOLD "A" ANSI_NORMAL "llow always/" ANSI_BOLD "n" ANSI_NORMAL
|
"[" ANSI_BOLD "a" ANSI_NORMAL "]lways allow/"
|
||||||
"o/" ANSI_BOLD "N" ANSI_NORMAL "o to all) ")
|
"[" ANSI_BOLD "I" ANSI_NORMAL "]ndividually review";
|
||||||
)
|
// clang-format on
|
||||||
.value_or('n');
|
|
||||||
|
|
||||||
if (reply == 'N') {
|
auto reply =
|
||||||
printWarning("Rejecting all untrusted nix.conf entries");
|
logger
|
||||||
printTaggedWarning(
|
->ask(
|
||||||
"you can set '%s' to '%b' to automatically reject configuration options supplied by "
|
fmt("Do you want to allow these configuration settings to be applied?\n" ANSI_BOLD
|
||||||
"flakes",
|
"This may allow the flake to gain root" ANSI_NORMAL ", see the nix.conf manual page.\n"
|
||||||
"accept-flake-config",
|
"(%s) ",
|
||||||
false
|
globalPrompt)
|
||||||
);
|
)
|
||||||
negativeTrustOverride = true;
|
.value_or("I"); // if the answer is empty (or there is no interactive prompt),
|
||||||
return false;
|
// just default to reviewing each individually
|
||||||
}
|
|
||||||
|
reply = toLower(reply);
|
||||||
|
|
||||||
|
static const std::string yes = "yes for now";
|
||||||
|
static const std::string no = "no for now";
|
||||||
|
static const std::string always = "always allow";
|
||||||
|
static const std::string review = "individually review";
|
||||||
|
|
||||||
|
// when interactive, loops and reprompts until the reply is one of y/n/a/i (or any prefix of the answers)
|
||||||
|
while (true) {
|
||||||
|
if (no.starts_with(reply)) {
|
||||||
|
printWarning("Rejecting all untrusted nix.conf entries");
|
||||||
|
printTaggedWarning(
|
||||||
|
"you can set '%s' to '%b' to automatically reject configuration options supplied by "
|
||||||
|
"flakes",
|
||||||
|
"accept-flake-config",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
negativeTrustOverride = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yes.starts_with(reply) || always.starts_with(reply)) {
|
||||||
|
auto alwaysAllow = reply[0] == 'a';
|
||||||
|
for (const auto & [name, valueS] : untrustedSettings) {
|
||||||
|
if (alwaysAllow) {
|
||||||
|
trustedList[name][valueS] = true;
|
||||||
|
}
|
||||||
|
globalConfig.set(name, valueS);
|
||||||
|
}
|
||||||
|
|
||||||
if (reply == 'y' || reply == 'A') {
|
|
||||||
auto alwaysAllow = reply == 'A';
|
|
||||||
for (const auto & [name, valueS] : untrustedSettings) {
|
|
||||||
if (alwaysAllow) {
|
if (alwaysAllow) {
|
||||||
trustedList[name][valueS] = true;
|
printTaggedWarning(
|
||||||
}
|
"adding these configuration settings to the trusted list at %s, "
|
||||||
globalConfig.set(name, valueS);
|
"edit it if you want to remove them in the future",
|
||||||
}
|
trustedListPath()
|
||||||
|
);
|
||||||
if (alwaysAllow) {
|
writeTrustedList(trustedList);
|
||||||
writeTrustedList(trustedList);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
printTaggedWarning(
|
|
||||||
"you can set '%s' to '%b' to automatically reject configuration options supplied "
|
|
||||||
"by flakes",
|
|
||||||
"accept-flake-config",
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto didTrustedListChange = false;
|
|
||||||
for (const auto & [name, valueS] : untrustedSettings) {
|
|
||||||
auto individualReply = logger
|
|
||||||
->ask(
|
|
||||||
fmt("Do you want to allow setting '%s = %s'? (" ANSI_BOLD
|
|
||||||
"y" ANSI_NORMAL "es for now/" ANSI_BOLD "A" ANSI_NORMAL
|
|
||||||
"llow always/" ANSI_BOLD "n" ANSI_NORMAL "o for now) ",
|
|
||||||
name,
|
|
||||||
valueS)
|
|
||||||
)
|
|
||||||
.value_or('n');
|
|
||||||
|
|
||||||
if (individualReply == 'y' || individualReply == 'A') {
|
|
||||||
if (individualReply == 'A') {
|
|
||||||
trustedList[name][valueS] = true;
|
|
||||||
didTrustedListChange = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
globalConfig.set(name, valueS);
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (didTrustedListChange) {
|
if (review.starts_with(reply)) {
|
||||||
writeTrustedList(trustedList);
|
return askForEachSetting(trustedList, untrustedSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
// if the reply wasn't a prefix of any, ask the user again
|
||||||
|
reply = logger->ask(fmt("Couldn't understand reply.\n%s: ", globalPrompt)).value_or("n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigFile::apply()
|
void ConfigFile::apply()
|
||||||
|
|||||||
@@ -78,10 +78,21 @@ try {
|
|||||||
co_return result::current_exception();
|
co_return result::current_exception();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Force a value that cannot contain any function calls */
|
||||||
static void forceTrivialValue(EvalState & state, Value & value, const PosIdx pos)
|
static void forceTrivialValue(EvalState & state, Value & value, const PosIdx pos)
|
||||||
{
|
{
|
||||||
if (value.isThunk() && value.isTrivial())
|
/* /piegames sighs at the settings API */
|
||||||
state.forceValue(value, pos);
|
auto prevOverridden = evalSettings.maxCallDepth.overridden;
|
||||||
|
auto prevMaxCallDepth = evalSettings.maxCallDepth;
|
||||||
|
evalSettings.maxCallDepth.override(0);
|
||||||
|
|
||||||
|
/* /piegames sighs at C++ */
|
||||||
|
KJ_DEFER({
|
||||||
|
evalSettings.maxCallDepth.override(prevMaxCallDepth);
|
||||||
|
evalSettings.maxCallDepth.overridden = prevOverridden;
|
||||||
|
});
|
||||||
|
|
||||||
|
state.forceValue(value, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -327,11 +338,17 @@ static Flake getFlake(
|
|||||||
Expr & flakeExpr = state.ctx.parseExprFromFile(resolvedFlakeFile);
|
Expr & flakeExpr = state.ctx.parseExprFromFile(resolvedFlakeFile);
|
||||||
|
|
||||||
// Enforce that 'flake.nix' is a direct attrset, not a computation.
|
// Enforce that 'flake.nix' is a direct attrset, not a computation.
|
||||||
if (!flakeExpr.try_cast<ExprAttrs>()) {
|
// We do this by disallowing any function calls (maxCallDepth 0) and checking that the resulting value is
|
||||||
state.ctx.errors.make<EvalError>("file '%s' must be an attribute set", resolvedFlakeFile).debugThrow();
|
// an attrset.
|
||||||
}
|
// The logic is already implemented in `forceTrivialValue`, but that takes a value instead of an Expr, so
|
||||||
|
// we wrap the expression in a thunk to be able to call it.
|
||||||
|
Value vInfo = flakeExpr.maybeThunk(state, state.ctx.builtins.env);
|
||||||
|
forceTrivialValue(state, vInfo, flakeExpr.getPos());
|
||||||
|
|
||||||
Value vInfo = state.eval(flakeExpr);
|
if (vInfo.type() != nAttrs) {
|
||||||
|
state.ctx.errors.make<EvalError>("file '%s' must be an attribute set", resolvedFlakeFile)
|
||||||
|
.debugThrow();
|
||||||
|
}
|
||||||
|
|
||||||
if (auto description = vInfo.attrs()->get(state.ctx.symbols.sym_description)) {
|
if (auto description = vInfo.attrs()->get(state.ctx.symbols.sym_description)) {
|
||||||
expectType(state, nString, description->value, description->pos);
|
expectType(state, nString, description->value, description->pos);
|
||||||
|
|||||||
@@ -67,16 +67,6 @@ FlakeRef parseFlakeRef(
|
|||||||
return flakeRef;
|
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(
|
std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
||||||
const std::string & url,
|
const std::string & url,
|
||||||
const std::optional<Path> & baseDir,
|
const std::optional<Path> & baseDir,
|
||||||
@@ -234,16 +224,6 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
FlakeRef FlakeRef::fromAttrs(const fetchers::Attrs & attrs)
|
||||||
{
|
{
|
||||||
auto attrs2(attrs);
|
auto attrs2(attrs);
|
||||||
|
|||||||
@@ -70,18 +70,12 @@ FlakeRef parseFlakeRef(
|
|||||||
bool allowMissing = false,
|
bool allowMissing = false,
|
||||||
bool isFlake = true);
|
bool isFlake = true);
|
||||||
|
|
||||||
std::optional<FlakeRef> maybeParseFlake(
|
|
||||||
const std::string & url, const std::optional<Path> & baseDir = {});
|
|
||||||
|
|
||||||
std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
||||||
const std::string & url,
|
const std::string & url,
|
||||||
const std::optional<Path> & baseDir = {},
|
const std::optional<Path> & baseDir = {},
|
||||||
bool allowMissing = false,
|
bool allowMissing = false,
|
||||||
bool isFlake = true);
|
bool isFlake = true);
|
||||||
|
|
||||||
std::optional<std::pair<FlakeRef, std::string>> maybeParseFlakeRefWithFragment(
|
|
||||||
const std::string & url, const std::optional<Path> & baseDir = {});
|
|
||||||
|
|
||||||
std::tuple<FlakeRef, std::string, ExtendedOutputsSpec> parseFlakeRefWithFragmentAndExtendedOutputsSpec(
|
std::tuple<FlakeRef, std::string, ExtendedOutputsSpec> parseFlakeRefWithFragmentAndExtendedOutputsSpec(
|
||||||
const std::string & url,
|
const std::string & url,
|
||||||
const std::optional<Path> & baseDir = {},
|
const std::optional<Path> & baseDir = {},
|
||||||
|
|||||||
@@ -1,125 +1,75 @@
|
|||||||
#include "lix/libexpr/json-to-value.hh"
|
#include "lix/libexpr/json-to-value.hh"
|
||||||
#include "gc-alloc.hh"
|
#include "gc-alloc.hh"
|
||||||
|
#include "libutil/types.hh"
|
||||||
#include "lix/libexpr/value.hh"
|
#include "lix/libexpr/value.hh"
|
||||||
#include "lix/libexpr/eval.hh"
|
#include "lix/libexpr/eval.hh"
|
||||||
#include "lix/libutil/json.hh"
|
#include "lix/libutil/json.hh"
|
||||||
|
|
||||||
#include <limits>
|
#include <limits>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
// for more information, refer to
|
// for more information, refer to
|
||||||
// https://github.com/nlohmann/json/blob/master/include/nlohmann/detail/input/json_sax.hpp
|
// https://github.com/nlohmann/json/blob/master/include/nlohmann/detail/input/json_sax.hpp
|
||||||
class JSONSax : nlohmann::json_sax<JSON> {
|
class JSONSax : nlohmann::json_sax<JSON> {
|
||||||
class JSONState {
|
struct WIPObject
|
||||||
protected:
|
|
||||||
std::unique_ptr<JSONState> parent;
|
|
||||||
JSONState() = default;
|
|
||||||
public:
|
|
||||||
virtual std::unique_ptr<JSONState> resolve(EvalState &) = 0;
|
|
||||||
explicit JSONState(std::unique_ptr<JSONState> && p) : parent(std::move(p)) {}
|
|
||||||
JSONState(JSONState & p) = delete;
|
|
||||||
virtual Value & finalValue()
|
|
||||||
{
|
|
||||||
assert(false && "tried to read a final value from a non-toplevel json parser state");
|
|
||||||
}
|
|
||||||
virtual ~JSONState() {}
|
|
||||||
virtual void addValue(Value v) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class TopLevelJSONState : public JSONState
|
|
||||||
{
|
{
|
||||||
RootValue v;
|
GcMap<Symbol, Value> entries;
|
||||||
public:
|
Symbol nextKey;
|
||||||
std::unique_ptr<JSONState> resolve(EvalState &) override
|
|
||||||
{
|
|
||||||
assert(false && "tried to close toplevel json parser state");
|
|
||||||
}
|
|
||||||
TopLevelJSONState() = default;
|
|
||||||
TopLevelJSONState(TopLevelJSONState & p) = delete;
|
|
||||||
Value & finalValue() override
|
|
||||||
{
|
|
||||||
assert(v && "tried to read nonexistent final value from json parser");
|
|
||||||
return *v;
|
|
||||||
}
|
|
||||||
void addValue(Value v) override
|
|
||||||
{
|
|
||||||
assert(!this->v && "duplicate value in toplevel JSON scope");
|
|
||||||
this->v = allocRootValue(v);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
using WIPArray = GcVector<Value>;
|
||||||
|
|
||||||
class JSONObjectState : public JSONState {
|
// A stack of all of the currently opened and not yet closed JSON objects and arrays at the current moment
|
||||||
using JSONState::JSONState;
|
// of parsing
|
||||||
GcMap<Symbol, Value> attrs;
|
std::vector<std::variant<WIPObject, WIPArray>> stack;
|
||||||
Symbol _key;
|
|
||||||
std::unique_ptr<JSONState> resolve(EvalState & state) override
|
|
||||||
{
|
|
||||||
auto attrs2 = state.ctx.buildBindings(attrs.size());
|
|
||||||
for (auto & i : attrs)
|
|
||||||
attrs2.insert(i.first, i.second);
|
|
||||||
parent->addValue({NewValueAs::attrs, attrs2.alreadySorted()});
|
|
||||||
return std::move(parent);
|
|
||||||
}
|
|
||||||
void addValue(Value v) override
|
|
||||||
{
|
|
||||||
attrs.insert_or_assign(_key, v);
|
|
||||||
}
|
|
||||||
public:
|
|
||||||
void key(string_t & name, EvalState & state)
|
|
||||||
{
|
|
||||||
_key = state.ctx.symbols.create(name);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class JSONListState : public JSONState {
|
std::optional<Value> final_value;
|
||||||
GcVector<Value> values;
|
|
||||||
std::unique_ptr<JSONState> resolve(EvalState & state) override
|
void addValue(Value v)
|
||||||
{
|
{
|
||||||
auto list = state.ctx.mem.newList(values.size());
|
if (stack.size() == 0) {
|
||||||
parent->addValue({NewValueAs::list, list});
|
assert(!final_value);
|
||||||
for (size_t n = 0; n < values.size(); ++n) {
|
final_value = v;
|
||||||
list->elems[n] = values[n];
|
return;
|
||||||
}
|
|
||||||
return std::move(parent);
|
|
||||||
}
|
}
|
||||||
void addValue(Value v) override
|
std::visit(
|
||||||
{
|
overloaded{
|
||||||
values.push_back(v);
|
[&](WIPObject & currentObject) {
|
||||||
}
|
currentObject.entries.insert_or_assign(currentObject.nextKey, v);
|
||||||
public:
|
currentObject.nextKey = {}; // clear the current symbol
|
||||||
JSONListState(std::unique_ptr<JSONState> && p, std::size_t reserve) : JSONState(std::move(p))
|
},
|
||||||
{
|
[&](WIPArray & arr) { arr.push_back(v); }
|
||||||
values.reserve(reserve);
|
},
|
||||||
}
|
stack.back()
|
||||||
};
|
);
|
||||||
|
}
|
||||||
|
|
||||||
EvalState & state;
|
EvalState & state;
|
||||||
std::unique_ptr<JSONState> rs;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
JSONSax(EvalState & state) : state(state), rs(new TopLevelJSONState()) {};
|
JSONSax(EvalState & state) : state(state) {};
|
||||||
|
|
||||||
Value result()
|
Value result()
|
||||||
{
|
{
|
||||||
return rs->finalValue();
|
return final_value.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool null() override
|
bool null() override
|
||||||
{
|
{
|
||||||
rs->addValue(Value::VNULL);
|
addValue(Value::VNULL);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool boolean(bool val) override
|
bool boolean(bool val) override
|
||||||
{
|
{
|
||||||
rs->addValue({NewValueAs::boolean, val});
|
addValue({NewValueAs::boolean, val});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool number_integer(number_integer_t val) override
|
bool number_integer(number_integer_t val) override
|
||||||
{
|
{
|
||||||
rs->addValue({NewValueAs::integer, val});
|
addValue({NewValueAs::integer, val});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,19 +81,19 @@ public:
|
|||||||
return number_float(static_cast<number_float_t>(val_), "");
|
return number_float(static_cast<number_float_t>(val_), "");
|
||||||
}
|
}
|
||||||
NixInt::Inner val = val_;
|
NixInt::Inner val = val_;
|
||||||
rs->addValue({NewValueAs::integer, val});
|
addValue({NewValueAs::integer, val});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool number_float(number_float_t val, const string_t & s) override
|
bool number_float(number_float_t val, const string_t & s) override
|
||||||
{
|
{
|
||||||
rs->addValue({NewValueAs::floating, val});
|
addValue({NewValueAs::floating, val});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool string(string_t & val) override
|
bool string(string_t & val) override
|
||||||
{
|
{
|
||||||
rs->addValue({NewValueAs::string, val});
|
addValue({NewValueAs::string, val});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,28 +108,66 @@ public:
|
|||||||
|
|
||||||
bool start_object(std::size_t len) override
|
bool start_object(std::size_t len) override
|
||||||
{
|
{
|
||||||
rs = std::make_unique<JSONObjectState>(std::move(rs));
|
stack.emplace_back(std::in_place_type<WIPObject>);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool key(string_t & name) override
|
bool key(string_t & name) override
|
||||||
{
|
{
|
||||||
dynamic_cast<JSONObjectState*>(rs.get())->key(name, state);
|
auto & back = stack.back();
|
||||||
|
std::visit(
|
||||||
|
overloaded{
|
||||||
|
[&](WIPObject & frame) {
|
||||||
|
assert(!frame.nextKey);
|
||||||
|
frame.nextKey = state.ctx.symbols.create(name);
|
||||||
|
},
|
||||||
|
[](const WIPArray &) { assert(false && "tried adding a json key to an array value??"); }
|
||||||
|
},
|
||||||
|
back
|
||||||
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool end_object() override {
|
bool end_object() override {
|
||||||
rs = rs->resolve(state);
|
auto back = std::move(stack.back());
|
||||||
|
stack.pop_back();
|
||||||
|
std::visit(
|
||||||
|
overloaded{
|
||||||
|
[&](const WIPObject & frame) {
|
||||||
|
auto attrs2 = state.ctx.buildBindings(frame.entries.size());
|
||||||
|
for (auto & i : frame.entries) {
|
||||||
|
attrs2.insert(i.first, i.second);
|
||||||
|
}
|
||||||
|
addValue({NewValueAs::attrs, attrs2.alreadySorted()});
|
||||||
|
},
|
||||||
|
[](const WIPArray &) { assert(false && "tried to close a JSON object while in an array"); }
|
||||||
|
},
|
||||||
|
back
|
||||||
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool end_array() override {
|
bool end_array() override {
|
||||||
return end_object();
|
auto back = std::move(stack.back());
|
||||||
|
stack.pop_back();
|
||||||
|
std::visit(
|
||||||
|
overloaded{
|
||||||
|
[](const WIPObject &) { assert(false && "tried to close a JSON array while in an object"); },
|
||||||
|
[&](const WIPArray & values) {
|
||||||
|
auto list = state.ctx.mem.newList(values.size());
|
||||||
|
std::ranges::copy(values, list->elems);
|
||||||
|
addValue({NewValueAs::list, list});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
back
|
||||||
|
);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool start_array(size_t len) override {
|
bool start_array(size_t len) override {
|
||||||
rs = std::make_unique<JSONListState>(std::move(rs),
|
auto v = WIPArray{};
|
||||||
len != std::numeric_limits<size_t>::max() ? len : 128);
|
v.reserve(len != std::numeric_limits<size_t>::max() ? len : 128);
|
||||||
|
stack.push_back(std::move(v));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -99,13 +99,6 @@ JSON ExprVar::toJSON(const SymbolTable & symbols) const
|
|||||||
return {{"_type", "ExprVar"}, {"value", stringToJSON(symbols[name])}};
|
return {{"_type", "ExprVar"}, {"value", stringToJSON(symbols[name])}};
|
||||||
}
|
}
|
||||||
|
|
||||||
JSON ExprInheritFrom::toJSON(SymbolTable const & symbols) const
|
|
||||||
{
|
|
||||||
return {
|
|
||||||
{"_type", "ExprInheritFrom"}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
JSON ExprSelect::toJSON(const SymbolTable & symbols) const
|
JSON ExprSelect::toJSON(const SymbolTable & symbols) const
|
||||||
{
|
{
|
||||||
JSON out = {
|
JSON out = {
|
||||||
@@ -747,17 +740,6 @@ void ExprLambda::setName(Symbol name)
|
|||||||
body->setName(name);
|
body->setName(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::string ExprLambda::showNamePos(const EvalState & state) const
|
|
||||||
{
|
|
||||||
std::string id(name
|
|
||||||
? concatStrings("'", state.ctx.symbols[name], "'")
|
|
||||||
: "anonymous function");
|
|
||||||
return fmt("%1% at %2%", id, state.ctx.positions[pos]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Position table. */
|
/* Position table. */
|
||||||
|
|
||||||
Pos PosTable::operator[](PosIdx p) const
|
Pos PosTable::operator[](PosIdx p) const
|
||||||
|
|||||||
+12
-3
@@ -126,9 +126,10 @@ public:
|
|||||||
std::unique_ptr<Expr> parsed, Evaluator & es, const std::shared_ptr<const StaticEnv> & env
|
std::unique_ptr<Expr> parsed, Evaluator & es, const std::shared_ptr<const StaticEnv> & env
|
||||||
);
|
);
|
||||||
|
|
||||||
virtual JSON toJSON(const SymbolTable & symbols) const;
|
virtual JSON toJSON(const SymbolTable & symbols) const = 0;
|
||||||
virtual void accept(ExprVisitor & ev, std::unique_ptr<Expr> & ptr) = 0;
|
virtual void accept(ExprVisitor & ev, std::unique_ptr<Expr> & ptr) = 0;
|
||||||
virtual Value eval(EvalState & state, Env & env);
|
virtual Value eval(EvalState & state, Env & env);
|
||||||
|
Value makeThunk(Evaluator & ctx, Env & env);
|
||||||
virtual Value maybeThunk(EvalState & state, Env & env);
|
virtual Value maybeThunk(EvalState & state, Env & env);
|
||||||
/* Lambdas have a name associated with them, when they are declared in a binding:
|
/* Lambdas have a name associated with them, when they are declared in a binding:
|
||||||
* `identity = x: x` will print the resulting value as `«lambda identity @ «string»:1:14»`.
|
* `identity = x: x` will print the resulting value as `«lambda identity @ «string»:1:14»`.
|
||||||
@@ -302,7 +303,11 @@ struct ExprInheritFrom : Expr
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
JSON toJSON(const SymbolTable & symbols) const override;
|
JSON toJSON(const SymbolTable & symbols) const override
|
||||||
|
{
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
Value eval(EvalState & state, Env & env) override;
|
Value eval(EvalState & state, Env & env) override;
|
||||||
void accept(ExprVisitor & ev, std::unique_ptr<Expr> & ptr) override { ev.visit(*this, ptr); }
|
void accept(ExprVisitor & ev, std::unique_ptr<Expr> & ptr) override { ev.visit(*this, ptr); }
|
||||||
};
|
};
|
||||||
@@ -517,7 +522,6 @@ struct ExprLambda : Expr
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
void setName(Symbol name) override;
|
void setName(Symbol name) override;
|
||||||
std::string showNamePos(const EvalState & state) const;
|
|
||||||
|
|
||||||
/** Returns the name of the lambda,
|
/** Returns the name of the lambda,
|
||||||
* or "anonymous lambda" if it doesn't have one.
|
* or "anonymous lambda" if it doesn't have one.
|
||||||
@@ -656,6 +660,11 @@ struct ExprPos : Expr
|
|||||||
/* only used to mark thunks as black holes. */
|
/* only used to mark thunks as black holes. */
|
||||||
struct ExprBlackHole : Expr
|
struct ExprBlackHole : Expr
|
||||||
{
|
{
|
||||||
|
JSON toJSON(const SymbolTable & symbols) const override
|
||||||
|
{
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
Value eval(EvalState & state, Env & env) override;
|
Value eval(EvalState & state, Env & env) override;
|
||||||
void accept(ExprVisitor & ev, std::unique_ptr<Expr> & ptr) override { ev.visit(*this, ptr); }
|
void accept(ExprVisitor & ev, std::unique_ptr<Expr> & ptr) override { ev.visit(*this, ptr); }
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -133,14 +133,24 @@ inline void State::badEscapeFound(const PosIdx pos, char found, bool isIndented)
|
|||||||
{
|
{
|
||||||
auto escape = std::string(isIndented ? "''\\" : "\\");
|
auto escape = std::string(isIndented ? "''\\" : "\\");
|
||||||
auto interpolEscape = std::string(isIndented ? "''${" : "\\${");
|
auto interpolEscape = std::string(isIndented ? "''${" : "\\${");
|
||||||
|
auto backslashChar = std::string("\\") + found;
|
||||||
|
|
||||||
|
HintFmt dedicatedInsert = isIndented
|
||||||
|
? HintFmt("you can simply write it as %s in the string", backslashChar)
|
||||||
|
: HintFmt("you need to escape the %s itself: %s", escape, escape + escape + found);
|
||||||
|
|
||||||
HintFmt msg = HintFmt(
|
HintFmt msg = HintFmt(
|
||||||
"%s is an ill-defined escape. You can drop the %s and simply write %s instead. "
|
"%s is an ill-defined escape sequence. In Nix, it simply means %s, therefore the %s is redundant and "
|
||||||
|
"should be removed. If the intent of the string was to mean %s instead (e.g. in a regex), %s. "
|
||||||
"Use %s to silence this warning.",
|
"Use %s to silence this warning.",
|
||||||
escape + found,
|
escape + found,
|
||||||
escape,
|
|
||||||
found,
|
found,
|
||||||
|
escape,
|
||||||
|
backslashChar,
|
||||||
|
Uncolored(dedicatedInsert.str()),
|
||||||
"--extra-deprecated-features broken-string-escape"
|
"--extra-deprecated-features broken-string-escape"
|
||||||
);
|
);
|
||||||
|
|
||||||
/* Special case some common escapes to provide better messages */
|
/* Special case some common escapes to provide better messages */
|
||||||
if (found == '$' || found == '{') {
|
if (found == '$' || found == '{') {
|
||||||
/* Someone possibly tried to escape an interpolation but used the wrong sequence.
|
/* Someone possibly tried to escape an interpolation but used the wrong sequence.
|
||||||
|
|||||||
+79
-6
@@ -1,3 +1,4 @@
|
|||||||
|
#include "libutil/error-trace.hh"
|
||||||
#include "lix/libutil/archive.hh"
|
#include "lix/libutil/archive.hh"
|
||||||
#include "lix/libstore/derivations.hh"
|
#include "lix/libstore/derivations.hh"
|
||||||
#include "lix/libexpr/eval.hh"
|
#include "lix/libexpr/eval.hh"
|
||||||
@@ -36,6 +37,7 @@
|
|||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
#include <cfenv>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
@@ -300,8 +302,16 @@ Value prim_importNative(EvalState & state, Value ** args)
|
|||||||
state.ctx.errors.make<EvalError>("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected", sym, path).debugThrow();
|
state.ctx.errors.make<EvalError>("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected", sym, path).debugThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default construction of `Value` is deprecated, and uses of `Value&` out parameters
|
||||||
|
// have mostly been removed in favor of returning a `Value` instead. However in this
|
||||||
|
// particular case, this signature (ValueInitializer defined above) is externally visible,
|
||||||
|
// changing it would be an API breaking change, so for this one instance we just suppress
|
||||||
|
// the warning instead.
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||||
Value v;
|
Value v;
|
||||||
(func)(state, v);
|
(func)(state, v);
|
||||||
|
#pragma clang diagnostic pop
|
||||||
|
|
||||||
/* We don't dlclose because v may be a primop referencing a function in the shared object file */
|
/* We don't dlclose because v may be a primop referencing a function in the shared object file */
|
||||||
return v;
|
return v;
|
||||||
@@ -690,22 +700,85 @@ static Value prim_addErrorContext(EvalState & state, Value ** args)
|
|||||||
auto message = state.coerceToString(noPos, *args[0], context,
|
auto message = state.coerceToString(noPos, *args[0], context,
|
||||||
"while evaluating the error message passed to builtins.addErrorContext",
|
"while evaluating the error message passed to builtins.addErrorContext",
|
||||||
StringCoercionMode::Strict, false).toOwned();
|
StringCoercionMode::Strict, false).toOwned();
|
||||||
e.addTrace(nullptr, HintFmt(message));
|
e.addTrace(nullptr, HintFmt(message), TraceKind::UserTrace);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::optional<NixInt> floatToIntChecked(NixFloat f)
|
||||||
|
{
|
||||||
|
// Required to detect overflows when converting floats to integers
|
||||||
|
#pragma STDC FENV_ACCESS ON
|
||||||
|
|
||||||
|
std::feclearexcept(FE_ALL_EXCEPT);
|
||||||
|
auto converted = llrint(f);
|
||||||
|
if (std::fetestexcept(FE_ALL_EXCEPT)) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return NixInt{converted};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Note [floor/ceil corrupt integers]:
|
||||||
|
Integers above 2**54 that aren't a power of two get corrupted when passed
|
||||||
|
through floor/ceil.
|
||||||
|
|
||||||
|
Corrupting integers would become impossible if we just passed through integer
|
||||||
|
inputs, since the corruption actually happens when casting from int to float,
|
||||||
|
*not* from float to int, which is the one we actually safely cast.
|
||||||
|
|
||||||
|
Floats generate an error as intended if they are out of range.
|
||||||
|
|
||||||
|
In a future release, we'd like to pass through all integers, but it would be
|
||||||
|
an eval semantics change, so it's safer to error first before relaxing the
|
||||||
|
semantics again.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using FloorCeilFunc = auto (*)(NixFloat) -> NixFloat;
|
||||||
|
|
||||||
|
static Value
|
||||||
|
floorCeil(std::string_view const which, FloorCeilFunc f, EvalState & state, NixFloat value, Value * arg0)
|
||||||
|
{
|
||||||
|
bool isInt = arg0->type() == nInt;
|
||||||
|
|
||||||
|
NixFloat result = f(value);
|
||||||
|
|
||||||
|
if (auto checked = floatToIntChecked(result); checked.has_value()) {
|
||||||
|
// See Note [floor/ceil corrupt integers].
|
||||||
|
if (isInt && *checked != arg0->integer()
|
||||||
|
&& !featureSettings.isEnabled(DeprecatedFeature::FloorCeilCorruptIntegers))
|
||||||
|
{
|
||||||
|
state.ctx.errors
|
||||||
|
.make<EvalError>(
|
||||||
|
"%s was corrupting your integer (was %d, became %d) in previous versions due to a "
|
||||||
|
"historical Nix bug (https://github.com/NixOS/nix/issues/12899).\n"
|
||||||
|
"This may be changed in the future to pass through integers as-is, which will change the "
|
||||||
|
"semantics of this code.\n"
|
||||||
|
"To suppress this error, use %s",
|
||||||
|
which,
|
||||||
|
arg0->integer(),
|
||||||
|
*checked,
|
||||||
|
"--extra-deprecated-features floor-ceil-corrupt-integers"
|
||||||
|
)
|
||||||
|
.debugThrow();
|
||||||
|
}
|
||||||
|
return {NewValueAs::integer, NixInt::Inner(*checked)};
|
||||||
|
} else {
|
||||||
|
state.ctx.errors.make<EvalError>("%s result %f is out of range for Nix integer (i64)", which, result)
|
||||||
|
.debugThrow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static Value prim_ceil(EvalState & state, Value ** args)
|
static Value prim_ceil(EvalState & state, Value ** args)
|
||||||
{
|
{
|
||||||
auto value = state.forceFloat(*args[0], noPos,
|
auto value = state.forceFloat(*args[0], noPos, "while evaluating the argument passed to builtins.ceil");
|
||||||
"while evaluating the first argument passed to builtins.ceil");
|
return floorCeil("builtins.ceil", ceil, state, value, args[0]);
|
||||||
return {NewValueAs::integer, NixInt::Inner(ceil(value))};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Value prim_floor(EvalState & state, Value ** args)
|
static Value prim_floor(EvalState & state, Value ** args)
|
||||||
{
|
{
|
||||||
auto value = state.forceFloat(*args[0], noPos, "while evaluating the first argument passed to builtins.floor");
|
auto value = state.forceFloat(*args[0], noPos, "while evaluating the argument passed to builtins.floor");
|
||||||
return {NewValueAs::integer, NixInt::Inner(floor(value))};
|
return floorCeil("builtins.floor", floor, state, value, args[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Try evaluating the argument. Success => {success=true; value=something;},
|
/* Try evaluating the argument. Success => {success=true; value=something;},
|
||||||
|
|||||||
@@ -308,10 +308,16 @@ static Value fetch(
|
|||||||
// https://github.com/NixOS/nix/issues/4313
|
// https://github.com/NixOS/nix/issues/4313
|
||||||
auto storePath = unpack
|
auto storePath = unpack
|
||||||
? state.aio
|
? state.aio
|
||||||
.blockOn(fetchers::downloadTarball(state.ctx.store, *url, name, (bool) expectedHash))
|
.blockOn(
|
||||||
|
fetchers::downloadTarball(
|
||||||
|
state.ctx.store, *url, name, expectedHash != Hash(HashType::SHA256)
|
||||||
|
)
|
||||||
|
)
|
||||||
.tree.storePath
|
.tree.storePath
|
||||||
: state.aio
|
: state.aio
|
||||||
.blockOn(fetchers::downloadFile(state.ctx.store, *url, name, (bool) expectedHash))
|
.blockOn(
|
||||||
|
fetchers::downloadFile(state.ctx.store, *url, name, expectedHash != Hash(HashType::SHA256))
|
||||||
|
)
|
||||||
.storePath;
|
.storePath;
|
||||||
|
|
||||||
if (expectedHash) {
|
if (expectedHash) {
|
||||||
|
|||||||
@@ -53,15 +53,6 @@ void Value::print(EvalState & state, std::ostream & str, PrintOptions options)
|
|||||||
printValue(state, str, *this, options);
|
printValue(state, str, *this, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Value::isTrivial() const
|
|
||||||
{
|
|
||||||
return internalType() != tApp
|
|
||||||
&& (internalType() != tThunk
|
|
||||||
|| (thunk().expr->try_cast<ExprSet>()
|
|
||||||
&& static_cast<ExprSet *>(thunk().expr)->dynamicAttrs.empty())
|
|
||||||
|| thunk().expr->try_cast<ExprLambda>() || thunk().expr->try_cast<ExprList>());
|
|
||||||
}
|
|
||||||
|
|
||||||
Value::Value(string_t, Str * s, const NixStringContext & context)
|
Value::Value(string_t, Str * s, const NixStringContext & context)
|
||||||
: Value(NewValueAs::string, s, copyContext(context))
|
: Value(NewValueAs::string, s, copyContext(context))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -284,6 +284,7 @@ private:
|
|||||||
|
|
||||||
InternalType internalType() const
|
InternalType internalType() const
|
||||||
{
|
{
|
||||||
|
// NOLINTNEXTLINE(lix-cast-to-non-fixed-enum): TAG_MASK ensures it's in range
|
||||||
return InternalType(raw & TAG_MASK);
|
return InternalType(raw & TAG_MASK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -723,6 +724,8 @@ public:
|
|||||||
|
|
||||||
Type type() const
|
Type type() const
|
||||||
{
|
{
|
||||||
|
// TAG_MASK == 7, max enumerator is 5, so all possible values are in range
|
||||||
|
// NOLINTNEXTLINE(lix-cast-to-non-fixed-enum)
|
||||||
return Type(raw & TAG_MASK);
|
return Type(raw & TAG_MASK);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -775,13 +778,6 @@ public:
|
|||||||
|
|
||||||
size_t listSize() const;
|
size_t listSize() const;
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether forcing this value requires a trivial amount of
|
|
||||||
* computation. In particular, function applications are
|
|
||||||
* non-trivial.
|
|
||||||
*/
|
|
||||||
bool isTrivial() const;
|
|
||||||
|
|
||||||
auto listItems() const
|
auto listItems() const
|
||||||
{
|
{
|
||||||
struct ListIterable
|
struct ListIterable
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#include "lix/libfetchers/cache.hh"
|
#include "lix/libfetchers/cache.hh"
|
||||||
|
#include "libstore/pathlocks.hh"
|
||||||
|
#include "libutil/hash.hh"
|
||||||
#include "lix/libstore/sqlite.hh"
|
#include "lix/libstore/sqlite.hh"
|
||||||
#include "lix/libutil/async.hh"
|
#include "lix/libutil/async.hh"
|
||||||
#include "lix/libutil/sync.hh"
|
#include "lix/libutil/sync.hh"
|
||||||
@@ -90,6 +92,28 @@ struct CacheImpl : Cache
|
|||||||
co_return result::current_exception();
|
co_return result::current_exception();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kj::Promise<Result<std::variant<std::pair<Attrs, StorePath>, PathLock>>> lookupOrLock(ref<Store> store, const Attrs & inAttrs) override try {
|
||||||
|
// In order to avoid fetching the same input multiple times
|
||||||
|
// concurrently, we first acquire a lock based on the input attributes.
|
||||||
|
auto hashResult = hashString(HashType::SHA256, attrsToJSON(inAttrs).dump());
|
||||||
|
auto lockPath = getCacheDir() + "/nix/fetcher-lock-" + hashResult.to_string(HashFormat::Base32, false);
|
||||||
|
auto pathLock = TRY_AWAIT(lockPathAsync(lockPath));
|
||||||
|
// Once this lock is acquired, we check if the input is already present
|
||||||
|
// in the cache (which requires locking the cache db)
|
||||||
|
auto lookedUp = TRY_AWAIT(lookup(store, inAttrs));
|
||||||
|
// We return either the cache hit or the lock; this allows fetchers to
|
||||||
|
// keep a lock on fetching the input in question if it's not already in
|
||||||
|
// the cache, and otherwise frees the cache db up again.
|
||||||
|
std::variant<std::pair<Attrs, StorePath>, PathLock> result(std::move(pathLock));
|
||||||
|
if (lookedUp) {
|
||||||
|
// Reassigning the variant frees the lock
|
||||||
|
result = *lookedUp;
|
||||||
|
}
|
||||||
|
co_return result;
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
kj::Promise<Result<std::optional<LookupResult>>> lookupExpired(
|
kj::Promise<Result<std::optional<LookupResult>>> lookupExpired(
|
||||||
ref<Store> store,
|
ref<Store> store,
|
||||||
const Attrs & inAttrs) override
|
const Attrs & inAttrs) override
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
|
#include "libstore/pathlocks.hh"
|
||||||
#include "lix/libfetchers/fetchers.hh"
|
#include "lix/libfetchers/fetchers.hh"
|
||||||
#include "lix/libstore/path.hh"
|
#include "lix/libstore/path.hh"
|
||||||
|
|
||||||
@@ -21,6 +22,10 @@ struct Cache
|
|||||||
ref<Store> store,
|
ref<Store> store,
|
||||||
const Attrs & inAttrs) = 0;
|
const Attrs & inAttrs) = 0;
|
||||||
|
|
||||||
|
virtual kj::Promise<Result<std::variant<std::pair<Attrs, StorePath>, PathLock>>> lookupOrLock(
|
||||||
|
ref<Store> store,
|
||||||
|
const Attrs & inAttrs) = 0;
|
||||||
|
|
||||||
struct LookupResult
|
struct LookupResult
|
||||||
{
|
{
|
||||||
bool expired = false;
|
bool expired = false;
|
||||||
|
|||||||
+43
-16
@@ -1,3 +1,4 @@
|
|||||||
|
#include "libfetchers/attrs.hh"
|
||||||
#include "lix/libutil/archive.hh"
|
#include "lix/libutil/archive.hh"
|
||||||
#include "lix/libutil/async-io.hh"
|
#include "lix/libutil/async-io.hh"
|
||||||
#include "lix/libutil/async.hh"
|
#include "lix/libutil/async.hh"
|
||||||
@@ -15,9 +16,10 @@
|
|||||||
#include "lix/libutil/url-parts.hh"
|
#include "lix/libutil/url-parts.hh"
|
||||||
#include "lix/libstore/pathlocks.hh"
|
#include "lix/libstore/pathlocks.hh"
|
||||||
#include "lix/libutil/users.hh"
|
#include "lix/libutil/users.hh"
|
||||||
#include "lix/libutil/git.hh"
|
|
||||||
#include "lix/libutil/logging.hh"
|
#include "lix/libutil/logging.hh"
|
||||||
#include "lix/libutil/finally.hh"
|
#include "lix/libutil/finally.hh"
|
||||||
|
#include "lix/lix-rs/main.gen.hh"
|
||||||
|
#include "lix/lix-rs/utils.hh"
|
||||||
|
|
||||||
#include "lix/libfetchers/fetch-settings.hh"
|
#include "lix/libfetchers/fetch-settings.hh"
|
||||||
|
|
||||||
@@ -80,17 +82,17 @@ try {
|
|||||||
));
|
));
|
||||||
|
|
||||||
std::string_view line = output;
|
std::string_view line = output;
|
||||||
line = line.substr(0, line.find("\n"));
|
auto line_rs = rust::to_string(line.substr(0, line.find("\n")));
|
||||||
if (const auto parseResult = git::parseLsRemoteLine(line)) {
|
if (const auto parseResult =
|
||||||
switch (parseResult->kind) {
|
to_std(rust::lix::fetchers::git::LsRemoteRefLine::try_from(line_rs.as_str()).ok()))
|
||||||
case git::LsRemoteRefLine::Kind::Symbolic:
|
{
|
||||||
debug("resolved HEAD ref '%s' for repo '%s'", parseResult->target, path);
|
auto target = to_std_string(parseResult->target.as_str());
|
||||||
break;
|
if (parseResult->kind.matches_Object()) {
|
||||||
case git::LsRemoteRefLine::Kind::Object:
|
debug("resolved HEAD rev '%s' for repo '%s'", target, path);
|
||||||
debug("resolved HEAD rev '%s' for repo '%s'", parseResult->target, path);
|
} else {
|
||||||
break;
|
debug("resolved HEAD ref '%s' for repo '%s'", target, path);
|
||||||
}
|
}
|
||||||
co_return parseResult->target;
|
co_return to_std_string(parseResult->target.as_str());
|
||||||
}
|
}
|
||||||
co_return std::nullopt;
|
co_return std::nullopt;
|
||||||
} catch (ExecError &) {
|
} catch (ExecError &) {
|
||||||
@@ -605,9 +607,18 @@ struct GitInputScheme : InputScheme
|
|||||||
return {std::move(storePath), input};
|
return {std::move(storePath), input};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// If the input isn't present in the fetcher cache, we get a lock for
|
||||||
|
// fetching the given input. We want to hold this lock until we're done
|
||||||
|
// fetching, so we keep it in this scope.
|
||||||
|
std::optional<PathLock> fetchLock;
|
||||||
if (input.getRev()) {
|
if (input.getRev()) {
|
||||||
if (auto res = TRY_AWAIT(getCache()->lookup(store, getLockedAttrs())))
|
auto resOrLock = TRY_AWAIT(getCache()->lookupOrLock(store, getLockedAttrs()));
|
||||||
|
|
||||||
|
if (auto res = std::get_if<std::pair<Attrs, StorePath>>(&resOrLock))
|
||||||
co_return makeResult(res->first, std::move(res->second));
|
co_return makeResult(res->first, std::move(res->second));
|
||||||
|
auto lock = std::get_if<PathLock>(&resOrLock);
|
||||||
|
assert(lock);
|
||||||
|
fetchLock = std::move(*lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto [isLocal, actualUrl_] = getActualUrl(input);
|
auto [isLocal, actualUrl_] = getActualUrl(input);
|
||||||
@@ -676,7 +687,10 @@ struct GitInputScheme : InputScheme
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto res = TRY_AWAIT(getCache()->lookup(store, unlockedAttrs))) {
|
// If the input isn't in the cache, we get a lock for fetching it.
|
||||||
|
// Make sure to keep this until we're done fetching!
|
||||||
|
auto cached = TRY_AWAIT(getCache()->lookupOrLock(store, unlockedAttrs));
|
||||||
|
if (auto res = std::get_if<std::pair<Attrs, StorePath>>(&cached)) {
|
||||||
auto rev2 = Hash::parseAny(getStrAttr(res->first, "rev"), HashType::SHA1);
|
auto rev2 = Hash::parseAny(getStrAttr(res->first, "rev"), HashType::SHA1);
|
||||||
if (!input.getRev() || input.getRev() == rev2) {
|
if (!input.getRev() || input.getRev() == rev2) {
|
||||||
input.attrs.insert_or_assign("rev", rev2.gitRev());
|
input.attrs.insert_or_assign("rev", rev2.gitRev());
|
||||||
@@ -858,8 +872,21 @@ struct GitInputScheme : InputScheme
|
|||||||
|
|
||||||
/* Now that we know the ref, check again whether we have it in
|
/* Now that we know the ref, check again whether we have it in
|
||||||
the store. */
|
the store. */
|
||||||
if (auto res = TRY_AWAIT(getCache()->lookup(store, getLockedAttrs())))
|
// If we already have a lock for fetching this path, we can't use lookupOrLock because it would deadlock.
|
||||||
co_return makeResult(res->first, std::move(res->second));
|
if (fetchLock) {
|
||||||
|
if (auto res = TRY_AWAIT(getCache()->lookup(store, getLockedAttrs()))) {
|
||||||
|
co_return makeResult(res->first, std::move(res->second));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If we don't have the lock, we need to acquire it by using lookupOrLock.
|
||||||
|
auto resultOrLock = TRY_AWAIT(getCache()->lookupOrLock(store, getLockedAttrs()));
|
||||||
|
if (auto res = std::get_if<std::pair<Attrs, StorePath>>(&resultOrLock)) {
|
||||||
|
co_return makeResult(res->first, std::move(res->second));
|
||||||
|
}
|
||||||
|
auto lock = std::get_if<PathLock>(&resultOrLock);
|
||||||
|
assert(lock);
|
||||||
|
fetchLock = std::move(*lock);
|
||||||
|
}
|
||||||
|
|
||||||
Path tmpDir = createTempDir();
|
Path tmpDir = createTempDir();
|
||||||
AutoDelete delTmpDir(tmpDir, true);
|
AutoDelete delTmpDir(tmpDir, true);
|
||||||
@@ -974,7 +1001,7 @@ struct GitInputScheme : InputScheme
|
|||||||
});
|
});
|
||||||
Finally const _wait([&] { proc.waitAndCheck(); });
|
Finally const _wait([&] { proc.waitAndCheck(); });
|
||||||
|
|
||||||
TRY_AWAIT(unpackTarfile(*proc.getStdout(), tmpDir));
|
TRY_AWAIT(unpackTarfile(_input.toURLString(), *proc.getStdout(), tmpDir));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto storePath = TRY_AWAIT(
|
auto storePath = TRY_AWAIT(
|
||||||
|
|||||||
@@ -7,10 +7,11 @@
|
|||||||
#include "lix/libutil/result.hh"
|
#include "lix/libutil/result.hh"
|
||||||
#include "lix/libutil/types.hh"
|
#include "lix/libutil/types.hh"
|
||||||
#include "lix/libutil/url-parts.hh"
|
#include "lix/libutil/url-parts.hh"
|
||||||
#include "lix/libutil/git.hh"
|
|
||||||
#include "lix/libutil/json.hh"
|
#include "lix/libutil/json.hh"
|
||||||
#include "lix/libfetchers/fetchers.hh"
|
#include "lix/libfetchers/fetchers.hh"
|
||||||
#include "lix/libfetchers/fetch-settings.hh"
|
#include "lix/libfetchers/fetch-settings.hh"
|
||||||
|
#include "lix/lix-rs/main.gen.hh"
|
||||||
|
#include "lix/lix-rs/utils.hh"
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@@ -486,15 +487,16 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
|||||||
std::string line;
|
std::string line;
|
||||||
getline(is, line);
|
getline(is, line);
|
||||||
|
|
||||||
auto remoteLine = git::parseLsRemoteLine(line);
|
auto remoteLine = to_std(
|
||||||
|
rust::lix::fetchers::git::LsRemoteRefLine::try_from(rust::to_string(line).as_str()).ok()
|
||||||
|
);
|
||||||
if (!remoteLine) {
|
if (!remoteLine) {
|
||||||
throw BadURL("in '%d', couldn't resolve HEAD ref '%d'", input.to_string(), ref);
|
throw BadURL("in '%d', couldn't resolve HEAD ref '%d'", input.to_string(), ref);
|
||||||
}
|
}
|
||||||
refUri = remoteLine->target;
|
refUri = to_std_string(remoteLine->target.as_str());
|
||||||
} else {
|
} else {
|
||||||
refUri = fmt("refs/(heads|tags)/%s", ref);
|
refUri = fmt("refs/(heads|tags)/%s", ref);
|
||||||
}
|
}
|
||||||
std::regex refRegex = regex::parse(refUri);
|
|
||||||
|
|
||||||
auto file = store->toRealPath(
|
auto file = store->toRealPath(
|
||||||
TRY_AWAIT(downloadFile(store, fmt("%s/info/refs", base_url), "source", false, headers))
|
TRY_AWAIT(downloadFile(store, fmt("%s/info/refs", base_url), "source", false, headers))
|
||||||
@@ -505,9 +507,12 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
|||||||
std::string line;
|
std::string line;
|
||||||
std::optional<std::string> id;
|
std::optional<std::string> id;
|
||||||
while(!id && getline(is, line)) {
|
while(!id && getline(is, line)) {
|
||||||
auto parsedLine = git::parseLsRemoteLine(line);
|
auto parsedLine = to_std(
|
||||||
if (parsedLine && parsedLine->reference && std::regex_match(*parsedLine->reference, refRegex))
|
rust::lix::fetchers::git::LsRemoteRefLine::try_from(rust::to_string(line).as_str()).ok()
|
||||||
id = parsedLine->target;
|
);
|
||||||
|
if (parsedLine && (*parsedLine).matches_ref_uri(rust::to_string(refUri).as_str())) {
|
||||||
|
id = to_std_string(parsedLine->target.as_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!id)
|
if(!id)
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ try {
|
|||||||
} else {
|
} else {
|
||||||
Path tmpDir = createTempDir();
|
Path tmpDir = createTempDir();
|
||||||
AutoDelete autoDelete(tmpDir, true);
|
AutoDelete autoDelete(tmpDir, true);
|
||||||
unpackTarfile(store->toRealPath(res.storePath), tmpDir);
|
unpackTarfile(url, store->toRealPath(res.storePath), tmpDir);
|
||||||
auto members = readDirectory(tmpDir);
|
auto members = readDirectory(tmpDir);
|
||||||
if (members.size() != 1)
|
if (members.size() != 1)
|
||||||
throw nix::Error("tarball '%s' contains an unexpected number of top-level files", url);
|
throw nix::Error("tarball '%s' contains an unexpected number of top-level files", url);
|
||||||
|
|||||||
@@ -16,21 +16,21 @@ MixCommonArgs::MixCommonArgs(const std::string & programName)
|
|||||||
.shortName = 'v',
|
.shortName = 'v',
|
||||||
.description = "Increase the logging verbosity level.",
|
.description = "Increase the logging verbosity level.",
|
||||||
.category = loggingCategory,
|
.category = loggingCategory,
|
||||||
.handler = {[]() { verbosity = verbosityFromIntClamped(int(verbosity) + 1); }},
|
.handler = {[]() { setVerbosity(verbosityFromIntClamped(int(getVerbosity()) + 1)); }},
|
||||||
});
|
});
|
||||||
|
|
||||||
addFlag({
|
addFlag({
|
||||||
.longName = "quiet",
|
.longName = "quiet",
|
||||||
.description = "Decrease the logging verbosity level.",
|
.description = "Decrease the logging verbosity level.",
|
||||||
.category = loggingCategory,
|
.category = loggingCategory,
|
||||||
.handler = {[]() { verbosity = verbosityFromIntClamped(int(verbosity) - 1); }},
|
.handler = {[]() { setVerbosity(verbosityFromIntClamped(int(getVerbosity()) - 1)); }},
|
||||||
});
|
});
|
||||||
|
|
||||||
addFlag({
|
addFlag({
|
||||||
.longName = "debug",
|
.longName = "debug",
|
||||||
.description = "Set the logging verbosity level to 'debug'.",
|
.description = "Set the logging verbosity level to 'debug'.",
|
||||||
.category = loggingCategory,
|
.category = loggingCategory,
|
||||||
.handler = {[]() { verbosity = lvlDebug; }},
|
.handler = {[]() { setVerbosity(lvlDebug); }},
|
||||||
});
|
});
|
||||||
|
|
||||||
addFlag({
|
addFlag({
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ bool ProgressBar::isVerbose()
|
|||||||
|
|
||||||
Logger::BufferState ProgressBar::log(Verbosity lvl, std::string_view s)
|
Logger::BufferState ProgressBar::log(Verbosity lvl, std::string_view s)
|
||||||
{
|
{
|
||||||
if (lvl > verbosity) {
|
if (lvl > getVerbosity()) {
|
||||||
return BufferState::HasSpace;
|
return BufferState::HasSpace;
|
||||||
}
|
}
|
||||||
auto state(state_.lock());
|
auto state(state_.lock());
|
||||||
@@ -142,8 +142,9 @@ Logger::BufferState ProgressBar::startActivityImpl(
|
|||||||
{
|
{
|
||||||
auto state(state_.lock());
|
auto state(state_.lock());
|
||||||
|
|
||||||
if (lvl <= verbosity && !s.empty() && type != actBuildWaiting)
|
if (lvl <= getVerbosity() && !s.empty() && type != actBuildWaiting) {
|
||||||
(void) log(*state, lvl, s + "...");
|
(void) log(*state, lvl, s + "...");
|
||||||
|
}
|
||||||
|
|
||||||
state->activities.emplace_back(ActInfo {
|
state->activities.emplace_back(ActInfo {
|
||||||
.s = s,
|
.s = s,
|
||||||
@@ -197,8 +198,10 @@ Logger::BufferState ProgressBar::startActivityImpl(
|
|||||||
|
|
||||||
if ((type == actFileTransfer && hasAncestor(*state, actCopyPath, parent))
|
if ((type == actFileTransfer && hasAncestor(*state, actCopyPath, parent))
|
||||||
|| (type == actFileTransfer && hasAncestor(*state, actQueryPathInfo, parent))
|
|| (type == actFileTransfer && hasAncestor(*state, actQueryPathInfo, parent))
|
||||||
|| (type == actCopyPath && hasAncestor(*state, actSubstitute, parent)))
|
|| (type == actCopyPath && hasAncestor(*state, actSubstitute, parent)) || (s == "daemon connection"))
|
||||||
|
{
|
||||||
i->visible = false;
|
i->visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
update(*state);
|
update(*state);
|
||||||
return BufferState::HasSpace;
|
return BufferState::HasSpace;
|
||||||
@@ -560,7 +563,7 @@ void ProgressBar::writeToStdout(std::string_view s)
|
|||||||
restoreProgressDisplay(*state);
|
restoreProgressDisplay(*state);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<char> ProgressBar::ask(std::string_view msg)
|
std::optional<std::string> ProgressBar::ask(std::string_view msg)
|
||||||
{
|
{
|
||||||
auto state(state_.lock());
|
auto state(state_.lock());
|
||||||
if (state->paused > 0 || !isatty(STDIN_FILENO)) return {};
|
if (state->paused > 0 || !isatty(STDIN_FILENO)) return {};
|
||||||
@@ -569,9 +572,9 @@ std::optional<char> ProgressBar::ask(std::string_view msg)
|
|||||||
std::cerr << msg;
|
std::cerr << msg;
|
||||||
auto s = trim(readLine(STDIN_FILENO));
|
auto s = trim(readLine(STDIN_FILENO));
|
||||||
writeLogsToStderr("\e[?2026h"); // begin synchronized update
|
writeLogsToStderr("\e[?2026h"); // begin synchronized update
|
||||||
if (s.size() != 1) return {};
|
|
||||||
restoreProgressDisplay(*state);
|
restoreProgressDisplay(*state);
|
||||||
return s[0];
|
// only return the string if it's not empty
|
||||||
|
return s.size() != 0 ? s : std::optional<std::string>{};
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProgressBar::setPrintBuildLogs(bool printBuildLogs)
|
void ProgressBar::setPrintBuildLogs(bool printBuildLogs)
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ struct ProgressBar : public Logger
|
|||||||
|
|
||||||
void writeToStdout(std::string_view s) override;
|
void writeToStdout(std::string_view s) override;
|
||||||
|
|
||||||
std::optional<char> ask(std::string_view msg) override;
|
std::optional<std::string> ask(std::string_view msg) override;
|
||||||
|
|
||||||
void setPrintBuildLogs(bool printBuildLogs) override;
|
void setPrintBuildLogs(bool printBuildLogs) override;
|
||||||
|
|
||||||
|
|||||||
+2
-12
@@ -21,6 +21,7 @@
|
|||||||
#include "lix/libutil/terminal.hh"
|
#include "lix/libutil/terminal.hh"
|
||||||
#include "lix/libutil/strings.hh"
|
#include "lix/libutil/strings.hh"
|
||||||
#include "lix/libutil/exit.hh"
|
#include "lix/libutil/exit.hh"
|
||||||
|
#include "lix/libutil/compile-time-features.hh"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
@@ -175,12 +176,6 @@ void initNix()
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if __APPLE__
|
#if __APPLE__
|
||||||
/* HACK: on darwin, we need can’t use sigprocmask with SIGWINCH.
|
|
||||||
* Instead, add a dummy sigaction handler, and signalHandlerThread
|
|
||||||
* can handle the rest. */
|
|
||||||
act.sa_handler = sigHandler;
|
|
||||||
if (sigaction(SIGWINCH, &act, 0)) throw SysError("handling SIGWINCH");
|
|
||||||
|
|
||||||
/* Disable SA_RESTART for interrupts, so that system calls on this thread
|
/* Disable SA_RESTART for interrupts, so that system calls on this thread
|
||||||
* error with EINTR like they do on Linux.
|
* error with EINTR like they do on Linux.
|
||||||
* Most signals on BSD systems default to SA_RESTART on, but Nix
|
* Most signals on BSD systems default to SA_RESTART on, but Nix
|
||||||
@@ -302,14 +297,9 @@ bool LegacyArgs::processArgs(const Strings & args, bool finish)
|
|||||||
void printVersion(const std::string & programName)
|
void printVersion(const std::string & programName)
|
||||||
{
|
{
|
||||||
std::cout << fmt("%1% (Lix, like Nix) %2%", programName, nixVersion) << std::endl;
|
std::cout << fmt("%1% (Lix, like Nix) %2%", programName, nixVersion) << std::endl;
|
||||||
Strings cfg;
|
|
||||||
#if HAVE_BOEHMGC
|
|
||||||
cfg.push_back("gc");
|
|
||||||
#endif
|
|
||||||
cfg.push_back("signed-caches");
|
|
||||||
std::cout << "System type: " << settings.thisSystem << "\n";
|
std::cout << "System type: " << settings.thisSystem << "\n";
|
||||||
std::cout << "Additional system types: " << concatStringsSep(", ", settings.extraPlatforms.get()) << "\n";
|
std::cout << "Additional system types: " << concatStringsSep(", ", settings.extraPlatforms.get()) << "\n";
|
||||||
std::cout << "Features: " << concatStringsSep(", ", cfg) << "\n";
|
std::cout << "Features: " << concatStringsSep(", ", getNixFeatures()) << "\n";
|
||||||
std::cout << "System configuration file: " << settings.nixConfDir + "/nix.conf" << "\n";
|
std::cout << "System configuration file: " << settings.nixConfDir + "/nix.conf" << "\n";
|
||||||
std::cout << "User configuration files: " <<
|
std::cout << "User configuration files: " <<
|
||||||
concatStringsSep(":", settings.nixUserConfFiles)
|
concatStringsSep(":", settings.nixUserConfFiles)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ struct BuildResult
|
|||||||
* Therefore, don't remove status codes, and only add new status
|
* Therefore, don't remove status codes, and only add new status
|
||||||
* codes at the end of the list.
|
* codes at the end of the list.
|
||||||
*/
|
*/
|
||||||
enum Status {
|
enum Status : uint8_t {
|
||||||
Built = 0,
|
Built = 0,
|
||||||
Substituted,
|
Substituted,
|
||||||
AlreadyValid,
|
AlreadyValid,
|
||||||
|
|||||||
@@ -1094,8 +1094,8 @@ try {
|
|||||||
RPC_FILL(buildReq, setNeededSystem, drv->platform);
|
RPC_FILL(buildReq, setNeededSystem, drv->platform);
|
||||||
RPC_FILL(buildReq, initDrvPath, drvPath, worker.store);
|
RPC_FILL(buildReq, initDrvPath, drvPath, worker.store);
|
||||||
RPC_FILL(buildReq, initRequiredFeatures, parsedDrv->getRequiredSystemFeatures());
|
RPC_FILL(buildReq, initRequiredFeatures, parsedDrv->getRequiredSystemFeatures());
|
||||||
auto buildRespPromise = buildReq.send();
|
auto buildRespV = TRY_AWAIT_RPC(buildReq.send());
|
||||||
auto buildResp = TRY_AWAIT_RPC(buildRespPromise);
|
auto buildResp = buildRespV.getResult();
|
||||||
|
|
||||||
debug("hook reply is '%1%'", buildResp.toString().flatten().cStr());
|
debug("hook reply is '%1%'", buildResp.toString().flatten().cStr());
|
||||||
|
|
||||||
@@ -1136,10 +1136,9 @@ try {
|
|||||||
missingOutputs.insert(outputName);
|
missingOutputs.insert(outputName);
|
||||||
}
|
}
|
||||||
RPC_FILL(runReq, initWantedOutputs, missingOutputs);
|
RPC_FILL(runReq, initWantedOutputs, missingOutputs);
|
||||||
RPC_FILL(runReq, setDescription, buildDescription());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto runPromise = runReq.send();
|
auto runPromise = LIX_WRAP_RPC_PROMISE_V1(runReq.send());
|
||||||
|
|
||||||
// build via hook is now properly running. wait for it to finish
|
// build via hook is now properly running. wait for it to finish
|
||||||
actLock.reset();
|
actLock.reset();
|
||||||
@@ -1150,8 +1149,8 @@ try {
|
|||||||
wrapChildHandler(runPromise.then([&](auto result) -> kj::Promise<Result<WorkResult>> {
|
wrapChildHandler(runPromise.then([&](auto result) -> kj::Promise<Result<WorkResult>> {
|
||||||
try {
|
try {
|
||||||
std::shared_ptr<Error> remoteError;
|
std::shared_ptr<Error> remoteError;
|
||||||
if (result.getResult().isBad()) {
|
if (result.has_error()) {
|
||||||
remoteError = std::make_shared<Error>(from(result.getResult().getBad()));
|
remoteError = std::make_shared<Error>(detail::wrap_exception_as_lix(result.error()));
|
||||||
logErrorInfo(remoteError->info().level, remoteError->info());
|
logErrorInfo(remoteError->info().level, remoteError->info());
|
||||||
}
|
}
|
||||||
// close the rpc connection to have the hook exit
|
// close the rpc connection to have the hook exit
|
||||||
|
|||||||
@@ -360,10 +360,6 @@ public:
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
JobCategory jobCategory() const override {
|
|
||||||
return JobCategory::Build;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
MakeError(NotDeterministic, BuildError);
|
MakeError(NotDeterministic, BuildError);
|
||||||
|
|||||||
@@ -27,23 +27,6 @@ typedef std::shared_ptr<Goal> GoalPtr;
|
|||||||
*/
|
*/
|
||||||
typedef std::set<GoalPtr> Goals;
|
typedef std::set<GoalPtr> Goals;
|
||||||
|
|
||||||
/**
|
|
||||||
* Used as a hint to the worker on how to schedule a particular goal. For example,
|
|
||||||
* builds are typically CPU- and memory-bound, while substitutions are I/O bound.
|
|
||||||
* Using this information, the worker might decide to schedule more or fewer goals
|
|
||||||
* of each category in parallel.
|
|
||||||
*/
|
|
||||||
enum struct JobCategory {
|
|
||||||
/**
|
|
||||||
* A build of a derivation; it will use CPU and disk resources.
|
|
||||||
*/
|
|
||||||
Build,
|
|
||||||
/**
|
|
||||||
* A substitution an arbitrary store object; it will use network resources.
|
|
||||||
*/
|
|
||||||
Substitution,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Goal
|
struct Goal
|
||||||
{
|
{
|
||||||
typedef enum {ecSuccess, ecFailed, ecNoSubstituters, ecIncompleteClosure} ExitCode;
|
typedef enum {ecSuccess, ecFailed, ecNoSubstituters, ecIncompleteClosure} ExitCode;
|
||||||
@@ -139,13 +122,7 @@ public:
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void cleanup() { }
|
virtual void cleanup() {}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Hint for the scheduler, which concurrency limit applies.
|
|
||||||
* @see JobCategory
|
|
||||||
*/
|
|
||||||
virtual JobCategory jobCategory() const = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,14 +8,13 @@ using Types = import "/lix/libutil/types.capnp";
|
|||||||
using Log = import "/lix/libutil/logging.capnp";
|
using Log = import "/lix/libutil/logging.capnp";
|
||||||
using StoreTypes = import "/lix/libstore/types.capnp";
|
using StoreTypes = import "/lix/libstore/types.capnp";
|
||||||
|
|
||||||
interface HookInstance {
|
interface HookInstance $Types.throws(Types.v1Errors) {
|
||||||
interface AcceptedBuild {
|
interface AcceptedBuild {
|
||||||
run @0 (
|
run @0 (
|
||||||
logger :Log.LogStream,
|
logger :Log.LogStream,
|
||||||
inputs :List(StoreTypes.StorePath), # actual a set
|
inputs :List(StoreTypes.StorePath), # actual a set
|
||||||
wantedOutputs :List(Data), # actually StringSet
|
wantedOutputs :List(Data), # actually StringSet
|
||||||
description :Text, # root activity description for this build
|
);
|
||||||
) -> (result :Types.ResultV);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BuildResponse {
|
struct BuildResponse {
|
||||||
@@ -32,11 +31,11 @@ interface HookInstance {
|
|||||||
init @0 (
|
init @0 (
|
||||||
logger :Log.LogStream,
|
logger :Log.LogStream,
|
||||||
settings :Types.Settings,
|
settings :Types.Settings,
|
||||||
) -> (result :Types.ResultV);
|
);
|
||||||
build @1 (
|
build @1 (
|
||||||
amWilling :Bool,
|
amWilling :Bool,
|
||||||
neededSystem :Data,
|
neededSystem :Data,
|
||||||
drvPath :StoreTypes.StorePath,
|
drvPath :StoreTypes.StorePath,
|
||||||
requiredFeatures :List(Data),
|
requiredFeatures :List(Data),
|
||||||
) -> (result :Types.Result(BuildResponse));
|
) -> (result :BuildResponse);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ try {
|
|||||||
|
|
||||||
auto buildHook = canonPath(buildHookArgs.front());
|
auto buildHook = canonPath(buildHookArgs.front());
|
||||||
buildHookArgs.emplace(std::next(buildHookArgs.begin()), baseNameOf(buildHook));
|
buildHookArgs.emplace(std::next(buildHookArgs.begin()), baseNameOf(buildHook));
|
||||||
buildHookArgs.push_back(std::to_string(verbosity));
|
buildHookArgs.push_back(std::to_string(getVerbosity()));
|
||||||
|
|
||||||
/* Create the communication pipes. */
|
/* Create the communication pipes. */
|
||||||
auto [selfRPC, hookRPC] = SocketPair::stream();
|
auto [selfRPC, hookRPC] = SocketPair::stream();
|
||||||
|
|||||||
@@ -897,7 +897,7 @@ try {
|
|||||||
|
|
||||||
if (drv->isBuiltin()) {
|
if (drv->isBuiltin()) {
|
||||||
args.push_back("builtin-builder");
|
args.push_back("builtin-builder");
|
||||||
args.push_back(std::to_string(verbosity));
|
args.push_back(std::to_string(getVerbosity()));
|
||||||
|
|
||||||
std::map<std::string, AbstractConfig::SettingInfo> changedSettings;
|
std::map<std::string, AbstractConfig::SettingInfo> changedSettings;
|
||||||
globalConfig.getChangedSettings(changedSettings);
|
globalConfig.getChangedSettings(changedSettings);
|
||||||
@@ -984,7 +984,7 @@ try {
|
|||||||
auto groups = buildUser->getSupplementaryGIDs();
|
auto groups = buildUser->getSupplementaryGIDs();
|
||||||
creds.setSupplementaryGroups({groups.data(), groups.size()});
|
creds.setSupplementaryGroups({groups.data(), groups.size()});
|
||||||
}
|
}
|
||||||
request.setDebug(verbosity >= lvlDebug);
|
request.setDebug(getVerbosity() >= lvlDebug);
|
||||||
|
|
||||||
fillBuilderConfig(request);
|
fillBuilderConfig(request);
|
||||||
|
|
||||||
@@ -1711,6 +1711,23 @@ try {
|
|||||||
anyHashMismatchSeen = true;
|
anyHashMismatchSeen = true;
|
||||||
// XXX: shameless layering violation hack that makes the hash mismatch error at least not utterly worthless
|
// XXX: shameless layering violation hack that makes the hash mismatch error at least not utterly worthless
|
||||||
auto guessedUrl = getOr(drv->env, "urls", getOr(drv->env, "url", "(unknown)"));
|
auto guessedUrl = getOr(drv->env, "urls", getOr(drv->env, "url", "(unknown)"));
|
||||||
|
if (guessedUrl == "(unknown)") {
|
||||||
|
if (auto structuredAttrs = parsedDrv->getStructuredAttrs()) {
|
||||||
|
auto json = *structuredAttrs;
|
||||||
|
if (json.contains("urls") && json["urls"].is_array() && !json["urls"].empty()) {
|
||||||
|
guessedUrl = "";
|
||||||
|
for (auto it = json["urls"].begin(); it != json["urls"].end(); ++it) {
|
||||||
|
if (!it->is_string()) continue;
|
||||||
|
if (guessedUrl != "") {
|
||||||
|
guessedUrl += " or ";
|
||||||
|
}
|
||||||
|
guessedUrl += it->get<std::string>();
|
||||||
|
}
|
||||||
|
} else if (json.contains("url") && json["url"].is_string()) {
|
||||||
|
guessedUrl = json["url"].get<std::string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
delayedException = std::make_exception_ptr(BuildError(
|
delayedException = std::make_exception_ptr(BuildError(
|
||||||
"hash mismatch in fixed-output derivation '%s':\n likely URL: %s\n "
|
"hash mismatch in fixed-output derivation '%s':\n likely URL: %s\n "
|
||||||
"specified: %s\n got: %s\n expected path: %s\n got path: %s",
|
"specified: %s\n got: %s\n expected path: %s\n got path: %s",
|
||||||
|
|||||||
@@ -90,10 +90,6 @@ public:
|
|||||||
kj::Promise<Result<WorkResult>> referencesValid() noexcept;
|
kj::Promise<Result<WorkResult>> referencesValid() noexcept;
|
||||||
kj::Promise<Result<WorkResult>> tryToRun() noexcept;
|
kj::Promise<Result<WorkResult>> tryToRun() noexcept;
|
||||||
kj::Promise<Result<WorkResult>> finished() noexcept;
|
kj::Promise<Result<WorkResult>> finished() noexcept;
|
||||||
|
|
||||||
JobCategory jobCategory() const override {
|
|
||||||
return JobCategory::Substitution;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ void builtinUnpackChannel(const Path & out, const std::string & channelName, con
|
|||||||
{
|
{
|
||||||
createDirs(out);
|
createDirs(out);
|
||||||
|
|
||||||
unpackTarfile(src, out);
|
unpackTarfile(fmt("channel:%s", channelName), src, out);
|
||||||
|
|
||||||
auto entries = readDirectory(out);
|
auto entries = readDirectory(out);
|
||||||
if (entries.size() != 1)
|
if (entries.size() != 1)
|
||||||
|
|||||||
@@ -0,0 +1,470 @@
|
|||||||
|
#include <cstddef>
|
||||||
|
#include <new>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <curl/urlapi.h>
|
||||||
|
#include <kj/async.h>
|
||||||
|
#include <kj/common.h>
|
||||||
|
#include <kj/encoding.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#if ENABLE_S3
|
||||||
|
#include <aws/core/client/ClientConfiguration.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "lix/libstore/curlfiletransfer.hh"
|
||||||
|
#include "lix/libstore/curlmulti.hh"
|
||||||
|
#include "lix/libstore/filetransfer.hh"
|
||||||
|
#include "lix/libstore/s3.hh"
|
||||||
|
#include "lix/libstore/store-api.hh"
|
||||||
|
#include "lix/libutil/async-io.hh"
|
||||||
|
#include "lix/libutil/async.hh"
|
||||||
|
#include "lix/libutil/box_ptr.hh"
|
||||||
|
#include "lix/libutil/c-calls.hh"
|
||||||
|
#include "lix/libutil/error.hh"
|
||||||
|
#include "lix/libutil/logging.hh"
|
||||||
|
#include "lix/libutil/result.hh"
|
||||||
|
#include "lix/libutil/tracepoint.hh"
|
||||||
|
#include "lix/libutil/types.hh"
|
||||||
|
|
||||||
|
#if ENABLE_DTRACE
|
||||||
|
#include "trace-probes.gen.hh"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
curlFileTransfer::curlFileTransfer(unsigned int baseRetryTimeMs)
|
||||||
|
: multi(std::make_shared<CurlMulti>(baseRetryTimeMs))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
curlFileTransfer::~curlFileTransfer()
|
||||||
|
{
|
||||||
|
multi->stopWorkerThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ENABLE_S3
|
||||||
|
std::tuple<std::string, std::string, StoreConfig::Params> curlFileTransfer::parseS3Uri(std::string uri)
|
||||||
|
{
|
||||||
|
auto [path, params] = splitUriAndParams(uri);
|
||||||
|
|
||||||
|
auto slash = path.find('/', 5); // 5 is the length of "s3://" prefix
|
||||||
|
if (slash == std::string::npos)
|
||||||
|
throw nix::Error("bad S3 URI '%s'", path);
|
||||||
|
|
||||||
|
std::string bucketName(path, 5, slash - 5);
|
||||||
|
std::string key(path, slash + 1);
|
||||||
|
|
||||||
|
return {bucketName, key, params};
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
kj::Promise<Result<void>> curlFileTransfer::upload(
|
||||||
|
const std::string & uri,
|
||||||
|
std::string data,
|
||||||
|
FileTransferOptions options,
|
||||||
|
const Activity * context
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
TRY_AWAIT(enqueueFileTransfer(uri, std::move(options), std::move(data), false, context));
|
||||||
|
co_return result::success();
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<Result<std::optional<std::pair<FileTransferResult, box_ptr<AsyncInputStream>>>>>
|
||||||
|
curlFileTransfer::tryEagerTransfers(
|
||||||
|
const std::string & uri,
|
||||||
|
const FileTransferOptions & options,
|
||||||
|
const std::optional<std::string> & data,
|
||||||
|
bool noBody
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
// curl transfers using file:// urls cannot be paused, and are a bit unruly
|
||||||
|
// in other ways too. since their metadata is trivial and we already have a
|
||||||
|
// backend for simple file system reads we can use that instead. we'll pass
|
||||||
|
// uploads to files to curl even so, those will fail in enqueueItem anyway.
|
||||||
|
// on all other decoding failures we also let curl fail for us a bit later.
|
||||||
|
//
|
||||||
|
// note that we use kj to decode the url, not curl. curl uses only the path
|
||||||
|
// component of the url to determine the file name, but it does note expose
|
||||||
|
// the decoding method it uses for this. for file:// transfers curl forbids
|
||||||
|
// only \0 characters in the urldecoded path, not all control characters as
|
||||||
|
// it does in the public curl_url_get(CURLUPART_PATH, CURLU_URLDECODE) api.
|
||||||
|
//
|
||||||
|
// also note: everything weird you see here is for compatibility with curl.
|
||||||
|
// we can't even fix it because nix-channel relies on this. even reading of
|
||||||
|
// directories being allowed and returning something (though hopefully it's
|
||||||
|
// enough to return anything instead of a directory listing like curl does)
|
||||||
|
if (uri.starts_with("file://") && !data.has_value()) {
|
||||||
|
if (!uri.starts_with("file:///")) {
|
||||||
|
throw FileTransferError(NotFound, std::nullopt, "file not found");
|
||||||
|
}
|
||||||
|
auto url = curl_url();
|
||||||
|
if (!url) {
|
||||||
|
throw std::bad_alloc();
|
||||||
|
}
|
||||||
|
KJ_DEFER(curl_url_cleanup(url));
|
||||||
|
curl_url_set(url, CURLUPART_URL, requireCString(uri), 0);
|
||||||
|
char * path = nullptr;
|
||||||
|
curl_url_get(url, CURLUPART_PATH, &path, 0);
|
||||||
|
auto decoded = kj::decodeUriComponent(kj::arrayPtr(path, path + strlen(path)));
|
||||||
|
if (!decoded.hadErrors && decoded.findFirst(0) == nullptr) {
|
||||||
|
Path fsPath(decoded.cStr(), decoded.size());
|
||||||
|
FileTransferResult metadata{.effectiveUri = std::string("file://") + path};
|
||||||
|
struct stat st;
|
||||||
|
AutoCloseFD fd(sys::open(fsPath, O_RDONLY));
|
||||||
|
if (!fd || fstat(fd.get(), &st) != 0) {
|
||||||
|
throw FileTransferError(
|
||||||
|
NotFound, std::nullopt, "%s: file not found (%s)", fsPath, strerror(errno)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (S_ISDIR(st.st_mode)) {
|
||||||
|
co_return std::pair{
|
||||||
|
std::move(metadata), make_box_ptr<AsyncStringInputStream>("")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
struct OwningFdStream : AsyncInputStream
|
||||||
|
{
|
||||||
|
AutoCloseFD fd;
|
||||||
|
OwningFdStream(AutoCloseFD fd) : fd(std::move(fd)) {}
|
||||||
|
kj::Promise<Result<std::optional<size_t>>>
|
||||||
|
read(void * buffer, size_t size) override
|
||||||
|
{
|
||||||
|
// NOTE the synchronous implementation used to have a buffer for
|
||||||
|
// file data, but we cannot be bothered to treat this edge case.
|
||||||
|
if (const auto got = ::read(fd.get(), buffer, size); got >= 0) {
|
||||||
|
if (got == 0) {
|
||||||
|
return {result::success(std::nullopt)};
|
||||||
|
} else {
|
||||||
|
return {result::success(got)};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {result::failure(std::make_exception_ptr(SysError("reading file")
|
||||||
|
))};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
co_return std::pair{
|
||||||
|
std::move(metadata), make_box_ptr<OwningFdStream>(std::move(fd))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ugly hack to support s3:// URIs. */
|
||||||
|
if (uri.starts_with("s3://")) {
|
||||||
|
// FIXME: do this on a worker thread
|
||||||
|
#if ENABLE_S3
|
||||||
|
auto [bucketName, key, params] = parseS3Uri(uri);
|
||||||
|
|
||||||
|
std::string profile = getOr(params, "profile", "");
|
||||||
|
std::string region = getOr(params, "region", Aws::Region::US_EAST_1);
|
||||||
|
std::string scheme = getOr(params, "scheme", "");
|
||||||
|
std::string endpoint = getOr(params, "endpoint", "");
|
||||||
|
|
||||||
|
S3Helper s3Helper(profile, region, scheme, endpoint);
|
||||||
|
|
||||||
|
// FIXME: implement ETag
|
||||||
|
auto s3Res = TRY_AWAIT(s3Helper.getObject(bucketName, key));
|
||||||
|
FileTransferResult res;
|
||||||
|
if (!s3Res.data)
|
||||||
|
throw FileTransferError(NotFound, "S3 object '%s' does not exist", uri);
|
||||||
|
struct OwningStringStream : private std::string, AsyncStringInputStream
|
||||||
|
{
|
||||||
|
OwningStringStream(std::string data)
|
||||||
|
: std::string(std::move(data))
|
||||||
|
, AsyncStringInputStream(*this)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
co_return std::pair{res, make_box_ptr<OwningStringStream>(std::move(*s3Res.data))};
|
||||||
|
#else
|
||||||
|
throw nix::Error(
|
||||||
|
"cannot download '%s' because Lix is not built with S3 support", uri
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
co_return std::nullopt;
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<Result<std::pair<FileTransferResult, box_ptr<AsyncInputStream>>>>
|
||||||
|
curlFileTransfer::enqueueFileTransfer(
|
||||||
|
const std::string & uri,
|
||||||
|
FileTransferOptions && options,
|
||||||
|
std::optional<std::string> data,
|
||||||
|
bool noBody,
|
||||||
|
const Activity * context
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
if (auto eager = TRY_AWAIT(tryEagerTransfers(uri, options, data, noBody))) {
|
||||||
|
co_return std::move(*eager);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto source = make_box_ptr<TransferStream>(
|
||||||
|
*this, uri, std::move(options), std::move(data), noBody, context
|
||||||
|
);
|
||||||
|
TRY_AWAIT(source->init());
|
||||||
|
TRY_AWAIT(source->awaitData());
|
||||||
|
co_return {source->metadata, std::move(source)};
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
curlFileTransfer::TransferStream::TransferStream(
|
||||||
|
curlFileTransfer & parent,
|
||||||
|
const std::string & uri,
|
||||||
|
FileTransferOptions && options,
|
||||||
|
std::optional<std::string> data,
|
||||||
|
bool noBody,
|
||||||
|
const Activity * context
|
||||||
|
)
|
||||||
|
: parent(parent.multi)
|
||||||
|
, uri(uri)
|
||||||
|
, options(options)
|
||||||
|
, data(std::move(data))
|
||||||
|
, noBody(noBody)
|
||||||
|
, parentAct(context)
|
||||||
|
, backoff(backoffTimeouts(
|
||||||
|
fileTransferSettings.tries,
|
||||||
|
std::chrono::seconds(fileTransferSettings.maxConnectTimeout.get()),
|
||||||
|
std::chrono::seconds(fileTransferSettings.initialConnectTimeout.get()),
|
||||||
|
std::chrono::milliseconds(this->parent->baseRetryTimeMs)
|
||||||
|
))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
curlFileTransfer::TransferStream::~TransferStream()
|
||||||
|
{
|
||||||
|
// wake up the download thread if it's still going and have it abort
|
||||||
|
try {
|
||||||
|
if (transfer) {
|
||||||
|
parent->cancel(transfer);
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
ignoreExceptionInDestructor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<Result<void>> curlFileTransfer::TransferStream::init()
|
||||||
|
try {
|
||||||
|
metadata = TRY_AWAIT(withRetries(
|
||||||
|
[&]() {
|
||||||
|
return startTransfer(
|
||||||
|
uri, std::chrono::seconds(fileTransferSettings.initialConnectTimeout.get())
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[&](const std::chrono::milliseconds & timeout) {
|
||||||
|
return startTransfer(uri, timeout);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
co_return result::success();
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<Result<FileTransferResult>> curlFileTransfer::TransferStream::startTransfer(
|
||||||
|
const std::string & uri,
|
||||||
|
const std::chrono::milliseconds & timeout,
|
||||||
|
curl_off_t offset
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
auto uploadData = data ? std::optional(std::string_view(*data)) : std::nullopt;
|
||||||
|
auto pfp = kj::newPromiseAndCrossThreadFulfiller<Result<FileTransferResult>>();
|
||||||
|
transfer = std::make_shared<TransferItem>(
|
||||||
|
uri,
|
||||||
|
std::move(options),
|
||||||
|
parentAct,
|
||||||
|
uploadData,
|
||||||
|
noBody,
|
||||||
|
offset,
|
||||||
|
std::move(pfp.fulfiller),
|
||||||
|
timeout
|
||||||
|
);
|
||||||
|
parent->enqueueItem(transfer);
|
||||||
|
co_return TRY_AWAIT(pfp.promise);
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<Result<void>> curlFileTransfer::TransferStream::prepareRetry(
|
||||||
|
const std::string & context,
|
||||||
|
const std::chrono::milliseconds & waitTime,
|
||||||
|
unsigned int attempt
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
if (totalReceived) {
|
||||||
|
printTaggedWarning(
|
||||||
|
"%s; retrying from offset %d in %d ms (attempt %d/%d)",
|
||||||
|
Uncolored(context),
|
||||||
|
totalReceived,
|
||||||
|
waitTime.count(),
|
||||||
|
Uncolored(attempt),
|
||||||
|
Uncolored(tries)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
printTaggedWarning(
|
||||||
|
"%s; retrying in %d ms (attempt %d/%d)",
|
||||||
|
Uncolored(context),
|
||||||
|
waitTime.count(),
|
||||||
|
Uncolored(attempt),
|
||||||
|
Uncolored(tries)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
co_await AIO().provider.getTimer().afterDelay(waitTime.count() * kj::MILLISECONDS);
|
||||||
|
co_return result::success();
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<Result<void>> curlFileTransfer::TransferStream::restartTransfer(const std::chrono::milliseconds & timeout)
|
||||||
|
try {
|
||||||
|
auto onChange =
|
||||||
|
[&](std::string_view what, std::string_view from, std::string_view to, bool throw_
|
||||||
|
) -> void {
|
||||||
|
if (!from.empty() && from != to) {
|
||||||
|
FileTransferError e = FileTransferError(
|
||||||
|
Misc,
|
||||||
|
{},
|
||||||
|
"uri %s changed %s from %s to %s during transfer",
|
||||||
|
uri,
|
||||||
|
what,
|
||||||
|
from,
|
||||||
|
to
|
||||||
|
);
|
||||||
|
|
||||||
|
if (throw_) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
logWarning(e.info());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// use the effective URI of the previous transfer for retries. this avoids
|
||||||
|
// some silent corruption if a redirect changes between starting and retry
|
||||||
|
const auto & uri = metadata.effectiveUri.empty() ? this->uri : metadata.effectiveUri;
|
||||||
|
|
||||||
|
auto newMeta = TRY_AWAIT(startTransfer(uri, timeout, totalReceived));
|
||||||
|
|
||||||
|
onChange("final destination", metadata.effectiveUri, newMeta.effectiveUri, false);
|
||||||
|
onChange("ETag", metadata.etag, newMeta.etag, true);
|
||||||
|
onChange(
|
||||||
|
"immutable url",
|
||||||
|
metadata.immutableUrl.value_or(""),
|
||||||
|
newMeta.immutableUrl.value_or(""),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
co_return result::success();
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<Result<bool>> curlFileTransfer::TransferStream::waitForData()
|
||||||
|
try {
|
||||||
|
/* Grab data if available, otherwise wait for the download
|
||||||
|
thread to wake us up. */
|
||||||
|
std::optional<kj::Promise<void>> signal;
|
||||||
|
|
||||||
|
while (buffered.empty()) {
|
||||||
|
if (signal) {
|
||||||
|
co_await *signal;
|
||||||
|
signal.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto state(transfer->downloadState.lock());
|
||||||
|
|
||||||
|
if (!state->data.empty()) {
|
||||||
|
chunk = std::exchange(state->data, {});
|
||||||
|
buffered = chunk;
|
||||||
|
totalReceived += chunk.size();
|
||||||
|
parent->unpause(transfer);
|
||||||
|
} else if (state->exc) {
|
||||||
|
std::rethrow_exception(state->exc);
|
||||||
|
} else if (state->done) {
|
||||||
|
co_return false;
|
||||||
|
} else {
|
||||||
|
parent->unpause(transfer);
|
||||||
|
signal = state->wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
co_return true;
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<Result<bool>> curlFileTransfer::TransferStream::restartAndWaitForData(const std::chrono::milliseconds & timeout)
|
||||||
|
try {
|
||||||
|
TRY_AWAIT(restartTransfer(timeout));
|
||||||
|
co_return TRY_AWAIT(waitForData());
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<Result<bool>> curlFileTransfer::TransferStream::awaitData()
|
||||||
|
try {
|
||||||
|
co_return TRY_AWAIT(withRetries(
|
||||||
|
[&] { return waitForData(); },
|
||||||
|
[&](const std::chrono::milliseconds & timeout) {
|
||||||
|
return restartAndWaitForData(timeout);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<Result<std::optional<size_t>>> curlFileTransfer::TransferStream::read(void * buffer, size_t len)
|
||||||
|
try {
|
||||||
|
TRACE(LIX_STORE_FILETRANSFER_READ(uri.c_str(), len));
|
||||||
|
|
||||||
|
size_t total = 0;
|
||||||
|
auto data = static_cast<char *>(buffer);
|
||||||
|
while (total < len && TRY_AWAIT(awaitData())) {
|
||||||
|
const auto available = std::min(len - total, buffered.size());
|
||||||
|
memcpy(data + total, buffered.data(), available);
|
||||||
|
buffered.remove_prefix(available);
|
||||||
|
total += available;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (total == 0) {
|
||||||
|
co_return std::nullopt;
|
||||||
|
} else {
|
||||||
|
co_return total;
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<Result<bool>>
|
||||||
|
curlFileTransfer::exists(const std::string & uri, FileTransferOptions options, const Activity * context)
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
TRY_AWAIT(enqueueFileTransfer(uri, std::move(options), std::nullopt, true, context));
|
||||||
|
co_return true;
|
||||||
|
} catch (FileTransferError & e) {
|
||||||
|
/* S3 buckets return 403 if a file doesn't exist and the
|
||||||
|
bucket is unlistable, so treat 403 as 404. */
|
||||||
|
if (e.error == FileTransfer::NotFound || e.error == FileTransfer::Forbidden)
|
||||||
|
co_return false;
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<Result<std::pair<FileTransferResult, box_ptr<AsyncInputStream>>>> curlFileTransfer::download(
|
||||||
|
const std::string & uri, FileTransferOptions options, const Activity * context
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return enqueueFileTransfer(uri, std::move(options), std::nullopt, false, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
#pragma once
|
||||||
|
///@file
|
||||||
|
|
||||||
|
#include "lix/libstore/filetransfer.hh"
|
||||||
|
#include "lix/libstore/store-api.hh"
|
||||||
|
#include "lix/libstore/transferitem.hh"
|
||||||
|
#include "lix/libutil/async-io.hh"
|
||||||
|
#include "lix/libutil/backoff.hh"
|
||||||
|
#include "lix/libutil/box_ptr.hh"
|
||||||
|
#include "lix/libutil/result.hh"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <tuple>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <kj/async.h>
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
struct CurlMulti;
|
||||||
|
|
||||||
|
struct curlFileTransfer : public FileTransfer
|
||||||
|
{
|
||||||
|
// Types.
|
||||||
|
public:
|
||||||
|
template<typename T>
|
||||||
|
using Async = kj::Promise<Result<T>>;
|
||||||
|
|
||||||
|
struct TransferStream : AsyncInputStream
|
||||||
|
{
|
||||||
|
std::shared_ptr<CurlMulti> parent;
|
||||||
|
std::string uri;
|
||||||
|
FileTransferOptions options;
|
||||||
|
std::optional<std::string> data;
|
||||||
|
bool noBody;
|
||||||
|
const Activity * parentAct;
|
||||||
|
|
||||||
|
std::shared_ptr<TransferItem> transfer;
|
||||||
|
FileTransferResult metadata;
|
||||||
|
std::string chunk;
|
||||||
|
std::string_view buffered;
|
||||||
|
|
||||||
|
const size_t tries = fileTransferSettings.tries;
|
||||||
|
curl_off_t totalReceived = 0;
|
||||||
|
|
||||||
|
Generator<BackoffTiming> backoff;
|
||||||
|
|
||||||
|
TransferStream(
|
||||||
|
curlFileTransfer & parent,
|
||||||
|
const std::string & uri,
|
||||||
|
FileTransferOptions && options,
|
||||||
|
std::optional<std::string> data,
|
||||||
|
bool noBody,
|
||||||
|
const Activity * context
|
||||||
|
);
|
||||||
|
|
||||||
|
~TransferStream();
|
||||||
|
|
||||||
|
kj::Promise<Result<void>> init();
|
||||||
|
|
||||||
|
inline auto withRetries(auto && initial, auto && retry) -> decltype(initial())
|
||||||
|
try {
|
||||||
|
std::optional<std::string> retryContext;
|
||||||
|
BackoffTiming timings;
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
if (retryContext) {
|
||||||
|
TRY_AWAIT(prepareRetry(*retryContext, timings.waitTime, timings.attempt));
|
||||||
|
co_return TRY_AWAIT(retry(timings.downloadTimeout));
|
||||||
|
} else {
|
||||||
|
co_return TRY_AWAIT(initial());
|
||||||
|
}
|
||||||
|
} catch (FileTransferError & e) {
|
||||||
|
auto next = backoff.next();
|
||||||
|
// If this is a transient error, then maybe retry after a while. after any
|
||||||
|
// bytes have been received we require range support to proceed, otherwise
|
||||||
|
// we'd need to start from scratch and discard everything we already have.
|
||||||
|
if (e.error != Transient || data.has_value() || !next.has_value()
|
||||||
|
|| (totalReceived > 0 && !transfer->acceptsRanges()))
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
retryContext = e.what();
|
||||||
|
timings = *next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::current_exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<Result<FileTransferResult>> startTransfer(
|
||||||
|
const std::string & uri,
|
||||||
|
const std::chrono::milliseconds & timeout,
|
||||||
|
curl_off_t offset = 0
|
||||||
|
);
|
||||||
|
|
||||||
|
kj::Promise<Result<void>> prepareRetry(
|
||||||
|
const std::string & context,
|
||||||
|
const std::chrono::milliseconds & waitTime,
|
||||||
|
unsigned int attempt
|
||||||
|
);
|
||||||
|
|
||||||
|
kj::Promise<Result<void>> restartTransfer(const std::chrono::milliseconds & timeout);
|
||||||
|
|
||||||
|
kj::Promise<Result<bool>> waitForData();
|
||||||
|
|
||||||
|
kj::Promise<Result<bool>> restartAndWaitForData(const std::chrono::milliseconds & timeout);
|
||||||
|
|
||||||
|
kj::Promise<Result<bool>> awaitData();
|
||||||
|
|
||||||
|
kj::Promise<Result<std::optional<size_t>>> read(void * buffer, size_t len) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fields.
|
||||||
|
public:
|
||||||
|
std::shared_ptr<CurlMulti> multi;
|
||||||
|
|
||||||
|
// Specials.
|
||||||
|
public:
|
||||||
|
curlFileTransfer(unsigned int baseRetryTimeMs);
|
||||||
|
~curlFileTransfer();
|
||||||
|
|
||||||
|
#if ENABLE_S3
|
||||||
|
using S3Uri = std::tuple<std::string, std::string, StoreConfig::Params>;
|
||||||
|
static auto parseS3Uri(std::string url) -> S3Uri;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Overrides.
|
||||||
|
public:
|
||||||
|
auto upload(
|
||||||
|
std::string const & uri,
|
||||||
|
std::string data,
|
||||||
|
FileTransferOptions options,
|
||||||
|
Activity const * context
|
||||||
|
) -> Async<void> override;
|
||||||
|
|
||||||
|
auto exists(
|
||||||
|
std::string const & uri,
|
||||||
|
FileTransferOptions options,
|
||||||
|
Activity const * context
|
||||||
|
) -> Async<bool> override;
|
||||||
|
|
||||||
|
auto download(
|
||||||
|
std::string const & uri,
|
||||||
|
FileTransferOptions options,
|
||||||
|
Activity const * context = nullptr
|
||||||
|
) -> Async<std::pair<FileTransferResult, box_ptr<AsyncInputStream>>> override;
|
||||||
|
|
||||||
|
// Actual API.
|
||||||
|
public:
|
||||||
|
auto tryEagerTransfers(
|
||||||
|
std::string const & url,
|
||||||
|
FileTransferOptions const & options,
|
||||||
|
std::optional<std::string> const & data,
|
||||||
|
bool noBody
|
||||||
|
) -> Async<std::optional<std::pair<FileTransferResult, box_ptr<AsyncInputStream>>>>;
|
||||||
|
|
||||||
|
auto enqueueFileTransfer(
|
||||||
|
std::string const & uri,
|
||||||
|
FileTransferOptions && options,
|
||||||
|
std::optional<std::string> data,
|
||||||
|
bool noBody,
|
||||||
|
Activity const * context
|
||||||
|
) -> Async<std::pair<FileTransferResult, box_ptr<AsyncInputStream>>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
#include "lix/libstore/curlmulti.hh"
|
||||||
|
#include "lix/libstore/filetransfer.hh"
|
||||||
|
#include "lix/libutil/error.hh"
|
||||||
|
#include "lix/libutil/logging.hh"
|
||||||
|
#include "lix/libutil/namespaces.hh"
|
||||||
|
#include "lix/libutil/signals.hh"
|
||||||
|
#include "lix/libutil/thread-name.hh"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <climits>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <exception>
|
||||||
|
#include <future>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <curl/curl.h>
|
||||||
|
#include <curl/multi.h>
|
||||||
|
#include <kj/common.h>
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
void CurlMulti::State::quit()
|
||||||
|
{
|
||||||
|
quitting = true;
|
||||||
|
// We will not be processing any more requests.
|
||||||
|
for (auto & item : incoming) {
|
||||||
|
item->finish(CURLE_ABORTED_BY_CALLBACK);
|
||||||
|
}
|
||||||
|
incoming.clear();
|
||||||
|
unpause.clear();
|
||||||
|
// make a note that we're dying and acknowledge all pending cancel
|
||||||
|
// requests by individual transfers. not doing this can cause bugs
|
||||||
|
// like #1218 in which the process deadlocks waiting for transfers
|
||||||
|
// to cancel with no download thread to make this happen; this was
|
||||||
|
// likely caused by a transfer requesting a cancellation *exactly*
|
||||||
|
// before a signal was received, causing the curl thread to die in
|
||||||
|
// a hurry without processing cancellations. the transfer is stuck
|
||||||
|
// from that point on, and since this happened in a destructor the
|
||||||
|
// entire process locked up solid. curl exceptions could have also
|
||||||
|
// caused this; we set the `quit` flag just in case to avoid this.
|
||||||
|
for (auto & [item, promise] : cancel) {
|
||||||
|
promise.set_value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CurlMulti::CurlMulti(unsigned int baseRetryTimeMs)
|
||||||
|
: curlm(curl_multi_init())
|
||||||
|
, baseRetryTimeMs(baseRetryTimeMs)
|
||||||
|
{
|
||||||
|
if (curlm == nullptr) {
|
||||||
|
throw FileTransferError(FileTransfer::Misc, {}, "could not allocate curl handle");
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::once_flag globalInit;
|
||||||
|
std::call_once(globalInit, curl_global_init, CURL_GLOBAL_ALL);
|
||||||
|
|
||||||
|
curl_multi_setopt(curlm.get(), CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
|
||||||
|
curl_multi_setopt(curlm.get(), CURLMOPT_MAX_TOTAL_CONNECTIONS,
|
||||||
|
fileTransferSettings.httpConnections.get());
|
||||||
|
|
||||||
|
workerThread = std::thread([&]() {
|
||||||
|
setCurrentThreadName("curlFileTransfer worker");
|
||||||
|
workerThreadEntry();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
CurlMulti::~CurlMulti()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
stopWorkerThread();
|
||||||
|
} catch (nix::Error & e) {
|
||||||
|
// This can only fail if a socket to our own process cannot be
|
||||||
|
// written to, so it is always a bug in the program if it fails.
|
||||||
|
//
|
||||||
|
// Joining the thread would probably only cause a deadlock if this
|
||||||
|
// happened, so just die on purpose.
|
||||||
|
printError("failed to join curl file transfer worker thread: %1%", e.what());
|
||||||
|
std::terminate();
|
||||||
|
}
|
||||||
|
workerThread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CurlMulti::unpause(const std::shared_ptr<TransferItem> & transfer)
|
||||||
|
{
|
||||||
|
auto lock = state_.lock();
|
||||||
|
lock->unpause.push_back(transfer);
|
||||||
|
wakeup(*lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CurlMulti::cancel(const std::shared_ptr<TransferItem> & transfer)
|
||||||
|
{
|
||||||
|
std::promise<void> promise;
|
||||||
|
auto wait = promise.get_future();
|
||||||
|
{
|
||||||
|
auto lock = state_.lock();
|
||||||
|
if (lock->quitting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lock->cancel[transfer] = std::move(promise);
|
||||||
|
wakeup(*lock);
|
||||||
|
}
|
||||||
|
wait.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CurlMulti::wakeup(State & locked)
|
||||||
|
{
|
||||||
|
locked.workAvailable = true;
|
||||||
|
if (auto mc = curl_multi_wakeup(curlm.get()))
|
||||||
|
throw nix::Error("unexpected error from curl_multi_wakeup(): %s", curl_multi_strerror(mc));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CurlMulti::stopWorkerThread()
|
||||||
|
{
|
||||||
|
/* Signal the worker thread to exit. */
|
||||||
|
auto state(state_.lock());
|
||||||
|
state->quit();
|
||||||
|
wakeup(*state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CurlMulti::workerThreadMain()
|
||||||
|
{
|
||||||
|
/* Cause this thread to be notified on SIGINT. */
|
||||||
|
auto callback = createInterruptCallback([&]() {
|
||||||
|
stopWorkerThread();
|
||||||
|
});
|
||||||
|
|
||||||
|
unshareFilesystem();
|
||||||
|
|
||||||
|
std::map<CURL *, std::shared_ptr<TransferItem>> items;
|
||||||
|
|
||||||
|
// clear all current transfers in case of an early exit, as can happen
|
||||||
|
// via Interrupted if the interruption occured right before a log call
|
||||||
|
KJ_DEFER({
|
||||||
|
for (auto & [_, item] : items) {
|
||||||
|
item->finish(CURLE_ABORTED_BY_CALLBACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lock = state_.lock();
|
||||||
|
lock->quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
bool quit = false;
|
||||||
|
|
||||||
|
// NOTE: we will need to use CURLMOPT_TIMERFUNCTION to integrate this
|
||||||
|
// loop with kj. until then curl will handle its timeouts internally.
|
||||||
|
int64_t timeoutMs = INT64_MAX;
|
||||||
|
|
||||||
|
while (!quit) {
|
||||||
|
/* Let curl do its thing. */
|
||||||
|
int running;
|
||||||
|
CURLMcode mc = curl_multi_perform(curlm.get(), &running);
|
||||||
|
if (mc != CURLM_OK)
|
||||||
|
throw nix::Error("unexpected error from curl_multi_perform(): %s", curl_multi_strerror(mc));
|
||||||
|
|
||||||
|
/* Set the promises of any finished requests. */
|
||||||
|
CURLMsg * msg;
|
||||||
|
int left;
|
||||||
|
while ((msg = curl_multi_info_read(curlm.get(), &left))) {
|
||||||
|
if (msg->msg == CURLMSG_DONE) {
|
||||||
|
auto i = items.find(msg->easy_handle);
|
||||||
|
assert(i != items.end());
|
||||||
|
i->second->finish(msg->data.result);
|
||||||
|
curl_multi_remove_handle(curlm.get(), i->second->req.get());
|
||||||
|
items.erase(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto cancel = [&] { return std::exchange(state_.lock()->cancel, {}); }();
|
||||||
|
for (auto & [item, promise] : cancel) {
|
||||||
|
curl_multi_remove_handle(curlm.get(), item->req.get());
|
||||||
|
items.erase(item->req.get());
|
||||||
|
promise.set_value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add new curl requests from the incoming requests queue,
|
||||||
|
except for requests that are embargoed (waiting for a
|
||||||
|
retry timeout to expire). */
|
||||||
|
|
||||||
|
std::vector<std::shared_ptr<TransferItem>> incoming;
|
||||||
|
std::vector<std::shared_ptr<TransferItem>> unpause;
|
||||||
|
bool haveWork = false;
|
||||||
|
|
||||||
|
timeoutMs = 10'000;
|
||||||
|
|
||||||
|
{
|
||||||
|
auto state(state_.lock());
|
||||||
|
incoming = std::exchange(state->incoming, {});
|
||||||
|
unpause = std::exchange(state->unpause, {});
|
||||||
|
haveWork = std::exchange(state->workAvailable, false);
|
||||||
|
quit = state->quitting;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto & item : incoming) {
|
||||||
|
debug("starting %s of %s", item->verb(), item->uri);
|
||||||
|
curl_multi_add_handle(curlm.get(), item->req.get());
|
||||||
|
items[item->req.get()] = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
// exit immediately and abort all running transfers. waiting for transfers to finish
|
||||||
|
// before exiting this loop may hang the shutdown procedure forever, e.g. if blocked
|
||||||
|
// transfers would be destroyed (thus aborted) after the curl thread for any reason.
|
||||||
|
if (quit) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto & item : unpause) {
|
||||||
|
curl_easy_pause(item->req.get(), CURLPAUSE_CONT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (haveWork) {
|
||||||
|
timeoutMs = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait for activity, including wakeup events. */
|
||||||
|
mc = curl_multi_poll(curlm.get(), nullptr, 0, timeoutMs, nullptr);
|
||||||
|
if (mc != CURLM_OK)
|
||||||
|
throw nix::Error("unexpected error from curl_multi_poll(): %s", curl_multi_strerror(mc));
|
||||||
|
}
|
||||||
|
|
||||||
|
debug("download thread shutting down");
|
||||||
|
}
|
||||||
|
|
||||||
|
void CurlMulti::workerThreadEntry()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
workerThreadMain();
|
||||||
|
} catch (nix::Interrupted & e) {
|
||||||
|
} catch (std::exception & e) { // NOLINT(lix-foreign-exceptions)
|
||||||
|
printError("unexpected error in download thread: %s", e.what());
|
||||||
|
} catch (...) {
|
||||||
|
printError("unexpected error in download thread");
|
||||||
|
}
|
||||||
|
|
||||||
|
state_.lock()->quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CurlMulti::enqueueItem(std::shared_ptr<TransferItem> item)
|
||||||
|
{
|
||||||
|
if (item->uploadData
|
||||||
|
&& !item->uri.starts_with("http://")
|
||||||
|
&& !item->uri.starts_with("https://"))
|
||||||
|
throw nix::Error("uploading to '%s' is not supported", item->uri);
|
||||||
|
|
||||||
|
{
|
||||||
|
auto state(state_.lock());
|
||||||
|
if (state->quitting)
|
||||||
|
throw nix::Error("cannot enqueue download request because the download thread is shutting down");
|
||||||
|
state->incoming.push_back(item);
|
||||||
|
wakeup(*state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nix
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#pragma once
|
||||||
|
///@file
|
||||||
|
|
||||||
|
#include "lix/libstore/transferitem.hh"
|
||||||
|
#include "lix/libutil/sync.hh"
|
||||||
|
|
||||||
|
#include <future>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <curl/multi.h>
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
struct CurlMulti
|
||||||
|
{
|
||||||
|
// Types.
|
||||||
|
public:
|
||||||
|
struct State
|
||||||
|
{
|
||||||
|
bool quitting = false;
|
||||||
|
bool workAvailable = false;
|
||||||
|
std::vector<std::shared_ptr<TransferItem>> incoming;
|
||||||
|
std::vector<std::shared_ptr<TransferItem>> unpause;
|
||||||
|
std::map<std::shared_ptr<TransferItem>, std::promise<void>> cancel;
|
||||||
|
|
||||||
|
void quit();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fields.
|
||||||
|
public:
|
||||||
|
std::unique_ptr<CURLM, decltype([](auto * m) { curl_multi_cleanup(m); })> curlm;
|
||||||
|
const unsigned int baseRetryTimeMs;
|
||||||
|
Sync<State> state_;
|
||||||
|
std::thread workerThread;
|
||||||
|
|
||||||
|
// Specials.
|
||||||
|
public:
|
||||||
|
CurlMulti(unsigned int baseRetryTimeMs);
|
||||||
|
~CurlMulti();
|
||||||
|
|
||||||
|
// Actual API.
|
||||||
|
public:
|
||||||
|
void unpause(std::shared_ptr<TransferItem> const & transfer);
|
||||||
|
|
||||||
|
void cancel(std::shared_ptr<TransferItem> const & transfer);
|
||||||
|
|
||||||
|
void wakeup(State & locked);
|
||||||
|
|
||||||
|
void stopWorkerThread();
|
||||||
|
|
||||||
|
void workerThreadMain();
|
||||||
|
|
||||||
|
void workerThreadEntry();
|
||||||
|
|
||||||
|
void enqueueItem(std::shared_ptr<TransferItem> item);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace nix
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
@0xd8aa4d286ba6797b;
|
||||||
|
|
||||||
|
# IMPORTANT NOTICE
|
||||||
|
#
|
||||||
|
# these definitions are EXPERIMENTAL and come with NO stability guarantees
|
||||||
|
|
||||||
|
using Cxx = import "/capnp/c++.capnp";
|
||||||
|
$Cxx.namespace("nix::rpc::daemon");
|
||||||
|
$Cxx.allowCancellation;
|
||||||
|
|
||||||
|
using T = import "/lix/libutil/types.capnp";
|
||||||
|
using Log = import "/lix/libutil/logging.capnp";
|
||||||
|
|
||||||
|
struct ProtocolDescription {
|
||||||
|
id @0 :Text;
|
||||||
|
description @1 :Text;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Bootstrap {
|
||||||
|
supported @0 () -> (protocols :List(ProtocolDescription));
|
||||||
|
request @1 (
|
||||||
|
clientInfo :Text,
|
||||||
|
protocol :Text,
|
||||||
|
) -> (result :Protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Protocol {
|
||||||
|
# TODO maybe add information or something
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LegacyBoot extends(Protocol) $T.throws(T.v1Errors) {
|
||||||
|
enum Trust {
|
||||||
|
unknown @0;
|
||||||
|
untrusted @1;
|
||||||
|
trusted @2;
|
||||||
|
}
|
||||||
|
|
||||||
|
init @0 (
|
||||||
|
logger :Log.LogStream,
|
||||||
|
replyStream :LegacyStream,
|
||||||
|
) -> (result :InitResult);
|
||||||
|
|
||||||
|
struct InitResult {
|
||||||
|
requestStream @0 :LegacyStream;
|
||||||
|
trust @1 :Trust;
|
||||||
|
version @2 :Text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LegacyStream $T.throws(T.v1Errors) {
|
||||||
|
feed @0 (raw :Data) -> stream;
|
||||||
|
# must be called before a new op is started, otherwise errors may get lost
|
||||||
|
sync @1 ();
|
||||||
|
}
|
||||||
+353
-51
@@ -1,5 +1,7 @@
|
|||||||
#include "lix/libstore/daemon.hh"
|
#include "lix/libstore/daemon.hh"
|
||||||
#include "filetransfer.hh"
|
#include "filetransfer.hh"
|
||||||
|
#include "libutil/async.hh"
|
||||||
|
#include "libutil/logging-rpc.hh"
|
||||||
#include "lix/libutil/async-io.hh"
|
#include "lix/libutil/async-io.hh"
|
||||||
#include "lix/libutil/monitor-fd.hh"
|
#include "lix/libutil/monitor-fd.hh"
|
||||||
#include "lix/libstore/worker-protocol.hh"
|
#include "lix/libstore/worker-protocol.hh"
|
||||||
@@ -17,12 +19,21 @@
|
|||||||
#include "lix/libutil/serialise.hh"
|
#include "lix/libutil/serialise.hh"
|
||||||
#include "lix/libutil/strings.hh"
|
#include "lix/libutil/strings.hh"
|
||||||
#include "lix/libutil/args.hh"
|
#include "lix/libutil/args.hh"
|
||||||
|
#include "lix/libstore/daemon.capnp.h"
|
||||||
|
#include "lix/libutil/rpc.hh"
|
||||||
|
#include "lix/libutil/types-rpc.hh"
|
||||||
|
|
||||||
#include <boost/core/demangle.hpp>
|
#include <boost/core/demangle.hpp>
|
||||||
|
#include <capnp/rpc-twoparty.h>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
#include <kj/encoding.h>
|
||||||
|
#include <kj/exception.h>
|
||||||
|
#include <kj/memory.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
const std::string nix::rpc::daemon::UNSTABLE_LEGACY_TUNNELED = "lix/legacy/" PACKAGE_VERSION;
|
||||||
|
|
||||||
namespace nix::daemon {
|
namespace nix::daemon {
|
||||||
|
|
||||||
Sink & operator << (Sink & sink, const Logger::Fields & fields)
|
Sink & operator << (Sink & sink, const Logger::Fields & fields)
|
||||||
@@ -86,7 +97,7 @@ struct TunnelLogger : public Logger
|
|||||||
|
|
||||||
BufferState log(Verbosity lvl, std::string_view s) override
|
BufferState log(Verbosity lvl, std::string_view s) override
|
||||||
{
|
{
|
||||||
if (lvl > verbosity) {
|
if (lvl > getVerbosity()) {
|
||||||
return BufferState::HasSpace;
|
return BufferState::HasSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +108,7 @@ struct TunnelLogger : public Logger
|
|||||||
|
|
||||||
BufferState logEI(const ErrorInfo & ei) override
|
BufferState logEI(const ErrorInfo & ei) override
|
||||||
{
|
{
|
||||||
if (ei.level > verbosity) {
|
if (ei.level > getVerbosity()) {
|
||||||
return BufferState::HasSpace;
|
return BufferState::HasSpace;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +197,7 @@ struct ClientSettings
|
|||||||
settings.keepFailed.override(keepFailed);
|
settings.keepFailed.override(keepFailed);
|
||||||
settings.keepGoing.override(keepGoing);
|
settings.keepGoing.override(keepGoing);
|
||||||
settings.tryFallback.override(tryFallback);
|
settings.tryFallback.override(tryFallback);
|
||||||
nix::verbosity = verbosity;
|
setVerbosity(verbosity);
|
||||||
settings.maxBuildJobs.override(maxBuildJobs);
|
settings.maxBuildJobs.override(maxBuildJobs);
|
||||||
settings.maxSilentTime.override(maxSilentTime);
|
settings.maxSilentTime.override(maxSilentTime);
|
||||||
settings.verboseBuild = verboseBuild;
|
settings.verboseBuild = verboseBuild;
|
||||||
@@ -834,11 +845,12 @@ static void performOp(AsyncIoRoot & aio, TunnelLogger * logger, ref<Store> store
|
|||||||
case WorkerProto::Op::AddBuildLog: {
|
case WorkerProto::Op::AddBuildLog: {
|
||||||
StorePath path{readString(from)};
|
StorePath path{readString(from)};
|
||||||
logger->startWork();
|
logger->startWork();
|
||||||
if (!trusted)
|
|
||||||
throw Error("you are not privileged to add logs");
|
|
||||||
auto & logStore = require<LogStore>(*store);
|
auto & logStore = require<LogStore>(*store);
|
||||||
{
|
{
|
||||||
FramedSource source(from);
|
FramedSource source(from);
|
||||||
|
if (!trusted) {
|
||||||
|
throw Error("you are not privileged to add logs");
|
||||||
|
}
|
||||||
StringSink sink;
|
StringSink sink;
|
||||||
source.drainInto(sink);
|
source.drainInto(sink);
|
||||||
aio.blockOn(logStore.addBuildLog(path, sink.s));
|
aio.blockOn(logStore.addBuildLog(path, sink.s));
|
||||||
@@ -857,12 +869,66 @@ static void performOp(AsyncIoRoot & aio, TunnelLogger * logger, ref<Store> store
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void processConnection(
|
static void processLegacyRequests(
|
||||||
AsyncIoRoot & aio,
|
AsyncIoRoot & aio,
|
||||||
|
Logger * prevLogger,
|
||||||
|
TunnelLogger * tunnelLogger,
|
||||||
ref<Store> store,
|
ref<Store> store,
|
||||||
FdSource & from,
|
FdSource & from,
|
||||||
FdSink & to,
|
FdSink & to,
|
||||||
TrustedFlag trusted)
|
TrustedFlag trusted,
|
||||||
|
WorkerProto::Version clientVersion
|
||||||
|
)
|
||||||
|
{
|
||||||
|
unsigned int opCount = 0;
|
||||||
|
|
||||||
|
Finally finally([&]() { printMsgUsing(prevLogger, lvlDebug, "%d operations", opCount); });
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
WorkerProto::Op op;
|
||||||
|
try {
|
||||||
|
op = (enum WorkerProto::Op) readNum<unsigned>(from);
|
||||||
|
} catch (Interrupted & e) {
|
||||||
|
break;
|
||||||
|
} catch (EndOfFile & e) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
printMsgUsing(prevLogger, lvlDebug, "received daemon op %d", op);
|
||||||
|
|
||||||
|
opCount++;
|
||||||
|
|
||||||
|
debug("performing daemon worker op: %d", op);
|
||||||
|
|
||||||
|
try {
|
||||||
|
KJ_DEFER(aio.blockOn(logger->flush()));
|
||||||
|
performOp(aio, tunnelLogger, store, trusted, clientVersion, from, to, op);
|
||||||
|
} catch (Error & e) {
|
||||||
|
/* If we're not in a state where we can send replies, then
|
||||||
|
something went wrong processing the input of the
|
||||||
|
client. This can happen especially if I/O errors occur
|
||||||
|
during addTextToStore() / importPath(). If that
|
||||||
|
happens, just send the error message and exit. */
|
||||||
|
bool errorAllowed = tunnelLogger->state_.lock()->canSendStderr;
|
||||||
|
tunnelLogger->stopWork(&e);
|
||||||
|
if (!errorAllowed) {
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
} catch (std::bad_alloc & e) {
|
||||||
|
auto ex = Error("Lix daemon out of memory");
|
||||||
|
tunnelLogger->stopWork(&ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
to.flush();
|
||||||
|
|
||||||
|
assert(!tunnelLogger->state_.lock()->canSendStderr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void processLegacyConnection(
|
||||||
|
AsyncIoRoot & aio, ref<Store> store, FdSource & from, FdSink & to, TrustedFlag trusted
|
||||||
|
)
|
||||||
{
|
{
|
||||||
auto monitor = std::make_unique<MonitorFdHup>(from.fd);
|
auto monitor = std::make_unique<MonitorFdHup>(from.fd);
|
||||||
|
|
||||||
@@ -880,10 +946,6 @@ void processConnection(
|
|||||||
auto prevLogger = nix::logger;
|
auto prevLogger = nix::logger;
|
||||||
logger = tunnelLogger;
|
logger = tunnelLogger;
|
||||||
|
|
||||||
unsigned int opCount = 0;
|
|
||||||
|
|
||||||
Finally finally([&]() { printMsgUsing(prevLogger, lvlDebug, "%d operations", opCount); });
|
|
||||||
|
|
||||||
// FIXME: what is *supposed* to be in this even?
|
// FIXME: what is *supposed* to be in this even?
|
||||||
if (readNum<unsigned>(from)) {
|
if (readNum<unsigned>(from)) {
|
||||||
// Obsolete CPU affinity.
|
// Obsolete CPU affinity.
|
||||||
@@ -906,49 +968,10 @@ void processConnection(
|
|||||||
tunnelLogger->startWork();
|
tunnelLogger->startWork();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
tunnelLogger->stopWork();
|
tunnelLogger->stopWork();
|
||||||
to.flush();
|
to.flush();
|
||||||
|
|
||||||
/* Process client requests. */
|
processLegacyRequests(aio, prevLogger, tunnelLogger, store, from, to, trusted, clientVersion);
|
||||||
while (true) {
|
|
||||||
WorkerProto::Op op;
|
|
||||||
try {
|
|
||||||
op = (enum WorkerProto::Op) readNum<unsigned>(from);
|
|
||||||
} catch (Interrupted & e) {
|
|
||||||
break;
|
|
||||||
} catch (EndOfFile & e) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
printMsgUsing(prevLogger, lvlDebug, "received daemon op %d", op);
|
|
||||||
|
|
||||||
opCount++;
|
|
||||||
|
|
||||||
debug("performing daemon worker op: %d", op);
|
|
||||||
|
|
||||||
try {
|
|
||||||
performOp(aio, tunnelLogger, store, trusted, clientVersion, from, to, op);
|
|
||||||
} catch (Error & e) {
|
|
||||||
/* If we're not in a state where we can send replies, then
|
|
||||||
something went wrong processing the input of the
|
|
||||||
client. This can happen especially if I/O errors occur
|
|
||||||
during addTextToStore() / importPath(). If that
|
|
||||||
happens, just send the error message and exit. */
|
|
||||||
bool errorAllowed = tunnelLogger->state_.lock()->canSendStderr;
|
|
||||||
tunnelLogger->stopWork(&e);
|
|
||||||
if (!errorAllowed) throw;
|
|
||||||
} catch (std::bad_alloc & e) {
|
|
||||||
auto ex = Error("Lix daemon out of memory");
|
|
||||||
tunnelLogger->stopWork(&ex);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
to.flush();
|
|
||||||
|
|
||||||
assert(!tunnelLogger->state_.lock()->canSendStderr);
|
|
||||||
};
|
|
||||||
|
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
tunnelLogger->stopWork(&e);
|
tunnelLogger->stopWork(&e);
|
||||||
to.flush();
|
to.flush();
|
||||||
@@ -967,4 +990,283 @@ void processConnection(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
using namespace rpc::daemon;
|
||||||
|
|
||||||
|
// Shared state for all legacy protocol implementation structs
|
||||||
|
struct LegacyState
|
||||||
|
{
|
||||||
|
ref<Store> store;
|
||||||
|
TrustedFlag trusted;
|
||||||
|
|
||||||
|
LegacyState(ref<Store> store, TrustedFlag trusted) : store(store), trusted(trusted) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RequestStreamImpl final : LegacyStream::Server
|
||||||
|
{
|
||||||
|
ref<LegacyState> state;
|
||||||
|
std::exception_ptr error;
|
||||||
|
AsyncFdIoStream workerSock;
|
||||||
|
kj::Promise<void> responseForwarder;
|
||||||
|
|
||||||
|
RequestStreamImpl(
|
||||||
|
ref<LegacyState> state,
|
||||||
|
kj::Promise<std::exception_ptr> error,
|
||||||
|
LegacyStream::Client callbacks,
|
||||||
|
AutoCloseFD workerFd
|
||||||
|
)
|
||||||
|
: state(state)
|
||||||
|
, workerSock(std::move(workerFd))
|
||||||
|
, responseForwarder(forwardResponse(callbacks).exclusiveJoin(
|
||||||
|
error.then([&](std::exception_ptr e) -> kj::Promise<void> {
|
||||||
|
onError(e);
|
||||||
|
return kj::NEVER_DONE;
|
||||||
|
})
|
||||||
|
))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void onError(std::exception_ptr e)
|
||||||
|
{
|
||||||
|
if (!error) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<void> forwardResponse(LegacyStream::Client callbacks)
|
||||||
|
try {
|
||||||
|
std::array<char, 8192> buf;
|
||||||
|
while (true) {
|
||||||
|
if (auto got = TRY_AWAIT(workerSock.read(buf.data(), buf.size())); !got) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
auto req = callbacks.feedRequest();
|
||||||
|
req.initRaw(*got);
|
||||||
|
std::copy(buf.begin(), buf.begin() + *got, req.getRaw().begin());
|
||||||
|
TRY_AWAIT_RPC(req.send());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TRY_AWAIT_RPC(callbacks.syncRequest().send());
|
||||||
|
} catch (...) {
|
||||||
|
onError(std::current_exception());
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<void> feed(FeedContext context) override
|
||||||
|
try {
|
||||||
|
if (error) {
|
||||||
|
std::rethrow_exception(error);
|
||||||
|
}
|
||||||
|
auto bytes = context.getParams().getRaw();
|
||||||
|
TRY_AWAIT(workerSock.writeFull(bytes.begin(), bytes.size()));
|
||||||
|
} catch (...) {
|
||||||
|
onError(std::current_exception());
|
||||||
|
rpc::rethrow_as_rpc_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<void> sync(SyncContext context) override
|
||||||
|
try {
|
||||||
|
TRY_AWAIT(logger->flush());
|
||||||
|
if (error) {
|
||||||
|
std::rethrow_exception(error);
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
rpc::rethrow_as_rpc_error();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LegacyBootImpl final : LegacyBoot::Server
|
||||||
|
{
|
||||||
|
ref<LegacyState> state;
|
||||||
|
bool used = false;
|
||||||
|
|
||||||
|
LegacyBootImpl(TrustedFlag trusted, ref<Store> store) : state(make_ref<LegacyState>(store, trusted)) {}
|
||||||
|
|
||||||
|
kj::Promise<void> init(InitContext context) override
|
||||||
|
try {
|
||||||
|
if (used) {
|
||||||
|
throw Error("connection already initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto prevLogger = logger;
|
||||||
|
logger = rpc::log::makeRpcLoggerClient(context.getParams().getLogger());
|
||||||
|
|
||||||
|
auto args = context.getParams();
|
||||||
|
auto result = context.initResults().initResult();
|
||||||
|
// We and the underlying store both need to trust the client for it to be trusted.
|
||||||
|
if (!state->trusted) {
|
||||||
|
result.setTrust(LegacyBoot::Trust::UNTRUSTED);
|
||||||
|
} else if (auto trust = TRY_AWAIT(state->store->isTrustedClient()); trust) {
|
||||||
|
result.setTrust(*trust ? LegacyBoot::Trust::TRUSTED : LegacyBoot::Trust::UNTRUSTED);
|
||||||
|
} else {
|
||||||
|
result.setTrust(LegacyBoot::Trust::UNKNOWN);
|
||||||
|
}
|
||||||
|
result.setVersion(PACKAGE_VERSION);
|
||||||
|
|
||||||
|
auto [rpcSock, workerSock] = SocketPair::stream();
|
||||||
|
auto pfp = kj::newPromiseAndCrossThreadFulfiller<std::exception_ptr>();
|
||||||
|
|
||||||
|
struct Request
|
||||||
|
{
|
||||||
|
AutoCloseFD fd;
|
||||||
|
kj::Own<kj::CrossThreadPromiseFulfiller<std::exception_ptr>> signal;
|
||||||
|
|
||||||
|
Request(AutoCloseFD fd, kj::Own<kj::CrossThreadPromiseFulfiller<std::exception_ptr>> fulfiller)
|
||||||
|
: fd(std::move(fd))
|
||||||
|
, signal(std::move(fulfiller))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto req = make_ref<Request>(std::move(rpcSock), std::move(pfp.fulfiller));
|
||||||
|
|
||||||
|
auto legacyThread = std::async(std::launch::async, [prevLogger, state{state}, req] {
|
||||||
|
AsyncIoRoot aio;
|
||||||
|
FdSource from(req->fd.get());
|
||||||
|
FdSink to(req->fd.get());
|
||||||
|
TunnelLogger logger(to, PROTOCOL_VERSION);
|
||||||
|
|
||||||
|
try {
|
||||||
|
processLegacyRequests(
|
||||||
|
aio, prevLogger, &logger, state->store, from, to, state->trusted, PROTOCOL_VERSION
|
||||||
|
);
|
||||||
|
} catch (Error & e) {
|
||||||
|
req->signal->fulfill(std::current_exception());
|
||||||
|
} catch (std::bad_alloc & e) {
|
||||||
|
req->signal->fulfill(std::make_exception_ptr(Error("Lix daemon out of memory")));
|
||||||
|
} catch (std::exception & e) { // NOLINT(lix-foreign-exceptions)
|
||||||
|
// TODO print stack trace to daemon log, maybe crash?
|
||||||
|
// boost stacktrace has from_current_exception (at a cost) with not-great symbolization,
|
||||||
|
// cpptrace has a *much* better symbolizer (at unknown cost)
|
||||||
|
req->signal->fulfill(
|
||||||
|
std::make_exception_ptr(Error(
|
||||||
|
"Unexpected exception on the Lix daemon; this is a bug in Lix.\n"
|
||||||
|
"We would appreciate a report of the circumstances it happened in at "
|
||||||
|
"https://git.lix.systems/lix-project/lix.\n%s: %s",
|
||||||
|
Uncolored(boost::core::demangle(typeid(e).name())),
|
||||||
|
e.what()
|
||||||
|
))
|
||||||
|
);
|
||||||
|
} catch (...) {
|
||||||
|
// TODO print stack trace to daemon log, maybe crash?
|
||||||
|
req->signal->fulfill(
|
||||||
|
std::make_exception_ptr(Error(
|
||||||
|
"Unexpected exception on the Lix daemon; this is a bug in Lix.\n"
|
||||||
|
"We would appreciate a report of the circumstances it happened in at "
|
||||||
|
"https://git.lix.systems/lix-project/lix.\n"
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
result.setRequestStream(
|
||||||
|
kj::heap<RequestStreamImpl>(
|
||||||
|
state, std::move(pfp.promise), args.getReplyStream(), std::move(workerSock)
|
||||||
|
)
|
||||||
|
.attach(std::move(legacyThread))
|
||||||
|
);
|
||||||
|
used = true;
|
||||||
|
} catch (...) {
|
||||||
|
rpc::rethrow_as_rpc_error();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BootstrapImpl final : Bootstrap::Server
|
||||||
|
{
|
||||||
|
struct ProtocolEntry
|
||||||
|
{
|
||||||
|
std::string description;
|
||||||
|
std::function<rpc::daemon::Protocol::Client(TrustedFlag, ref<Store>)> factory;
|
||||||
|
};
|
||||||
|
|
||||||
|
TrustedFlag trusted;
|
||||||
|
ref<Store> store;
|
||||||
|
std::map<kj::StringPtr, ProtocolEntry> protocols;
|
||||||
|
bool used = false;
|
||||||
|
|
||||||
|
BootstrapImpl(TrustedFlag trusted, ref<Store> store) : trusted(trusted), store(store)
|
||||||
|
{
|
||||||
|
if (store->isThreadSafe()) {
|
||||||
|
protocols.emplace(
|
||||||
|
rpc::daemon::UNSTABLE_LEGACY_TUNNELED,
|
||||||
|
ProtocolEntry{"tunneled legacy wire protocol", [](TrustedFlag trusted, ref<Store> store) {
|
||||||
|
return kj::heap<LegacyBootImpl>(trusted, store);
|
||||||
|
}}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<void> supported(SupportedContext context) override
|
||||||
|
{
|
||||||
|
if (!experimentalFeatureSettings.isEnabled(Xp::RpcSockets)) {
|
||||||
|
kj::throwFatalException(
|
||||||
|
kj::Exception(
|
||||||
|
kj::Exception::Type::UNIMPLEMENTED, "main", 0, kj::str("rpc sockets not enabled")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = context.initResults();
|
||||||
|
auto protocols = result.initProtocols(this->protocols.size());
|
||||||
|
for (auto [i, proto] : enumerate(this->protocols)) {
|
||||||
|
protocols[i].setId(proto.first);
|
||||||
|
protocols[i].setDescription(proto.second.description);
|
||||||
|
}
|
||||||
|
return kj::READY_NOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<void> request(RequestContext context) override
|
||||||
|
{
|
||||||
|
auto id = rpc::to<std::string>(context.getParams().getProtocol());
|
||||||
|
if (used) {
|
||||||
|
kj::throwFatalException(
|
||||||
|
kj::Exception(
|
||||||
|
kj::Exception::Type::FAILED, "main", 0, kj::str("connection already initialized")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else if (const auto & protocol = get(protocols, id)) {
|
||||||
|
used = true;
|
||||||
|
context.initResults().setResult(protocol->factory(trusted, store));
|
||||||
|
} else {
|
||||||
|
kj::throwFatalException(
|
||||||
|
kj::Exception(
|
||||||
|
kj::Exception::Type::UNIMPLEMENTED, "main", 0, kj::str("unsupported protocol", id)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return kj::READY_NOW;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<Result<void>>
|
||||||
|
processConnection(ref<Store> store, kj::AsyncIoStream & connection, TrustedFlag trusted)
|
||||||
|
try {
|
||||||
|
// TODO trace encoders can do neat error info things, use them. we could stuff some serialized
|
||||||
|
// error struct into the remote trace field instead of using result types and get pipelineable
|
||||||
|
// calls out of it. needs more investigation to say if it's worth the possible reporting skew.
|
||||||
|
capnp::TwoPartyServer server{kj::heap<BootstrapImpl>(trusted, store)};
|
||||||
|
|
||||||
|
// NOTE we can't easily disconnect a peer without shutting shutting down the socket connection
|
||||||
|
// independently of capnp since capnp does not offer such functionality. shutting down sockets
|
||||||
|
// in this manner is very disruptive and pretty unreliable, so we will have to find some other
|
||||||
|
// way to disconnect clients. or we just don't do it because the DoS risk is not large anyway.
|
||||||
|
//
|
||||||
|
// since we have control over the promise we can have the following await finish early to stop
|
||||||
|
// processing events, and if we close the socket after that we've dropped the connection. this
|
||||||
|
// does not guarantee that responses have been sent though, so we can only do this on requests
|
||||||
|
// received *after* a fatal error response has been *sent*, inflicting per-operation overhead.
|
||||||
|
{
|
||||||
|
auto prevLogger = logger;
|
||||||
|
co_await server.accept(connection).exclusiveJoin(connection.whenWriteDisconnected());
|
||||||
|
// NOTE: we do not flush the logger here because the connection is already closed! we only
|
||||||
|
// delete the non-local logger (if one was set) to ensure all rpc references were dropped.
|
||||||
|
if (prevLogger != logger) {
|
||||||
|
std::swap(prevLogger, logger);
|
||||||
|
delete prevLogger;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
co_return result::success();
|
||||||
|
} catch (...) {
|
||||||
|
co_return result::current_exception();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-6
@@ -4,14 +4,20 @@
|
|||||||
#include "lix/libutil/async.hh"
|
#include "lix/libutil/async.hh"
|
||||||
#include "lix/libutil/serialise.hh"
|
#include "lix/libutil/serialise.hh"
|
||||||
#include "lix/libstore/store-api.hh"
|
#include "lix/libstore/store-api.hh"
|
||||||
|
#include <kj/async-io.h>
|
||||||
|
|
||||||
|
namespace nix::rpc::daemon {
|
||||||
|
// legacy boot protocol. EXPLICITLY UNSTABLE, this id will change frequently and without notice.
|
||||||
|
// every change to the experimental tunneling protocol may also change this protocol identifier.
|
||||||
|
extern const std::string UNSTABLE_LEGACY_TUNNELED;
|
||||||
|
}
|
||||||
|
|
||||||
namespace nix::daemon {
|
namespace nix::daemon {
|
||||||
|
|
||||||
void processConnection(
|
void processLegacyConnection(
|
||||||
AsyncIoRoot & aio,
|
AsyncIoRoot & aio, ref<Store> store, FdSource & from, FdSink & to, TrustedFlag trusted
|
||||||
ref<Store> store,
|
);
|
||||||
FdSource & from,
|
|
||||||
FdSink & to,
|
|
||||||
TrustedFlag trusted);
|
|
||||||
|
|
||||||
|
kj::Promise<Result<void>>
|
||||||
|
processConnection(ref<Store> store, kj::AsyncIoStream & connection, TrustedFlag trusted);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -744,12 +744,6 @@ WireFormatGenerator serializeDerivation(const Store & store, const BasicDerivati
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void writeDerivation(Sink & out, const Store & store, const BasicDerivation & drv)
|
|
||||||
{
|
|
||||||
out << serializeDerivation(store, drv);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
std::string hashPlaceholder(const OutputNameView outputName)
|
std::string hashPlaceholder(const OutputNameView outputName)
|
||||||
{
|
{
|
||||||
// FIXME: memoize?
|
// FIXME: memoize?
|
||||||
|
|||||||
@@ -359,7 +359,6 @@ struct Sink;
|
|||||||
|
|
||||||
Source & readDerivation(Source & in, const Store & store, BasicDerivation & drv, std::string_view name);
|
Source & readDerivation(Source & in, const Store & store, BasicDerivation & drv, std::string_view name);
|
||||||
WireFormatGenerator serializeDerivation(const Store & store, const BasicDerivation & drv);
|
WireFormatGenerator serializeDerivation(const Store & store, const BasicDerivation & drv);
|
||||||
void writeDerivation(Sink & out, const Store & store, const BasicDerivation & drv);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This creates an opaque and almost certainly unique string
|
* This creates an opaque and almost certainly unique string
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ type: bool
|
|||||||
default: true
|
default: true
|
||||||
---
|
---
|
||||||
Whether to enable HTTP/2 support.
|
Whether to enable HTTP/2 support.
|
||||||
|
HTTP/2 support cannot be disabled if HTTP/3 support is enabled, the `http2` setting will be ignored in this case.
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ type: bool
|
|||||||
default: false
|
default: false
|
||||||
---
|
---
|
||||||
Whether to enable HTTP/3 support.
|
Whether to enable HTTP/3 support.
|
||||||
|
Enabling HTTP/3 support forcibly enables HTTP/2 as well; the `http2` will be ignored in this case.
|
||||||
|
|||||||
+10
-1271
File diff suppressed because it is too large
Load Diff
@@ -3,17 +3,17 @@
|
|||||||
|
|
||||||
#include "lix/libutil/async-io.hh"
|
#include "lix/libutil/async-io.hh"
|
||||||
#include "lix/libutil/box_ptr.hh"
|
#include "lix/libutil/box_ptr.hh"
|
||||||
#include "lix/libutil/ref.hh"
|
#include "lix/libutil/config.hh"
|
||||||
#include "lix/libutil/logging.hh"
|
#include "lix/libutil/logging.hh"
|
||||||
|
#include "lix/libutil/ref.hh"
|
||||||
#include "lix/libutil/result.hh"
|
#include "lix/libutil/result.hh"
|
||||||
#include "lix/libutil/serialise.hh"
|
#include "lix/libutil/serialise.hh"
|
||||||
|
#include "lix/libutil/strings.hh"
|
||||||
#include "lix/libutil/types.hh"
|
#include "lix/libutil/types.hh"
|
||||||
#include "lix/libutil/config.hh"
|
|
||||||
|
|
||||||
#include <kj/async.h>
|
#include <kj/async.h>
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <future>
|
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
@@ -118,7 +118,18 @@ public:
|
|||||||
std::optional<std::string> response;
|
std::optional<std::string> response;
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
FileTransferError(FileTransfer::Error error, std::optional<std::string> response, const Args & ... args);
|
FileTransferError(FileTransfer::Error error, std::optional<std::string> response, const Args & ... args)
|
||||||
|
: Error(args...), error(error), response(response)
|
||||||
|
{
|
||||||
|
const auto hf = HintFmt(args...);
|
||||||
|
// FIXME: Due to https://github.com/NixOS/nix/issues/3841 we don't know how
|
||||||
|
// to print different messages for different verbosity levels. For now
|
||||||
|
// we add some heuristics for detecting when we want to show the response.
|
||||||
|
if (response && (response->size() < 1024 || response->find("<html>") != std::string::npos))
|
||||||
|
err.msg = HintFmt("%1%\n\nresponse body:\n\n%2%", Uncolored(hf.str()), chomp(*response));
|
||||||
|
else
|
||||||
|
err.msg = hf;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,13 +39,13 @@ struct GCOptions
|
|||||||
* Any that could not be deleted are returned via the
|
* Any that could not be deleted are returned via the
|
||||||
* `kept` field of GCResults.
|
* `kept` field of GCResults.
|
||||||
*/
|
*/
|
||||||
typedef enum {
|
enum GCAction : uint8_t {
|
||||||
gcReturnLive,
|
gcReturnLive,
|
||||||
gcReturnDead,
|
gcReturnDead,
|
||||||
gcDeleteDead,
|
gcDeleteDead,
|
||||||
gcDeleteSpecific,
|
gcDeleteSpecific,
|
||||||
gcTryDeleteSpecific,
|
gcTryDeleteSpecific,
|
||||||
} GCAction;
|
};
|
||||||
|
|
||||||
GCAction action{gcDeleteDead};
|
GCAction action{gcDeleteDead};
|
||||||
|
|
||||||
|
|||||||
+47
-5
@@ -67,15 +67,16 @@ Settings::Settings()
|
|||||||
, nixManDir(canonPath(NIX_MAN_DIR))
|
, nixManDir(canonPath(NIX_MAN_DIR))
|
||||||
{
|
{
|
||||||
if (auto socketDirFromEnv = getEnvNonEmpty("LIX_DAEMON_SOCKET_DIR")) {
|
if (auto socketDirFromEnv = getEnvNonEmpty("LIX_DAEMON_SOCKET_DIR")) {
|
||||||
nixDaemonSockets_ = {{canonPath(*socketDirFromEnv + LEGACY_SOCKET_COMBINED)}};
|
nixDaemonSockets_ = daemon::supportedProtocols(*socketDirFromEnv);
|
||||||
|
socketsPath = *socketDirFromEnv;
|
||||||
} else if (auto socketPathFromEnv = getEnvNonEmpty("NIX_DAEMON_SOCKET_PATH")) {
|
} else if (auto socketPathFromEnv = getEnvNonEmpty("NIX_DAEMON_SOCKET_PATH")) {
|
||||||
nixDaemonSockets_ = {{canonPath(*socketPathFromEnv)}};
|
nixDaemonSockets_ = {{canonPath(*socketPathFromEnv), daemon::Protocol::LEGACY_COMBINED}};
|
||||||
|
socketsPath = *socketPathFromEnv;
|
||||||
} else {
|
} else {
|
||||||
auto baseDir = nixStateDir + DEFAULT_SOCKET_DIR;
|
auto baseDir = nixStateDir + DEFAULT_SOCKET_DIR;
|
||||||
|
socketsPath = baseDir;
|
||||||
// this should always match the list of sockets created by daemonLoop and the socket units
|
// this should always match the list of sockets created by daemonLoop and the socket units
|
||||||
nixDaemonSockets_ = {
|
nixDaemonSockets_ = daemon::supportedProtocols(baseDir);
|
||||||
{canonPath(baseDir + LEGACY_SOCKET_COMBINED)},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildUsersGroup.setDefault(getuid() == 0 ? "nixbld" : "");
|
buildUsersGroup.setDefault(getuid() == 0 ? "nixbld" : "");
|
||||||
@@ -530,4 +531,45 @@ void initLibStore()
|
|||||||
|
|
||||||
initLibStoreDone = true;
|
initLibStoreDone = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace daemon {
|
||||||
|
static inline constexpr auto LEGACY_COMBINED_STR = "legacy-combined";
|
||||||
|
static inline constexpr auto LEGACY_STR = "legacy";
|
||||||
|
static inline constexpr auto LIX_XP_1_STR = "lix-xp-1";
|
||||||
|
|
||||||
|
std::list<daemon::Protocol> supportedProtocols(std::optional<PathView> prefix)
|
||||||
|
{
|
||||||
|
const Path base = prefix ? *prefix + "/" : "";
|
||||||
|
return {
|
||||||
|
{base + "lix-xp-1/socket", Protocol::RPC_V1},
|
||||||
|
{base + "socket", Protocol::LEGACY},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view daemon::Protocol::id() const
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case LEGACY_COMBINED:
|
||||||
|
return LEGACY_COMBINED_STR;
|
||||||
|
case LEGACY:
|
||||||
|
return LEGACY_STR;
|
||||||
|
case RPC_V1:
|
||||||
|
return LIX_XP_1_STR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
daemon::Protocol getProtocol(std::string_view protocol, std::optional<PathView> prefix)
|
||||||
|
{
|
||||||
|
const Path base = prefix ? *prefix + "/" : "";
|
||||||
|
if (protocol == LEGACY_COMBINED_STR) {
|
||||||
|
return {prefix ? Path(*prefix) : "", Protocol::LEGACY_COMBINED};
|
||||||
|
} else if (protocol == LEGACY_STR) {
|
||||||
|
return {base + "socket", Protocol::LEGACY_COMBINED};
|
||||||
|
} else if (protocol == LIX_XP_1_STR) {
|
||||||
|
return {base + "lix-xp-1/socket", Protocol::RPC_V1};
|
||||||
|
} else {
|
||||||
|
throw Error("unsupported daemon protocol %s", protocol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+38
-9
@@ -10,6 +10,37 @@
|
|||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
namespace daemon {
|
||||||
|
struct Protocol
|
||||||
|
{
|
||||||
|
/// path of socket. may be relative or absolute, depending on context.
|
||||||
|
Path path;
|
||||||
|
enum Type : int {
|
||||||
|
LEGACY_COMBINED,
|
||||||
|
LEGACY,
|
||||||
|
RPC_V1,
|
||||||
|
} type;
|
||||||
|
|
||||||
|
/// external identifier of the protocol (eg for `protocol` store parameters)
|
||||||
|
std::string_view id() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the list of protocols we can serve, most preferred first. set the `prefix` to
|
||||||
|
* retrieve socket paths beneath the prefix, otherwise the search suffices are returned.
|
||||||
|
*/
|
||||||
|
std::list<Protocol> supportedProtocols(std::optional<PathView> prefix = {});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the protocol description with id `protocol` at `prefix`. if `prefix` is `nullopt`
|
||||||
|
* the returned protocol will contain a relative path. throws an exception if `protocol`
|
||||||
|
* is not supported or unknown. understands all protocols in `supportedProtocols` and is
|
||||||
|
* able to resolve `legacy-combined` even though `supportedProtocols` would never return
|
||||||
|
* it; this is mainly useful for client-side socket resolution using default store uris.
|
||||||
|
*/
|
||||||
|
Protocol getProtocol(std::string_view protocol, std::optional<PathView> prefix = {});
|
||||||
|
}
|
||||||
|
|
||||||
typedef enum { smEnabled, smRelaxed, smDisabled } SandboxMode;
|
typedef enum { smEnabled, smRelaxed, smDisabled } SandboxMode;
|
||||||
|
|
||||||
void to_json(JSON & j, const SandboxMode & e);
|
void to_json(JSON & j, const SandboxMode & e);
|
||||||
@@ -63,14 +94,7 @@ const uint32_t maxIdsPerBuild =
|
|||||||
|
|
||||||
class Settings : public Config
|
class Settings : public Config
|
||||||
{
|
{
|
||||||
public:
|
std::list<daemon::Protocol> nixDaemonSockets_;
|
||||||
struct DaemonSocketPath
|
|
||||||
{
|
|
||||||
Path path;
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::list<DaemonSocketPath> nixDaemonSockets_;
|
|
||||||
|
|
||||||
unsigned int getDefaultCores();
|
unsigned int getDefaultCores();
|
||||||
|
|
||||||
@@ -105,6 +129,11 @@ public:
|
|||||||
*/
|
*/
|
||||||
Path nixStateDir;
|
Path nixStateDir;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The directory where sockets are stored.
|
||||||
|
*/
|
||||||
|
Path socketsPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The directory where system configuration files are stored.
|
* The directory where system configuration files are stored.
|
||||||
*/
|
*/
|
||||||
@@ -128,7 +157,7 @@ public:
|
|||||||
/**
|
/**
|
||||||
* Socket paths a client should connect to, in order of decreasing preference.
|
* Socket paths a client should connect to, in order of decreasing preference.
|
||||||
*/
|
*/
|
||||||
const std::list<DaemonSocketPath> & nixDaemonSockets() const
|
const std::list<daemon::Protocol> & nixDaemonSockets() const
|
||||||
{
|
{
|
||||||
return nixDaemonSockets_;
|
return nixDaemonSockets_;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
#include "lix/libstore/legacy-ssh-store.hh"
|
#include "lix/libstore/legacy-ssh-store.hh"
|
||||||
|
#include "libutil/error.hh"
|
||||||
|
#include "libutil/logging.hh"
|
||||||
|
#include "libutil/sync.hh"
|
||||||
#include "lix/libutil/archive.hh"
|
#include "lix/libutil/archive.hh"
|
||||||
#include "lix/libutil/async-io.hh"
|
#include "lix/libutil/async-io.hh"
|
||||||
#include "lix/libutil/async.hh"
|
#include "lix/libutil/async.hh"
|
||||||
@@ -20,6 +23,8 @@
|
|||||||
#include "path-info.hh"
|
#include "path-info.hh"
|
||||||
#include "path.hh"
|
#include "path.hh"
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <exception>
|
||||||
|
#include <kj/async.h>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
@@ -82,9 +87,6 @@ struct LegacySSHStoreConfig : CommonSSHStoreConfig
|
|||||||
const Setting<Path> remoteProgram{this, "nix-store", "remote-program",
|
const Setting<Path> remoteProgram{this, "nix-store", "remote-program",
|
||||||
"Path to the `nix-store` executable on the remote machine."};
|
"Path to the `nix-store` executable on the remote machine."};
|
||||||
|
|
||||||
const Setting<int> maxConnections{this, 1, "max-connections",
|
|
||||||
"Maximum number of concurrent SSH connections."};
|
|
||||||
|
|
||||||
const std::string name() override { return "SSH Store"; }
|
const std::string name() override { return "SSH Store"; }
|
||||||
|
|
||||||
std::string doc() override
|
std::string doc() override
|
||||||
@@ -95,25 +97,25 @@ struct LegacySSHStoreConfig : CommonSSHStoreConfig
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct LegacySSHStoreConfigWithLog : LegacySSHStoreConfig
|
|
||||||
{
|
|
||||||
using LegacySSHStoreConfig::LegacySSHStoreConfig;
|
|
||||||
|
|
||||||
// Hack for getting remote build log output.
|
|
||||||
// Intentionally not in `LegacySSHStoreConfig` so that it doesn't appear in
|
|
||||||
// the documentation
|
|
||||||
const Setting<int> logFD{this, -1, "log-fd", "file descriptor to which SSH's stderr is connected"};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct LegacySSHStore final : public Store
|
struct LegacySSHStore final : public Store
|
||||||
{
|
{
|
||||||
LegacySSHStoreConfigWithLog config_;
|
LegacySSHStoreConfig config_;
|
||||||
|
|
||||||
LegacySSHStoreConfigWithLog & config() override { return config_; }
|
LegacySSHStoreConfig & config() override
|
||||||
const LegacySSHStoreConfigWithLog & config() const override { return config_; }
|
{
|
||||||
|
return config_;
|
||||||
|
}
|
||||||
|
const LegacySSHStoreConfig & config() const override
|
||||||
|
{
|
||||||
|
return config_;
|
||||||
|
}
|
||||||
|
|
||||||
struct Connection
|
struct Connection
|
||||||
{
|
{
|
||||||
|
// make sure this is destroyed last so the sshConn that feeds it dies first
|
||||||
|
kj::Promise<void> logHandlerPromise{nullptr};
|
||||||
|
std::list<Activity> act;
|
||||||
|
|
||||||
ref<IoBuffer> fromBuf{make_ref<IoBuffer>()};
|
ref<IoBuffer> fromBuf{make_ref<IoBuffer>()};
|
||||||
std::unique_ptr<SSH::Connection> sshConn;
|
std::unique_ptr<SSH::Connection> sshConn;
|
||||||
ServeProto::Version remoteVersion;
|
ServeProto::Version remoteVersion;
|
||||||
@@ -136,6 +138,54 @@ struct LegacySSHStore final : public Store
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kj::Promise<void> logHandler(std::string storeUri)
|
||||||
|
try {
|
||||||
|
// NOTE this is very similar to handleBuilderOutput in DerivationGoal, but unlike
|
||||||
|
// the derivation goal we do not need to handle EIO from a pty here. we also have
|
||||||
|
// no timeouts or limits to keep track of, which makes deduplication less useful.
|
||||||
|
|
||||||
|
std::map<ActivityId, Activity> activities;
|
||||||
|
|
||||||
|
auto stderrPipe = std::move(sshConn->stderrPipe);
|
||||||
|
auto reader = AIO().lowLevelProvider.wrapInputFd(stderrPipe.get());
|
||||||
|
|
||||||
|
LogLineSplitter splitter;
|
||||||
|
|
||||||
|
auto flushLine = [&](const std::string & line) {
|
||||||
|
if (const auto state = handleJSONLogMessage(line, act.back(), activities, storeUri)) {
|
||||||
|
return *state;
|
||||||
|
} else {
|
||||||
|
return act.back().result(resBuildLogLine, line);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto buf = kj::heapArray<char>(4096);
|
||||||
|
while (true) {
|
||||||
|
const auto got = co_await reader->tryRead(buf.begin(), 1, buf.size());
|
||||||
|
if (got == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view data{buf.begin(), got};
|
||||||
|
while (!data.empty()) {
|
||||||
|
if (auto line = splitter.feed(data)) {
|
||||||
|
if (flushLine(*line) == Logger::BufferState::NeedsFlush) {
|
||||||
|
TRY_AWAIT(act.back().getLogger().flush());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto line = splitter.finish(); !line.empty()) {
|
||||||
|
(void) flushLine(line);
|
||||||
|
TRY_AWAIT(act.back().getLogger().flush());
|
||||||
|
}
|
||||||
|
} catch (std::exception & e) { // NOLINT(lix-foreign-exceptions)
|
||||||
|
logException("remote store error", e);
|
||||||
|
} catch (...) {
|
||||||
|
std::terminate();
|
||||||
|
}
|
||||||
|
|
||||||
template<typename Arg>
|
template<typename Arg>
|
||||||
kj::Promise<Result<void>>
|
kj::Promise<Result<void>>
|
||||||
sendArg(AsyncOutputStream & stream, StringSink & buffer, Arg && arg)
|
sendArg(AsyncOutputStream & stream, StringSink & buffer, Arg && arg)
|
||||||
@@ -204,72 +254,70 @@ struct LegacySSHStore final : public Store
|
|||||||
|
|
||||||
std::string host;
|
std::string host;
|
||||||
|
|
||||||
ref<Pool<Connection>> connections;
|
Sync<Connection, AsyncMutex> connection;
|
||||||
|
|
||||||
SSH ssh;
|
SSH ssh;
|
||||||
|
|
||||||
static std::set<std::string> uriSchemes() { return {"ssh"}; }
|
static std::set<std::string> uriSchemes() { return {"ssh"}; }
|
||||||
|
|
||||||
LegacySSHStore(
|
LegacySSHStore(const std::string & scheme, const std::string & host, LegacySSHStoreConfig config)
|
||||||
const std::string & scheme, const std::string & host, LegacySSHStoreConfigWithLog config
|
|
||||||
)
|
|
||||||
: Store(config)
|
: Store(config)
|
||||||
, config_(std::move(config))
|
, config_(std::move(config))
|
||||||
, host(host)
|
, host(host)
|
||||||
, connections(make_ref<Pool<Connection>>(
|
, ssh(host, config_.port, config_.sshKey, config_.sshPublicHostKey, config_.compress)
|
||||||
std::max(1, (int) config_.maxConnections),
|
|
||||||
[this]() { return openConnection(); },
|
|
||||||
[](const ref<Connection> & r) { return r->good; }
|
|
||||||
))
|
|
||||||
, ssh(
|
|
||||||
host,
|
|
||||||
config_.port,
|
|
||||||
config_.sshKey,
|
|
||||||
config_.sshPublicHostKey,
|
|
||||||
config_.compress,
|
|
||||||
config_.logFD)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
kj::Promise<Result<ref<Connection>>> openConnection()
|
kj::Promise<Result<decltype(connection)::Lock>> getConnection()
|
||||||
try {
|
try {
|
||||||
auto conn = make_ref<Connection>();
|
auto conn = co_await connection.lock();
|
||||||
conn->sshConn = ssh.startCommand(
|
if (conn->good && conn->sshConn) {
|
||||||
fmt("%s --serve --write", config_.remoteProgram)
|
co_return conn;
|
||||||
+ (config_.remoteStore.get() == ""
|
|
||||||
? ""
|
|
||||||
: " --store " + shellEscape(config_.remoteStore.get()))
|
|
||||||
);
|
|
||||||
FdSink to(conn->sshConn->socket.get());
|
|
||||||
FdSource from(conn->sshConn->socket.get(), conn->fromBuf);
|
|
||||||
conn->store = this;
|
|
||||||
|
|
||||||
try {
|
|
||||||
to << SERVE_MAGIC_1 << SERVE_PROTOCOL_VERSION;
|
|
||||||
to.flush();
|
|
||||||
|
|
||||||
uint64_t magic = readNum<uint64_t>(from);
|
|
||||||
if (magic != SERVE_MAGIC_2)
|
|
||||||
throw Error("'nix-store --serve' protocol mismatch from '%s'", host);
|
|
||||||
conn->remoteVersion = readNum<unsigned>(from);
|
|
||||||
if (GET_PROTOCOL_MAJOR(conn->remoteVersion) != 0x200)
|
|
||||||
throw Error("unsupported 'nix-store --serve' protocol version on '%s'", host);
|
|
||||||
|
|
||||||
/* No longer support protocols this old*/
|
|
||||||
if (GET_PROTOCOL_MINOR(conn->remoteVersion) < 4) {
|
|
||||||
throw Error(
|
|
||||||
"remote '%s' is too old (protocol version %x)", host, conn->remoteVersion
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (EndOfFile & e) {
|
|
||||||
throw Error("cannot connect to '%1%'", host);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {conn};
|
*conn = {};
|
||||||
|
conn->sshConn = ssh.startCommand(
|
||||||
|
fmt("%s --serve --write", config_.remoteProgram)
|
||||||
|
+ (config_.remoteStore.get() == "" ? "" : " --store " + shellEscape(config_.remoteStore.get()))
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
FdSink to(conn->sshConn->socket.get());
|
||||||
|
FdSource from(conn->sshConn->socket.get(), conn->fromBuf);
|
||||||
|
conn->store = this;
|
||||||
|
|
||||||
|
try {
|
||||||
|
to << SERVE_MAGIC_1 << SERVE_PROTOCOL_VERSION;
|
||||||
|
to.flush();
|
||||||
|
|
||||||
|
uint64_t magic = readNum<uint64_t>(from);
|
||||||
|
if (magic != SERVE_MAGIC_2) {
|
||||||
|
throw Error("'nix-store --serve' protocol mismatch from '%s'", host);
|
||||||
|
}
|
||||||
|
conn->remoteVersion = readNum<unsigned>(from);
|
||||||
|
if (GET_PROTOCOL_MAJOR(conn->remoteVersion) != 0x200) {
|
||||||
|
throw Error("unsupported 'nix-store --serve' protocol version on '%s'", host);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No longer support protocols this old*/
|
||||||
|
if (GET_PROTOCOL_MINOR(conn->remoteVersion) < 4) {
|
||||||
|
throw Error("remote '%s' is too old (protocol version %x)", host, conn->remoteVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (EndOfFile & e) {
|
||||||
|
throw Error("cannot connect to '%1%'", host);
|
||||||
|
}
|
||||||
|
|
||||||
|
conn->act.emplace_back(logger->startActivity(lvlDebug, actUnknown, "remote store " + getUri()));
|
||||||
|
conn->logHandlerPromise = conn->logHandler(host);
|
||||||
|
co_return conn;
|
||||||
|
} catch (Error & e) {
|
||||||
|
std::string msg = chomp(drainFD(conn->sshConn->stderrPipe.get(), false));
|
||||||
|
throw Error("cannot connect to %s: %s (%s)", getUri(), e.msg(), msg);
|
||||||
|
}
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
return {result::current_exception()};
|
co_return result::current_exception();
|
||||||
};
|
}
|
||||||
|
|
||||||
std::string getUri() override
|
std::string getUri() override
|
||||||
{
|
{
|
||||||
@@ -279,7 +327,7 @@ struct LegacySSHStore final : public Store
|
|||||||
kj::Promise<Result<std::shared_ptr<const ValidPathInfo>>>
|
kj::Promise<Result<std::shared_ptr<const ValidPathInfo>>>
|
||||||
queryPathInfoUncached(const StorePath & path, const Activity * context) override
|
queryPathInfoUncached(const StorePath & path, const Activity * context) override
|
||||||
try {
|
try {
|
||||||
auto conn(TRY_AWAIT(connections->get()));
|
auto conn(TRY_AWAIT(getConnection()));
|
||||||
|
|
||||||
debug("querying remote host '%s' for info on '%s'", host, printStorePath(path));
|
debug("querying remote host '%s' for info on '%s'", host, printStorePath(path));
|
||||||
|
|
||||||
@@ -307,7 +355,7 @@ struct LegacySSHStore final : public Store
|
|||||||
try {
|
try {
|
||||||
debug("adding path '%s' to remote host '%s'", printStorePath(info.path), host);
|
debug("adding path '%s' to remote host '%s'", printStorePath(info.path), host);
|
||||||
|
|
||||||
auto conn(TRY_AWAIT(connections->get()));
|
auto conn(TRY_AWAIT(getConnection()));
|
||||||
unsigned result;
|
unsigned result;
|
||||||
|
|
||||||
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 5) {
|
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 5) {
|
||||||
@@ -351,16 +399,16 @@ struct LegacySSHStore final : public Store
|
|||||||
kj::Promise<Result<box_ptr<AsyncInputStream>>>
|
kj::Promise<Result<box_ptr<AsyncInputStream>>>
|
||||||
narFromPath(const StorePath & path, const Activity * context) override
|
narFromPath(const StorePath & path, const Activity * context) override
|
||||||
try {
|
try {
|
||||||
auto conn(TRY_AWAIT(connections->get()));
|
auto conn(TRY_AWAIT(getConnection()));
|
||||||
|
|
||||||
struct NarStream : AsyncInputStream
|
struct NarStream : AsyncInputStream
|
||||||
{
|
{
|
||||||
Pool<Connection>::Handle conn;
|
Sync<Connection, AsyncMutex>::Lock conn;
|
||||||
AsyncFdIoStream stream{AsyncFdIoStream::shared_fd{}, conn->sshConn->socket.get()};
|
AsyncFdIoStream stream{AsyncFdIoStream::shared_fd{}, conn->sshConn->socket.get()};
|
||||||
AsyncBufferedInputStream buffered{stream, conn->fromBuf};
|
AsyncBufferedInputStream buffered{stream, conn->fromBuf};
|
||||||
box_ptr<AsyncInputStream> copier{copyNAR(buffered)};
|
box_ptr<AsyncInputStream> copier{copyNAR(buffered)};
|
||||||
|
|
||||||
NarStream(Pool<Connection>::Handle conn) : conn(std::move(conn)) {}
|
NarStream(Sync<Connection, AsyncMutex>::Lock conn) : conn(std::move(conn)) {}
|
||||||
|
|
||||||
kj::Promise<Result<std::optional<size_t>>> read(void * buffer, size_t size) override
|
kj::Promise<Result<std::optional<size_t>>> read(void * buffer, size_t size) override
|
||||||
{
|
{
|
||||||
@@ -424,7 +472,19 @@ public:
|
|||||||
const StorePath & drvPath, const BasicDerivation & drv, BuildMode buildMode
|
const StorePath & drvPath, const BasicDerivation & drv, BuildMode buildMode
|
||||||
) override
|
) override
|
||||||
try {
|
try {
|
||||||
auto conn(TRY_AWAIT(connections->get()));
|
auto conn(TRY_AWAIT(getConnection()));
|
||||||
|
|
||||||
|
// this is a duplicate of DerivationGoal::buildDescription because ugh.
|
||||||
|
// getting that information into here where needed is nigh *impossible*
|
||||||
|
auto description = fmt(buildMode == bmRepair ? "repairing outputs of '%s'"
|
||||||
|
: buildMode == bmCheck ? "checking outputs of '%s'"
|
||||||
|
: "building '%s'",
|
||||||
|
printStorePath(drvPath))
|
||||||
|
+ "on " + getUri();
|
||||||
|
conn->act.emplace_back(logger->startActivity(
|
||||||
|
lvlInfo, actBuild, description, Logger::Fields{printStorePath(drvPath), getUri(), 1, 1}
|
||||||
|
));
|
||||||
|
KJ_DEFER(conn->act.pop_back());
|
||||||
|
|
||||||
co_return TRY_AWAIT(conn->sendCommand<BuildResult>(
|
co_return TRY_AWAIT(conn->sendCommand<BuildResult>(
|
||||||
ServeProto::Command::BuildDerivation,
|
ServeProto::Command::BuildDerivation,
|
||||||
@@ -445,7 +505,7 @@ public:
|
|||||||
if (evalStore && evalStore.get() != this)
|
if (evalStore && evalStore.get() != this)
|
||||||
throw Error("building on an SSH store is incompatible with '--eval-store'");
|
throw Error("building on an SSH store is incompatible with '--eval-store'");
|
||||||
|
|
||||||
auto conn(TRY_AWAIT(connections->get()));
|
auto conn(TRY_AWAIT(getConnection()));
|
||||||
|
|
||||||
Strings ss;
|
Strings ss;
|
||||||
for (auto & p : drvPaths) {
|
for (auto & p : drvPaths) {
|
||||||
@@ -504,7 +564,7 @@ public:
|
|||||||
co_return result::success();
|
co_return result::success();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto conn(TRY_AWAIT(connections->get()));
|
auto conn(TRY_AWAIT(getConnection()));
|
||||||
|
|
||||||
out.merge(TRY_AWAIT(conn->sendCommand<StorePathSet>(
|
out.merge(TRY_AWAIT(conn->sendCommand<StorePathSet>(
|
||||||
ServeProto::Command::QueryClosure, includeOutputs, ServeProto::write(*conn, paths)
|
ServeProto::Command::QueryClosure, includeOutputs, ServeProto::write(*conn, paths)
|
||||||
@@ -518,7 +578,7 @@ public:
|
|||||||
kj::Promise<Result<StorePathSet>> queryValidPaths(const StorePathSet & paths,
|
kj::Promise<Result<StorePathSet>> queryValidPaths(const StorePathSet & paths,
|
||||||
SubstituteFlag maybeSubstitute = NoSubstitute) override
|
SubstituteFlag maybeSubstitute = NoSubstitute) override
|
||||||
try {
|
try {
|
||||||
auto conn(TRY_AWAIT(connections->get()));
|
auto conn(TRY_AWAIT(getConnection()));
|
||||||
|
|
||||||
co_return TRY_AWAIT(conn->sendCommand<StorePathSet>(
|
co_return TRY_AWAIT(conn->sendCommand<StorePathSet>(
|
||||||
ServeProto::Command::QueryValidPaths,
|
ServeProto::Command::QueryValidPaths,
|
||||||
@@ -530,9 +590,9 @@ public:
|
|||||||
co_return result::current_exception();
|
co_return result::current_exception();
|
||||||
}
|
}
|
||||||
|
|
||||||
kj::Promise<Result<void>> connect() override
|
kj::Promise<Result<void>> init() override
|
||||||
try {
|
try {
|
||||||
auto conn(TRY_AWAIT(connections->get()));
|
auto conn(TRY_AWAIT(getConnection()));
|
||||||
co_return result::success();
|
co_return result::success();
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
co_return result::current_exception();
|
co_return result::current_exception();
|
||||||
@@ -540,7 +600,7 @@ public:
|
|||||||
|
|
||||||
kj::Promise<Result<unsigned int>> getProtocol() override
|
kj::Promise<Result<unsigned int>> getProtocol() override
|
||||||
try {
|
try {
|
||||||
auto conn(TRY_AWAIT(connections->get()));
|
auto conn(TRY_AWAIT(getConnection()));
|
||||||
co_return conn->remoteVersion;
|
co_return conn->remoteVersion;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
co_return result::current_exception();
|
co_return result::current_exception();
|
||||||
|
|||||||
@@ -34,19 +34,11 @@ bool Machine::mandatoryMet(const std::set<std::string> & features) const
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
kj::Promise<Result<std::pair<ref<Store>, Pipe>>> Machine::openStore() const
|
kj::Promise<Result<ref<Store>>> Machine::openStore() const
|
||||||
try {
|
try {
|
||||||
Pipe pipe;
|
|
||||||
|
|
||||||
StoreConfig::Params storeParams;
|
StoreConfig::Params storeParams;
|
||||||
if (storeUri.starts_with("ssh://")) {
|
|
||||||
// Remote builds become flakey, when having more than one ssh connection
|
|
||||||
storeParams["max-connections"] = "1";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (storeUri.starts_with("ssh://") || storeUri.starts_with("ssh-ng://")) {
|
if (storeUri.starts_with("ssh://") || storeUri.starts_with("ssh-ng://")) {
|
||||||
pipe.create();
|
|
||||||
storeParams["log-fd"] = std::to_string(pipe.writeSide.get());
|
|
||||||
if (sshKey != "")
|
if (sshKey != "")
|
||||||
storeParams["ssh-key"] = sshKey;
|
storeParams["ssh-key"] = sshKey;
|
||||||
if (sshPublicHostKey != "")
|
if (sshPublicHostKey != "")
|
||||||
@@ -65,7 +57,7 @@ try {
|
|||||||
append(mandatoryFeatures);
|
append(mandatoryFeatures);
|
||||||
}
|
}
|
||||||
|
|
||||||
co_return {TRY_AWAIT(nix::openStore(storeUri, storeParams)), std::move(pipe)};
|
co_return TRY_AWAIT(nix::openStore(storeUri, storeParams));
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
co_return result::current_exception();
|
co_return result::current_exception();
|
||||||
}
|
}
|
||||||
@@ -167,6 +159,7 @@ static Machine parseBuilderLine(const std::string & line)
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
storeUri,
|
||||||
storeUri,
|
storeUri,
|
||||||
systemTypes,
|
systemTypes,
|
||||||
sshKey,
|
sshKey,
|
||||||
@@ -262,7 +255,8 @@ static toml::result<float, std::string> getSpeedFactor(const toml::value & data)
|
|||||||
return toml::success(1.0f);
|
return toml::success(1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
static toml::result<Machine, std::vector<std::string>> parseMachine(const toml::value & data)
|
static toml::result<Machine, std::vector<std::string>>
|
||||||
|
parseMachine(const std::string name, const toml::value & data)
|
||||||
{
|
{
|
||||||
std::vector<std::string> errs;
|
std::vector<std::string> errs;
|
||||||
|
|
||||||
@@ -339,6 +333,7 @@ static toml::result<Machine, std::vector<std::string>> parseMachine(const toml::
|
|||||||
return toml::failure(errs);
|
return toml::failure(errs);
|
||||||
}
|
}
|
||||||
return toml::success<Machine>({
|
return toml::success<Machine>({
|
||||||
|
name,
|
||||||
storeUri.unwrap(),
|
storeUri.unwrap(),
|
||||||
std::set(systemTypes.unwrap().begin(), systemTypes.unwrap().end()),
|
std::set(systemTypes.unwrap().begin(), systemTypes.unwrap().end()),
|
||||||
sshKey.unwrap(),
|
sshKey.unwrap(),
|
||||||
@@ -404,7 +399,7 @@ static toml::result<Machines, std::vector<std::string>> parseToml(const toml::va
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const auto & [name, machine] : data.at(array_name).as_table()) {
|
for (const auto & [name, machine] : data.at(array_name).as_table()) {
|
||||||
auto const res = parseMachine(machine);
|
auto const res = parseMachine(name, machine);
|
||||||
if (res.is_err()) {
|
if (res.is_err()) {
|
||||||
auto err = res.as_err();
|
auto err = res.as_err();
|
||||||
parserErrors.push_back(fmt("for machine %s:", name));
|
parserErrors.push_back(fmt("for machine %s:", name));
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class Store;
|
|||||||
|
|
||||||
struct Machine {
|
struct Machine {
|
||||||
|
|
||||||
|
const std::string name;
|
||||||
const std::string storeUri;
|
const std::string storeUri;
|
||||||
const std::set<std::string> systemTypes;
|
const std::set<std::string> systemTypes;
|
||||||
const std::string sshKey;
|
const std::string sshKey;
|
||||||
@@ -41,7 +42,7 @@ struct Machine {
|
|||||||
*/
|
*/
|
||||||
bool mandatoryMet(const std::set<std::string> & features) const;
|
bool mandatoryMet(const std::set<std::string> & features) const;
|
||||||
|
|
||||||
kj::Promise<Result<std::pair<ref<Store>, Pipe>>> openStore() const;
|
kj::Promise<Result<ref<Store>>> openStore() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::vector<Machine> Machines;
|
typedef std::vector<Machine> Machines;
|
||||||
|
|||||||
+31
-20
@@ -11,28 +11,33 @@ endforeach
|
|||||||
|
|
||||||
libstore_rpc = []
|
libstore_rpc = []
|
||||||
|
|
||||||
libstore_rpc += custom_target(
|
libstore_rpc_files = files(
|
||||||
command : [
|
# keep-sorted start
|
||||||
capnpc_wrapper,
|
'daemon.capnp',
|
||||||
'--language=c++',
|
'types.capnp',
|
||||||
'--src-prefix=@CURRENT_SOURCE_DIR@',
|
# keep-sorted end
|
||||||
'--outdir=@OUTDIR@',
|
|
||||||
'--depfile=@DEPFILE@',
|
|
||||||
'-I@SOURCE_ROOT@',
|
|
||||||
'@INPUT@',
|
|
||||||
],
|
|
||||||
input : files(
|
|
||||||
# keep-sorted start
|
|
||||||
'types.capnp',
|
|
||||||
# keep-sorted end
|
|
||||||
),
|
|
||||||
output : [
|
|
||||||
'@PLAINNAME@.h',
|
|
||||||
'@PLAINNAME@.c++',
|
|
||||||
],
|
|
||||||
depfile : '@PLAINNAME@.d',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
foreach rpc_file : libstore_rpc_files
|
||||||
|
libstore_rpc += custom_target(
|
||||||
|
command : [
|
||||||
|
capnpc_wrapper,
|
||||||
|
'--language=c++',
|
||||||
|
'--src-prefix=@CURRENT_SOURCE_DIR@',
|
||||||
|
'--outdir=@OUTDIR@',
|
||||||
|
'--depfile=@DEPFILE@',
|
||||||
|
'-I@SOURCE_ROOT@',
|
||||||
|
'@INPUT@',
|
||||||
|
],
|
||||||
|
input : rpc_file,
|
||||||
|
output : [
|
||||||
|
'@PLAINNAME@.h',
|
||||||
|
'@PLAINNAME@.c++',
|
||||||
|
],
|
||||||
|
depfile : '@PLAINNAME@.d',
|
||||||
|
)
|
||||||
|
endforeach
|
||||||
|
|
||||||
# rpc definitions only here to get relative paths of generated files right
|
# rpc definitions only here to get relative paths of generated files right
|
||||||
subdir('build')
|
subdir('build')
|
||||||
|
|
||||||
@@ -189,6 +194,8 @@ liblix_sources += files(
|
|||||||
'common-protocol.cc',
|
'common-protocol.cc',
|
||||||
'content-address.cc',
|
'content-address.cc',
|
||||||
'crypto.cc',
|
'crypto.cc',
|
||||||
|
'curlfiletransfer.cc',
|
||||||
|
'curlmulti.cc',
|
||||||
'daemon.cc',
|
'daemon.cc',
|
||||||
'derivations.cc',
|
'derivations.cc',
|
||||||
'derived-path.cc',
|
'derived-path.cc',
|
||||||
@@ -232,6 +239,7 @@ liblix_sources += files(
|
|||||||
'ssh.cc',
|
'ssh.cc',
|
||||||
'store-api.cc',
|
'store-api.cc',
|
||||||
'temporary-dir.cc',
|
'temporary-dir.cc',
|
||||||
|
'transferitem.cc',
|
||||||
'uds-remote-store.cc',
|
'uds-remote-store.cc',
|
||||||
'worker-protocol.cc',
|
'worker-protocol.cc',
|
||||||
# keep-sorted end
|
# keep-sorted end
|
||||||
@@ -255,6 +263,8 @@ libstore_headers = files(
|
|||||||
'common-protocol.hh',
|
'common-protocol.hh',
|
||||||
'content-address.hh',
|
'content-address.hh',
|
||||||
'crypto.hh',
|
'crypto.hh',
|
||||||
|
'curlfiletransfer.hh',
|
||||||
|
'curlmulti.hh',
|
||||||
'daemon.hh',
|
'daemon.hh',
|
||||||
'derivations.hh',
|
'derivations.hh',
|
||||||
'derived-path.hh',
|
'derived-path.hh',
|
||||||
@@ -302,6 +312,7 @@ libstore_headers = files(
|
|||||||
'store-api.hh',
|
'store-api.hh',
|
||||||
'store-cast.hh',
|
'store-cast.hh',
|
||||||
'temporary-dir.hh',
|
'temporary-dir.hh',
|
||||||
|
'transferitem.hh',
|
||||||
'types-rpc.hh',
|
'types-rpc.hh',
|
||||||
'uds-remote-store.hh',
|
'uds-remote-store.hh',
|
||||||
'worker-protocol-impl.hh',
|
'worker-protocol-impl.hh',
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user