difftreelog
refactor c bindings
in: trunk
26 files changed
Cargo.lockdiffbeforeafterboth198198199[[package]]199[[package]]200name = "anyhow"200name = "anyhow"201version = "1.0.83"201version = "1.0.86"202source = "registry+https://github.com/rust-lang/crates.io-index"202source = "registry+https://github.com/rust-lang/crates.io-index"203checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3"203checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"204204205[[package]]205[[package]]206name = "arc-swap"206name = "arc-swap"222dependencies = [222dependencies = [223 "proc-macro2",223 "proc-macro2",224 "quote",224 "quote",225 "syn 2.0.63",225 "syn 2.0.66",226]226]227227228[[package]]228[[package]]374374375[[package]]375[[package]]376name = "cc"376name = "cc"377version = "1.0.97"377version = "1.0.98"378source = "registry+https://github.com/rust-lang/crates.io-index"378source = "registry+https://github.com/rust-lang/crates.io-index"379checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4"379checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f"380380381[[package]]381[[package]]382name = "cfg-if"382name = "cfg-if"468 "heck",468 "heck",469 "proc-macro2",469 "proc-macro2",470 "quote",470 "quote",471 "syn 2.0.63",471 "syn 2.0.66",472]472]473473474[[package]]474[[package]]587 "cfg-if",587 "cfg-if",588 "cpufeatures",588 "cpufeatures",589 "curve25519-dalek-derive",589 "curve25519-dalek-derive",590 "digest",590 "fiat-crypto",591 "fiat-crypto",591 "platforms",592 "platforms",592 "rustc_version",593 "rustc_version",602dependencies = [603dependencies = [603 "proc-macro2",604 "proc-macro2",604 "quote",605 "quote",605 "syn 2.0.63",606 "syn 2.0.66",606]607]607608608[[package]]609[[package]]658dependencies = [659dependencies = [659 "proc-macro2",660 "proc-macro2",660 "quote",661 "quote",661 "syn 2.0.63",662 "syn 2.0.66",662]663]664665[[package]]666name = "ed25519"667version = "2.2.3"668source = "registry+https://github.com/rust-lang/crates.io-index"669checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"670dependencies = [671 "pkcs8",672 "signature",673]674675[[package]]676name = "ed25519-dalek"677version = "2.1.1"678source = "registry+https://github.com/rust-lang/crates.io-index"679checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871"680dependencies = [681 "curve25519-dalek",682 "ed25519",683 "rand_core",684 "serde",685 "sha2",686 "subtle",687 "zeroize",688]663689664[[package]]690[[package]]665name = "either"691name = "either"666version = "1.11.0"692version = "1.12.0"667source = "registry+https://github.com/rust-lang/crates.io-index"693source = "registry+https://github.com/rust-lang/crates.io-index"668checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2"694checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"669695670[[package]]696[[package]]671name = "encode_unicode"697name = "encode_unicode"894dependencies = [920dependencies = [895 "proc-macro2",921 "proc-macro2",896 "quote",922 "quote",897 "syn 2.0.63",923 "syn 2.0.66",898]924]899925900[[package]]926[[package]]927 "slab",953 "slab",928]954]955956[[package]]957name = "generator-helper"958version = "0.1.0"959dependencies = [960 "age",961 "anyhow",962 "clap",963 "ed25519-dalek",964 "fleet-shared",965 "rand",966]929967930[[package]]968[[package]]931name = "generic-array"969name = "generic-array"1033 "serde",1071 "serde",1034 "serde_derive",1072 "serde_derive",1035 "thiserror",1073 "thiserror",1036 "toml 0.8.12",1074 "toml 0.8.13",1037 "unic-langid",1075 "unic-langid",1038]1076]103910771075 "proc-macro2",1113 "proc-macro2",1076 "quote",1114 "quote",1077 "strsim",1115 "strsim",1078 "syn 2.0.63",1116 "syn 2.0.66",1079 "unic-langid",1117 "unic-langid",1080]1118]108111191089 "i18n-config",1127 "i18n-config",1090 "proc-macro2",1128 "proc-macro2",1091 "quote",1129 "quote",1092 "syn 2.0.63",1130 "syn 2.0.66",1093]1131]109411321095[[package]]1133[[package]]115111891152[[package]]1190[[package]]1153name = "instant"1191name = "instant"1154version = "0.1.12"1192version = "0.1.13"1155source = "registry+https://github.com/rust-lang/crates.io-index"1193source = "registry+https://github.com/rust-lang/crates.io-index"1156checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"1194checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"1157dependencies = [1195dependencies = [1158 "cfg-if",1196 "cfg-if",1159]1197]124112791242[[package]]1280[[package]]1243name = "libc"1281name = "libc"1244version = "0.2.154"1282version = "0.2.155"1245source = "registry+https://github.com/rust-lang/crates.io-index"1283source = "registry+https://github.com/rust-lang/crates.io-index"1246checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346"1284checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"124712851248[[package]]1286[[package]]1249name = "libm"1287name = "libm"125312911254[[package]]1292[[package]]1255name = "libmimalloc-sys"1293name = "libmimalloc-sys"1256version = "0.1.37"1294version = "0.1.38"1257source = "registry+https://github.com/rust-lang/crates.io-index"1295source = "registry+https://github.com/rust-lang/crates.io-index"1258checksum = "81eb4061c0582dedea1cbc7aff2240300dd6982e0239d1c99e65c1dbf4a30ba7"1296checksum = "0e7bb23d733dfcc8af652a78b7bf232f0e967710d044732185e561e47c0336b6"1259dependencies = [1297dependencies = [1260 "cc",1298 "cc",1261 "libc",1299 "libc",126913071270[[package]]1308[[package]]1271name = "linux-raw-sys"1309name = "linux-raw-sys"1272version = "0.4.13"1310version = "0.4.14"1273source = "registry+https://github.com/rust-lang/crates.io-index"1311source = "registry+https://github.com/rust-lang/crates.io-index"1274checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"1312checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"127513131276[[package]]1314[[package]]1277name = "lock_api"1315name = "lock_api"132113591322[[package]]1360[[package]]1323name = "mimalloc"1361name = "mimalloc"1324version = "0.1.41"1362version = "0.1.42"1325source = "registry+https://github.com/rust-lang/crates.io-index"1363source = "registry+https://github.com/rust-lang/crates.io-index"1326checksum = "9f41a2280ded0da56c8cf898babb86e8f10651a34adcfff190ae9a1159c6908d"1364checksum = "e9186d86b79b52f4a77af65604b51225e8db1d6ee7e3f41aec1e40829c71a176"1327dependencies = [1365dependencies = [1328 "libmimalloc-sys",1366 "libmimalloc-sys",1329]1367]133613741337[[package]]1375[[package]]1338name = "miniz_oxide"1376name = "miniz_oxide"1339version = "0.7.2"1377version = "0.7.3"1340source = "registry+https://github.com/rust-lang/crates.io-index"1378source = "registry+https://github.com/rust-lang/crates.io-index"1341checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"1379checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae"1342dependencies = [1380dependencies = [1343 "adler",1381 "adler",1344]1382]153315711534[[package]]1572[[package]]1535name = "parking_lot"1573name = "parking_lot"1536version = "0.12.2"1574version = "0.12.3"1537source = "registry+https://github.com/rust-lang/crates.io-index"1575source = "registry+https://github.com/rust-lang/crates.io-index"1538checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb"1576checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"1539dependencies = [1577dependencies = [1540 "lock_api",1578 "lock_api",1541 "parking_lot_core",1579 "parking_lot_core",1608dependencies = [1646dependencies = [1609 "proc-macro2",1647 "proc-macro2",1610 "quote",1648 "quote",1611 "syn 2.0.63",1649 "syn 2.0.66",1612]1650]161316511614[[package]]1652[[package]]171717551718[[package]]1756[[package]]1719name = "proc-macro2"1757name = "proc-macro2"1720version = "1.0.82"1758version = "1.0.84"1721source = "registry+https://github.com/rust-lang/crates.io-index"1759source = "registry+https://github.com/rust-lang/crates.io-index"1722checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"1760checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6"1723dependencies = [1761dependencies = [1724 "unicode-ident",1762 "unicode-ident",1725]1763]1911 "proc-macro2",1949 "proc-macro2",1912 "quote",1950 "quote",1913 "rust-embed-utils",1951 "rust-embed-utils",1914 "syn 2.0.63",1952 "syn 2.0.66",1915 "walkdir",1953 "walkdir",1916]1954]19171955204120792042[[package]]2080[[package]]2043name = "serde"2081name = "serde"2044version = "1.0.202"2082version = "1.0.203"2045source = "registry+https://github.com/rust-lang/crates.io-index"2083source = "registry+https://github.com/rust-lang/crates.io-index"2046checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395"2084checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"2047dependencies = [2085dependencies = [2048 "serde_derive",2086 "serde_derive",2049]2087]205920972060[[package]]2098[[package]]2061name = "serde_derive"2099name = "serde_derive"2062version = "1.0.202"2100version = "1.0.203"2063source = "registry+https://github.com/rust-lang/crates.io-index"2101source = "registry+https://github.com/rust-lang/crates.io-index"2064checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838"2102checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"2065dependencies = [2103dependencies = [2066 "proc-macro2",2104 "proc-macro2",2067 "quote",2105 "quote",2068 "syn 2.0.63",2106 "syn 2.0.66",2069]2107]207021082071[[package]]2109[[package]]208121192082[[package]]2120[[package]]2083name = "serde_spanned"2121name = "serde_spanned"2084version = "0.6.5"2122version = "0.6.6"2085source = "registry+https://github.com/rust-lang/crates.io-index"2123source = "registry+https://github.com/rust-lang/crates.io-index"2086checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"2124checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"2087dependencies = [2125dependencies = [2088 "serde",2126 "serde",2089]2127]224522832246[[package]]2284[[package]]2247name = "syn"2285name = "syn"2248version = "2.0.63"2286version = "2.0.66"2249source = "registry+https://github.com/rust-lang/crates.io-index"2287source = "registry+https://github.com/rust-lang/crates.io-index"2250checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704"2288checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"2251dependencies = [2289dependencies = [2252 "proc-macro2",2290 "proc-macro2",2253 "quote",2291 "quote",230823462309[[package]]2347[[package]]2310name = "thiserror"2348name = "thiserror"2311version = "1.0.60"2349version = "1.0.61"2312source = "registry+https://github.com/rust-lang/crates.io-index"2350source = "registry+https://github.com/rust-lang/crates.io-index"2313checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"2351checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"2314dependencies = [2352dependencies = [2315 "thiserror-impl",2353 "thiserror-impl",2316]2354]231723552318[[package]]2356[[package]]2319name = "thiserror-impl"2357name = "thiserror-impl"2320version = "1.0.60"2358version = "1.0.61"2321source = "registry+https://github.com/rust-lang/crates.io-index"2359source = "registry+https://github.com/rust-lang/crates.io-index"2322checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"2360checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"2323dependencies = [2361dependencies = [2324 "proc-macro2",2362 "proc-macro2",2325 "quote",2363 "quote",2326 "syn 2.0.63",2364 "syn 2.0.66",2327]2365]232823662329[[package]]2367[[package]]2401dependencies = [2439dependencies = [2402 "proc-macro2",2440 "proc-macro2",2403 "quote",2441 "quote",2404 "syn 2.0.63",2442 "syn 2.0.66",2405]2443]240624442407[[package]]2445[[package]]243824762439[[package]]2477[[package]]2440name = "toml"2478name = "toml"2441version = "0.8.12"2479version = "0.8.13"2442source = "registry+https://github.com/rust-lang/crates.io-index"2480source = "registry+https://github.com/rust-lang/crates.io-index"2443checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3"2481checksum = "a4e43f8cc456c9704c851ae29c67e17ef65d2c30017c17a9765b89c382dc8bba"2444dependencies = [2482dependencies = [2445 "serde",2483 "serde",2446 "serde_spanned",2484 "serde_spanned",245024882451[[package]]2489[[package]]2452name = "toml_datetime"2490name = "toml_datetime"2453version = "0.6.5"2491version = "0.6.6"2454source = "registry+https://github.com/rust-lang/crates.io-index"2492source = "registry+https://github.com/rust-lang/crates.io-index"2455checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"2493checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"2456dependencies = [2494dependencies = [2457 "serde",2495 "serde",2458]2496]245924972460[[package]]2498[[package]]2461name = "toml_edit"2499name = "toml_edit"2462version = "0.22.12"2500version = "0.22.13"2463source = "registry+https://github.com/rust-lang/crates.io-index"2501source = "registry+https://github.com/rust-lang/crates.io-index"2464checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef"2502checksum = "c127785850e8c20836d49732ae6abfa47616e60bf9d9f57c43c250361a9db96c"2465dependencies = [2503dependencies = [2466 "indexmap",2504 "indexmap",2467 "serde",2505 "serde",2489dependencies = [2527dependencies = [2490 "proc-macro2",2528 "proc-macro2",2491 "quote",2529 "quote",2492 "syn 2.0.63",2530 "syn 2.0.66",2493]2531]249425322495[[package]]2533[[package]]2708 "once_cell",2746 "once_cell",2709 "proc-macro2",2747 "proc-macro2",2710 "quote",2748 "quote",2711 "syn 2.0.63",2749 "syn 2.0.66",2712 "wasm-bindgen-shared",2750 "wasm-bindgen-shared",2713]2751]271427522730dependencies = [2768dependencies = [2731 "proc-macro2",2769 "proc-macro2",2732 "quote",2770 "quote",2733 "syn 2.0.63",2771 "syn 2.0.66",2734 "wasm-bindgen-backend",2772 "wasm-bindgen-backend",2735 "wasm-bindgen-shared",2773 "wasm-bindgen-shared",2736]2774]2964dependencies = [3002dependencies = [2965 "proc-macro2",3003 "proc-macro2",2966 "quote",3004 "quote",2967 "syn 2.0.63",3005 "syn 2.0.66",2968]3006]29693007Cargo.tomldiffbeforeafterboth6[workspace.dependencies]6[workspace.dependencies]7nixlike = { path = "./crates/nixlike" }7nixlike = { path = "./crates/nixlike" }8better-command = { path = "./crates/better-command" }8better-command = { path = "./crates/better-command" }9bifrostlink = "0.1.0"10uuid = { version = "1.7.0", features = ["v4"] }9fleet-shared = { path = "./crates/fleet-shared" }11tokio = { version = "1.36.0", features = ["fs", "rt", "macros", "sync", "time", "rt-multi-thread"] }10tokio = { version = "1.36.0", features = [11 "fs",12 "rt",13 "macros",14 "sync",15 "time",16 "rt-multi-thread",17] }18# Using fixed version for rust on stable nixos branches.19clap = { version = ">=4.4, <4.5", features = [20 "derive",21 "env",22 "wrap_help",23 "unicode",24] }25age = { version = "0.10", features = ["ssh"] }26anyhow = "1.0"27tracing = "0.1"28tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] }29serde = { version = "1.0", features = ["derive"] }30serde_json = "1.0"31tempfile = "3.10"12fleet-shared = { path = "./crates/fleet-shared" }32nix = {version = "0.27.1", features = ["user", "fs"]}1333cmds/fleet/Cargo.tomldiffbeforeafterboth9nixlike.workspace = true9nixlike.workspace = true10better-command.workspace = true10better-command.workspace = true11tokio.workspace = true11tokio.workspace = true12anyhow = "1.0"12clap.workspace = true13serde = { version = "1.0", features = ["derive"] }13age = { workspace = true, features = ["armor"] }14anyhow.workspace = true15tracing.workspace = true16tracing-subscriber.workspace = true17serde.workspace = true14serde_json = "1.0"18serde_json.workspace = true19tempfile.workspace = true15time = { version = "0.3", features = ["serde"] }20time = { version = "0.3", features = ["serde"] }16tempfile = "3.10"17once_cell = "1.19"21once_cell = "1.19"18hostname = "0.3"22hostname = "0.3"19age-core = "0.10"23age-core = "0.10"20peg = "0.8"24peg = "0.8"21age = { version = "0.10", features = ["ssh", "armor"] }22base64 = "0.22.1"25base64 = "0.22.1"23chrono = { version = "0.4", features = ["serde"] }26chrono = { version = "0.4", features = ["serde"] }24# Using fixed version for rust on stable nixos branches.25clap = { version = ">=4.4, <4.5", features = [26 "derive",27 "env",28 "wrap_help",29 "unicode",30] }31tracing = "0.1"32tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] }33tokio-util = { version = "0.7", features = ["codec"] }27tokio-util = { version = "0.7", features = ["codec"] }34async-trait = "0.1"28async-trait = "0.1"35futures = "0.3"29futures = "0.3"40 "supports-color",34 "supports-color",41 "supports-colors",35 "supports-colors",42] }36] }43r2d2 = "0.8.10"44abort-on-drop = "0.2"37abort-on-drop = "0.2"45unindent = "0.2"46regex = "1.10"38regex = "1.10"47openssh = "0.10"39openssh = "0.10"48crossterm = { version = "0.27.0", features = ["use-dev-tty"] }40crossterm = { version = "0.27.0", features = ["use-dev-tty"] }51tracing-indicatif = { version = "0.3", optional = true }43tracing-indicatif = { version = "0.3", optional = true }52human-repr = { version = "1.1", optional = true }44human-repr = { version = "1.1", optional = true }53indicatif = { version = "0.17", optional = true }45indicatif = { version = "0.17", optional = true }46nix-eval = { version = "0.1.0", path = "../../crates/nix-eval" }544755[features]48[features]56# Not quite stable49# Not quite stable57indicatif = [50indicatif = [58 "tracing-indicatif",51 "dep:tracing-indicatif",59 "dep:indicatif",52 "dep:indicatif",60 "human-repr",53 "dep:human-repr",61 "better-command/indicatif",54 "better-command/indicatif",62]55]6356cmds/fleet/src/better_nix_eval.rsdiffbeforeafterboth12use better_command::{ClonableHandler, Handler, NixHandler, NoopHandler};12use better_command::{ClonableHandler, Handler, NixHandler, NoopHandler};13use futures::StreamExt;13use futures::StreamExt;14use itertools::Itertools;14use itertools::Itertools;15use r2d2::{Pool, PooledConnection};16use serde::de::DeserializeOwned;15use serde::de::DeserializeOwned;17use serde::{Deserialize, Serialize};16use serde::{Deserialize, Serialize};18use tokio::io::AsyncWriteExt;17use tokio::io::AsyncWriteExt;19use tokio::process::{ChildStderr, ChildStdin, ChildStdout, Command};18use tokio::process::{ChildStderr, ChildStdin, ChildStdout, Command};20use tokio::select;19use tokio::select;21use tokio::sync::{mpsc, oneshot, Mutex};20use tokio::sync::{mpsc, oneshot, Mutex};22use tokio_util::codec::{FramedRead, LinesCodec};23use tracing::{debug, error, warn, Level};21use tracing::{debug, error, warn, Level};2425const REPL_DELIMITER: &str = "\"FLEET_MAGIC_REPL_DELIMITER\"";2627pub struct NixSessionInner {28 full_delimiter: String,29 nix_handler: ClonableHandler<NixHandler>,30 out: OutputHandler,31 stdin: ChildStdin,32 string_wrapping: (String, String),33 number_wrapping: (String, String),3435 executing_command: Arc<Mutex<()>>,3637 next_id: u32,38 free_list: Vec<u32>,39}40const TRAIN_STRING: &str = "\"TRAIN_STRING\"";41const TRAIN_NUMBER: &str = "13141516";4243#[must_use]44struct ErrorCollector<'i, H> {45 collected: Vec<String>,46 inner: &'i mut H,47}48impl<'i, H> ErrorCollector<'i, H> {49 fn new(inner: &'i mut H) -> Self {50 Self {51 collected: vec![],52 inner,53 }54 }55}56impl<H> ErrorCollector<'_, H> {57 fn handle_line_inner(&mut self, msg: &str) -> bool {58 let Some(msg) = msg.strip_prefix("@nix ") else {59 return false;60 };61 #[derive(Deserialize)]62 struct ErrorAction {63 action: String,64 level: u32,65 msg: String,66 }67 let Ok(act) = serde_json::from_str::<ErrorAction>(msg) else {68 return false;69 };70 if act.action != "msg" || act.level != 0 {71 return false;72 }73 self.collected.push(act.msg);74 true75 }76 fn finish(self) -> Result<()> {77 // fn dedent(s: String) -> String {78 // s.split('\n').filter(|s| !s.trim().is_empty()).map(|v| v.)79 // }80 if !self.collected.is_empty() {81 bail!(82 "{}",83 self.collected84 .iter()85 .map(|v| {86 if let Some(f) = v.strip_prefix("\u{1b}[31;1merror:\u{1b}[0m ") {87 let v = unindent::unindent(f.trim_start());88 v.trim().to_owned()89 } else {90 v.to_owned()91 }92 })93 .join("\n")94 );95 }96 Ok(())97 }98 fn flush(self) {99 for line in self.collected {100 warn!("{line}");101 }102 }103}104impl<H: Handler> Handler for ErrorCollector<'_, H> {105 fn handle_line(&mut self, e: &str) {106 if self.handle_line_inner(e) {107 return;108 }109 self.inner.handle_line(e)110 }111}112113enum OutputLine {114 Out(String),115 Err(String),116}117struct OutputHandler {118 rx: mpsc::Receiver<OutputLine>,119 _cancel_handle: oneshot::Receiver<()>,120}121impl OutputHandler {122 fn new(out: ChildStdout, err: ChildStderr) -> Self {123 let mut out = FramedRead::new(out, LinesCodec::new());124 let mut err = FramedRead::new(err, LinesCodec::new());125 let (tx, rx) = mpsc::channel(20);126 let (mut cancelled, _cancel_handle) = oneshot::channel();127 tokio::spawn(async move {128 loop {129 select! {130 // We should receive errors earlier than synchronization131 biased;132 e = err.next() => {133 let Some(Ok(e)) = e else {134 if e.is_some() {135 error!("bad repl stderr: {e:?}");136 }137 continue;138 };139 let _ = tx.send(OutputLine::Err(e)).await;140 }141 o = out.next() => {142 let Some(Ok(o)) = o else {143 if o.is_some() {144 error!("bad repl stdout: {o:?}");145 }146 continue;147 };148 let _ = tx.send(OutputLine::Out(o)).await;149 }150 // Reader doesn't care about stdout, as this is cancelled.151 // Error still might be useful, to process leftover span closures?152 _ = cancelled.closed() => {153 break;154 }155 }156 }157 });158 Self { rx, _cancel_handle }159 }160 async fn next(&mut self) -> Option<OutputLine> {161 self.rx.recv().await162 }163}164165struct WarnHandler;166impl Handler for WarnHandler {167 fn handle_line(&mut self, e: &str) {168 warn!(target: "nix", "{e}")169 }170}171172impl NixSessionInner {173 async fn new(flake: &OsStr, extra_args: impl IntoIterator<Item = &OsStr>) -> Result<Self> {174 let mut cmd = Command::new("nix");175 cmd.arg("repl")176 .arg(flake)177 .arg("--log-format")178 .arg("internal-json");179 for arg in extra_args {180 cmd.arg(arg);181 }182 cmd.stdin(Stdio::piped());183 cmd.stdout(Stdio::piped());184 cmd.stderr(Stdio::piped());185 let cmd = cmd.spawn()?;186 let stdout = cmd.stdout.unwrap();187 let stderr = cmd.stderr.unwrap();188 let mut out = OutputHandler::new(stdout, stderr);189 let mut stdin = cmd.stdin.unwrap();190 // Standard repl hello doesn't work with internal-json logger191 stdin.write_all(REPL_DELIMITER.as_bytes()).await?;192 stdin.write_all(b"\n").await?;193 stdin.flush().await?;194 let nix_handler = NixHandler::default();195 let mut full_delimiter = None;196 let mut errors = vec![];197 while let Some(line) = out.next().await {198 let line = match line {199 OutputLine::Out(o) => o,200 OutputLine::Err(_e) => {201 // Handle startup errors, but skip repl hello?202 errors.push(_e);203 continue;204 }205 };206 if line.contains(REPL_DELIMITER) {207 debug!("discovered repl delimiter with added colors: {line}");208 full_delimiter = Some(line.to_owned());209 break;210 }211 }212 let Some(full_delimiter) = full_delimiter else {213 for e in errors {214 error!("{e}");215 }216 bail!("failed to discover delimiter");217 };218 let mut res = Self {219 full_delimiter,220 nix_handler: ClonableHandler::new(nix_handler),221 out,222 stdin,223 string_wrapping: Default::default(),224 number_wrapping: Default::default(),225226 executing_command: Arc::new(Mutex::new(())),227228 next_id: 0,229 free_list: vec![],230 };231 res.train().await?;232 Ok(res)233 }234 async fn train(&mut self) -> Result<()> {235 {236 let full_string = self237 .execute_expression_raw(TRAIN_STRING, &mut NoopHandler)238 .await?;239 let string_offset = full_string.find(TRAIN_STRING).expect("contained");240 let string_prefix = &full_string[..string_offset];241 let string_suffix = &full_string[string_offset + TRAIN_STRING.len()..];242 self.string_wrapping = (string_prefix.to_owned(), string_suffix.to_owned());243 }244 {245 let full_number = self246 .execute_expression_raw(TRAIN_NUMBER, &mut NoopHandler)247 .await?;248 let number_offset = full_number.find(TRAIN_NUMBER).expect("contained");249 let number_prefix = &full_number[..number_offset];250 let number_suffix = &full_number[number_offset + TRAIN_NUMBER.len()..];251 self.number_wrapping = (number_prefix.to_owned(), number_suffix.to_owned());252 }253 Ok(())254 }255 async fn send_command(&mut self, cmd: impl AsRef<[u8]>) -> Result<()> {256 if tracing::enabled!(Level::DEBUG) && cmd.as_ref() != REPL_DELIMITER.as_bytes() {257 let cmd_str = String::from_utf8_lossy(cmd.as_ref());258 tracing::debug!("{cmd_str}");259 };260 self.stdin.write_all(cmd.as_ref()).await?;261 self.stdin.write_all(b"\n").await?;262 Ok(())263 }264 async fn read_until_delimiter(&mut self, err_handler: &mut dyn Handler) -> Result<String> {265 let mut out = String::new();266 while let Some(line) = self.out.next().await {267 let line = match line {268 OutputLine::Out(out) => out,269 OutputLine::Err(err) => {270 err_handler.handle_line(&err);271 continue;272 }273 };274 if line == self.full_delimiter {275 return Ok(out);276 }277 if !out.is_empty() {278 out.push('\n');279 }280 out.push_str(&line);281 }282 bail!("didn't reached delimiter");283 }284 async fn execute_expression_number(&mut self, expr: impl AsRef<[u8]>) -> Result<u64> {285 let num = self.number_wrapping.clone();286 let n = self.execute_expression_wrapping(expr, &num).await?;287 Ok(n.parse::<u64>()?)288 }289 async fn execute_expression_string(&mut self, expr: impl AsRef<[u8]>) -> Result<String> {290 let num = self.string_wrapping.clone();291 let n = self.execute_expression_wrapping(expr, &num).await?;292 let str: String = serde_json::from_str(&n)?;293 Ok(str)294 }295 async fn execute_expression_to_json<V: DeserializeOwned>(296 &mut self,297 expr: impl AsRef<[u8]>,298 ) -> Result<V> {299 let mut fexpr = b"builtins.toJSON (".to_vec();300 fexpr.extend_from_slice(expr.as_ref());301 fexpr.push(b')');302 let v = self303 .execute_expression_string(fexpr)304 .await305 .context("string expression")?;306 serde_json::from_str(&v).context("json parse")307 }308 async fn execute_expression_wrapping(309 &mut self,310 expr: impl AsRef<[u8]>,311 wrapping: &(String, String),312 ) -> Result<String> {313 let mut nix_handler = self.nix_handler.clone();314 let mut collected = ErrorCollector::new(&mut nix_handler);315 let res = self.execute_expression_raw(expr, &mut collected).await?;316 if res.is_empty() {317 collected.finish()?;318 bail!("expected expression, got nothing")319 } else {320 collected.flush()321 };322 let Some(res) = res.strip_prefix(&wrapping.0) else {323 bail!("invalid type")324 };325 let Some(res) = res.strip_suffix(&wrapping.1) else {326 bail!("invalid type")327 };328 Ok(res.to_owned())329 }330 async fn execute_expression_empty(&mut self, expr: impl AsRef<[u8]>) -> Result<()> {331 let mut nix_handler = self.nix_handler.clone();332 let mut collected = ErrorCollector::new(&mut nix_handler);333 let v = self.execute_expression_raw(expr, &mut collected).await?;334 collected.finish()?;335 ensure!(v.is_empty(), "unexpected expression result");336 Ok(())337 }338 async fn execute_expression_raw(339 &mut self,340 expr: impl AsRef<[u8]>,341 err_handler: &mut dyn Handler,342 ) -> Result<String> {343 // Prevent two commands from being executed in parallel, messing with each other.344 let _lock = self.executing_command.clone();345 let _guard = _lock.lock().await;346347 self.send_command(expr).await?;348 // It will be echoed349 self.send_command(REPL_DELIMITER).await?;350 self.read_until_delimiter(err_handler).await351 }352 async fn execute_assign(&mut self, expr: impl AsRef<str>) -> Result<u32> {353 let id = self.allocate_id();354 self.execute_expression_empty(format!("sess_field_{id} = {}", expr.as_ref()))355 .await?;356 Ok(id)357 }358359 /// Id should be immediately used360 fn allocate_id(&mut self) -> u32 {361 if let Some(free) = self.free_list.pop() {362 free363 } else {364 let v = self.next_id;365 self.next_id += 1;366 v367 }368 }369 // Nix has no way to deallocate variable, yet GC will correct everything not reachable.370 // async fn free_id(&mut self, id: u32) -> Result<()> {371 // self.execute_expression_empty(format!("sess_field_{id} = null"))372 // .await?;373 // self.free_list.push(id);374 // Ok(())375 // }376}377378#[derive(Clone)]379pub struct NixSession(Arc<tokio::sync::Mutex<PooledConnection<NixSessionPoolInner>>>);380381#[derive(Clone)]382pub struct NixExprBuilder {383 out: String,384 used_fields: Vec<Field>,385}386impl NixExprBuilder {387 pub fn object() -> Self {388 NixExprBuilder {389 out: "{ ".to_owned(),390 used_fields: Vec::new(),391 }392 }393 pub fn string(s: &str) -> Self {394 NixExprBuilder {395 out: nixlike::serialize(s)396 .expect("no problems with serializing_string")397 .trim_end()398 .to_owned(),399 used_fields: Vec::new(),400 }401 }402 pub fn serialized(v: impl Serialize) -> Self {403 let serialized = nixlike::serialize(v).expect("invalid value for apply");404 Self {405 out: serialized.trim_end().to_owned(),406 used_fields: Vec::new(),407 }408 }409 pub fn field(f: Field) -> Self {410 Self {411 out: format!("sess_field_{}", f.0.value.expect("no value")),412 used_fields: vec![f],413 }414 }415 pub fn end_obj(&mut self) {416 self.out.push('}');417 }418 pub fn obj_key(&mut self, name: Self, value: Self) {419 self.out.push_str(r#""${"#);420 self.extend(name);421 self.out.push_str(r#"}" = "#);422 self.extend(value);423 self.out.push_str("; ");424 }425426 pub fn extend(&mut self, e: Self) {427 self.out.push_str(&e.out);428 self.used_fields.extend(e.used_fields);429 }430431 #[allow(dead_code)]432 pub fn session(&self) -> NixSession {433 let mut session = None;434 for ele in &self.used_fields {435 if session.is_none() {436 session = Some(ele.0.session.clone());437 continue;438 }439 let session = &session.as_ref().expect("checked").0;440 let ele_sess = &ele.0.session.0;441 assert!(442 Arc::ptr_eq(session, ele_sess),443 "can't mix fields from different session"444 );445 }446 session.expect("expr without fields used")447 }448 #[allow(dead_code)]449 pub fn index_attr(&mut self, s: &str) {450 let escaped = nixlike::serialize(s).expect("string");451 self.out.push('.');452 self.out.push_str(escaped.trim_end());453 }454}455456#[macro_export]457macro_rules! nix_expr_inner {458 //(@munch_object FIXME: value should be arbitrary nix_expr_inner input... Time to write proc-macro?459 (@obj($o:ident) $field:ident, $($tt:tt)*) => {{460 $o.obj_key(461 NixExprBuilder::string(stringify!($field)),462 NixExprBuilder::field($field),463 );464 nix_expr_inner!(@obj($o) $($tt)*);465 }};466 (@obj($o:ident) $field:ident: $v:block, $($tt:tt)*) => {{467 $o.obj_key(468 NixExprBuilder::string(stringify!($field)),469 NixExprBuilder::serialized(&$v),470 );471 nix_expr_inner!(@obj($o) $($tt)*);472 }};473 (@obj($o:ident)) => {{}};474 (Obj { $($tt:tt)* }) => {{475 use $crate::{better_nix_eval::NixExprBuilder, nix_expr_inner};476 let mut out = NixExprBuilder::object();477 nix_expr_inner!(@obj(out) $($tt)*);478 out.end_obj();479 out480 }};481 (@field($o:ident) . $var:ident $($tt:tt)*) => {{482 $o.index_attr(stringify!($var));483 nix_expr_inner!(@field($o) $($tt)*);484 }};485 (@field($o:ident) [{ $v:expr }] $($tt:tt)*) => {{486 $o.push(Index::attr(&$v));487 nix_expr_inner!(@o($o) $($tt)*);488 }};489 (@field($o:ident) [ $($var:tt)+ ] $($tt:tt)*) => {{490 $o.push(Index::Expr($crate::nix_expr_inner!($($var)+)));491 nix_expr_inner!(@o($o) $($tt)*);492 }};493 (@field($o:ident) ($($var:tt)*) $($tt:tt)*) => {494 $o.push(Index::ExprApply($crate::nix_expr_inner!($($var)+)));495 nix_expr_inner!(@o($o) $($tt)*);496 };497 (@field($o:ident)) => {};498 ($field:ident $($tt:tt)*) => {{499 use $crate::{better_nix_eval::NixExprBuilder, nix_expr_inner};500 #[allow(unused_mut, reason = "might be used if indexed")]501 let mut out = NixExprBuilder::field($field.clone());502 nix_expr_inner!(@field(out) $($tt)*);503 out504 }};505 ($v:literal) => {{506 use $crate::better_nix_eval::NixExprBuilder;507 NixExprBuilder::string($v)508 }};509 ({$v:expr}) => {{510 use $crate::better_nix_eval::NixExprBuilder;511 NixExprBuilder::serialized(&$v)512 }}513}514#[macro_export]515macro_rules! nix_expr {516 ($($tt:tt)+) => {{517 use $crate::{better_nix_eval::{NixExprBuilder, Field}, nix_expr_inner};518 let expr = nix_expr_inner!($($tt)+);519 Field::new(expr.session(), expr.out)520 }};521}522523#[macro_export]524macro_rules! nix_go {525 (@o($o:ident) . $var:ident $($tt:tt)*) => {{526 $o.push(Index::attr(stringify!($var)));527 nix_go!(@o($o) $($tt)*);528 }};529 (@o($o:ident) [{ $v:expr }] $($tt:tt)*) => {{530 $o.push(Index::attr(&$v));531 nix_go!(@o($o) $($tt)*);532 }};533 (@o($o:ident) [ $($var:tt)+ ] $($tt:tt)*) => {{534 $o.push(Index::Expr($crate::nix_expr_inner!($($var)+)));535 nix_go!(@o($o) $($tt)*);536 }};537 (@o($o:ident) ($($var:tt)*) $($tt:tt)*) => {538 $o.push(Index::ExprApply($crate::nix_expr_inner!($($var)+)));539 nix_go!(@o($o) $($tt)*);540 };541 (@o($o:ident) | $($var:tt)*) => {542 $o.push(Index::Pipe($crate::nix_expr_inner!($($var)+)));543 };544 (@o($o:ident)) => {};545 ($field:ident $($tt:tt)+) => {{546 use $crate::{nix_go, better_nix_eval::Index};547 let field = $field.clone();548 let mut out = vec![];549 nix_go!(@o(out) $($tt)*);550 field.select(out).await?551 }}552}553#[macro_export]554macro_rules! nix_go_json {555 ($($tt:tt)*) => {{556 $crate::nix_go!($($tt)*).as_json().await?557 }};558}559560#[derive(Clone)]561pub enum Index {562 Var(String),563 String(String),564 #[allow(dead_code)]565 Apply(String),566 #[allow(dead_code)]567 Expr(NixExprBuilder),568 ExprApply(NixExprBuilder),569 Pipe(NixExprBuilder),570}571impl Index {572 pub fn var(v: impl AsRef<str>) -> Self {573 let v = v.as_ref();574 assert!(575 !(v.contains('.') | v.contains(' ')),576 "bad variable name: {v}"577 );578 Self::Var(v.to_owned())579 }580 pub fn attr(v: impl AsRef<str>) -> Self {581 Self::String(v.as_ref().to_owned())582 }583 #[allow(dead_code)]584 pub fn apply(v: impl Serialize) -> Self {585 let serialized = nixlike::serialize(v).expect("invalid value for apply");586 Self::Apply(serialized.trim_end().to_owned())587 }588}589impl Display for Index {590 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {591 match self {592 Index::Var(v) => {593 write!(f, "{v}")594 }595 Index::String(k) => {596 let v = nixlike::format_identifier(k.as_str());597 write!(f, ".{v}")598 }599 Index::Apply(o) => {600 write!(f, "<apply>({o})")601 }602 Index::Expr(e) => {603 write!(f, "[{}]", e.out)604 }605 Index::ExprApply(e) => {606 write!(f, "<apply>({})", e.out)607 }608 Index::Pipe(e) => {609 write!(f, "<map>({})", e.out)610 }611 }612 }613}614impl fmt::Debug for Index {615 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {616 write!(f, "{self}")617 }618}619struct PathDisplay<'i>(&'i [Index]);620impl Display for PathDisplay<'_> {621 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {622 for i in self.0 {623 write!(f, "{i}")?;624 }625 Ok(())626 }627}628struct FieldInner {629 full_path: Option<Vec<Index>>,630 session: NixSession,631 value: Option<u32>,632}633fn context(op: &str, full_path: Option<&[Index]>, query: &str) -> String {634 if let Some(full_path) = &full_path {635 format!("on {op}, full path: {}", PathDisplay(full_path))636 } else {637 format!("query: {query:?}")638 }639}640#[derive(Clone)]641pub struct Field(Arc<FieldInner>);642impl Field {643 fn root(session: NixSession) -> Self {644 Self(Arc::new(FieldInner {645 full_path: Some(vec![]),646 session,647 value: None,648 }))649 }650 async fn new(session: NixSession, query: &str) -> Result<Self> {651 let vid = session652 .0653 .lock()654 .await655 .execute_assign(query)656 .await657 .with_context(|| context("new root", None, query))?;658 Ok(Self(Arc::new(FieldInner {659 full_path: None,660 session,661 value: Some(vid),662 })))663 }664 pub async fn field(session: NixSession, field: &str) -> Result<Self> {665 Self::root(session).select([Index::var(field)]).await666 }667 pub async fn select<'a>(&self, name: impl IntoIterator<Item = Index>) -> Result<Self> {668 let mut used_fields = Vec::new();669 let mut name = name.into_iter();670671 let mut full_path = self.0.full_path.clone();672 let mut query = if let Some(id) = self.0.value {673 format!("sess_field_{id}")674 } else {675 let first = name.next();676 if let Some(Index::Var(i)) = first {677 if let Some(full_path) = &mut full_path {678 full_path.push(Index::Var(i.clone()));679 }680 i.clone()681 } else {682 panic!("first path item should be variable, got {first:?}")683 }684 };685 for v in name {686 if let Some(full_path) = &mut full_path {687 full_path.push(v.clone());688 }689 match v {690 Index::Var(_) => panic!("var item may only be first"),691 Index::String(s) => {692 let escaped = nixlike::serialize(s)?;693 query.push('.');694 query.push_str(escaped.trim());695 }696 Index::Apply(a) => {697 // In cases like `a {}.b` first `{}.b` will be evaluated, so `a {}` should be encased in `()`698 query = format!("({query} {a})");699 }700 Index::Expr(e) => {701 let index = Field::new(self.0.session.clone(), &e.out).await?;702 used_fields.push(index.clone());703 query.push('.');704 let index = format!("${{sess_field_{}}}", index.0.value.expect("value"));705 query.push_str(&index);706 }707 Index::ExprApply(e) => {708 let index = Field::new(self.0.session.clone(), &e.out).await?;709 used_fields.push(index.clone());710 query.push(' ');711 let index = format!("sess_field_{}", index.0.value.expect("value"));712 query.push_str(&index);713 query = format!("({query})");714 }715 Index::Pipe(v) => {716 let index = Field::new(self.0.session.clone(), &v.out).await?;717 used_fields.push(index.clone());718 let index = format!("sess_field_{}", index.0.value.expect("value"));719 query = format!("({index} {query})");720 }721 }722 }723724 let vid = self725 .0726 .session727 .0728 .lock()729 .await730 .execute_assign(&query)731 .await732 .with_context(|| {733 if let Some(full_path) = &full_path {734 format!("full path: {}", PathDisplay(full_path))735 } else {736 format!("query: {query:?}")737 }738 })?;739 Ok(Self(Arc::new(FieldInner {740 full_path,741 session: self.0.session.clone(),742 value: Some(vid),743 })))744 }745 pub async fn as_json<V: DeserializeOwned>(&self) -> Result<V> {746 let id = self.0.value.expect("can't serialize root field");747 let query = format!("sess_field_{id}");748 self.0749 .session750 .0751 .lock()752 .await753 .execute_expression_to_json(&query)754 .await755 .with_context(|| context("as_json", self.0.full_path.as_deref(), &query))756 }757 #[allow(dead_code)]758 pub async fn has_field(&self, name: &str) -> Result<bool> {759 let id = self.0.value.expect("can't list root fields");760 let key = nixlike::escape_string(name);761 let query = format!("sess_field_{id} ? {key}");762 self.0763 .session764 .0765 .lock()766 .await767 .execute_expression_to_json(&query)768 .await769 .with_context(|| context("has_field", self.0.full_path.as_deref(), &query))770 }771 pub async fn list_fields(&self) -> Result<Vec<String>> {772 let id = self.0.value.expect("can't list root fields");773 let query = format!("builtins.attrNames sess_field_{id}");774 self.0775 .session776 .0777 .lock()778 .await779 .execute_expression_to_json(&query)780 .await781 .with_context(|| context("list field", self.0.full_path.as_deref(), &query))782 }783 pub async fn type_of(&self) -> Result<String> {784 let id = self.0.value.expect("can't list root fields");785 let query = format!("builtins.typeOf sess_field_{id}");786 self.0787 .session788 .0789 .lock()790 .await791 .execute_expression_to_json(&query)792 .await793 .with_context(|| context("type_of", self.0.full_path.as_deref(), &query))794 }795 #[allow(dead_code)]796 pub async fn import(&self) -> Result<Self> {797 let import = Self::new(self.0.session.clone(), "import").await?;798 Ok(nix_go!(self | import))799 }800 pub async fn build(&self) -> Result<HashMap<String, PathBuf>> {801 let id = self.0.value.expect("can't use build on not-value");802 let query = format!(":b sess_field_{id}");803 let vid = self804 .0805 .session806 .0807 .lock()808 .await809 .execute_expression_raw(&query, &mut NixHandler::default())810 .await?;811 ensure!(812 !vid.is_empty(),813 "build failed: {}",814 context("build", self.0.full_path.as_deref(), &query),815 );816 let Some(vid) = vid.strip_prefix("This derivation produced the following outputs:\n")817 else {818 panic!("unexpected build output: {vid:?}");819 };820 let outputs = vid821 .split('\n')822 .filter(|v| !v.is_empty())823 .map(|v| v.split_once(" -> ").expect("unexpected build output"))824 .map(|(a, b)| (a.trim_start().to_owned(), PathBuf::from(b)))825 .collect();826 Ok(outputs)827 }828}829impl Drop for FieldInner {830 fn drop(&mut self) {831 if let Some(id) = self.value {832 if let Ok(mut lock) = self.session.0.try_lock() {833 lock.free_list.push(id)834 }835 // Leaked836 }837 }838}839struct NixSessionPoolInner {840 flake: OsString,841 nix_args: Vec<OsString>,842}843844#[derive(Debug)]845pub struct NixPoolError(anyhow::Error);846impl From<anyhow::Error> for NixPoolError {847 fn from(value: anyhow::Error) -> Self {848 Self(value)849 }850}851impl std::error::Error for NixPoolError {}852impl std::fmt::Display for NixPoolError {853 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {854 self.0.fmt(f)855 }856}857impl r2d2::ManageConnection for NixSessionPoolInner {858 type Connection = NixSessionInner;859 type Error = NixPoolError;860 fn connect(&self) -> std::result::Result<Self::Connection, Self::Error> {861 let _v = TOKIO_RUNTIME862 .get()863 .expect("missed tokio runtime init!")864 .enter();865 Ok(futures::executor::block_on(NixSessionInner::new(866 self.flake.as_os_str(),867 self.nix_args.iter().map(OsString::as_os_str),868 ))?)869 }870871 fn is_valid(&self, conn: &mut Self::Connection) -> std::result::Result<(), Self::Error> {872 let _v = TOKIO_RUNTIME873 .get()874 .expect("missed tokio runtime init!")875 .enter();876 let res = futures::executor::block_on(conn.execute_expression_number("2 + 2"))?;877 if res != 4 {878 return Err(anyhow!("sanity check failed").into());879 };880 Ok(())881 }882883 fn has_broken(&self, _conn: &mut Self::Connection) -> bool {884 false885 }886}887pub struct NixSessionPool(Pool<NixSessionPoolInner>);888impl NixSessionPool {889 pub async fn new(flake: OsString, nix_args: Vec<OsString>) -> Result<Self> {890 let inner = tokio::task::block_in_place(|| {891 r2d2::Builder::<NixSessionPoolInner>::new()892 .min_idle(Some(0))893 .build(NixSessionPoolInner { flake, nix_args })894 })?;895 Ok(Self(inner))896 }897 pub async fn get(&self) -> Result<NixSession> {898 let v = tokio::task::block_in_place(|| self.0.get())?;899 Ok(NixSession(Arc::new(tokio::sync::Mutex::new(v))))900 }901}902903pub static TOKIO_RUNTIME: OnceLock<tokio::runtime::Handle> = OnceLock::new();904222324cmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth445use crate::command::MyCommand;5use crate::command::MyCommand;6use crate::host::{Config, ConfigHost};6use crate::host::{Config, ConfigHost};7use crate::nix_go;8use anyhow::{anyhow, Result};7use anyhow::{anyhow, Result};9use clap::{Parser, ValueEnum};8use clap::{Parser, ValueEnum};10use itertools::Itertools as _;9use itertools::Itertools as _;10use nix_eval::nix_go;11use tokio::{task::LocalSet, time::sleep};11use tokio::{task::LocalSet, time::sleep};12use tracing::{error, field, info, info_span, warn, Instrument};12use tracing::{error, field, info, info_span, warn, Instrument};1313cmds/fleet/src/cmds/info.rsdiffbeforeafterboth1use std::collections::BTreeSet;1use std::collections::BTreeSet;223use crate::host::Config;3use crate::host::Config;4use crate::nix_go_json;5use anyhow::{ensure, Result};4use anyhow::{ensure, Result};6use clap::Parser;5use clap::Parser;6use nix_eval::nix_go_json;778#[derive(Parser)]8#[derive(Parser)]9pub struct Info {9pub struct Info {cmds/fleet/src/cmds/secrets/mod.rsdiffbeforeafterboth11use crossterm::{terminal, tty::IsTty};11use crossterm::{terminal, tty::IsTty};12use fleet_shared::SecretData;12use fleet_shared::SecretData;13use itertools::Itertools;13use itertools::Itertools;14use nix_eval::{nix_go, nix_go_json, Value};14use owo_colors::OwoColorize;15use owo_colors::OwoColorize;15use serde::Deserialize;16use serde::Deserialize;16use tabled::{Table, Tabled};17use tabled::{Table, Tabled};17use tokio::{fs::read, process::Command};18use tokio::{fs::read, process::Command};18use tracing::{error, info, info_span, warn, Instrument};19use tracing::{error, info, info_span, warn, Instrument};192020use crate::{21use crate::{21 better_nix_eval::Field,22 fleetdata::{encrypt_secret_data, FleetSecret, FleetSecretPart, FleetSharedSecret},22 fleetdata::{encrypt_secret_data, FleetSecret, FleetSecretPart, FleetSharedSecret},23 host::Config,23 host::Config,24 nix_go, nix_go_json,25};24};262527#[derive(Parser)]26#[derive(Parser)]130 secret_name: &str,129 secret_name: &str,131 config: &Config,130 config: &Config,132 mut secret: FleetSharedSecret,131 mut secret: FleetSharedSecret,133 field: Field,132 field: Value,134 updated_set: &[String],133 updated_set: &[String],135 prefer_identities: &[String],134 prefer_identities: &[String],136) -> Result<FleetSharedSecret> {135) -> Result<FleetSharedSecret> {197async fn generate_pure(196async fn generate_pure(198 _config: &Config,197 _config: &Config,199 _display_name: &str,198 _display_name: &str,200 _secret: Field,199 _secret: Value,201 _default_generator: Field,200 _default_generator: Value,202 _owners: &[String],201 _owners: &[String],203) -> Result<FleetSecret> {202) -> Result<FleetSecret> {204 bail!("pure generators are broken for now")203 bail!("pure generators are broken for now")205}204}206async fn generate_impure(205async fn generate_impure(207 config: &Config,206 config: &Config,208 _display_name: &str,207 _display_name: &str,209 secret: Field,208 secret: Value,210 default_generator: Field,209 default_generator: Value,211 owners: &[String],210 owners: &[String],212) -> Result<FleetSecret> {211) -> Result<FleetSecret> {213 let generator = nix_go!(secret.generator);212 let generator = nix_go!(secret.generator);289async fn generate(288async fn generate(290 config: &Config,289 config: &Config,291 display_name: &str,290 display_name: &str,292 secret: Field,291 secret: Value,293 owners: &[String],292 owners: &[String],294) -> Result<FleetSecret> {293) -> Result<FleetSecret> {295 let generator = nix_go!(secret.generator);294 let generator = nix_go!(secret.generator);332async fn generate_shared(331async fn generate_shared(333 config: &Config,332 config: &Config,334 display_name: &str,333 display_name: &str,335 secret: Field,334 secret: Value,336 expected_owners: Vec<String>,335 expected_owners: Vec<String>,337) -> Result<FleetSharedSecret> {336) -> Result<FleetSharedSecret> {338 // let owners: Vec<String> = nix_go_json!(secret.expectedOwners);337 // let owners: Vec<String> = nix_go_json!(secret.expectedOwners);cmds/fleet/src/host.rsdiffbeforeafterboth12use anyhow::{anyhow, bail, ensure, Context, Result};12use anyhow::{anyhow, bail, ensure, Context, Result};13use clap::{ArgGroup, Parser};13use clap::{ArgGroup, Parser};14use fleet_shared::SecretData;14use fleet_shared::SecretData;15use nix_eval::{nix_go, nix_go_json, NixSessionPool, Value};15use openssh::SessionBuilder;16use openssh::SessionBuilder;16use serde::de::DeserializeOwned;17use serde::de::DeserializeOwned;17use tempfile::NamedTempFile;18use tempfile::NamedTempFile;181919use crate::{20use crate::{20 better_nix_eval::{Field, NixSessionPool},21 command::MyCommand,21 command::MyCommand,22 fleetdata::{FleetData, FleetSecret, FleetSharedSecret},22 fleetdata::{FleetData, FleetSecret, FleetSharedSecret},23 nix_go, nix_go_json,24};23};252426pub struct FleetConfigInternals {25pub struct FleetConfigInternals {30 pub data: Mutex<FleetData>,29 pub data: Mutex<FleetData>,31 pub nix_args: Vec<OsString>,30 pub nix_args: Vec<OsString>,32 /// fleet_config.config31 /// fleet_config.config33 pub config_field: Field,32 pub config_field: Value,34 /// fleet_config.unchecked.config33 /// fleet_config.unchecked.config35 pub config_unchecked_field: Field,34 pub config_unchecked_field: Value,363537 /// import nixpkgs {system = local};36 /// import nixpkgs {system = local};38 pub default_pkgs: Field,37 pub default_pkgs: Value,39}38}403941#[derive(Clone)]40#[derive(Clone)]55 pub local: bool,54 pub local: bool,56 pub session: OnceLock<Arc<openssh::Session>>,55 pub session: OnceLock<Arc<openssh::Session>>,575658 pub nixos_config: Option<Field>,57 pub nixos_config: Option<Value>,59}58}60impl ConfigHost {59impl ConfigHost {61 async fn open_session(&self) -> Result<Arc<openssh::Session>> {60 async fn open_session(&self) -> Result<Arc<openssh::Session>> {201 }200 }202 Ok(out)201 Ok(out)203 }202 }204 pub async fn secret_field(&self, name: &str) -> Result<Field> {203 pub async fn secret_field(&self, name: &str) -> Result<Value> {205 let Some(nixos) = &self.nixos_config else {204 let Some(nixos) = &self.nixos_config else {206 bail!("host is virtual and has no secrets");205 bail!("host is virtual and has no secrets");207 };206 };208 Ok(nix_go!(nixos.secrets[{ name }]))207 Ok(nix_go!(nixos.secrets[{ name }]))209 }208 }210209211 /// Packages for this host, resolved with nixpkgs overlays210 /// Packages for this host, resolved with nixpkgs overlays212 pub async fn pkgs(&self) -> Result<Field> {211 pub async fn pkgs(&self) -> Result<Value> {213 let Some(nixos) = &self.nixos_config else {212 let Some(nixos) = &self.nixos_config else {214 return Ok(self.config.default_pkgs.clone());213 return Ok(self.config.default_pkgs.clone());215 };214 };261 }260 }262 Ok(out)261 Ok(out)263 }262 }264 pub async fn system_config(&self, host: &str) -> Result<Field> {263 pub async fn system_config(&self, host: &str) -> Result<Value> {265 let fleet_field = &self.config_unchecked_field;264 let fleet_field = &self.config_unchecked_field;266 Ok(nix_go!(fleet_field.hosts[{ host }].nixosSystem.config))265 Ok(nix_go!(fleet_field.hosts[{ host }].nixosSystem.config))267 }266 }275 /// Shared secrets configured in fleet.nix or in flake274 /// Shared secrets configured in fleet.nix or in flake276 pub async fn list_configured_shared(&self) -> Result<Vec<String>> {275 pub async fn list_configured_shared(&self) -> Result<Vec<String>> {277 let config_field = &self.config_unchecked_field;276 let config_field = &self.config_unchecked_field;278 nix_go!(config_field.sharedSecrets).list_fields().await277 Ok(nix_go!(config_field.sharedSecrets).list_fields().await?)279 }278 }280 /// Shared secrets configured in fleet.nix279 /// Shared secrets configured in fleet.nix281 pub fn list_shared(&self) -> Vec<String> {280 pub fn list_shared(&self) -> Vec<String> {389 let pool = NixSessionPool::new(directory.as_os_str().to_owned(), nix_args.clone()).await?;388 let pool = NixSessionPool::new(directory.as_os_str().to_owned(), nix_args.clone()).await?;390 let root_field = pool.get().await?;389 let root_field = pool.get().await?;391390392 let builtins_field = Field::field(root_field.clone(), "builtins").await?;391 let builtins_field = Value::binding(root_field.clone(), "builtins").await?;393 if self.local_system == "detect" {392 if self.local_system == "detect" {394 self.local_system = nix_go_json!(builtins_field.currentSystem);393 self.local_system = nix_go_json!(builtins_field.currentSystem);395 }394 }396 let local_system = self.local_system.clone();395 let local_system = self.local_system.clone();397396398 let fleet_root = Field::field(root_field, "fleetConfigurations").await?;397 let fleet_root = Value::binding(root_field, "fleetConfigurations").await?;399 let fleet_field = nix_go!(fleet_root.default);398 let fleet_field = nix_go!(fleet_root.default);400399401 let config_field = nix_go!(fleet_field.config);400 let config_field = nix_go!(fleet_field.config);cmds/fleet/src/main.rsdiffbeforeafterboth1#![recursion_limit = "512"]1#![recursion_limit = "512"]2#![feature(try_blocks, lint_reasons)]2#![feature(try_blocks)]334pub(crate) mod cmds;4pub(crate) mod cmds;5pub(crate) mod command;5pub(crate) mod command;173 setup_logging();173 setup_logging();174 if let Err(e) = main_real().await {174 if let Err(e) = main_real().await {175 // If I remove this line, the next error!() line gets eaten.175 // If I remove this line, the next error!() line gets eaten.176 // This is a bug in indicatif, it needs to be fixed177 #[cfg(feature = "indicatif")]176 info!("fixme: this line gets eaten by tracing-indicatif on levels info+");178 info!("fixme: this line gets eaten by tracing-indicatif on levels info+");177 error!("{e:#}");179 error!("{e:#}");178 return ExitCode::FAILURE;180 return ExitCode::FAILURE;181}183}182184183async fn main_real() -> Result<()> {185async fn main_real() -> Result<()> {184 let _ = better_nix_eval::TOKIO_RUNTIME.set(tokio::runtime::Handle::current());186 nix_eval::init_tokio();185187186 let nix_args = std::env::var_os("NIX_ARGS")188 let nix_args = std::env::var_os("NIX_ARGS")187 .map(|a| extra_args::parse_os(&a))189 .map(|a| extra_args::parse_os(&a))cmds/generator-helper/Cargo.tomldiffbeforeafterbothno changes
cmds/generator-helper/src/main.rsdiffbeforeafterbothno changes
cmds/install-secrets/Cargo.tomldiffbeforeafterboth66778[dependencies]8[dependencies]9age = { version = "0.10.0", features = ["ssh"] }9clap.workspace = true10anyhow = "1.0.79"10fleet-shared.workspace = true11tracing-subscriber = { version = "0.3", features = ["env-filter"] }11age.workspace = true12tracing = "0.1"12anyhow.workspace = true13nix = {version = "0.27.1", features = ["user", "fs"]}13tracing.workspace = true14serde = { version = "1.0.196", features = ["derive"] }14tracing-subscriber.workspace = true15serde_json = "1.0.113"15serde.workspace = true16clap = { version = ">=4.4, <4.5", features = [16serde_json.workspace = true17 "derive",18 "env",19 "wrap_help",20 "unicode",21] }22tempfile = "3.10.0"17tempfile.workspace = true23fleet-shared.workspace = true18nix.workspace = true2419cmds/remowt-agent/Cargo.tomldiffbeforeafterboth6# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html6# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html778[dependencies]8[dependencies]9iroh-net = "0.17.0"10tracing.workspace = true911crates/nix-eval/Cargo.tomldiffbeforeafterbothno changes
crates/nix-eval/src/lib.rsdiffbeforeafterbothno changes
crates/nix-eval/src/macros.rsdiffbeforeafterbothno changes
crates/nix-eval/src/pool.rsdiffbeforeafterbothno changes
crates/nix-eval/src/session.rsdiffbeforeafterbothno changes
crates/nix-eval/src/value.rsdiffbeforeafterbothno changes
crates/nixlike/src/lib.rsdiffbeforeafterboth1//! Serialization/deserialization for nix subset usable for static configurations1//! Serialization/deserialization for nix subset usable for static configurations2//!2//! Serialized results from this library are readable by both this library and standard nix tools3//! Serialized results from this library are readable by both this library and standard nix tools.4//! Nix produced output should also be readable by this library, however, you can't write arbitrary nix5//! expressions and expect it to work, only basic primitives are supported, and there is no6//! variables/recursive records, interpolation, e.t.c.374use linked_hash_map::LinkedHashMap;8use linked_hash_map::LinkedHashMap;5use peg::str::LineCol;9use peg::str::LineCol;198202199#[test]203#[test]200fn parse_multiline() {204fn parse_multiline() {205 // First line is ignored, unless there is a significant characters.201 assert_eq!(nixlike::multiline_string("''\n''").expect("parse"), "");206 assert_eq!(nixlike::multiline_string("''\n''").expect("parse"), "");207 // Rest of the lines are processed normally.202 assert_eq!(nixlike::multiline_string("''\n\n''").expect("parse"), "\n");208 assert_eq!(nixlike::multiline_string("''\n\n''").expect("parse"), "\n");209 // Example with significant character on first line.203 assert_eq!(nixlike::multiline_string("''t\n''").expect("parse"), "t\n");210 assert_eq!(nixlike::multiline_string("''t\n''").expect("parse"), "t\n");211 // There might be nothing in multiline string block.204 assert_eq!(nixlike::multiline_string("''''").expect("parse"), "");212 assert_eq!(nixlike::multiline_string("''''").expect("parse"), "");213 // And there also might just be spaces, they are removed due to dedent, and output is empty because214 // first line was also ignored due to missing significant characters.205 assert_eq!(nixlike::multiline_string("'' ''").expect("parse"), "");215 assert_eq!(nixlike::multiline_string("'' ''").expect("parse"), "");206}216}207217flake.lockdiffbeforeafterboth7 ]7 ]8 },8 },9 "locked": {9 "locked": {10 "lastModified": 1715274763,10 "lastModified": 1716569590,11 "narHash": "sha256-3Iv1PGHJn9sV3HO4FlOVaaztOxa9uGLfOmUWrH7v7+A=",11 "narHash": "sha256-5eDbq8TuXFGGO3mqJFzhUbt5zHVTf5zilQoyW5jnJwo=",12 "owner": "ipetkov",12 "owner": "ipetkov",13 "repo": "crane",13 "repo": "crane",14 "rev": "27025ab71bdca30e7ed0a16c88fd74c5970fc7f5",14 "rev": "109987da061a1bf452f435f1653c47511587d919",15 "type": "github"15 "type": "github"16 },16 },17 "original": {17 "original": {40 },40 },41 "nixpkgs": {41 "nixpkgs": {42 "locked": {42 "locked": {43 "lastModified": 1715619775,43 "lastModified": 1716658583,44 "narHash": "sha256-c1XVqTH9IeUukc4LcWLzHCSpMfo4Dj4K8t/kLV3c80c=",44 "narHash": "sha256-A93mYmlLvCz0YjQiQ5Tc3DpLrP6Brs+gAlK9nlnSOVg=",45 "owner": "nixos",45 "owner": "nixos",46 "repo": "nixpkgs",46 "repo": "nixpkgs",47 "rev": "0cb78770f66945bb3130f762aef05373e283f2b9",47 "rev": "3e280884c0b0e8222ec6b05a99db01505964e1c3",48 "type": "github"48 "type": "github"49 },49 },50 "original": {50 "original": {54 "type": "github"54 "type": "github"55 }55 }56 },56 },57 "nixpkgs-stable-for-tests": {58 "locked": {59 "lastModified": 1716361217,60 "narHash": "sha256-mzZDr00WUiUXVm1ujBVv6A0qRd8okaITyUp4ezYRgc4=",61 "owner": "nixos",62 "repo": "nixpkgs",63 "rev": "46397778ef1f73414b03ed553a3368f0e7e33c2f",64 "type": "github"65 },66 "original": {67 "owner": "nixos",68 "ref": "nixos-23.11",69 "repo": "nixpkgs",70 "type": "github"71 }72 },57 "root": {73 "root": {58 "inputs": {74 "inputs": {59 "crane": "crane",75 "crane": "crane",60 "flake-utils": "flake-utils",76 "flake-utils": "flake-utils",61 "nixpkgs": "nixpkgs",77 "nixpkgs": "nixpkgs",78 "nixpkgs-stable-for-tests": "nixpkgs-stable-for-tests",62 "rust-overlay": "rust-overlay"79 "rust-overlay": "rust-overlay"63 }80 }64 },81 },72 ]89 ]73 },90 },74 "locked": {91 "locked": {75 "lastModified": 1715566659,92 "lastModified": 1716603336,76 "narHash": "sha256-OpI0TnN+uE0vvxjPStlTzf5RTohIXVSMwrP9NEgMtaY=",93 "narHash": "sha256-81u/zd7V+XRTq88zwRLxw5GnwZyEiAvGA2BvAXUe864=",77 "owner": "oxalica",94 "owner": "oxalica",78 "repo": "rust-overlay",95 "repo": "rust-overlay",79 "rev": "6c465248316cd31502c82f81f1a3acf2d621b01c",96 "rev": "4d0f1e4d5d65c23cdbb77e4b0d91940be7309bd4",80 "type": "github"97 "type": "github"81 },98 },82 "original": {99 "original": {flake.nixdiffbeforeafterboth334 inputs = {4 inputs = {5 nixpkgs.url = "github:nixos/nixpkgs/master";5 nixpkgs.url = "github:nixos/nixpkgs/master";6 nixpkgs-stable-for-tests.url = "github:nixos/nixpkgs/nixos-23.11";6 rust-overlay = {7 rust-overlay = {7 url = "github:oxalica/rust-overlay";8 url = "github:oxalica/rust-overlay";8 inputs = {9 inputs = {21 rust-overlay,22 rust-overlay,22 flake-utils,23 flake-utils,23 nixpkgs,24 nixpkgs,25 nixpkgs-stable-for-tests,24 crane,26 crane,25 }:27 }:26 with nixpkgs.lib;28 with nixpkgs.lib;37 rust = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;39 rust = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;38 craneLib = (crane.mkLib pkgs).overrideToolchain rust;40 craneLib = (crane.mkLib pkgs).overrideToolchain rust;39 in {41 in {42 packages = let40 packages = import ./pkgs {43 packages = import ./pkgs {41 inherit (pkgs) callPackage;44 inherit (pkgs) callPackage;42 inherit craneLib;45 inherit craneLib;43 };46 };47 in48 packages // {default = packages.fleet;};4950 checks = let51 packages = import ./pkgs {52 inherit (pkgs) callPackage;53 craneLib = crane.mkLib (import nixpkgs {inherit system;});54 };55 packages-with-nixpkgs-stable = import ./pkgs {56 inherit (pkgs) callPackage;57 craneLib = crane.mkLib (import nixpkgs-stable-for-tests {inherit system;});58 };59 prefixAttrs = prefix: attrs:60 nixpkgs.lib.attrsets.mapAttrs' (name: value: {61 name = "${prefix}${name}";62 value = value.overrideAttrs (prev: {63 pname = "${prefix}${prev.pname}";64 });65 })66 attrs;67 in68 # `fleet` crate wants nightly rust, also little sense of supporting it on stable nixpkgs.69 (prefixAttrs "nixpkgs-" (removeAttrs packages ["fleet"]))70 // (prefixAttrs "nixpkgs-stable-" (removeAttrs packages-with-nixpkgs-stable ["fleet"]));7144 devShell = craneLib.devShell {72 devShells.default = craneLib.devShell {45 nativeBuildInputs = with pkgs; [73 nativeBuildInputs = with pkgs; [46 alejandra74 alejandra47 lld75 lldnixos/fleetPkgs.nixdiffbeforeafterbothno changes
pkgs/default.nixdiffbeforeafterboth1{1{2 callPackage,2 callPackage,3 craneLib,3 craneLib,4}: rec {4}: {5 default = fleet;67 fleet-install-secrets = callPackage ./fleet-install-secrets.nix {inherit craneLib;};5 fleet-install-secrets = callPackage ./fleet-install-secrets.nix {inherit craneLib;};8 fleet = callPackage ./fleet.nix {inherit craneLib;};6 fleet = callPackage ./fleet.nix {inherit craneLib;};pkgs/generator-helper.nixdiffbeforeafterbothno changes
rust-toolchain.tomldiffbeforeafterboth1[toolchain]1[toolchain]2channel = "nightly-2024-02-10"2channel = "nightly-2024-05-01"3components = ["rustfmt", "clippy", "rust-analyzer", "rust-src"]3components = ["rustfmt", "clippy", "rust-analyzer", "rust-src"]44