Compare commits

..
504 changed files with 7280 additions and 14706 deletions
-14
View File
@@ -1,14 +0,0 @@
[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',
]
+1 -2
View File
@@ -1,5 +1,4 @@
/build
/outputs
outputs/
# GNU Global
GPATH
Generated
+4 -878
View File
@@ -2,320 +2,18 @@
# It is not intended for manual editing.
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]]
name = "countme"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "dissimilar"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "expect-test"
version = "1.5.0"
@@ -326,139 +24,12 @@ dependencies = [
"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]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "lix-doc"
version = "0.0.1"
@@ -469,87 +40,8 @@ dependencies = [
]
[[package]]
name = "log"
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",
]
name = "lixutil-rs"
version = "0.0.0"
[[package]]
name = "once_cell"
@@ -557,108 +49,6 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "rnix"
version = "0.12.0"
@@ -668,28 +58,6 @@ dependencies = [
"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]]
name = "rowan"
version = "0.15.16"
@@ -697,8 +65,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a542b0253fa46e632d27a1dc5cf7b930de4df8659dc6e720b647fc72147ae3d"
dependencies = [
"countme",
"hashbrown 0.14.5",
"rustc-hash 1.1.0",
"hashbrown",
"rustc-hash",
"text-size",
]
@@ -708,250 +76,8 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "text-size"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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",
]
+1 -23
View File
@@ -1,28 +1,6 @@
[workspace]
resolver = "2"
members = [
"lix/lix-doc",
"lix/lix-rs",
"tools/licxxbridge",
]
members = ["lix/lix-doc", "lix/libutil"]
[workspace.package]
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
+1 -2
View File
@@ -10,7 +10,6 @@ import platform
import shlex
import textwrap
import dataclasses
from pathlib import Path
flake_args = ["--extra-experimental-features", "nix-command flakes"]
cases = {
@@ -19,7 +18,7 @@ cases = {
*flake_args,
"search",
"--no-eval-cache",
f"path:{Path('./bench/nixpkgs/').readlink()}",
"github:nixos/nixpkgs/e1fa12d4f6c6fe19ccb59cac54b5b3f25e160870",
"hello",
],
"rebuild": lambda build: [
+13 -7
View File
@@ -1,13 +1,19 @@
# Darwin: don't link liblix* into plugins (host process provides them at runtime).
# Explicitly link curl so it binds to Nix-store libcurl, not /usr/lib/libcurl.
if is_darwin
plugin_deps = [
liblix.partial_dependency(includes : true, compile_args : true),
curl,
]
else
plugin_deps = [liblix, curl]
endif
plugin_mtls_store = shared_module(
'plugin_mtls_store',
'plugin_mtls_store.cc',
# 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,
],
dependencies : plugin_deps,
install : false,
build_by_default : true,
link_args : plugin_link_args,
link_args : is_darwin ? shared_module_link_args : strict_shared_module_link_args,
)
+2 -1
View File
@@ -24,7 +24,8 @@ def map_contents_recursively(transformer):
def process_command:
.[0] as $context |
.[1] as $body |
$body | .items |= map(map_contents_recursively(if $context.renderer == "html" then transform_anchors_html else transform_anchors_strip end))
# XXX FUTURE: drop sections once mdBook is at 0.5.0 or above in nixpkgs
$body | (.items? // .sections) |= map(map_contents_recursively(if $context.renderer == "html" then transform_anchors_html else transform_anchors_strip end))
;
process_command
+4 -3
View File
@@ -31,11 +31,12 @@ command = "jq --from-file anchors.jq"
[output.markdown]
[output.linkcheck2]
# XXX FUTURE: may be reenabled once mdBook 0.5.0 or above and matching mdbook-linkchecker are in nixpkgs
#[output.linkcheck]
# no Internet during the build (in the sandbox)
follow-web-links = false
#follow-web-links = false
# mdbook-linkcheck does not understand [foo]{#bar} style links, resulting in
# excessive "Potential incomplete link" warnings. No other kind of warning was
# produced at the time of writing.
warning-policy = "ignore"
#warning-policy = "ignore"
-22
View File
@@ -48,11 +48,6 @@ artemist:
display_name: Artemis Tosini
forgejo: artemist
astreaprtcl:
display_name: Astreaprtcl
forgejo: astreaprtcl
github: astreaprtcl
bb010g:
display_name: Dusk Banks
forgejo: bb010g
@@ -82,9 +77,6 @@ detroyejr:
display_name: Jonathan De Troye
github: detroyejr
edef:
github: edef1c
edolstra:
display_name: Eelco Dolstra
github: edolstra
@@ -150,9 +142,6 @@ kasimeka:
forgejo: janw4ld
github: kasimeka
keysmashes:
github: keysmashes
kfears:
display_name: KFears
forgejo: kfearsoff
@@ -256,11 +245,6 @@ raito:
forgejo: raito
github: RaitoBezarius
rkjnsn:
display_name: Erik Jensen
forgejo: rkjnsn
github: rkjnsn
roberth:
display_name: Robert Hensing
github: roberth
@@ -269,9 +253,6 @@ rootile:
display_name: rootile (Rutile)
forgejo: rootile
sandydoo:
github: sandydoo
seppel3210:
github: Seppel3210
@@ -336,8 +317,5 @@ yorickvp:
yshui:
github: yshui
ysndr:
github: ysndr
zimbatm:
github: zimbatm
@@ -1,9 +0,0 @@
---
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.
-10
View File
@@ -1,10 +0,0 @@
---
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.
@@ -1,13 +0,0 @@
---
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`.
@@ -1,21 +0,0 @@
---
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.
-16
View File
@@ -1,16 +0,0 @@
---
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 **512% 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.
-12
View File
@@ -1,12 +0,0 @@
---
synopsis: "nix-eval-jobs support `--apply` flag"
cls: [5748]
category: "Features"
credits: [isabelroses,mic92,ysndr]
issues: [fj#1214]
---
`nix-eval-jobs` now supports the `--apply` flag. With this you can apply the
provided function to the each derivation, the result of this function will then
be serialized as a JSON value and stored inside `"extraValue"` key of the json
line output.
@@ -1,10 +0,0 @@
---
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.
-14
View File
@@ -1,14 +0,0 @@
---
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
-11
View File
@@ -1,11 +0,0 @@
---
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.
-17
View File
@@ -1,17 +0,0 @@
---
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).
-12
View File
@@ -1,12 +0,0 @@
---
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.
@@ -1,11 +0,0 @@
---
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.
-18
View File
@@ -1,18 +0,0 @@
---
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.
@@ -1,28 +0,0 @@
---
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.
-23
View File
@@ -1,23 +0,0 @@
---
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.
+18 -6
View File
@@ -51,7 +51,7 @@ $ nix-shell -A native-clangStdenvPackages
### Building from the development shell
We have a [justfile](https://just.systems/) for extra convenient building.
We have a [justfile](just.systems) for extra convenient building.
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:
@@ -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:
```bash
$ just setup -Dc_link_args=-fuse-ld=ld -Dcpp_link_args=-fuse-ld=ld
$ just setup-custom -Dc_link_args=-fuse-ld=ld -Dcpp_link_args=-fuse-ld=ld
```
While using LLD, you may find it helpful to use ThinLTO for even further improvements to link times for incremental builds:
```bash
$ just setup -Db_lto=true -Db_lto_mode=thin -Db_thinlto_cache=true
$ just setup-custom -Db_lto=true -Db_lto_mode=thin -Db_thinlto_cache=true
```
## 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.
The Snix project also has some Gerrit information [in their contributing docs][snix-gerrit].
[wiki-gerrit]: https://wiki.lix.systems/books/contributing/chapter/intro-to-gerrit
[wiki-gerrit]: https://wiki.lix.systems/books/lix-contributors/chapter/gerrit
[snix-gerrit]: https://snix.dev/docs/guides/contributing/
The gist is that once you have your SSH key and git remote set up, you can send commits for review with:
@@ -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.
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/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.
You can log in to the CI via [SSO](https://buildkite.com/sso/lix-project). On your job you can then hit the "Retry failed" button to rerun it, normally you will not have a repeat of the transient issue.
If the build still fails on CI issues or all builds are failing this should be reported via [Zulip on #T-infra](https://zulip.lix.systems/#narrow/channel/7-T-infra) or [Matrix on #dev](https://matrix.to/#/%23dev%3Alix.systems?via=lix.systems).
## Building Lix with `nix`
@@ -444,6 +444,18 @@ You can build it yourself:
# xdg-open ./result/coverage/index.html
```
Or, in a dev shell, set `-Dcoverage=true` when running `meson setup`.
Coverage data goes into `build/profraw` when you run executables in the dev shell.
Then, run `ninja -C build coverage-report` to produce an HTML report of coverage in `build/coverage/index.html` alongside a LLVM `.lcov` file.
> [!NOTE]
> We use the [llvm source-based coverage], which has better precision than using clang with gcov, which is debuginfo based (but likely worse performance, which is fine).
>
> It should be noted that Meson [allegedly has coverage support][meson-coverage], but it only supports gcov-style coverage, so we don't use it.
[llvm source-based coverage]: https://clang.llvm.org/docs/SourceBasedCodeCoverage.html
[meson-coverage]: https://mesonbuild.com/Unit-tests.html#coverage
Metrics about the change in line/function coverage over time will be available in the future (FIXME(lix-hydra)).
## Add a release note {#release-notes}
@@ -566,7 +578,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:
* `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.
It must be a function of signature `Value(EvalState &, PosIdx, Value * *)`.
It must be a function of signature `void(EvalState &, PosIdx, Value * *, Value &)`.
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.
If not specified, defaults to `true`.
@@ -50,6 +50,10 @@ 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
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
because Nix needs GLR support in Bison and reentrancy support in
Flex.) For Bison, you need version 2.6, which can be obtained from
+1 -1
View File
@@ -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 name can be an identifier or a [double-quoted string](#type-string).
An attribute name can be an identifier or a [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 (`-`).
> *name* = *identifier* | *string* \
+5
View File
@@ -70,6 +70,11 @@ def do_include(content: str, relative_md_path: Path, source_root: Path, search_p
def recursive_replace(data, book_root, search_path):
match data:
# XXX FUTURE: drop sections once mdBook is at 0.5.0 or above in nixpkgs
case {'sections': sections}:
return data | dict(
sections = [recursive_replace(section, book_root, search_path) for section in sections],
)
case {'items': items}:
return data | dict(
items = [recursive_replace(item, book_root, search_path) for item in items],
Generated
+4 -4
View File
@@ -106,16 +106,16 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1783770249,
"narHash": "sha256-K8pGvFito5dp9T0+clr60q+bJPGEskK75aAJ39w7HBM=",
"lastModified": 1773082486,
"narHash": "sha256-TKUDrM0nKUo5s/b8jhjXa2prcu5KU5Cck3HBTRLDjfo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "62463162b3ce92919f19ada41a70a0d943a08da8",
"rev": "7f8b8875bdb38a70c7b5ceb9ba6a6a8d69859e16",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-26.05-small",
"ref": "nixos-25.11-small",
"repo": "nixpkgs",
"type": "github"
}
+435 -56
View File
@@ -2,7 +2,7 @@
description = "Lix: A modern, delicious implementation of the Nix package manager";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05-small";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11-small";
nixpkgs-regression.url = "github:NixOS/nixpkgs/215d4d0fd80ca5163643b03a33fde804a29cc1e2";
# Required because Nix 2.18 is not in Nixpkgs ≥ 25.05 anymore.
@@ -42,7 +42,6 @@
let
inherit (nixpkgs) lib;
lixSrc = self;
# This notice gets echoed as a dev shell hook, and can be turned off with
# `touch .nocontribmsg`
@@ -77,35 +76,206 @@
(Run `touch .nocontribmsg` to hide this message.)
'';
scope = import ./nix-support/build/inputs.nix {
inherit
lib
nixpkgs
nix_2_18
nix2container
lixSrc
nixpkgs-regression
;
};
versionJson = builtins.fromJSON (builtins.readFile ./version.json);
officialRelease = versionJson.official_release;
inherit (scope)
crossSystems
darwinSystems
forAllStdenvs
forAllSystems
forAvailableSystems
linux64BitSystems
nixpkgsFor
overlayFor
systems
versionSuffix
;
# Set to true to build the release notes for the next release.
buildUnreleasedNotes = true;
inherit (scope.callPackage ./nix-support/build/outputs.nix { })
packages
ciArtifacts
tests
;
versionSuffix =
if officialRelease then
""
else
"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
{
# for repl debugging
@@ -115,13 +285,202 @@
# 'nix.perl-bindings' packages.
overlays.default = overlayFor (p: p.clangStdenv);
hydraJobs = ciArtifacts // {
hydraJobs = {
# 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: {
default = self.devShells.${system}.default;
clang = self.devShells.${system}.native-clangStdenvPackages;
});
inherit tests;
rl-next = forAllSystems (
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 (
system:
@@ -181,7 +540,45 @@
}
);
inherit packages;
packages = forAllSystems (
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 =
let
@@ -214,30 +611,12 @@
in
(makeShells "native" nixpkgsFor.${system}.native)
// (makeShells "static" nixpkgsFor.${system}.static)
// (lib.listToAttrs (
# Provide e.g., both '.#native-aarch64-linux` and `.#static-aarch64-linux`,
# for each cross-system.
# "native" feels like a misnomer here since it's literally cross compiling,
# but at least it's consistent with the native/static dichotomy we've set up.
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
// (forAllCrossSystems (
crossSystem:
let
pkgs = nixpkgsFor.${system}.cross.${crossSystem};
in
makeShell pkgs pkgs.clangStdenv
))
// {
default = self.devShells.${system}.native-clangStdenvPackages;
+8 -26
View File
@@ -3,14 +3,6 @@
# Take a look at ./doc/manual/src/contributing/hacking.md for a detailed
# 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}"
builddir := "build"
@@ -18,33 +10,28 @@ builddir := "build"
list:
just --list
# Clean build artifacts and outputs.
# Clean build artifacts
clean:
rm -rf {{ quote(builddir) }}/* {{ quote(builddir) }}/.* {{ quote(outdir) }}/* {{ quote(outdir) }}/.*
cargo clean
rm -rf {{ builddir }}
# Prepare meson for building.
[positional-arguments]
setup *OPTIONS:
meson setup {{ builddir }} --reconfigure --prefix="{{outdir}}" $mesonFlags "$@"
meson setup {{ builddir }} --reconfigure --prefix="{{outdir}}" $mesonFlags {{ OPTIONS }}
# Build lix with extra options
[positional-arguments]
build *OPTIONS:
meson compile -C {{ builddir }} "$@"
meson compile -C {{ builddir }} {{ OPTIONS }}
alias compile := build
# `meson install` will automatically build anything that needs to be built to install it.
[doc("Install Lix for local development")]
[positional-arguments]
install *OPTIONS:
meson install --quiet -C {{ builddir }} "$@"
meson install --quiet -C {{ builddir }} {{ OPTIONS }}
# Run all tests tests (installs first).
[positional-arguments]
test *OPTIONS: (install)
meson test -C {{ builddir }} --print-errorlogs --max-lines 10000 "$@"
meson test -C {{ builddir }} --print-errorlogs --max-lines 10000 {{ OPTIONS }}
# Run unit tests only
test-unit *OPTIONS: (test "--suite" "check")
@@ -53,15 +40,10 @@ test-unit *OPTIONS: (test "--suite" "check")
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
[positional-arguments]
test-functional2 *OPTIONS:
cd tests/functional2 && python -m pytest -v "$@"
cd tests/functional2 && python -m pytest -v {{ OPTIONS }}
# 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 "$@"
alias clang-tidy := lint
# Lint with `clang-tidy`
lint:
+1 -1
View File
@@ -52,7 +52,7 @@ class ExtraFeature:
{
"\n ".join(
[
f"- {event.date}, {event.release}: {event.message} [{", ".join([f'[CL {cl}](https://gerrit.lix.systems/c/lix/+/{cl})' for cl in event.cls])}]"
f"- {event.date}, {event.release}: {event.message} [{", ".join([f'[CL {cl}](https://git.lix.systems/c/lix/+/{cl})' for cl in event.cls])}]"
for event in self.timeline
]
)
+112 -32
View File
@@ -5,7 +5,6 @@
#include "lix/libutil/file-descriptor.hh"
#include "lix/libutil/logging-rpc.hh"
#include "lix/libutil/logging.hh"
#include "lix/libutil/result.hh"
#include "lix/libutil/rpc.hh"
#include "lix/libutil/types-rpc.hh" // IWYU pragma: keep
#include "lix/libutil/types.hh"
@@ -51,7 +50,7 @@ struct Instance final : rpc::build_remote::HookInstance::Server
kj::Promise<void> init(InitContext context) override;
kj::Promise<Result<void>> buildImpl(BuildContext context);
kj::Promise<void> buildImpl(BuildContext context);
kj::Promise<void> build(BuildContext context) override;
};
}
@@ -97,7 +96,7 @@ static std::tuple<bool, Machine *, AutoCloseFD> selectBestMachine(
uint64_t bestLoad = 0;
for (auto & m : machines) {
debug("considering building on remote machine '%s'", m.name);
debug("considering building on remote machine '%s'", m.storeUri);
if (m.enabled && m.systemSupported(neededSystem) && m.allSupported(requiredFeatures)
&& m.mandatoryMet(requiredFeatures))
@@ -185,6 +184,67 @@ struct BuilderConnection
AutoCloseFD slotLock;
std::shared_ptr<Store> sshStore;
std::string storeUri;
Pipe logPipe;
// start the thread that reads ssh stderr and turns it into log items.
// this future *must* outlive sshStore, otherwise it will never finish
kj::Promise<Result<void>> startLogThread(std::string buildDescription, std::string drvPath)
try {
if (!logPipe.readSide) {
co_return result::success();
}
logPipe.writeSide.close();
// NOTE this is very similar to handleBuilderOutput in DerivationGoal, but unlike
// the derivation goal we do not need to handle EIO from a pty here. we also have
// no timeouts or limits to keep track of, which makes deduplication less useful.
auto act = logger->startActivity(
lvlInfo, actBuild, buildDescription, Logger::Fields{drvPath, storeUri, 1, 1}
);
std::map<ActivityId, Activity> activities;
auto reader = AIO().lowLevelProvider.wrapInputFd(logPipe.readSide.get());
LogLineSplitter splitter;
auto flushLine = [&](const std::string & line) {
if (const auto state =
handleJSONLogMessage(line, act, activities, "the derivation builder"))
{
return *state;
} else {
return act.result(resBuildLogLine, line);
}
};
auto buf = kj::heapArray<char>(4096);
while (true) {
const auto got = co_await reader->tryRead(buf.begin(), 1, buf.size());
if (got == 0) {
break;
}
std::string_view data{buf.begin(), got};
while (!data.empty()) {
if (auto line = splitter.feed(data)) {
if (flushLine(*line) == Logger::BufferState::NeedsFlush) {
TRY_AWAIT(act.getLogger().flush());
}
}
}
}
if (auto line = splitter.finish(); !line.empty()) {
(void) flushLine(line);
TRY_AWAIT(act.getLogger().flush());
}
co_return result::success();
} catch (...) {
co_return result::current_exception();
}
};
struct AcceptedBuild final : rpc::build_remote::HookInstance::AcceptedBuild::Server
@@ -201,7 +261,7 @@ struct AcceptedBuild final : rpc::build_remote::HookInstance::AcceptedBuild::Ser
{
}
kj::Promise<Result<void>> runImpl(RunContext context);
kj::Promise<void> runImpl(RunContext context);
kj::Promise<void> run(RunContext context) override;
};
@@ -266,15 +326,26 @@ try {
lock.reset();
std::shared_ptr<Store> sshStore;
Pipe logPipe;
try {
auto act =
logger->startActivity(lvlTalkative, actUnknown, fmt("connecting to '%s'", bestMachine->name));
auto act = logger->startActivity(
lvlTalkative, actUnknown, fmt("connecting to '%s'", bestMachine->storeUri)
);
sshStore = TRY_AWAIT(bestMachine->openStore());
co_return BuilderConnection{std::move(bestSlotLock), sshStore, bestMachine->storeUri};
std::tie(sshStore, logPipe) = TRY_AWAIT(bestMachine->openStore());
TRY_AWAIT(sshStore->connect());
co_return BuilderConnection{
std::move(bestSlotLock), sshStore, bestMachine->storeUri, std::move(logPipe)
};
} catch (std::exception & e) { // NOLINT(lix-foreign-exceptions)
printError("cannot build on '%s': %s", bestMachine->name, e.what());
std::string msg = logPipe.readSide ? chomp(drainFD(logPipe.readSide.get(), false)) : "";
printError(
"cannot build on '%s': %s%s",
bestMachine->storeUri,
e.what(),
msg.empty() ? "" : ": " + msg
);
bestMachine->enabled = false;
}
}
@@ -295,7 +366,7 @@ static int main_build_remote(AsyncIoRoot & aio, std::string programName, Strings
if (argv.size() != 1)
throw UsageError("called without required arguments");
setVerbosity((Verbosity) std::stoll(argv.front()));
verbosity = (Verbosity) std::stoll(argv.front());
auto conn = aio.kj.lowLevelProvider->wrapUnixSocketFd(1);
capnp::TwoPartyServer srv(kj::heap<Instance>());
@@ -324,14 +395,16 @@ kj::Promise<void> Instance::init(InitContext context)
initPlugins();
initialized = true;
context.getResults().initResult().setGood();
} catch (...) {
rpc::rethrow_as_rpc_error();
RPC_FILL(context.getResults(), initResult, std::current_exception());
}
return kj::READY_NOW;
}
kj::Promise<Result<void>> Instance::buildImpl(BuildContext context)
kj::Promise<void> Instance::buildImpl(BuildContext context)
try {
if (!initialized) {
throw Error("build hook not fully initialized");
@@ -358,8 +431,8 @@ try {
debug("got %d remote builders", machines.size());
if (machines.empty()) {
context.getResults().initResult().setDeclinePermanently();
co_return result::success();
context.getResults().initResult().initGood().setDeclinePermanently();
co_return;
}
auto amWilling = context.getParams().getAmWilling();
@@ -375,23 +448,21 @@ try {
if (auto immediateResponse = std::get_if<BuildRejected>(&result)) {
switch (*immediateResponse) {
case BuildRejected::Temporarily:
context.getResults().initResult().setPostpone();
co_return result::success();
context.getResults().initResult().initGood().setPostpone();
co_return;
case BuildRejected::Permanently:
context.getResults().initResult().setDecline();
co_return result::success();
context.getResults().initResult().initGood().setDecline();
co_return;
}
}
auto builder = std::get_if<BuilderConnection>(&result);
assert(builder);
auto ac = context.getResults().initResult().initAccept();
auto ac = context.getResults().initResult().initGood().initAccept();
ac.setMachine(kj::heap<AcceptedBuild>(store, drvPath, std::move(*builder)));
co_return result::success();
} catch (...) {
co_return result::current_exception();
RPC_FILL(context.getResults(), initResult, std::current_exception());
}
kj::Promise<void> Instance::build(BuildContext context)
@@ -400,12 +471,11 @@ try {
throw Error("build hooks can only accept a single job");
}
used = true; // lock out other rpc calls during processing
auto result = co_await buildImpl(context);
co_await buildImpl(context);
TRY_AWAIT(logger->flush());
used = result.has_value() && context.getResults().getResult().isAccept();
result.value();
used = context.getResults().getResult().getGood().isAccept();
} catch (...) {
rpc::rethrow_as_rpc_error();
RPC_FILL(context.getResults(), getResult, std::current_exception());
}
kj::Promise<void> AcceptedBuild::run(RunContext context)
@@ -423,17 +493,23 @@ kj::Promise<void> AcceptedBuild::run(RunContext context)
throw Error("build hooks builds are single-use items");
}
used = true;
auto result = co_await runImpl(context);
co_await runImpl(context);
TRY_AWAIT(logger->flush());
result.value();
} catch (...) {
rpc::rethrow_as_rpc_error();
RPC_FILL(context.getResults(), getResult, std::current_exception());
}
}
kj::Promise<Result<void>> AcceptedBuild::runImpl(RunContext context)
kj::Promise<void> AcceptedBuild::runImpl(RunContext context)
{
try {
auto logHandler = builder.startLogThread(
fmt("%s on '%s'",
rpc::to<std::string_view>(context.getParams().getDescription()),
builder.storeUri),
store->printStorePath(drvPath)
);
auto & sshStore = builder.sshStore;
auto & storeUri = builder.storeUri;
@@ -543,9 +619,13 @@ kj::Promise<Result<void>> AcceptedBuild::runImpl(RunContext context)
);
}
co_return result::success();
// drop store connection, let log handler process any remaining input
builder.sshStore = nullptr;
TRY_AWAIT(logHandler);
context.getResults().initResult().setGood();
} catch (...) {
co_return result::current_exception();
RPC_FILL(context.getResults(), initResult, std::current_exception());
}
}
+1 -1
View File
@@ -35,7 +35,7 @@ static int main_builtin_builder(AsyncIoRoot & aio, std::string programName, Stri
};
if (auto val = string2Int<int>(getArg("verbosity"))) {
setVerbosity(verbosityFromIntClamped(*val));
verbosity = verbosityFromIntClamped(*val);
} else {
throw Error("expected a verbosity argument");
}
+1 -1
View File
@@ -55,7 +55,7 @@ static int main_nix_copy_closure(AsyncIoRoot & aio, std::string programName, Str
for (auto & path : storePaths)
storePaths2.insert(from->followLinksToStorePath(path));
aio.blockOn(copyClosure(*from, *to, storePaths2, NoRepair, NoCheckSigs, useSubstitutes, includeOutputs));
aio.blockOn(copyClosure(*from, *to, storePaths2, NoRepair, NoCheckSigs, useSubstitutes));
return 0;
}
+1 -1
View File
@@ -948,7 +948,7 @@ opServe(std::shared_ptr<Store> store, AsyncIoRoot & aio, Strings opFlags, String
auto getBuildSettings = [&]() {
// FIXME: changing options here doesn't work if we're
// building through the daemon.
setVerbosity(lvlError);
verbosity = lvlError;
settings.keepLog.override(false);
settings.useSubstitutes.override(false);
settings.maxSilentTime.override(readNum<unsigned>(in));
+215 -60
View File
@@ -1,101 +1,251 @@
#include "libutil/fmt.hh"
#include "libutil/terminal.hh"
#include "lix/libutil/c-calls.hh"
#include "lix/libutil/error.hh"
#include "lix/libutil/file-system.hh"
#include "lix/libutil/logging.hh"
#include "lix/lix-rs/main.gen.hh"
#include "lix/lix-rs/utils.hh"
#include <csignal>
#include <cstdio>
#include <iostream>
#include <memory>
#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"
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 {
ReadlineLikeInteracter::ReadlineLikeInteracter(std::string historyFile) : historyFile(historyFile) {}
namespace {
// Used to communicate to NixRepl::getLine whether a signal occurred in ::readline.
volatile sig_atomic_t g_signal_received = 0;
void ReadlineLikeInteracter::init(detail::ReplCompleterMixin * repl)
void sigintHandler(int signo)
{
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 {
createDirs(dirOf(historyFile));
} catch (SysError & e) {
logWarning(e.info());
}
auto rl = repl::Rustyline::new_(rust::to_string(historyFile).as_str(), *repl);
match_result(
std::move(rl),
[&](repl::Rustyline ok) { this->rl = std::make_unique<repl::Rustyline>(std::move(ok)); },
[&](rust::Box<rust::Dyn<rust::std::error::Error>> err) {
throw Error("%s", Uncolored(to_std_string(err.to_string())));
}
);
el_hist_size = 1000;
read_history(requireCString(historyFile));
auto oldRepl = curRepl;
curRepl = repl;
Guard restoreRepl([oldRepl] { curRepl = oldRepl; });
// editline does its own escaping of completions, so we rebind tab
// to our own completion function to skip that and do nix escaping
// instead of shell escaping.
el_bind_key(CTL('I'), doCompletion);
return restoreRepl;
}
static rust::Ref<rust::Str> promptForType(ReplPromptType promptType)
static constexpr const char * promptForType(ReplPromptType promptType)
{
switch (promptType) {
case ReplPromptType::ReplPrompt:
return "nix-repl> "_rs;
return "nix-repl> ";
case ReplPromptType::ContinuationPrompt:
return " "_rs;
return " ";
}
assert(false);
}
bool ReadlineLikeInteracter::getLine(std::string & input, ReplPromptType promptType)
{
auto s = rl->ask(promptForType(promptType));
struct sigaction act, old;
sigset_t savedSignalMask, set;
// rustyline temporarily sets a SIGWINCH handler
KJ_DEFER(invalidateWindowSize());
auto setupSignals = [&]() {
act.sa_handler = sigintHandler;
sigfillset(&act.sa_mask);
act.sa_flags = 0;
if (sigaction(SIGINT, &act, &old))
throw SysError("installing handler for SIGINT");
return match_result(
std::move(s),
[&](rust::String ok) {
input += to_std_string(ok);
input += '\n';
return true;
},
[&](rust::rustyline::error::ReadlineError err) {
if (err.matches_Interrupted()) {
input.clear();
return true;
}
sigemptyset(&set);
sigaddset(&set, SIGINT);
if (sigprocmask(SIG_UNBLOCK, &set, &savedSignalMask))
throw SysError("unblocking SIGINT");
};
auto restoreSignals = [&]() {
if (sigprocmask(SIG_SETMASK, &savedSignalMask, nullptr))
throw SysError("restoring signals");
if (err.matches_Eof()) {
return false;
}
if (sigaction(SIGINT, &old, 0))
throw SysError("restoring handler for SIGINT");
};
throw Error("%s", Uncolored(to_std_string(err.into().to_string())));
}
);
setupSignals();
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()
{
if (rl) {
rl->write_history();
int ret = write_history(requireCString(historyFile));
int writeHistErr = errno;
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()
@@ -103,6 +253,11 @@ ReadlineLikeInteracter::~ReadlineLikeInteracter()
this->writeHistory();
}
AutomationInteracter::Guard AutomationInteracter::init(detail::ReplCompleterMixin *)
{
return Guard([] {});
}
// ASCII ENQ character
constexpr const char * automationPrompt = "\x05";
+11 -11
View File
@@ -1,14 +1,11 @@
#pragma once
/// @file
#include "lix/libutil/finally.hh"
#include "lix/libutil/types.hh"
#include <memory>
#include <functional>
#include <string>
namespace rust::lix::repl {
struct Rustyline;
}
namespace nix {
namespace detail {
@@ -27,7 +24,9 @@ enum class ReplPromptType {
class ReplInteracter
{
public:
virtual void init(detail::ReplCompleterMixin * repl) {}
using Guard = Finally<std::function<void()>>;
virtual Guard init(detail::ReplCompleterMixin * repl) = 0;
/** Returns a boolean of whether the interacter got EOF */
virtual bool getLine(std::string & input, ReplPromptType promptType) = 0;
virtual ~ReplInteracter(){};
@@ -36,12 +35,12 @@ public:
class ReadlineLikeInteracter final : public ReplInteracter
{
std::string historyFile;
std::unique_ptr<rust::lix::repl::Rustyline> rl;
public:
ReadlineLikeInteracter(std::string historyFile);
virtual void init(detail::ReplCompleterMixin * repl) override;
ReadlineLikeInteracter(std::string historyFile)
: historyFile(historyFile)
{
}
virtual Guard init(detail::ReplCompleterMixin * repl) override;
virtual bool getLine(std::string & input, ReplPromptType promptType) override;
/** Writes the current history to the history file.
*
@@ -55,6 +54,7 @@ class AutomationInteracter final : public ReplInteracter
{
public:
AutomationInteracter() = default;
virtual Guard init(detail::ReplCompleterMixin * repl) override;
virtual bool getLine(std::string & input, ReplPromptType promptType) override;
virtual ~AutomationInteracter() override = default;
};
+19 -22
View File
@@ -1,5 +1,6 @@
#include <algorithm>
#include <cstdio>
#include <editline.h>
#include <iostream>
#include <cstdlib>
#include <cstring>
@@ -379,7 +380,7 @@ ReplExitStatus NixRepl::mainLoop()
std::list<ReplLoadable> loadables = std::exchange(loaded, {});
loadFiles(loadables);
interacter->init(static_cast<detail::ReplCompleterMixin *>(this));
auto _guard = interacter->init(static_cast<detail::ReplCompleterMixin *>(this));
/* Stop the progress bar because it interferes with the display of
the repl. */
@@ -440,10 +441,15 @@ StringSet NixRepl::completePrefix(const std::string &prefix)
{
StringSet completions;
if (prefix.starts_with(':')) {
// We should only complete colon commands if there's a colon at the beginning,
// 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) {
if ((!cmd->attributes.debugModeOnly || inDebugger()) && colonCmd.starts_with(prefix.substr(1))) {
completions.insert(":" + colonCmd);
if ((!cmd->attributes.debugModeOnly || inDebugger()) && colonCmd.starts_with(prefix)) {
completions.insert(std::string(colonCmd));
}
}
@@ -693,26 +699,17 @@ void NixRepl::initDebugBuiltinCommands()
addCommand(
"backtrace",
[](NixRepl & repl, const std::string & _arg) {
auto tracesGenerator = repl.evaluator.debug->traces();
// since we want to print the stack trace in reverse order,
// 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);
auto traces = repl.evaluator.debug->traces();
for (const auto & [idx, i] : enumerate(traces)) {
std::cout << "\n" << ANSI_BLUE << idx << ANSI_NORMAL << ": ";
showDebugTrace(std::cout, repl.evaluator.positions, *i);
}
return ProcessLineResult::PromptAgain;
},
{.aliases = {"bt"}, .debugModeOnly = true, .help = "Show trace stack", .section = "Debug mode"}
{.aliases = {"bt"},
.debugModeOnly = true,
.help = "Show trace stack",
.section = "Debug mode"}
);
addCommand(
@@ -820,7 +817,7 @@ void NixRepl::initDebugBuiltinCommands()
{.aliases = {"st"},
.debugModeOnly = true,
.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.",
.section = "Debug mode",
.positionalArgsSpecifiers = {{.placeholderText = "integer index", .optional = true}}}
-3
View File
@@ -36,12 +36,9 @@ int helperMain(const char * name, std::span<char *> args) noexcept
other things, determines if kill(-1, signo) affects the
calling process. In the OSX libc, it's set to true,
which means "follow POSIX", which we don't want here */
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if (syscall(SYS_kill, -1, SIGKILL, false) == 0) {
break;
}
#pragma clang diagnostic pop
#else
if (kill(-1, SIGKILL) == 0) {
break;
+6 -6
View File
@@ -59,7 +59,7 @@ static void setPersonality(std::string_view system)
bool pathExists(const fs::path & path)
{
return fs::exists(fs::symlink_status(path)); // NOLINT(lix-foreign-exceptions): caught by main
return fs::exists(fs::symlink_status(path));
}
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); // NOLINT(lix-foreign-exceptions): caught by main
auto st = fs::symlink_status(source);
if (st.type() == fs::file_type::not_found) {
if (optional) {
return;
@@ -82,14 +82,14 @@ void bindPath(const fs::path & source, const fs::path & target, bool optional =
}
if (st.type() == fs::file_type::directory) {
fs::create_directories(target); // NOLINT(lix-foreign-exceptions): caught by main
fs::create_directories(target);
bindMount();
} else if (st.type() == fs::file_type::symlink) {
// Symlinks can (apparently) not be bind-mounted, so just copy it
fs::create_directories(target.parent_path()); // NOLINT(lix-foreign-exceptions): caught by main
fs::copy_symlink(source, target); // NOLINT(lix-foreign-exceptions): caught by main
fs::create_directories(target.parent_path());
fs::copy_symlink(source, target);
} else {
fs::create_directories(target.parent_path()); // NOLINT(lix-foreign-exceptions): caught by main
fs::create_directories(target.parent_path());
if (kj::AutoCloseFd file{open(target.c_str(), O_RDWR | O_CREAT, 0644)}; file == nullptr) {
throw SysError("could not create %s", target);
}
+4 -20
View File
@@ -2,17 +2,7 @@
#include <sys/socket.h>
#include <sys/un.h>
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)));
}
LIBEXEC_HELPER(4)
int helperMain(const char *, std::span<char *> args) noexcept
{
@@ -20,12 +10,8 @@ int helperMain(const char *, std::span<char *> args) noexcept
std::string_view method = args[1];
const auto dir = args[2];
const auto name = args[3];
resultFd = argToInt<int>("result-fd", args[4]);
if (chdir(dir)) {
sendResult(errno);
return 0;
}
DIE_UNLESS_SYS("chdir", chdir(dir));
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
@@ -37,11 +23,9 @@ int helperMain(const char *, std::span<char *> args) noexcept
}
if (method == "bind") {
auto result = bind(socket, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) ? errno : 0;
sendResult(result);
DIE_UNLESS_SYS("bind", bind(socket, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)));
} else if (method == "connect") {
auto result = connect(socket, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)) ? errno : 0;
sendResult(result);
DIE_UNLESS_SYS("connect", connect(socket, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr)));
} else {
die(std::format("invalid method %s", method));
}
+58 -2
View File
@@ -23,14 +23,70 @@ countDown 2
Then, evaluating the file will give the following stack trace:
```console
$ nix-instantiate err.nix
$ nix-instantiate --show-trace err.nix
error:
… from call site
at /home/plop/git.lix.systems/lix-project/lix/err.nix:9:1:
8| in
9| countDown 2
| ^
10|
… while calling 'countDown'
at /home/plop/git.lix.systems/lix-project/lix/err.nix:3:5:
2| countDown =
3| n:
| ^
4| if n == 0 then
… while calling the 'addErrorContext' builtin
at /home/plop/git.lix.systems/lix-project/lix/err.nix:7:7:
6| else
7| builtins.addErrorContext "while counting down; n = ${toString n}" ("x" + countDown (n - 1));
| ^
8| in
… while counting down; n = 2
… from call site
at /home/plop/git.lix.systems/lix-project/lix/err.nix:7:80:
6| else
7| builtins.addErrorContext "while counting down; n = ${toString n}" ("x" + countDown (n - 1));
| ^
8| in
… while calling 'countDown'
at /home/plop/git.lix.systems/lix-project/lix/err.nix:3:5:
2| countDown =
3| n:
| ^
4| if n == 0 then
… while calling the 'addErrorContext' builtin
at /home/plop/git.lix.systems/lix-project/lix/err.nix:7:7:
6| else
7| builtins.addErrorContext "while counting down; n = ${toString n}" ("x" + countDown (n - 1));
| ^
8| in
… while counting down; n = 1
… from call site
at /home/plop/git.lix.systems/lix-project/lix/err.nix:7:80:
6| else
7| builtins.addErrorContext "while counting down; n = ${toString n}" ("x" + countDown (n - 1));
| ^
8| in
… while calling 'countDown'
at /home/plop/git.lix.systems/lix-project/lix/err.nix:3:5:
2| countDown =
3| n:
| ^
4| if n == 0 then
… caused by explicit throw
at err.nix:5:7:
at /home/plop/git.lix.systems/lix-project/lix/err.nix:5:7:
4| if n == 0 then
5| throw "kaboom"
| ^
+5 -6
View File
@@ -1,10 +1,9 @@
---
name: ceil
args: [number]
args: [double]
---
Returns an integer value containing the smallest integer greater than or equal to *number* (which is either a floating-point or integer value).
Converts an IEEE-754 double-precision floating-point number (*double*) to
the next higher integer.
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.
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.
If the datatype is neither an integer nor a "float", an evaluation error will be
thrown.
+5 -6
View File
@@ -1,10 +1,9 @@
---
name: floor
args: [number]
args: [double]
---
Returns an integer value containing the largest integer less than or equal to *number* (which is either a float or integer).
Converts an IEEE-754 double-precision floating-point number (*double*) to
the next lower integer.
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.
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.
If the datatype is neither an integer nor a "float", an evaluation error will be
thrown.
+1 -1
View File
@@ -53,7 +53,7 @@ public:
ref<AttrCursor> getRoot();
};
enum AttrType : uint8_t {
enum AttrType {
Placeholder = 0,
FullAttrs = 1,
String = 2,
+24 -29
View File
@@ -1,5 +1,4 @@
#include "eval.hh"
#include "libexpr/nixexpr.hh"
#include "primops.hh"
#include "gc-small-vector.hh"
@@ -11,19 +10,14 @@
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
in the given environment. But if the expression is a variable,
then look it up right away. This significantly reduces the number
of thunks allocated. */
Value Expr::maybeThunk(EvalState & state, Env & env)
{
return makeThunk(state.ctx, env);
state.ctx.stats.nrThunks++;
return {NewValueAs::thunk, state.ctx.mem, env, *this};
}
Value ExprVar::maybeThunk(EvalState & state, Env & env)
@@ -105,10 +99,18 @@ Value ExprSet::eval(EvalState & state, Env & env)
in the original environment. */
Displacement displ = 0;
for (auto & i : attrs) {
Env & thunkEnv = *i.second.chooseByKind(&env2, &env, inheritEnv);
Value vAttr = hasOverrides && i.second.kind != ExprAttrs::AttrDef::Kind::Inherited
? i.second.e->makeThunk(state.ctx, thunkEnv)
: i.second.e->maybeThunk(state, thunkEnv);
Value vAttr;
if (hasOverrides && i.second.kind != ExprAttrs::AttrDef::Kind::Inherited) {
vAttr = {
NewValueAs::thunk,
state.ctx.mem,
*i.second.chooseByKind(&env2, &env, inheritEnv),
*i.second.e
};
state.ctx.stats.nrThunks++;
} else {
vAttr = i.second.e->maybeThunk(state, *i.second.chooseByKind(&env2, &env, inheritEnv));
}
env2.values[displ++] = vAttr;
v.attrs()->push_back(Attr(i.first, vAttr, i.second.pos));
}
@@ -356,14 +358,12 @@ Value ExprOpConcatLists::eval(EvalState & state, Env & env)
{
state.ctx.stats.nrListConcats++;
/* 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*
*/
/* We don't call into `concatLists` as that loses the position information of the expressions. */
Value v1 = e1->eval(state, env);
state.checkList(v1, env, *this); // Pass in `this` instead of `e1` to make the error point to the `++`
state.checkList(v1, env, *e1);
Value v2 = e2->eval(state, env);
state.checkList(v2, env, *this); // Pass in `this` instead of `e2` to make the error point to the `++`
state.checkList(v2, env, *e2);
size_t l1 = v1.listSize(), l2 = v2.listSize(), len = l1 + l2;
@@ -436,7 +436,7 @@ Value ExprConcatStrings::eval(EvalState & state, Env & env)
} else {
state.ctx.errors
.make<EvalError>("integer overflow in adding %1% + %2%", n, vTmp.integer())
.atPos(isInterpolation ? i_pos : pos)
.atPos(i_pos)
.debugThrow();
}
} else if (vTmp.type() == nFloat) {
@@ -446,7 +446,7 @@ Value ExprConcatStrings::eval(EvalState & state, Env & env)
nf += vTmp.fpoint();
} else {
state.ctx.errors.make<EvalError>("cannot add %1% to an integer", showType(vTmp))
.atPos(isInterpolation ? i_pos : pos)
.atPos(i_pos)
.withFrame(env, *this)
.debugThrow();
}
@@ -457,7 +457,7 @@ Value ExprConcatStrings::eval(EvalState & state, Env & env)
nf += vTmp.fpoint();
} else {
state.ctx.errors.make<EvalError>("cannot add %1% to a float", showType(vTmp))
.atPos(isInterpolation ? i_pos : pos)
.atPos(i_pos)
.withFrame(env, *this)
.debugThrow();
}
@@ -473,18 +473,13 @@ Value ExprConcatStrings::eval(EvalState & state, Env & env)
: StringCoercionMode::Strict;
/* skip canonization of first path, which would only be not
canonized in the first place if it's coming from a ./${foo} type
path */
canonized in the first place if it's coming from a ./${foo} type
path */
auto part = state.coerceToString(
isInterpolation ? i_pos : pos,
i_pos,
vTmp,
context,
(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"),
"while evaluating a path segment",
coercionMode,
firstType == nString,
!first
+1
View File
@@ -6,6 +6,7 @@
#include "lix/libexpr/eval-error.hh"
#include "lix/libexpr/gc-alloc.hh"
#include "value.hh"
#include <cstdint>
namespace nix {
+19 -14
View File
@@ -847,7 +847,8 @@ Value::List * EvalMemory::newList(size_t size)
Value Evaluator::evalLazily(Expr & e)
{
return e.makeThunk(*this, builtins.env);
stats.nrThunks++;
return {NewValueAs::thunk, mem, builtins.env, e};
}
Value EvalState::mkPos(PosIdx p)
@@ -907,6 +908,19 @@ 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
{
Value result;
@@ -1118,9 +1132,8 @@ Env & AttrsPattern::match(
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();
}
MaintainCount _level(callDepth);
auto trace = evalSettings.traceFunctionCalls
@@ -1195,18 +1208,12 @@ Value EvalState::callFunction(Value & fun, std::span<Value> args, const PosIdx p
// was being evaluated and an explicit thrown error.
if (fn->name == "throw" && !e.hasTrace()) {
e.addTrace(ctx.positions[pos], "caused by explicit %s", "throw");
}
// 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")
{
} else {
e.addTrace(ctx.positions[pos], "while calling the '%s' builtin", fn->name);
}
throw;
} catch (Error & e) {
if (fn->name != "addErrorContext") {
e.addTrace(ctx.positions[pos], "while calling the '%1%' builtin", fn->name);
}
e.addTrace(ctx.positions[pos], "while calling the '%1%' builtin", fn->name);
throw;
}
@@ -1252,9 +1259,7 @@ 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.
vCur = fn->fun(*this, vArgs.data());
} catch (Error & e) {
if (fn->name != "addErrorContext") {
e.addTrace(ctx.positions[pos], "while calling the '%1%' builtin", fn->name);
}
e.addTrace(ctx.positions[pos], "while calling the '%1%' builtin", fn->name);
throw;
}
}
+9
View File
@@ -817,6 +817,15 @@ public:
*/
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 concatLists(std::span<Value> lists, const PosIdx pos, std::string_view errorCtx);
+69 -125
View File
@@ -4,7 +4,6 @@
#include "lix/libutil/json.hh"
#include "lix/libutil/users.hh"
#include "lix/libfetchers/fetch-settings.hh"
#include <cctype>
namespace nix::flake {
@@ -31,64 +30,6 @@ static void writeTrustedList(const TrustedList & trustedList)
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(
bool & negativeTrustOverride,
TrustedList & trustedList,
@@ -102,74 +43,77 @@ static bool batchAskForSetting(
printWarning("%s", warning);
// clang-format off
constexpr auto globalPrompt =
"[" ANSI_BOLD "y" ANSI_NORMAL "]es for now/"
"[" ANSI_BOLD "n" ANSI_NORMAL "]o for now/"
"[" ANSI_BOLD "a" ANSI_NORMAL "]lways allow/"
"[" ANSI_BOLD "I" ANSI_NORMAL "]ndividually review";
// clang-format on
auto reply = logger
->ask(
fmt("Do you want to allow configuration settings to be applied?\nThis may allow the "
"flake to gain root, see the nix.conf manual page (" ANSI_BOLD "y" ANSI_NORMAL
"es for now/" ANSI_BOLD "A" ANSI_NORMAL "llow always/" ANSI_BOLD "n" ANSI_NORMAL
"o/" ANSI_BOLD "N" ANSI_NORMAL "o to all) ")
)
.value_or('n');
auto reply =
logger
->ask(
fmt("Do you want to allow these configuration settings to be applied?\n" ANSI_BOLD
"This may allow the flake to gain root" ANSI_NORMAL ", see the nix.conf manual page.\n"
"(%s) ",
globalPrompt)
)
.value_or("I"); // if the answer is empty (or there is no interactive prompt),
// 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 (alwaysAllow) {
printTaggedWarning(
"adding these configuration settings to the trusted list at %s, "
"edit it if you want to remove them in the future",
trustedListPath()
);
writeTrustedList(trustedList);
}
return true;
}
if (review.starts_with(reply)) {
return askForEachSetting(trustedList, untrustedSettings);
}
// 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");
if (reply == 'N') {
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 (reply == 'y' || reply == 'A') {
auto alwaysAllow = reply == 'A';
for (const auto & [name, valueS] : untrustedSettings) {
if (alwaysAllow) {
trustedList[name][valueS] = true;
}
globalConfig.set(name, valueS);
}
if (alwaysAllow) {
writeTrustedList(trustedList);
}
return true;
} else {
printTaggedWarning(
"you can set '%s' to '%b' to automatically reject configuration options supplied "
"by flakes",
"accept-flake-config",
false
);
}
auto didTrustedListChange = false;
for (const auto & [name, valueS] : untrustedSettings) {
auto individualReply = logger
->ask(
fmt("Do you want to allow setting '%s = %s'? (" ANSI_BOLD
"y" ANSI_NORMAL "es for now/" ANSI_BOLD "A" ANSI_NORMAL
"llow always/" ANSI_BOLD "n" ANSI_NORMAL "o for now) ",
name,
valueS)
)
.value_or('n');
if (individualReply == 'y' || individualReply == 'A') {
if (individualReply == 'A') {
trustedList[name][valueS] = true;
didTrustedListChange = true;
}
globalConfig.set(name, valueS);
}
}
if (didTrustedListChange) {
writeTrustedList(trustedList);
}
return false;
}
void ConfigFile::apply()
+6 -23
View File
@@ -78,21 +78,10 @@ try {
co_return result::current_exception();
}
/** Force a value that cannot contain any function calls */
static void forceTrivialValue(EvalState & state, Value & value, const PosIdx pos)
{
/* /piegames sighs at the settings API */
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);
if (value.isThunk() && value.isTrivial())
state.forceValue(value, pos);
}
@@ -338,18 +327,12 @@ static Flake getFlake(
Expr & flakeExpr = state.ctx.parseExprFromFile(resolvedFlakeFile);
// Enforce that 'flake.nix' is a direct attrset, not a computation.
// We do this by disallowing any function calls (maxCallDepth 0) and checking that the resulting value is
// 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());
if (vInfo.type() != nAttrs) {
state.ctx.errors.make<EvalError>("file '%s' must be an attribute set", resolvedFlakeFile)
.debugThrow();
if (!flakeExpr.try_cast<ExprAttrs>()) {
state.ctx.errors.make<EvalError>("file '%s' must be an attribute set", resolvedFlakeFile).debugThrow();
}
Value vInfo = state.eval(flakeExpr);
if (auto description = vInfo.attrs()->get(state.ctx.symbols.sym_description)) {
expectType(state, nString, description->value, description->pos);
flake.description = description->value.str();
+20
View File
@@ -67,6 +67,16 @@ FlakeRef parseFlakeRef(
return flakeRef;
}
std::optional<FlakeRef> maybeParseFlakeRef(
const std::string & url, const std::optional<Path> & baseDir)
{
try {
return parseFlakeRef(url, baseDir);
} catch (Error &) {
return {};
}
}
std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
const std::string & url,
const std::optional<Path> & baseDir,
@@ -224,6 +234,16 @@ 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)
{
auto attrs2(attrs);
+6
View File
@@ -70,12 +70,18 @@ FlakeRef parseFlakeRef(
bool allowMissing = false,
bool isFlake = true);
std::optional<FlakeRef> maybeParseFlake(
const std::string & url, const std::optional<Path> & baseDir = {});
std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
const std::string & url,
const std::optional<Path> & baseDir = {},
bool allowMissing = false,
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(
const std::string & url,
const std::optional<Path> & baseDir = {},
+96 -84
View File
@@ -1,75 +1,125 @@
#include "lix/libexpr/json-to-value.hh"
#include "gc-alloc.hh"
#include "libutil/types.hh"
#include "lix/libexpr/value.hh"
#include "lix/libexpr/eval.hh"
#include "lix/libutil/json.hh"
#include <limits>
#include <variant>
namespace nix {
// for more information, refer to
// https://github.com/nlohmann/json/blob/master/include/nlohmann/detail/input/json_sax.hpp
class JSONSax : nlohmann::json_sax<JSON> {
struct WIPObject
{
GcMap<Symbol, Value> entries;
Symbol nextKey;
};
using WIPArray = GcVector<Value>;
// A stack of all of the currently opened and not yet closed JSON objects and arrays at the current moment
// of parsing
std::vector<std::variant<WIPObject, WIPArray>> stack;
std::optional<Value> final_value;
void addValue(Value v)
{
if (stack.size() == 0) {
assert(!final_value);
final_value = v;
return;
class JSONState {
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");
}
std::visit(
overloaded{
[&](WIPObject & currentObject) {
currentObject.entries.insert_or_assign(currentObject.nextKey, v);
currentObject.nextKey = {}; // clear the current symbol
},
[&](WIPArray & arr) { arr.push_back(v); }
},
stack.back()
);
}
virtual ~JSONState() {}
virtual void addValue(Value v) = 0;
};
class TopLevelJSONState : public JSONState
{
RootValue v;
public:
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);
}
};
class JSONObjectState : public JSONState {
using JSONState::JSONState;
GcMap<Symbol, Value> attrs;
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 {
GcVector<Value> values;
std::unique_ptr<JSONState> resolve(EvalState & state) override
{
auto list = state.ctx.mem.newList(values.size());
parent->addValue({NewValueAs::list, list});
for (size_t n = 0; n < values.size(); ++n) {
list->elems[n] = values[n];
}
return std::move(parent);
}
void addValue(Value v) override
{
values.push_back(v);
}
public:
JSONListState(std::unique_ptr<JSONState> && p, std::size_t reserve) : JSONState(std::move(p))
{
values.reserve(reserve);
}
};
EvalState & state;
std::unique_ptr<JSONState> rs;
public:
JSONSax(EvalState & state) : state(state) {};
JSONSax(EvalState & state) : state(state), rs(new TopLevelJSONState()) {};
Value result()
{
return final_value.value();
return rs->finalValue();
}
bool null() override
{
addValue(Value::VNULL);
rs->addValue(Value::VNULL);
return true;
}
bool boolean(bool val) override
{
addValue({NewValueAs::boolean, val});
rs->addValue({NewValueAs::boolean, val});
return true;
}
bool number_integer(number_integer_t val) override
{
addValue({NewValueAs::integer, val});
rs->addValue({NewValueAs::integer, val});
return true;
}
@@ -81,19 +131,19 @@ public:
return number_float(static_cast<number_float_t>(val_), "");
}
NixInt::Inner val = val_;
addValue({NewValueAs::integer, val});
rs->addValue({NewValueAs::integer, val});
return true;
}
bool number_float(number_float_t val, const string_t & s) override
{
addValue({NewValueAs::floating, val});
rs->addValue({NewValueAs::floating, val});
return true;
}
bool string(string_t & val) override
{
addValue({NewValueAs::string, val});
rs->addValue({NewValueAs::string, val});
return true;
}
@@ -108,66 +158,28 @@ public:
bool start_object(std::size_t len) override
{
stack.emplace_back(std::in_place_type<WIPObject>);
rs = std::make_unique<JSONObjectState>(std::move(rs));
return true;
}
bool key(string_t & name) override
{
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
);
dynamic_cast<JSONObjectState*>(rs.get())->key(name, state);
return true;
}
bool end_object() override {
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
);
rs = rs->resolve(state);
return true;
}
bool end_array() override {
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;
return end_object();
}
bool start_array(size_t len) override {
auto v = WIPArray{};
v.reserve(len != std::numeric_limits<size_t>::max() ? len : 128);
stack.push_back(std::move(v));
rs = std::make_unique<JSONListState>(std::move(rs),
len != std::numeric_limits<size_t>::max() ? len : 128);
return true;
}
+18
View File
@@ -99,6 +99,13 @@ JSON ExprVar::toJSON(const SymbolTable & symbols) const
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 out = {
@@ -740,6 +747,17 @@ void ExprLambda::setName(Symbol 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. */
Pos PosTable::operator[](PosIdx p) const
+3 -12
View File
@@ -126,10 +126,9 @@ public:
std::unique_ptr<Expr> parsed, Evaluator & es, const std::shared_ptr<const StaticEnv> & env
);
virtual JSON toJSON(const SymbolTable & symbols) const = 0;
virtual JSON toJSON(const SymbolTable & symbols) const;
virtual void accept(ExprVisitor & ev, std::unique_ptr<Expr> & ptr) = 0;
virtual Value eval(EvalState & state, Env & env);
Value makeThunk(Evaluator & ctx, Env & env);
virtual Value maybeThunk(EvalState & state, Env & env);
/* 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»`.
@@ -303,11 +302,7 @@ struct ExprInheritFrom : Expr
{
}
JSON toJSON(const SymbolTable & symbols) const override
{
abort();
}
JSON toJSON(const SymbolTable & symbols) const override;
Value eval(EvalState & state, Env & env) override;
void accept(ExprVisitor & ev, std::unique_ptr<Expr> & ptr) override { ev.visit(*this, ptr); }
};
@@ -522,6 +517,7 @@ struct ExprLambda : Expr
{
}
void setName(Symbol name) override;
std::string showNamePos(const EvalState & state) const;
/** Returns the name of the lambda,
* or "anonymous lambda" if it doesn't have one.
@@ -660,11 +656,6 @@ struct ExprPos : Expr
/* only used to mark thunks as black holes. */
struct ExprBlackHole : Expr
{
JSON toJSON(const SymbolTable & symbols) const override
{
abort();
}
Value eval(EvalState & state, Env & env) override;
void accept(ExprVisitor & ev, std::unique_ptr<Expr> & ptr) override { ev.visit(*this, ptr); }
};
+2 -12
View File
@@ -133,24 +133,14 @@ inline void State::badEscapeFound(const PosIdx pos, char found, bool isIndented)
{
auto escape = 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(
"%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. "
"%s is an ill-defined escape. You can drop the %s and simply write %s instead. "
"Use %s to silence this warning.",
escape + found,
found,
escape,
backslashChar,
Uncolored(dedicatedInsert.str()),
found,
"--extra-deprecated-features broken-string-escape"
);
/* Special case some common escapes to provide better messages */
if (found == '$' || found == '{') {
/* Someone possibly tried to escape an interpolation but used the wrong sequence.
+6 -79
View File
@@ -1,4 +1,3 @@
#include "libutil/error-trace.hh"
#include "lix/libutil/archive.hh"
#include "lix/libstore/derivations.hh"
#include "lix/libexpr/eval.hh"
@@ -37,7 +36,6 @@
#include <dlfcn.h>
#include <cmath>
#include <cfenv>
namespace nix {
@@ -302,16 +300,8 @@ 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();
}
// 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;
(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 */
return v;
@@ -700,85 +690,22 @@ static Value prim_addErrorContext(EvalState & state, Value ** args)
auto message = state.coerceToString(noPos, *args[0], context,
"while evaluating the error message passed to builtins.addErrorContext",
StringCoercionMode::Strict, false).toOwned();
e.addTrace(nullptr, HintFmt(message), TraceKind::UserTrace);
e.addTrace(nullptr, HintFmt(message));
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)
{
auto value = state.forceFloat(*args[0], noPos, "while evaluating the argument passed to builtins.ceil");
return floorCeil("builtins.ceil", ceil, state, value, args[0]);
auto value = state.forceFloat(*args[0], noPos,
"while evaluating the first argument passed to builtins.ceil");
return {NewValueAs::integer, NixInt::Inner(ceil(value))};
}
static Value prim_floor(EvalState & state, Value ** args)
{
auto value = state.forceFloat(*args[0], noPos, "while evaluating the argument passed to builtins.floor");
return floorCeil("builtins.floor", floor, state, value, args[0]);
auto value = state.forceFloat(*args[0], noPos, "while evaluating the first argument passed to builtins.floor");
return {NewValueAs::integer, NixInt::Inner(floor(value))};
}
/* Try evaluating the argument. Success => {success=true; value=something;},
+2 -8
View File
@@ -308,16 +308,10 @@ static Value fetch(
// https://github.com/NixOS/nix/issues/4313
auto storePath = unpack
? state.aio
.blockOn(
fetchers::downloadTarball(
state.ctx.store, *url, name, expectedHash != Hash(HashType::SHA256)
)
)
.blockOn(fetchers::downloadTarball(state.ctx.store, *url, name, (bool) expectedHash))
.tree.storePath
: state.aio
.blockOn(
fetchers::downloadFile(state.ctx.store, *url, name, expectedHash != Hash(HashType::SHA256))
)
.blockOn(fetchers::downloadFile(state.ctx.store, *url, name, (bool) expectedHash))
.storePath;
if (expectedHash) {
+9
View File
@@ -53,6 +53,15 @@ void Value::print(EvalState & state, std::ostream & str, PrintOptions 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(NewValueAs::string, s, copyContext(context))
{
+7 -3
View File
@@ -284,7 +284,6 @@ private:
InternalType internalType() const
{
// NOLINTNEXTLINE(lix-cast-to-non-fixed-enum): TAG_MASK ensures it's in range
return InternalType(raw & TAG_MASK);
}
@@ -724,8 +723,6 @@ public:
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);
}
};
@@ -778,6 +775,13 @@ public:
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
{
struct ListIterable
-24
View File
@@ -1,6 +1,4 @@
#include "lix/libfetchers/cache.hh"
#include "libstore/pathlocks.hh"
#include "libutil/hash.hh"
#include "lix/libstore/sqlite.hh"
#include "lix/libutil/async.hh"
#include "lix/libutil/sync.hh"
@@ -92,28 +90,6 @@ struct CacheImpl : Cache
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(
ref<Store> store,
const Attrs & inAttrs) override
-5
View File
@@ -1,7 +1,6 @@
#pragma once
///@file
#include "libstore/pathlocks.hh"
#include "lix/libfetchers/fetchers.hh"
#include "lix/libstore/path.hh"
@@ -22,10 +21,6 @@ struct Cache
ref<Store> store,
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
{
bool expired = false;
+16 -43
View File
@@ -1,4 +1,3 @@
#include "libfetchers/attrs.hh"
#include "lix/libutil/archive.hh"
#include "lix/libutil/async-io.hh"
#include "lix/libutil/async.hh"
@@ -16,10 +15,9 @@
#include "lix/libutil/url-parts.hh"
#include "lix/libstore/pathlocks.hh"
#include "lix/libutil/users.hh"
#include "lix/libutil/git.hh"
#include "lix/libutil/logging.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"
@@ -82,17 +80,17 @@ try {
));
std::string_view line = output;
auto line_rs = rust::to_string(line.substr(0, line.find("\n")));
if (const auto parseResult =
to_std(rust::lix::fetchers::git::LsRemoteRefLine::try_from(line_rs.as_str()).ok()))
{
auto target = to_std_string(parseResult->target.as_str());
if (parseResult->kind.matches_Object()) {
debug("resolved HEAD rev '%s' for repo '%s'", target, path);
} else {
debug("resolved HEAD ref '%s' for repo '%s'", target, path);
line = line.substr(0, line.find("\n"));
if (const auto parseResult = git::parseLsRemoteLine(line)) {
switch (parseResult->kind) {
case git::LsRemoteRefLine::Kind::Symbolic:
debug("resolved HEAD ref '%s' for repo '%s'", parseResult->target, path);
break;
case git::LsRemoteRefLine::Kind::Object:
debug("resolved HEAD rev '%s' for repo '%s'", parseResult->target, path);
break;
}
co_return to_std_string(parseResult->target.as_str());
co_return parseResult->target;
}
co_return std::nullopt;
} catch (ExecError &) {
@@ -607,18 +605,9 @@ struct GitInputScheme : InputScheme
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()) {
auto resOrLock = TRY_AWAIT(getCache()->lookupOrLock(store, getLockedAttrs()));
if (auto res = std::get_if<std::pair<Attrs, StorePath>>(&resOrLock))
if (auto res = TRY_AWAIT(getCache()->lookup(store, getLockedAttrs())))
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);
@@ -687,10 +676,7 @@ struct GitInputScheme : InputScheme
}
}
// 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)) {
if (auto res = TRY_AWAIT(getCache()->lookup(store, unlockedAttrs))) {
auto rev2 = Hash::parseAny(getStrAttr(res->first, "rev"), HashType::SHA1);
if (!input.getRev() || input.getRev() == rev2) {
input.attrs.insert_or_assign("rev", rev2.gitRev());
@@ -872,21 +858,8 @@ struct GitInputScheme : InputScheme
/* Now that we know the ref, check again whether we have it in
the store. */
// If we already have a lock for fetching this path, we can't use lookupOrLock because it would deadlock.
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);
}
if (auto res = TRY_AWAIT(getCache()->lookup(store, getLockedAttrs())))
co_return makeResult(res->first, std::move(res->second));
Path tmpDir = createTempDir();
AutoDelete delTmpDir(tmpDir, true);
@@ -1001,7 +974,7 @@ struct GitInputScheme : InputScheme
});
Finally const _wait([&] { proc.waitAndCheck(); });
TRY_AWAIT(unpackTarfile(_input.toURLString(), *proc.getStdout(), tmpDir));
TRY_AWAIT(unpackTarfile(*proc.getStdout(), tmpDir));
}
auto storePath = TRY_AWAIT(
+7 -12
View File
@@ -7,11 +7,10 @@
#include "lix/libutil/result.hh"
#include "lix/libutil/types.hh"
#include "lix/libutil/url-parts.hh"
#include "lix/libutil/git.hh"
#include "lix/libutil/json.hh"
#include "lix/libfetchers/fetchers.hh"
#include "lix/libfetchers/fetch-settings.hh"
#include "lix/lix-rs/main.gen.hh"
#include "lix/lix-rs/utils.hh"
#include <optional>
#include <fstream>
@@ -487,16 +486,15 @@ struct SourceHutInputScheme : GitArchiveInputScheme
std::string line;
getline(is, line);
auto remoteLine = to_std(
rust::lix::fetchers::git::LsRemoteRefLine::try_from(rust::to_string(line).as_str()).ok()
);
auto remoteLine = git::parseLsRemoteLine(line);
if (!remoteLine) {
throw BadURL("in '%d', couldn't resolve HEAD ref '%d'", input.to_string(), ref);
}
refUri = to_std_string(remoteLine->target.as_str());
refUri = remoteLine->target;
} else {
refUri = fmt("refs/(heads|tags)/%s", ref);
}
std::regex refRegex = regex::parse(refUri);
auto file = store->toRealPath(
TRY_AWAIT(downloadFile(store, fmt("%s/info/refs", base_url), "source", false, headers))
@@ -507,12 +505,9 @@ struct SourceHutInputScheme : GitArchiveInputScheme
std::string line;
std::optional<std::string> id;
while(!id && getline(is, line)) {
auto parsedLine = to_std(
rust::lix::fetchers::git::LsRemoteRefLine::try_from(rust::to_string(line).as_str()).ok()
);
if (parsedLine && (*parsedLine).matches_ref_uri(rust::to_string(refUri).as_str())) {
id = to_std_string(parsedLine->target.as_str());
}
auto parsedLine = git::parseLsRemoteLine(line);
if (parsedLine && parsedLine->reference && std::regex_match(*parsedLine->reference, refRegex))
id = parsedLine->target;
}
if(!id)
+1 -1
View File
@@ -195,7 +195,7 @@ try {
} else {
Path tmpDir = createTempDir();
AutoDelete autoDelete(tmpDir, true);
unpackTarfile(url, store->toRealPath(res.storePath), tmpDir);
unpackTarfile(store->toRealPath(res.storePath), tmpDir);
auto members = readDirectory(tmpDir);
if (members.size() != 1)
throw nix::Error("tarball '%s' contains an unexpected number of top-level files", url);
+3 -3
View File
@@ -16,21 +16,21 @@ MixCommonArgs::MixCommonArgs(const std::string & programName)
.shortName = 'v',
.description = "Increase the logging verbosity level.",
.category = loggingCategory,
.handler = {[]() { setVerbosity(verbosityFromIntClamped(int(getVerbosity()) + 1)); }},
.handler = {[]() { verbosity = verbosityFromIntClamped(int(verbosity) + 1); }},
});
addFlag({
.longName = "quiet",
.description = "Decrease the logging verbosity level.",
.category = loggingCategory,
.handler = {[]() { setVerbosity(verbosityFromIntClamped(int(getVerbosity()) - 1)); }},
.handler = {[]() { verbosity = verbosityFromIntClamped(int(verbosity) - 1); }},
});
addFlag({
.longName = "debug",
.description = "Set the logging verbosity level to 'debug'.",
.category = loggingCategory,
.handler = {[]() { setVerbosity(lvlDebug); }},
.handler = {[]() { verbosity = lvlDebug; }},
});
addFlag({
+6 -9
View File
@@ -106,7 +106,7 @@ bool ProgressBar::isVerbose()
Logger::BufferState ProgressBar::log(Verbosity lvl, std::string_view s)
{
if (lvl > getVerbosity()) {
if (lvl > verbosity) {
return BufferState::HasSpace;
}
auto state(state_.lock());
@@ -142,9 +142,8 @@ Logger::BufferState ProgressBar::startActivityImpl(
{
auto state(state_.lock());
if (lvl <= getVerbosity() && !s.empty() && type != actBuildWaiting) {
if (lvl <= verbosity && !s.empty() && type != actBuildWaiting)
(void) log(*state, lvl, s + "...");
}
state->activities.emplace_back(ActInfo {
.s = s,
@@ -198,10 +197,8 @@ Logger::BufferState ProgressBar::startActivityImpl(
if ((type == actFileTransfer && hasAncestor(*state, actCopyPath, parent))
|| (type == actFileTransfer && hasAncestor(*state, actQueryPathInfo, parent))
|| (type == actCopyPath && hasAncestor(*state, actSubstitute, parent)) || (s == "daemon connection"))
{
|| (type == actCopyPath && hasAncestor(*state, actSubstitute, parent)))
i->visible = false;
}
update(*state);
return BufferState::HasSpace;
@@ -563,7 +560,7 @@ void ProgressBar::writeToStdout(std::string_view s)
restoreProgressDisplay(*state);
}
std::optional<std::string> ProgressBar::ask(std::string_view msg)
std::optional<char> ProgressBar::ask(std::string_view msg)
{
auto state(state_.lock());
if (state->paused > 0 || !isatty(STDIN_FILENO)) return {};
@@ -572,9 +569,9 @@ std::optional<std::string> ProgressBar::ask(std::string_view msg)
std::cerr << msg;
auto s = trim(readLine(STDIN_FILENO));
writeLogsToStderr("\e[?2026h"); // begin synchronized update
if (s.size() != 1) return {};
restoreProgressDisplay(*state);
// only return the string if it's not empty
return s.size() != 0 ? s : std::optional<std::string>{};
return s[0];
}
void ProgressBar::setPrintBuildLogs(bool printBuildLogs)
+1 -1
View File
@@ -103,7 +103,7 @@ struct ProgressBar : public Logger
void writeToStdout(std::string_view s) override;
std::optional<std::string> ask(std::string_view msg) override;
std::optional<char> ask(std::string_view msg) override;
void setPrintBuildLogs(bool printBuildLogs) override;
+12 -2
View File
@@ -21,7 +21,6 @@
#include "lix/libutil/terminal.hh"
#include "lix/libutil/strings.hh"
#include "lix/libutil/exit.hh"
#include "lix/libutil/compile-time-features.hh"
#include <algorithm>
#include <exception>
@@ -176,6 +175,12 @@ void initNix()
}
#if __APPLE__
/* HACK: on darwin, we need cant 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
* error with EINTR like they do on Linux.
* Most signals on BSD systems default to SA_RESTART on, but Nix
@@ -297,9 +302,14 @@ bool LegacyArgs::processArgs(const Strings & args, bool finish)
void printVersion(const std::string & programName)
{
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 << "Additional system types: " << concatStringsSep(", ", settings.extraPlatforms.get()) << "\n";
std::cout << "Features: " << concatStringsSep(", ", getNixFeatures()) << "\n";
std::cout << "Features: " << concatStringsSep(", ", cfg) << "\n";
std::cout << "System configuration file: " << settings.nixConfDir + "/nix.conf" << "\n";
std::cout << "User configuration files: " <<
concatStringsSep(":", settings.nixUserConfFiles)
+1 -1
View File
@@ -21,7 +21,7 @@ struct BuildResult
* Therefore, don't remove status codes, and only add new status
* codes at the end of the list.
*/
enum Status : uint8_t {
enum Status {
Built = 0,
Substituted,
AlreadyValid,
+6 -5
View File
@@ -1094,8 +1094,8 @@ try {
RPC_FILL(buildReq, setNeededSystem, drv->platform);
RPC_FILL(buildReq, initDrvPath, drvPath, worker.store);
RPC_FILL(buildReq, initRequiredFeatures, parsedDrv->getRequiredSystemFeatures());
auto buildRespV = TRY_AWAIT_RPC(buildReq.send());
auto buildResp = buildRespV.getResult();
auto buildRespPromise = buildReq.send();
auto buildResp = TRY_AWAIT_RPC(buildRespPromise);
debug("hook reply is '%1%'", buildResp.toString().flatten().cStr());
@@ -1136,9 +1136,10 @@ try {
missingOutputs.insert(outputName);
}
RPC_FILL(runReq, initWantedOutputs, missingOutputs);
RPC_FILL(runReq, setDescription, buildDescription());
}
auto runPromise = LIX_WRAP_RPC_PROMISE_V1(runReq.send());
auto runPromise = runReq.send();
// build via hook is now properly running. wait for it to finish
actLock.reset();
@@ -1149,8 +1150,8 @@ try {
wrapChildHandler(runPromise.then([&](auto result) -> kj::Promise<Result<WorkResult>> {
try {
std::shared_ptr<Error> remoteError;
if (result.has_error()) {
remoteError = std::make_shared<Error>(detail::wrap_exception_as_lix(result.error()));
if (result.getResult().isBad()) {
remoteError = std::make_shared<Error>(from(result.getResult().getBad()));
logErrorInfo(remoteError->info().level, remoteError->info());
}
// close the rpc connection to have the hook exit
+4
View File
@@ -360,6 +360,10 @@ public:
{
return false;
}
JobCategory jobCategory() const override {
return JobCategory::Build;
};
};
MakeError(NotDeterministic, BuildError);
+24 -1
View File
@@ -27,6 +27,23 @@ typedef std::shared_ptr<Goal> GoalPtr;
*/
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
{
typedef enum {ecSuccess, ecFailed, ecNoSubstituters, ecIncompleteClosure} ExitCode;
@@ -122,7 +139,13 @@ public:
return name;
}
virtual void cleanup() {}
virtual void cleanup() { }
/**
* @brief Hint for the scheduler, which concurrency limit applies.
* @see JobCategory
*/
virtual JobCategory jobCategory() const = 0;
};
}
+5 -4
View File
@@ -8,13 +8,14 @@ using Types = import "/lix/libutil/types.capnp";
using Log = import "/lix/libutil/logging.capnp";
using StoreTypes = import "/lix/libstore/types.capnp";
interface HookInstance $Types.throws(Types.v1Errors) {
interface HookInstance {
interface AcceptedBuild {
run @0 (
logger :Log.LogStream,
inputs :List(StoreTypes.StorePath), # actual a set
wantedOutputs :List(Data), # actually StringSet
);
description :Text, # root activity description for this build
) -> (result :Types.ResultV);
}
struct BuildResponse {
@@ -31,11 +32,11 @@ interface HookInstance $Types.throws(Types.v1Errors) {
init @0 (
logger :Log.LogStream,
settings :Types.Settings,
);
) -> (result :Types.ResultV);
build @1 (
amWilling :Bool,
neededSystem :Data,
drvPath :StoreTypes.StorePath,
requiredFeatures :List(Data),
) -> (result :BuildResponse);
) -> (result :Types.Result(BuildResponse));
}
+1 -1
View File
@@ -75,7 +75,7 @@ try {
auto buildHook = canonPath(buildHookArgs.front());
buildHookArgs.emplace(std::next(buildHookArgs.begin()), baseNameOf(buildHook));
buildHookArgs.push_back(std::to_string(getVerbosity()));
buildHookArgs.push_back(std::to_string(verbosity));
/* Create the communication pipes. */
auto [selfRPC, hookRPC] = SocketPair::stream();
+2 -19
View File
@@ -897,7 +897,7 @@ try {
if (drv->isBuiltin()) {
args.push_back("builtin-builder");
args.push_back(std::to_string(getVerbosity()));
args.push_back(std::to_string(verbosity));
std::map<std::string, AbstractConfig::SettingInfo> changedSettings;
globalConfig.getChangedSettings(changedSettings);
@@ -984,7 +984,7 @@ try {
auto groups = buildUser->getSupplementaryGIDs();
creds.setSupplementaryGroups({groups.data(), groups.size()});
}
request.setDebug(getVerbosity() >= lvlDebug);
request.setDebug(verbosity >= lvlDebug);
fillBuilderConfig(request);
@@ -1711,23 +1711,6 @@ try {
anyHashMismatchSeen = true;
// 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)"));
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(
"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",
+4
View File
@@ -90,6 +90,10 @@ public:
kj::Promise<Result<WorkResult>> referencesValid() noexcept;
kj::Promise<Result<WorkResult>> tryToRun() noexcept;
kj::Promise<Result<WorkResult>> finished() noexcept;
JobCategory jobCategory() const override {
return JobCategory::Substitution;
};
};
}
+1 -1
View File
@@ -7,7 +7,7 @@ void builtinUnpackChannel(const Path & out, const std::string & channelName, con
{
createDirs(out);
unpackTarfile(fmt("channel:%s", channelName), src, out);
unpackTarfile(src, out);
auto entries = readDirectory(out);
if (entries.size() != 1)
-470
View File
@@ -1,470 +0,0 @@
#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);
}
}
-170
View File
@@ -1,170 +0,0 @@
#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>>>;
};
}
-259
View File
@@ -1,259 +0,0 @@
#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
-61
View File
@@ -1,61 +0,0 @@
#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
-54
View File
@@ -1,54 +0,0 @@
@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 ();
}
+51 -353
View File
@@ -1,7 +1,5 @@
#include "lix/libstore/daemon.hh"
#include "filetransfer.hh"
#include "libutil/async.hh"
#include "libutil/logging-rpc.hh"
#include "lix/libutil/async-io.hh"
#include "lix/libutil/monitor-fd.hh"
#include "lix/libstore/worker-protocol.hh"
@@ -19,21 +17,12 @@
#include "lix/libutil/serialise.hh"
#include "lix/libutil/strings.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 <capnp/rpc-twoparty.h>
#include <cstdint>
#include <ctime>
#include <kj/encoding.h>
#include <kj/exception.h>
#include <kj/memory.h>
#include <sstream>
const std::string nix::rpc::daemon::UNSTABLE_LEGACY_TUNNELED = "lix/legacy/" PACKAGE_VERSION;
namespace nix::daemon {
Sink & operator << (Sink & sink, const Logger::Fields & fields)
@@ -97,7 +86,7 @@ struct TunnelLogger : public Logger
BufferState log(Verbosity lvl, std::string_view s) override
{
if (lvl > getVerbosity()) {
if (lvl > verbosity) {
return BufferState::HasSpace;
}
@@ -108,7 +97,7 @@ struct TunnelLogger : public Logger
BufferState logEI(const ErrorInfo & ei) override
{
if (ei.level > getVerbosity()) {
if (ei.level > verbosity) {
return BufferState::HasSpace;
}
@@ -197,7 +186,7 @@ struct ClientSettings
settings.keepFailed.override(keepFailed);
settings.keepGoing.override(keepGoing);
settings.tryFallback.override(tryFallback);
setVerbosity(verbosity);
nix::verbosity = verbosity;
settings.maxBuildJobs.override(maxBuildJobs);
settings.maxSilentTime.override(maxSilentTime);
settings.verboseBuild = verboseBuild;
@@ -845,12 +834,11 @@ static void performOp(AsyncIoRoot & aio, TunnelLogger * logger, ref<Store> store
case WorkerProto::Op::AddBuildLog: {
StorePath path{readString(from)};
logger->startWork();
if (!trusted)
throw Error("you are not privileged to add logs");
auto & logStore = require<LogStore>(*store);
{
FramedSource source(from);
if (!trusted) {
throw Error("you are not privileged to add logs");
}
StringSink sink;
source.drainInto(sink);
aio.blockOn(logStore.addBuildLog(path, sink.s));
@@ -869,66 +857,12 @@ static void performOp(AsyncIoRoot & aio, TunnelLogger * logger, ref<Store> store
}
}
static void processLegacyRequests(
void processConnection(
AsyncIoRoot & aio,
Logger * prevLogger,
TunnelLogger * tunnelLogger,
ref<Store> store,
FdSource & from,
FdSink & to,
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
)
TrustedFlag trusted)
{
auto monitor = std::make_unique<MonitorFdHup>(from.fd);
@@ -946,6 +880,10 @@ void processLegacyConnection(
auto prevLogger = nix::logger;
logger = tunnelLogger;
unsigned int opCount = 0;
Finally finally([&]() { printMsgUsing(prevLogger, lvlDebug, "%d operations", opCount); });
// FIXME: what is *supposed* to be in this even?
if (readNum<unsigned>(from)) {
// Obsolete CPU affinity.
@@ -968,10 +906,49 @@ void processLegacyConnection(
tunnelLogger->startWork();
try {
tunnelLogger->stopWork();
to.flush();
processLegacyRequests(aio, prevLogger, tunnelLogger, store, from, to, trusted, clientVersion);
/* Process client requests. */
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) {
tunnelLogger->stopWork(&e);
to.flush();
@@ -990,283 +967,4 @@ void processLegacyConnection(
}
}
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();
}
}
+6 -12
View File
@@ -4,20 +4,14 @@
#include "lix/libutil/async.hh"
#include "lix/libutil/serialise.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 {
void processLegacyConnection(
AsyncIoRoot & aio, ref<Store> store, FdSource & from, FdSink & to, TrustedFlag trusted
);
void processConnection(
AsyncIoRoot & aio,
ref<Store> store,
FdSource & from,
FdSink & to,
TrustedFlag trusted);
kj::Promise<Result<void>>
processConnection(ref<Store> store, kj::AsyncIoStream & connection, TrustedFlag trusted);
}
+6
View File
@@ -744,6 +744,12 @@ 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)
{
// FIXME: memoize?
+1
View File
@@ -359,6 +359,7 @@ struct Sink;
Source & readDerivation(Source & in, const Store & store, BasicDerivation & drv, std::string_view name);
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
@@ -5,4 +5,3 @@ type: bool
default: true
---
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,4 +5,3 @@ type: bool
default: false
---
Whether to enable HTTP/3 support.
Enabling HTTP/3 support forcibly enables HTTP/2 as well; the `http2` will be ignored in this case.
File diff suppressed because it is too large Load Diff
+4 -15
View File
@@ -3,17 +3,17 @@
#include "lix/libutil/async-io.hh"
#include "lix/libutil/box_ptr.hh"
#include "lix/libutil/config.hh"
#include "lix/libutil/logging.hh"
#include "lix/libutil/ref.hh"
#include "lix/libutil/logging.hh"
#include "lix/libutil/result.hh"
#include "lix/libutil/serialise.hh"
#include "lix/libutil/strings.hh"
#include "lix/libutil/types.hh"
#include "lix/libutil/config.hh"
#include <kj/async.h>
#include <curl/curl.h>
#include <string>
#include <future>
namespace nix {
@@ -118,18 +118,7 @@ public:
std::optional<std::string> response;
template<typename... 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;
}
FileTransferError(FileTransfer::Error error, std::optional<std::string> response, const Args & ... args);
};
}
+2 -2
View File
@@ -39,13 +39,13 @@ struct GCOptions
* Any that could not be deleted are returned via the
* `kept` field of GCResults.
*/
enum GCAction : uint8_t {
typedef enum {
gcReturnLive,
gcReturnDead,
gcDeleteDead,
gcDeleteSpecific,
gcTryDeleteSpecific,
};
} GCAction;
GCAction action{gcDeleteDead};
+5 -47
View File
@@ -67,16 +67,15 @@ Settings::Settings()
, nixManDir(canonPath(NIX_MAN_DIR))
{
if (auto socketDirFromEnv = getEnvNonEmpty("LIX_DAEMON_SOCKET_DIR")) {
nixDaemonSockets_ = daemon::supportedProtocols(*socketDirFromEnv);
socketsPath = *socketDirFromEnv;
nixDaemonSockets_ = {{canonPath(*socketDirFromEnv + LEGACY_SOCKET_COMBINED)}};
} else if (auto socketPathFromEnv = getEnvNonEmpty("NIX_DAEMON_SOCKET_PATH")) {
nixDaemonSockets_ = {{canonPath(*socketPathFromEnv), daemon::Protocol::LEGACY_COMBINED}};
socketsPath = *socketPathFromEnv;
nixDaemonSockets_ = {{canonPath(*socketPathFromEnv)}};
} else {
auto baseDir = nixStateDir + DEFAULT_SOCKET_DIR;
socketsPath = baseDir;
// this should always match the list of sockets created by daemonLoop and the socket units
nixDaemonSockets_ = daemon::supportedProtocols(baseDir);
nixDaemonSockets_ = {
{canonPath(baseDir + LEGACY_SOCKET_COMBINED)},
};
}
buildUsersGroup.setDefault(getuid() == 0 ? "nixbld" : "");
@@ -531,45 +530,4 @@ void initLibStore()
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);
}
}
}
}
+9 -38
View File
@@ -10,37 +10,6 @@
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;
void to_json(JSON & j, const SandboxMode & e);
@@ -94,7 +63,14 @@ const uint32_t maxIdsPerBuild =
class Settings : public Config
{
std::list<daemon::Protocol> nixDaemonSockets_;
public:
struct DaemonSocketPath
{
Path path;
};
private:
std::list<DaemonSocketPath> nixDaemonSockets_;
unsigned int getDefaultCores();
@@ -129,11 +105,6 @@ public:
*/
Path nixStateDir;
/**
* The directory where sockets are stored.
*/
Path socketsPath;
/**
* The directory where system configuration files are stored.
*/
@@ -157,7 +128,7 @@ public:
/**
* Socket paths a client should connect to, in order of decreasing preference.
*/
const std::list<daemon::Protocol> & nixDaemonSockets() const
const std::list<DaemonSocketPath> & nixDaemonSockets() const
{
return nixDaemonSockets_;
}
+71 -131
View File
@@ -1,7 +1,4 @@
#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/async-io.hh"
#include "lix/libutil/async.hh"
@@ -23,8 +20,6 @@
#include "path-info.hh"
#include "path.hh"
#include <cstdint>
#include <exception>
#include <kj/async.h>
#include <optional>
namespace nix {
@@ -87,6 +82,9 @@ struct LegacySSHStoreConfig : CommonSSHStoreConfig
const Setting<Path> remoteProgram{this, "nix-store", "remote-program",
"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"; }
std::string doc() override
@@ -97,25 +95,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
{
LegacySSHStoreConfig config_;
LegacySSHStoreConfigWithLog config_;
LegacySSHStoreConfig & config() override
{
return config_;
}
const LegacySSHStoreConfig & config() const override
{
return config_;
}
LegacySSHStoreConfigWithLog & config() override { return config_; }
const LegacySSHStoreConfigWithLog & config() const override { return config_; }
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>()};
std::unique_ptr<SSH::Connection> sshConn;
ServeProto::Version remoteVersion;
@@ -138,54 +136,6 @@ 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>
kj::Promise<Result<void>>
sendArg(AsyncOutputStream & stream, StringSink & buffer, Arg && arg)
@@ -254,70 +204,72 @@ struct LegacySSHStore final : public Store
std::string host;
Sync<Connection, AsyncMutex> connection;
ref<Pool<Connection>> connections;
SSH ssh;
static std::set<std::string> uriSchemes() { return {"ssh"}; }
LegacySSHStore(const std::string & scheme, const std::string & host, LegacySSHStoreConfig config)
LegacySSHStore(
const std::string & scheme, const std::string & host, LegacySSHStoreConfigWithLog config
)
: Store(config)
, config_(std::move(config))
, host(host)
, ssh(host, config_.port, config_.sshKey, config_.sshPublicHostKey, config_.compress)
, connections(make_ref<Pool<Connection>>(
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<decltype(connection)::Lock>> getConnection()
kj::Promise<Result<ref<Connection>>> openConnection()
try {
auto conn = co_await connection.lock();
if (conn->good && conn->sshConn) {
co_return conn;
}
*conn = {};
auto conn = make_ref<Connection>();
conn->sshConn = ssh.startCommand(
fmt("%s --serve --write", config_.remoteProgram)
+ (config_.remoteStore.get() == "" ? "" : " --store " + shellEscape(config_.remoteStore.get()))
+ (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 {
FdSink to(conn->sshConn->socket.get());
FdSource from(conn->sshConn->socket.get(), conn->fromBuf);
conn->store = this;
to << SERVE_MAGIC_1 << SERVE_PROTOCOL_VERSION;
to.flush();
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);
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);
/* 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
);
}
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 (EndOfFile & e) {
throw Error("cannot connect to '%1%'", host);
}
return {conn};
} catch (...) {
co_return result::current_exception();
}
return {result::current_exception()};
};
std::string getUri() override
{
@@ -327,7 +279,7 @@ struct LegacySSHStore final : public Store
kj::Promise<Result<std::shared_ptr<const ValidPathInfo>>>
queryPathInfoUncached(const StorePath & path, const Activity * context) override
try {
auto conn(TRY_AWAIT(getConnection()));
auto conn(TRY_AWAIT(connections->get()));
debug("querying remote host '%s' for info on '%s'", host, printStorePath(path));
@@ -355,7 +307,7 @@ struct LegacySSHStore final : public Store
try {
debug("adding path '%s' to remote host '%s'", printStorePath(info.path), host);
auto conn(TRY_AWAIT(getConnection()));
auto conn(TRY_AWAIT(connections->get()));
unsigned result;
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 5) {
@@ -399,16 +351,16 @@ struct LegacySSHStore final : public Store
kj::Promise<Result<box_ptr<AsyncInputStream>>>
narFromPath(const StorePath & path, const Activity * context) override
try {
auto conn(TRY_AWAIT(getConnection()));
auto conn(TRY_AWAIT(connections->get()));
struct NarStream : AsyncInputStream
{
Sync<Connection, AsyncMutex>::Lock conn;
Pool<Connection>::Handle conn;
AsyncFdIoStream stream{AsyncFdIoStream::shared_fd{}, conn->sshConn->socket.get()};
AsyncBufferedInputStream buffered{stream, conn->fromBuf};
box_ptr<AsyncInputStream> copier{copyNAR(buffered)};
NarStream(Sync<Connection, AsyncMutex>::Lock conn) : conn(std::move(conn)) {}
NarStream(Pool<Connection>::Handle conn) : conn(std::move(conn)) {}
kj::Promise<Result<std::optional<size_t>>> read(void * buffer, size_t size) override
{
@@ -472,19 +424,7 @@ public:
const StorePath & drvPath, const BasicDerivation & drv, BuildMode buildMode
) override
try {
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());
auto conn(TRY_AWAIT(connections->get()));
co_return TRY_AWAIT(conn->sendCommand<BuildResult>(
ServeProto::Command::BuildDerivation,
@@ -505,7 +445,7 @@ public:
if (evalStore && evalStore.get() != this)
throw Error("building on an SSH store is incompatible with '--eval-store'");
auto conn(TRY_AWAIT(getConnection()));
auto conn(TRY_AWAIT(connections->get()));
Strings ss;
for (auto & p : drvPaths) {
@@ -564,7 +504,7 @@ public:
co_return result::success();
}
auto conn(TRY_AWAIT(getConnection()));
auto conn(TRY_AWAIT(connections->get()));
out.merge(TRY_AWAIT(conn->sendCommand<StorePathSet>(
ServeProto::Command::QueryClosure, includeOutputs, ServeProto::write(*conn, paths)
@@ -578,7 +518,7 @@ public:
kj::Promise<Result<StorePathSet>> queryValidPaths(const StorePathSet & paths,
SubstituteFlag maybeSubstitute = NoSubstitute) override
try {
auto conn(TRY_AWAIT(getConnection()));
auto conn(TRY_AWAIT(connections->get()));
co_return TRY_AWAIT(conn->sendCommand<StorePathSet>(
ServeProto::Command::QueryValidPaths,
@@ -590,9 +530,9 @@ public:
co_return result::current_exception();
}
kj::Promise<Result<void>> init() override
kj::Promise<Result<void>> connect() override
try {
auto conn(TRY_AWAIT(getConnection()));
auto conn(TRY_AWAIT(connections->get()));
co_return result::success();
} catch (...) {
co_return result::current_exception();
@@ -600,7 +540,7 @@ public:
kj::Promise<Result<unsigned int>> getProtocol() override
try {
auto conn(TRY_AWAIT(getConnection()));
auto conn(TRY_AWAIT(connections->get()));
co_return conn->remoteVersion;
} catch (...) {
co_return result::current_exception();
+12 -7
View File
@@ -34,11 +34,19 @@ bool Machine::mandatoryMet(const std::set<std::string> & features) const
});
}
kj::Promise<Result<ref<Store>>> Machine::openStore() const
kj::Promise<Result<std::pair<ref<Store>, Pipe>>> Machine::openStore() const
try {
Pipe pipe;
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://")) {
pipe.create();
storeParams["log-fd"] = std::to_string(pipe.writeSide.get());
if (sshKey != "")
storeParams["ssh-key"] = sshKey;
if (sshPublicHostKey != "")
@@ -57,7 +65,7 @@ try {
append(mandatoryFeatures);
}
co_return TRY_AWAIT(nix::openStore(storeUri, storeParams));
co_return {TRY_AWAIT(nix::openStore(storeUri, storeParams)), std::move(pipe)};
} catch (...) {
co_return result::current_exception();
}
@@ -159,7 +167,6 @@ static Machine parseBuilderLine(const std::string & line)
}
return {
storeUri,
storeUri,
systemTypes,
sshKey,
@@ -255,8 +262,7 @@ static toml::result<float, std::string> getSpeedFactor(const toml::value & data)
return toml::success(1.0f);
}
static toml::result<Machine, std::vector<std::string>>
parseMachine(const std::string name, const toml::value & data)
static toml::result<Machine, std::vector<std::string>> parseMachine(const toml::value & data)
{
std::vector<std::string> errs;
@@ -333,7 +339,6 @@ parseMachine(const std::string name, const toml::value & data)
return toml::failure(errs);
}
return toml::success<Machine>({
name,
storeUri.unwrap(),
std::set(systemTypes.unwrap().begin(), systemTypes.unwrap().end()),
sshKey.unwrap(),
@@ -399,7 +404,7 @@ static toml::result<Machines, std::vector<std::string>> parseToml(const toml::va
}
for (const auto & [name, machine] : data.at(array_name).as_table()) {
auto const res = parseMachine(name, machine);
auto const res = parseMachine(machine);
if (res.is_err()) {
auto err = res.as_err();
parserErrors.push_back(fmt("for machine %s:", name));
+1 -2
View File
@@ -14,7 +14,6 @@ class Store;
struct Machine {
const std::string name;
const std::string storeUri;
const std::set<std::string> systemTypes;
const std::string sshKey;
@@ -42,7 +41,7 @@ struct Machine {
*/
bool mandatoryMet(const std::set<std::string> & features) const;
kj::Promise<Result<ref<Store>>> openStore() const;
kj::Promise<Result<std::pair<ref<Store>, Pipe>>> openStore() const;
};
typedef std::vector<Machine> Machines;
+20 -31
View File
@@ -11,33 +11,28 @@ endforeach
libstore_rpc = []
libstore_rpc_files = files(
# keep-sorted start
'daemon.capnp',
'types.capnp',
# keep-sorted end
libstore_rpc += custom_target(
command : [
capnpc_wrapper,
'--language=c++',
'--src-prefix=@CURRENT_SOURCE_DIR@',
'--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
subdir('build')
@@ -194,8 +189,6 @@ liblix_sources += files(
'common-protocol.cc',
'content-address.cc',
'crypto.cc',
'curlfiletransfer.cc',
'curlmulti.cc',
'daemon.cc',
'derivations.cc',
'derived-path.cc',
@@ -239,7 +232,6 @@ liblix_sources += files(
'ssh.cc',
'store-api.cc',
'temporary-dir.cc',
'transferitem.cc',
'uds-remote-store.cc',
'worker-protocol.cc',
# keep-sorted end
@@ -263,8 +255,6 @@ libstore_headers = files(
'common-protocol.hh',
'content-address.hh',
'crypto.hh',
'curlfiletransfer.hh',
'curlmulti.hh',
'daemon.hh',
'derivations.hh',
'derived-path.hh',
@@ -312,7 +302,6 @@ libstore_headers = files(
'store-api.hh',
'store-cast.hh',
'temporary-dir.hh',
'transferitem.hh',
'types-rpc.hh',
'uds-remote-store.hh',
'worker-protocol-impl.hh',

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