Compare commits
9 commits
551e5a7851
...
447ae19b3c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
447ae19b3c | ||
|
|
15ed56d8ad | ||
|
|
0580ad02bd | ||
|
|
fe8d00b2c2 | ||
|
|
a06790a2af | ||
|
|
9ae0630db4 | ||
|
|
8477c73067 | ||
|
|
80ff0b36cb | ||
|
|
7bce1e7a6e |
25 changed files with 1375 additions and 282 deletions
427
Cargo.lock
generated
427
Cargo.lock
generated
|
|
@ -2,6 +2,15 @@
|
|||
# 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 = "anstream"
|
||||
version = "0.6.21"
|
||||
|
|
@ -38,7 +47,7 @@ version = "1.1.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -49,19 +58,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
|||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "append-override"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"command-error",
|
||||
"fs-err",
|
||||
"libc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -152,12 +149,45 @@ version = "1.0.20"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||
|
||||
[[package]]
|
||||
name = "dynix"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"command-error",
|
||||
"fs-err",
|
||||
"itertools",
|
||||
"libc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tap",
|
||||
"tracing",
|
||||
"tracing-human-layer",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fs-err"
|
||||
version = "3.2.2"
|
||||
|
|
@ -179,6 +209,12 @@ version = "0.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.13.0"
|
||||
|
|
@ -189,24 +225,86 @@ dependencies = [
|
|||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_ci"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
|
||||
|
||||
[[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.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.180"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
|
||||
dependencies = [
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
|
|
@ -225,6 +323,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
|
|
@ -237,6 +344,39 @@ version = "1.70.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "4.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52"
|
||||
dependencies = [
|
||||
"supports-color 2.1.0",
|
||||
"supports-color 3.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
|
|
@ -273,6 +413,51 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
|
|
@ -316,18 +501,58 @@ dependencies = [
|
|||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shell-words"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "smawk"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "supports-color"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89"
|
||||
dependencies = [
|
||||
"is-terminal",
|
||||
"is_ci",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "supports-color"
|
||||
version = "3.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6"
|
||||
dependencies = [
|
||||
"is_ci",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.114"
|
||||
|
|
@ -339,6 +564,43 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
|
||||
dependencies = [
|
||||
"smawk",
|
||||
"terminal_size",
|
||||
"unicode-linebreak",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.44"
|
||||
|
|
@ -368,6 +630,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-human-layer"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b285fd79bba4659408f5d290b3f30fd69d428c630d8c00bb4ba255f2501d50e3"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
"owo-colors",
|
||||
"parking_lot",
|
||||
"textwrap",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"regex-automata",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -376,6 +683,18 @@ version = "1.0.22"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-linebreak"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||
|
||||
[[package]]
|
||||
name = "utf8-command"
|
||||
version = "1.0.1"
|
||||
|
|
@ -388,6 +707,12 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.61.3"
|
||||
|
|
@ -496,6 +821,15 @@ dependencies = [
|
|||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
|
|
@ -505,6 +839,23 @@ dependencies = [
|
|||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.53.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
||||
dependencies = [
|
||||
"windows-link 0.2.1",
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-threading"
|
||||
version = "0.1.0"
|
||||
|
|
@ -514,6 +865,54 @@ dependencies = [
|
|||
"windows-link 0.1.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.53.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.16"
|
||||
|
|
|
|||
11
Cargo.toml
11
Cargo.toml
|
|
@ -1,20 +1,25 @@
|
|||
[package]
|
||||
name = "append-override"
|
||||
name = "dynix"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[[bin]]
|
||||
name = "append-override"
|
||||
name = "dynix"
|
||||
path = "src/main.rs"
|
||||
|
||||
[lib]
|
||||
name = "append_override"
|
||||
name = "dynix"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.54", features = ["color", "derive"] }
|
||||
command-error = "0.8.0"
|
||||
fs-err = "3.2.2"
|
||||
itertools = "0.14.0"
|
||||
libc = { version = "0.2.180", features = ["extra_traits"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
tap = "1.0.1"
|
||||
tracing = { version = "0.1.44", features = ["attributes"] }
|
||||
tracing-human-layer = "0.2.1"
|
||||
tracing-subscriber = { version = "0.3.22", default-features = false, features = ["std", "env-filter", "fmt", "ansi", "registry", "parking_lot"] }
|
||||
|
|
|
|||
36
configuration.nix
Normal file
36
configuration.nix
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{ pkgs, modulesPath, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
./modules/dynamic-overrides.nix
|
||||
./modules/dynamicism
|
||||
"${modulesPath}/profiles/qemu-guest.nix"
|
||||
];
|
||||
|
||||
dynamicism.for.gotosocial.enable = true;
|
||||
|
||||
# Just an example system.
|
||||
users.mutableUsers = false;
|
||||
users.users.root = {
|
||||
password = "root";
|
||||
};
|
||||
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
settings = {
|
||||
PermitRootLogin = "yes";
|
||||
};
|
||||
};
|
||||
|
||||
environment.shellAliases = {
|
||||
ls = "eza --long --header --group --group-directories-first --classify --binary";
|
||||
};
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
eza
|
||||
fd
|
||||
ripgrep
|
||||
];
|
||||
|
||||
system.stateVersion = "25.11";
|
||||
}
|
||||
67
default.nix
67
default.nix
|
|
@ -6,15 +6,70 @@
|
|||
}: let
|
||||
inherit (qpkgs) lib;
|
||||
|
||||
PKGNAME = qpkgs.callPackage ./package.nix { };
|
||||
dynix = qpkgs.callPackage ./package.nix { };
|
||||
|
||||
byStdenv = lib.mapAttrs (stdenvName: stdenv: let
|
||||
withStdenv = PKGNAME.override { inherit stdenv; };
|
||||
PKGNAME' = withStdenv.overrideAttrs (prev: {
|
||||
withStdenv = dynix.override { inherit stdenv; };
|
||||
dynix' = withStdenv.overrideAttrs (prev: {
|
||||
pname = "${prev.pname}-${stdenvName}";
|
||||
});
|
||||
in PKGNAME') qpkgs.validStdenvs;
|
||||
in dynix') qpkgs.validStdenvs;
|
||||
|
||||
in PKGNAME.overrideAttrs (prev: lib.recursiveUpdate prev {
|
||||
passthru = { inherit byStdenv; };
|
||||
evalNixos = import (pkgs.path + "/nixos");
|
||||
|
||||
doChange = {
|
||||
option,
|
||||
value,
|
||||
}:
|
||||
assert lib.isList option;
|
||||
assert lib.all lib.isString option;
|
||||
let
|
||||
nixosBefore = evalNixos {
|
||||
configuration = ./configuration.nix;
|
||||
};
|
||||
dynamicBefore = nixosBefore.config.dynamicism.finalSettings;
|
||||
|
||||
nixosAfter = evalNixos {
|
||||
configuration = { ... }: {
|
||||
imports = [
|
||||
./configuration.nix
|
||||
(lib.setAttrByPath option (lib.mkOverride (-999) value))
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
withActivationScripts = evalNixos {
|
||||
configuration = ({ ... }: {
|
||||
imports = [ ./configuration.nix ];
|
||||
config.environment.systemPackages = [ nixosAfter.config.dynamicism.for.gotosocial.activate ];
|
||||
});
|
||||
};
|
||||
in {
|
||||
inherit nixosBefore nixosAfter withActivationScripts;
|
||||
};
|
||||
|
||||
in dynix.overrideAttrs (final: prev: let
|
||||
self = final.finalPackage;
|
||||
in lib.recursiveUpdate prev {
|
||||
passthru = {
|
||||
ts = let
|
||||
scope = pkgs.callPackage ./modules/tests.nix { };
|
||||
in scope.packages scope;
|
||||
|
||||
dync = self.nixos.config.dynamicism;
|
||||
dyno = self.nixos.options.dynamicism;
|
||||
gotosocial = self.nixos.options.dynamicism.for.valueMeta.attrs.gotosocial.configuration;
|
||||
|
||||
inherit byStdenv;
|
||||
nixos = evalNixos {
|
||||
configuration = ./configuration.nix;
|
||||
};
|
||||
c = self.nixos;
|
||||
nixos-vm = self.nixos.config.system.build.vm;
|
||||
doChange = builtins.seq self.nixos.config.dynamicism doChange;
|
||||
withVox = self.doChange {
|
||||
option = lib.splitString "." "services.gotosocial.settings.application-name";
|
||||
value = "Vox is an asshole";
|
||||
};
|
||||
};
|
||||
})
|
||||
|
|
|
|||
16
flake.nix
16
flake.nix
|
|
@ -21,21 +21,21 @@
|
|||
qpkgs = import qyriad-nur { inherit pkgs; };
|
||||
inherit (qpkgs) lib;
|
||||
|
||||
PKGNAME = import ./default.nix { inherit pkgs qpkgs; };
|
||||
dynix = import ./default.nix { inherit pkgs qpkgs; };
|
||||
extraVersions = lib.mapAttrs' (stdenvName: value: {
|
||||
name = "${stdenvName}-PKGNAME";
|
||||
name = "${stdenvName}-dynix";
|
||||
inherit value;
|
||||
}) PKGNAME.byStdenv;
|
||||
}) dynix.byStdenv;
|
||||
|
||||
devShell = import ./shell.nix { inherit pkgs qpkgs PKGNAME; };
|
||||
devShell = import ./shell.nix { inherit pkgs qpkgs dynix; };
|
||||
extraDevShells = lib.mapAttrs' (stdenvName: value: {
|
||||
name = "${stdenvName}-PKGNAME";
|
||||
name = "${stdenvName}-dynix";
|
||||
inherit value;
|
||||
}) PKGNAME.byStdenv;
|
||||
}) dynix.byStdenv;
|
||||
in {
|
||||
packages = extraVersions // {
|
||||
default = PKGNAME;
|
||||
inherit PKGNAME;
|
||||
default = dynix;
|
||||
inherit dynix;
|
||||
};
|
||||
|
||||
devShells = extraDevShells // {
|
||||
|
|
|
|||
21
modules/dynamic-overrides.nix
Normal file
21
modules/dynamic-overrides.nix
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Managed by dynix.
|
||||
{ lib, ... }:
|
||||
|
||||
lib.mkMerge [
|
||||
{
|
||||
services.gotosocial = {
|
||||
enable = true;
|
||||
setupPostgresqlDB = true;
|
||||
settings = {
|
||||
application-name = "example!";
|
||||
host = "yuki.local";
|
||||
};
|
||||
};
|
||||
}
|
||||
{
|
||||
services.gotosocial.settings.application-name = lib.mkOverride 99 "removed herobrine";
|
||||
}
|
||||
{
|
||||
services.gotosocial.settings.application-name = lib.mkOverride 98 "reädded herobrine";
|
||||
}
|
||||
]
|
||||
138
modules/dynamicism/default.nix
Normal file
138
modules/dynamicism/default.nix
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
{ pkgs, lib, config, options, ... }:
|
||||
let
|
||||
inherit (lib.options)
|
||||
mkOption
|
||||
showOption
|
||||
;
|
||||
t = lib.types;
|
||||
|
||||
inherit (import ./lib.nix { inherit lib; })
|
||||
typeCheck
|
||||
convenientAttrPath
|
||||
concatFoldl
|
||||
recUpdateFoldl
|
||||
recUpdateFoldlAttrs
|
||||
;
|
||||
|
||||
evalNixos = import (pkgs.path + "/nixos");
|
||||
|
||||
opts = options.dynamicism;
|
||||
|
||||
subOpts = lib.mapAttrs (_: metaAttr: metaAttr.configuration.options) options.dynamicism.for.valueMeta.attrs;
|
||||
settingsFormat = pkgs.formats.yaml { };
|
||||
|
||||
finalSettingsFor = { ... }@submod: recUpdateFoldl (optPath:
|
||||
lib.setAttrByPath optPath (lib.getAttrFromPath optPath config)
|
||||
) submod.source-options;
|
||||
|
||||
ourAssertions = lib.concatAttrValues {
|
||||
unitsExist = subOpts
|
||||
|> lib.attrValues
|
||||
|> concatFoldl (submod: submod.systemd-services-updated.value
|
||||
|> lib.map (unit: {
|
||||
assertion = config.systemd.units.${unit}.enable or false;
|
||||
message = ''
|
||||
${showOption submod.systemd-services-updated.loc}' specified non-existent unit '${unit}'
|
||||
'';
|
||||
})
|
||||
|> lib.optionals submod.enable.value
|
||||
);
|
||||
|
||||
optsExist = concatFoldl (submod: lib.optionals submod.enable.value (lib.map (optPath: {
|
||||
assertion = lib.hasAttrByPath optPath options;
|
||||
message = "'${showOption submod.source-options.loc}' specified non-existent option '${showOption optPath}'";
|
||||
}) submod.source-options.value)) (lib.attrValues subOpts);
|
||||
};
|
||||
in
|
||||
{
|
||||
#
|
||||
# Interface.
|
||||
#
|
||||
options.dynamicism = {
|
||||
for = mkOption {
|
||||
type = t.attrsOf (t.submoduleWith {
|
||||
modules = [ ./submodule.nix ];
|
||||
shorthandOnlyDefinesConfig = false;
|
||||
specialArgs = { inherit pkgs; };
|
||||
});
|
||||
default = { };
|
||||
};
|
||||
|
||||
finalSettings = mkOption {
|
||||
type = t.attrsOf t.raw;
|
||||
internal = true;
|
||||
readOnly = true;
|
||||
description = ''
|
||||
Attrset of each `source-options` tree to their actual values.
|
||||
'';
|
||||
};
|
||||
|
||||
doChange = mkOption {
|
||||
type = t.functionTo t.pathInStore;
|
||||
readOnly = true;
|
||||
description = ''
|
||||
The function to call to Do The Thing.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
# Assertions.
|
||||
config.assertions = ourAssertions;
|
||||
|
||||
#
|
||||
# Generic implementation.
|
||||
#
|
||||
config.dynamicism.doChange = {
|
||||
option,
|
||||
value,
|
||||
configuration ? builtins.getEnv "NIXOS_CONFIG",
|
||||
}: let
|
||||
loc = opts.doChange.loc ++ [ "(function argument)" "value" ];
|
||||
option' = typeCheck loc convenientAttrPath option;
|
||||
nixosAfter = evalNixos {
|
||||
configuration = { config, ... }: {
|
||||
imports = [
|
||||
configuration
|
||||
(lib.setAttrByPath option' (lib.mkOverride (-999) value))
|
||||
];
|
||||
|
||||
environment.systemPackages = [
|
||||
config.dynamicism.for.gotosocial.activate
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
allActivations = lib.mapAttrsToList (name: submod: submod.activate) config.dynamicism.for;
|
||||
allActivationScripts = pkgs.writeShellApplication {
|
||||
name = "dynamicism-activate";
|
||||
runtimeInputs = allActivations;
|
||||
text = nixosAfter.config.dynamicism.for
|
||||
|> lib.mapAttrsToList (name: submod: ''
|
||||
echo "Activating dynamicism for ${name}"
|
||||
${lib.getExe submod.activate}
|
||||
'')
|
||||
|> lib.concatStringsSep "\n";
|
||||
};
|
||||
in allActivationScripts;
|
||||
|
||||
config.dynamicism.finalSettings = lib.asserts.checkAssertWarn ourAssertions [ ] (
|
||||
recUpdateFoldlAttrs (name: { ... }@submod: finalSettingsFor submod) config.dynamicism.for
|
||||
);
|
||||
|
||||
# Implementations.
|
||||
config.dynamicism.for.gotosocial = let
|
||||
cfg = config.dynamicism.for.gotosocial;
|
||||
in {
|
||||
source-options = [
|
||||
"services.gotosocial.settings"
|
||||
];
|
||||
|
||||
configFile = settingsFormat.generate "gotosocial-override.yml" config.services.gotosocial.settings;
|
||||
|
||||
unitDropins."gotosocial.service" = pkgs.writeText "gotosocial-override.conf" ''
|
||||
[Service]
|
||||
ExecStart=
|
||||
ExecStart=${lib.getExe' pkgs.gotosocial "gotosocial"} --config-path ${cfg.configFile} start
|
||||
'';
|
||||
};
|
||||
}
|
||||
24
modules/dynamicism/lib.nix
Normal file
24
modules/dynamicism/lib.nix
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
lib ? import <nixpkgs/lib>,
|
||||
}: let
|
||||
t = lib.types;
|
||||
in lib.fix (self: {
|
||||
/** Perform module-system type checking and resolving on a single option value. */
|
||||
typeCheck = loc: option: value:
|
||||
assert lib.isOptionType option;
|
||||
assert lib.isList loc;
|
||||
assert lib.all lib.isString loc;
|
||||
let
|
||||
merged = lib.modules.mergeDefinitions loc option [ {
|
||||
inherit value;
|
||||
file = "«inline»";
|
||||
} ];
|
||||
in merged.mergedValue;
|
||||
|
||||
/** Either a list of strings, or a dotted string that will be split. */
|
||||
convenientAttrPath = t.coercedTo t.str (lib.splitString ".") (t.listOf t.str);
|
||||
|
||||
concatFoldl = f: list: lib.foldl' (acc: value: acc ++ (f value)) [ ] list;
|
||||
recUpdateFoldl = f: list: lib.foldl' (acc: value: lib.recursiveUpdate acc (f value)) { } list;
|
||||
recUpdateFoldlAttrs = f: attrs: lib.foldlAttrs (acc: name: value: lib.recursiveUpdate acc (f name value)) { } attrs;
|
||||
})
|
||||
98
modules/dynamicism/submodule.nix
Normal file
98
modules/dynamicism/submodule.nix
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
{
|
||||
name,
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
let
|
||||
inherit (lib.modules)
|
||||
mkIf
|
||||
;
|
||||
inherit (lib.options)
|
||||
mkOption
|
||||
mkEnableOption
|
||||
literalExpression
|
||||
;
|
||||
inherit (lib.types)
|
||||
mkOptionType
|
||||
;
|
||||
t = lib.types;
|
||||
|
||||
/** Either a list of strings, or a dotted string that will be split. */
|
||||
convenientAttrPath = t.coercedTo t.str (lib.splitString ".") (t.listOf t.str);
|
||||
|
||||
executablePathInStore = mkOptionType {
|
||||
name = "exepath";
|
||||
description = "executable path in the Nix store";
|
||||
descriptionClass = "noun";
|
||||
merge = lib.mergeEqualOption;
|
||||
functor = lib.defaultFunctor "exepath";
|
||||
check = x: if lib.isDerivation x then (
|
||||
x.meta.mainProgram or null != null
|
||||
) else (
|
||||
lib.pathInStore.check x
|
||||
);
|
||||
};
|
||||
in
|
||||
{
|
||||
options = {
|
||||
enable = mkEnableOption "dynamicism for '${name}'";
|
||||
|
||||
source-options = mkOption {
|
||||
type = t.listOf convenientAttrPath;
|
||||
description = "A list of attrpaths of the NixOS option the dynamicism for '${name}' uses";
|
||||
example = literalExpression ''
|
||||
[ [ "services" "gotosocial" "settings" ] "services.nginx.settings" ]
|
||||
'';
|
||||
};
|
||||
|
||||
systemd-services-updated = mkOption {
|
||||
type = t.listOf t.str;
|
||||
description = ''
|
||||
A list of systemd unit names (including the suffix, e.g. `.service`) that need to be updated.
|
||||
'';
|
||||
example = literalExpression ''
|
||||
[ "gotosocial.service" ]
|
||||
'';
|
||||
|
||||
default = lib.attrNames config.unitDropins;
|
||||
};
|
||||
|
||||
configFile = mkOption {
|
||||
type = t.pathInStore;
|
||||
internal = true;
|
||||
};
|
||||
|
||||
unitDropins = mkOption {
|
||||
type = t.attrsOf t.pathInStore;
|
||||
internal = true;
|
||||
};
|
||||
|
||||
activate = mkOption {
|
||||
type = executablePathInStore;
|
||||
internal = true;
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf config.enable {
|
||||
activate = pkgs.writeShellApplication {
|
||||
name = "dynamicism-for-${name}-activate";
|
||||
runtimeInputs = [ pkgs.systemd ];
|
||||
text = let
|
||||
doEdits = config.unitDropins
|
||||
|> lib.mapAttrsToList (service: dropin: ''
|
||||
cat "${dropin}" | systemctl edit "${service}" --runtime --stdin
|
||||
'');
|
||||
doReloads = config.unitDropins
|
||||
|> lib.mapAttrsToList (service: _: ''
|
||||
systemctl reload-or-restart "${service}"
|
||||
'');
|
||||
in [
|
||||
doEdits
|
||||
doReloads
|
||||
] |> lib.concatLists
|
||||
|> lib.concatStringsSep "\n";
|
||||
};
|
||||
};
|
||||
}
|
||||
4
modules/tests-common.nix
Normal file
4
modules/tests-common.nix
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{ pkgs, ... }:
|
||||
{
|
||||
nix.package = pkgs.lixPackageSets.latest.lix;
|
||||
}
|
||||
76
modules/tests-main.py
Normal file
76
modules/tests-main.py
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#import re
|
||||
from pathlib import Path
|
||||
from typing import cast, TYPE_CHECKING
|
||||
|
||||
from test_driver.machine import Machine
|
||||
from test_driver.errors import RequestedAssertionFailed
|
||||
|
||||
DEFAULT_NIX = "@DEFAULT_NIX@"
|
||||
CONFIGURATION_NIX = "@CONFIGURATION_NIX@"
|
||||
DYNAMICISM = "@DYNAMICISM@"
|
||||
|
||||
if TYPE_CHECKING:
|
||||
global machine
|
||||
machine = cast(Machine, ...)
|
||||
|
||||
def run_log(machine: Machine, *commands: str, timeout: int | None = 60) -> str:
|
||||
output = ""
|
||||
for command in commands:
|
||||
with machine.nested(f"must succeed: {command}"):
|
||||
(status, out) = machine.execute(f"{command} | tee /dev/stderr", timeout=timeout)
|
||||
if status != 0:
|
||||
machine.log(f"output: {out}")
|
||||
raise RequestedAssertionFailed(
|
||||
f"command `{command}` failed (exit code {status})",
|
||||
)
|
||||
output += out
|
||||
|
||||
return output
|
||||
|
||||
def get_config_file() -> Path:
|
||||
machine.wait_for_unit("gotosocial.service")
|
||||
gotosocial_pid = int(machine.get_unit_property("gotosocial.service", "MainPID"))
|
||||
print(f"{gotosocial_pid=}")
|
||||
|
||||
cmdline = machine.succeed(f"cat /proc/{gotosocial_pid}/cmdline")
|
||||
cmdline_args = cmdline.split("\0")
|
||||
|
||||
config_file_idx = cmdline_args.index("--config-path") + 1
|
||||
config_file = Path(cmdline_args[config_file_idx])
|
||||
|
||||
machine.log(f"copying from VM: {config_file=}")
|
||||
machine.copy_from_vm(config_file.as_posix())
|
||||
|
||||
return machine.out_dir / config_file.name
|
||||
|
||||
|
||||
machine.wait_for_unit("default.target")
|
||||
assert "lix" in run_log(machine, "nix --version").lower()
|
||||
|
||||
print(f"{CONFIGURATION_NIX=}")
|
||||
machine.succeed("mkdir -vp /etc/nixos")
|
||||
machine.copy_from_host(CONFIGURATION_NIX, "/etc/nixos")
|
||||
machine.copy_from_host(DYNAMICISM, "/etc/nixos")
|
||||
|
||||
run_log(machine, f"nix build --log-format multiline-with-logs --impure -E 'import <nixpkgs/nixos> {{ configuration = {CONFIGURATION_NIX}; }}'")
|
||||
run_log(machine, f"nixos-rebuild switch --file {CONFIGURATION_NIX} --verbose --print-build-logs")
|
||||
|
||||
config_file_local = get_config_file()
|
||||
machine.log(f"opening copied file: {config_file_local=}")
|
||||
with open(config_file_local, "r") as f:
|
||||
text = f.read()
|
||||
lines = text.splitlines()
|
||||
application_name = next(line for line in lines if line.startswith("application-name:"))
|
||||
assert "gotosocial-for-machine" in application_name, f"'gotosocial-for-machine' should be in {application_name=}"
|
||||
|
||||
print(f"{DEFAULT_NIX=}")
|
||||
|
||||
run_log(machine, "eza -lah --color=always --group-directories-first --tree /etc/")
|
||||
|
||||
#exec_start = machine.succeed("systemctl show gotosocial.service --property=ExecStart --value")
|
||||
#exec_start = machine.succeed("systemctl show gotosocial.service --property=ExecStart --value")
|
||||
|
||||
#service_text = machine.succeed("systemctl show gotosocial.service")
|
||||
#service_props = dict(line.split("=", maxsplit=1) for line in service_text.splitlines())
|
||||
#exec_start = service_props['ExecStart']
|
||||
#print(f"{exec_start=}")
|
||||
52
modules/tests.nix
Normal file
52
modules/tests.nix
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
testers,
|
||||
}:
|
||||
let
|
||||
inherit (testers) runNixOSTest;
|
||||
in lib.makeScope lib.callPackageWith (self: {
|
||||
main = runNixOSTest {
|
||||
name = "nixos-test-dynamicism-main";
|
||||
|
||||
defaults = { pkgs, ... }: {
|
||||
imports = [ ./dynamicism ];
|
||||
|
||||
nix = {
|
||||
package = pkgs.lixPackageSets.latest.lix;
|
||||
settings.experimental-features = [ "nix-command" ];
|
||||
nixPath = [ "nixpkgs=${pkgs.path}" ];
|
||||
};
|
||||
|
||||
environment.shellAliases = {
|
||||
ls = "eza --long --header --group --group-directories-first --classify --binary";
|
||||
};
|
||||
environment.systemPackages = with pkgs; [
|
||||
eza
|
||||
fd
|
||||
ripgrep
|
||||
];
|
||||
};
|
||||
|
||||
nodes.machine = { name, ... }: {
|
||||
#services.gotosocial = {
|
||||
# enable = true;
|
||||
# setupPostgresqlDB = true;
|
||||
# settings = {
|
||||
# application-name = "gotosocial-for-${name}";
|
||||
# host = "${name}.local";
|
||||
# };
|
||||
#};
|
||||
#
|
||||
#dynamicism.for.gotosocial.enable = true;
|
||||
};
|
||||
|
||||
# What's a little IFD between friends?
|
||||
testScript = pkgs.replaceVars ./tests-main.py {
|
||||
DEFAULT_NIX = ../default.nix;
|
||||
CONFIGURATION_NIX = ./tests-configuration.nix;
|
||||
DYNAMICISM = ./dynamicism;
|
||||
}
|
||||
|> builtins.readFile;
|
||||
};
|
||||
})
|
||||
|
|
@ -81,6 +81,6 @@ in stdenv.mkDerivation (self: {
|
|||
});
|
||||
|
||||
meta = {
|
||||
mainProgram = "PKGNAME";
|
||||
mainProgram = "dynix";
|
||||
};
|
||||
}))
|
||||
|
|
|
|||
14
shell.nix
14
shell.nix
|
|
@ -3,15 +3,21 @@
|
|||
qpkgs ? let
|
||||
src = fetchTarball "https://github.com/Qyriad/nur-packages/archive/main.tar.gz";
|
||||
in import src { inherit pkgs; },
|
||||
PKGNAME ? import ./default.nix { inherit pkgs qpkgs; },
|
||||
dynix ? import ./default.nix { inherit pkgs qpkgs; },
|
||||
}: let
|
||||
inherit (pkgs) lib;
|
||||
|
||||
mkDevShell = PKGNAME: qpkgs.callPackage PKGNAME.mkDevShell { };
|
||||
devShell = mkDevShell PKGNAME;
|
||||
mkDevShell = dynix: qpkgs.callPackage dynix.mkDevShell { };
|
||||
devShell = mkDevShell dynix;
|
||||
|
||||
byStdenv = lib.mapAttrs (lib.const mkDevShell) PKGNAME.byStdenv;
|
||||
byStdenv = lib.mapAttrs (lib.const mkDevShell) dynix.byStdenv;
|
||||
|
||||
in devShell.overrideAttrs (prev: lib.recursiveUpdate prev {
|
||||
passthru = { inherit byStdenv; };
|
||||
env.PYTHONPATH = [
|
||||
"${pkgs.python3Packages.beartype}/${pkgs.python3.sitePackages}"
|
||||
] |> lib.concatStringsSep ":";
|
||||
packages = prev.packages or [ ] ++ [
|
||||
pkgs.python3Packages.beartype
|
||||
];
|
||||
})
|
||||
|
|
|
|||
42
src/args.rs
42
src/args.rs
|
|
@ -1,3 +1,5 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use clap::ColorChoice;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
|
@ -47,18 +49,39 @@ impl FromStr for NixOsOption {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, clap::Parser)]
|
||||
#[command(version, about, author, arg_required_else_help(true))]
|
||||
pub struct Parser {
|
||||
#[arg(long, default_value = "auto")]
|
||||
pub struct AppendCmd {
|
||||
#[arg(required = true)]
|
||||
pub name: Arc<str>,
|
||||
#[arg(required = true)]
|
||||
pub value: Arc<str>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, clap::Parser)]
|
||||
pub struct DeltaCmd {}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, clap::Subcommand)]
|
||||
#[command(flatten_help = true)]
|
||||
pub enum Subcommand {
|
||||
Append(AppendCmd),
|
||||
// TODO: rename
|
||||
Delta(DeltaCmd),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, clap::Parser)]
|
||||
#[command(version, about, author)]
|
||||
#[command(arg_required_else_help(true), args_override_self(true))]
|
||||
#[command(propagate_version = true)]
|
||||
pub struct Args {
|
||||
#[arg(long, global(true), default_value = "auto")]
|
||||
pub color: ColorChoice,
|
||||
|
||||
#[arg(long)]
|
||||
pub file: Box<OsStr>,
|
||||
// FIXME: default to /etc/configuration.nix, or something?
|
||||
#[arg(long, global(true), default_value = "./configuration.nix")]
|
||||
pub file: Arc<OsStr>,
|
||||
|
||||
#[arg(required = true)]
|
||||
pub name: Box<str>,
|
||||
#[arg(required = true)]
|
||||
pub value: Box<str>,
|
||||
#[command(subcommand)]
|
||||
pub subcommand: Subcommand,
|
||||
}
|
||||
///// Flakeref to a base configuration to modify.
|
||||
//#[arg(group = "config", long, default_value("."))]
|
||||
//#[arg(long, default_value(Some(".")))]
|
||||
|
|
@ -66,7 +89,6 @@ pub struct Parser {
|
|||
//
|
||||
//#[arg(group = "config", long)]
|
||||
//expr: Option<String>,
|
||||
}
|
||||
|
||||
//impl Parser {
|
||||
// fn eval_cmd(&self) {
|
||||
|
|
|
|||
15
src/color.rs
15
src/color.rs
|
|
@ -6,10 +6,14 @@ use std::{
|
|||
#[allow(unused_imports)]
|
||||
use crate::prelude::*;
|
||||
|
||||
pub static CLI_ENABLE_COLOR: OnceLock<bool> = OnceLock::new();
|
||||
/// The actual, final value for whether color should be used, based on CLI and environment values.
|
||||
pub static SHOULD_COLOR: LazyLock<bool> = LazyLock::new(|| is_clicolor_forced() || is_color_reqd());
|
||||
|
||||
/// Initialized from the `--color` value from the CLI, along with `io::stdin().is_terminal()`.
|
||||
pub static _CLI_ENABLE_COLOR: OnceLock<bool> = OnceLock::new();
|
||||
|
||||
fn is_color_reqd() -> bool {
|
||||
CLI_ENABLE_COLOR.get().copied().unwrap_or(false)
|
||||
_CLI_ENABLE_COLOR.get().copied().unwrap_or(false)
|
||||
}
|
||||
|
||||
fn is_clicolor_forced() -> bool {
|
||||
|
|
@ -24,8 +28,6 @@ fn is_clicolor_forced() -> bool {
|
|||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub static SHOULD_COLOR: LazyLock<bool> = LazyLock::new(|| is_clicolor_forced() || is_color_reqd());
|
||||
|
||||
/// Silly wrapper around LazyLock<&'static str> to impl Display.
|
||||
pub(crate) struct _LazyLockDisplay(LazyLock<&'static str>);
|
||||
impl Display for _LazyLockDisplay {
|
||||
|
|
@ -47,8 +49,5 @@ pub(crate) const ANSI_CYAN: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(||
|
|||
}));
|
||||
|
||||
pub(crate) const ANSI_RESET: _LazyLockDisplay = _LazyLockDisplay(LazyLock::new(|| {
|
||||
SHOULD_COLOR
|
||||
// C'mon rustfmt, just format it to match ^.
|
||||
.then_some("\x1b[0m")
|
||||
.unwrap_or_default()
|
||||
SHOULD_COLOR.then_some("\x1b[0m").unwrap_or_default()
|
||||
}));
|
||||
|
|
|
|||
158
src/lib.rs
158
src/lib.rs
|
|
@ -1,4 +1,4 @@
|
|||
use std::sync::{Arc, LazyLock};
|
||||
use std::{iter, sync::Arc};
|
||||
|
||||
pub(crate) mod prelude {
|
||||
#![allow(unused_imports)]
|
||||
|
|
@ -25,14 +25,18 @@ pub(crate) mod prelude {
|
|||
pub use fs_err::File;
|
||||
#[cfg(unix)]
|
||||
pub use fs_err::os::unix::fs::{FileExt, OpenOptionsExt};
|
||||
|
||||
pub use tap::{Pipe, Tap};
|
||||
|
||||
pub use tracing::{Level, debug, error, info, trace, warn};
|
||||
}
|
||||
|
||||
use prelude::*;
|
||||
|
||||
pub mod args;
|
||||
pub use args::Parser;
|
||||
pub use args::{AppendCmd, Args, DeltaCmd};
|
||||
mod color;
|
||||
pub use color::{CLI_ENABLE_COLOR, SHOULD_COLOR};
|
||||
pub use color::{_CLI_ENABLE_COLOR, SHOULD_COLOR};
|
||||
pub mod line;
|
||||
mod nixcmd;
|
||||
pub use line::Line;
|
||||
|
|
@ -45,23 +49,67 @@ use crate::source::SourceFile;
|
|||
|
||||
pub const ASCII_WHITESPACE: &[char] = &['\t', '\n', '\x0C', '\r', ' '];
|
||||
|
||||
#[tracing::instrument(level = "debug")]
|
||||
pub fn do_delta(args: Arc<Args>, delta_args: DeltaCmd) -> Result<(), BoxDynError> {
|
||||
todo!();
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug")]
|
||||
pub fn do_append(args: Arc<Args>, append_args: AppendCmd) -> Result<(), BoxDynError> {
|
||||
let filepath = Path::new(&args.file);
|
||||
let filepath: PathBuf = if filepath.is_relative() && !filepath.starts_with("./") {
|
||||
iter::once(OsStr::new("./"))
|
||||
.chain(filepath.iter())
|
||||
.collect()
|
||||
} else {
|
||||
filepath.to_path_buf()
|
||||
};
|
||||
|
||||
// Get what file that thing is defined in.
|
||||
let def_path = get_where(&append_args.name, &filepath)?;
|
||||
|
||||
let mut opts = File::options();
|
||||
opts.read(true)
|
||||
.write(true)
|
||||
.create(false)
|
||||
.custom_flags(libc::O_CLOEXEC);
|
||||
let source_file = SourceFile::open_from(Arc::from(def_path), opts)?;
|
||||
|
||||
let pri = get_highest_prio(&append_args.name, source_file.clone())?;
|
||||
let new_pri = pri - 1;
|
||||
|
||||
let new_pri_line = get_next_prio_line(
|
||||
source_file.clone(),
|
||||
append_args.name.into(),
|
||||
new_pri,
|
||||
append_args.value.into(),
|
||||
)?;
|
||||
|
||||
eprintln!("new_pri_line={new_pri_line}");
|
||||
|
||||
write_next_prio(source_file, new_pri_line)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Hash, Serialize, Deserialize)]
|
||||
pub struct DefinitionWithLocation {
|
||||
pub file: Box<Path>,
|
||||
pub value: Box<str>,
|
||||
pub value: Box<serde_json::Value>,
|
||||
}
|
||||
|
||||
pub fn get_where(option_name: &str, configuration_nix: &Path) -> Result<Box<Path>, BoxDynError> {
|
||||
let expr: OsString = [
|
||||
// foo
|
||||
pub fn expr_for_configuration(source_file: &Path) -> OsString {
|
||||
[
|
||||
OsStr::new("import <nixpkgs/nixos> { configuration = "),
|
||||
configuration_nix.as_os_str(),
|
||||
source_file.as_os_str(),
|
||||
OsStr::new("; }"),
|
||||
]
|
||||
.into_iter()
|
||||
.map(ToOwned::to_owned)
|
||||
.collect();
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn get_where(option_name: &str, configuration_nix: &Path) -> Result<Box<Path>, BoxDynError> {
|
||||
let expr = expr_for_configuration(configuration_nix);
|
||||
let attrpath = format!("options.{}.definitionsWithLocations", option_name);
|
||||
|
||||
let output = nixcmd::NixEvalExpr { expr, attrpath }
|
||||
|
|
@ -75,19 +123,10 @@ pub fn get_where(option_name: &str, configuration_nix: &Path) -> Result<Box<Path
|
|||
Ok(Box::from(last_location.file))
|
||||
}
|
||||
|
||||
pub fn get_highest_prio(
|
||||
option_name: &str,
|
||||
source: SourceFile,
|
||||
) -> Result<(i64, SourceLine), BoxDynError> {
|
||||
pub fn get_highest_prio(option_name: &str, source: SourceFile) -> Result<i64, BoxDynError> {
|
||||
// Get the current highest priority.
|
||||
|
||||
let expr: OsString = [
|
||||
OsStr::new("import <nixpkgs/nixos> { configuration = "),
|
||||
source.path().as_os_str(),
|
||||
OsStr::new("; }"),
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let expr = expr_for_configuration(&source.path());
|
||||
|
||||
// Get the highest priority, and the file its defined in.
|
||||
let attrpath = format!("options.{}.highestPrio", option_name);
|
||||
|
|
@ -97,93 +136,44 @@ pub fn get_highest_prio(
|
|||
let stdout = output.stdout();
|
||||
let highest_prio = i64::from_str(stdout.trim())?;
|
||||
|
||||
let needle = format!("lib.mkOverride ({})", highest_prio);
|
||||
|
||||
let path = source.path();
|
||||
let lines = source.lines()?;
|
||||
let line = lines
|
||||
.iter()
|
||||
// We're more likely to find it at the end, so let's start there.
|
||||
.rev()
|
||||
.find(|&line| line.text.contains(&needle))
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"couldn't find override number {highest_prio} in {}",
|
||||
path.display(),
|
||||
)
|
||||
});
|
||||
|
||||
Ok((highest_prio, line.clone()))
|
||||
Ok(highest_prio)
|
||||
}
|
||||
|
||||
pub fn get_next_prio_line(
|
||||
source: SourceFile,
|
||||
option_name: Arc<str>,
|
||||
last_line_def: SourceLine,
|
||||
new_prio: i64,
|
||||
new_value: Arc<str>,
|
||||
) -> Result<SourceLine, BoxDynError> {
|
||||
if !last_line_def.text.ends_with(';') {
|
||||
todo!();
|
||||
}
|
||||
let next_line = source.line(last_line_def.line.next())?;
|
||||
if next_line.text.trim() != "}" {
|
||||
todo!();
|
||||
}
|
||||
|
||||
let (indentation, _rest) = last_line_def.text.split_at(
|
||||
last_line_def
|
||||
.text
|
||||
.find(|ch: char| !ch.is_ascii_whitespace())
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
// FIXME: fix indentation
|
||||
let new_text = format!("{indentation}{option_name} = lib.mkOverride ({new_prio}) ({new_value});",);
|
||||
let source_lines = source.lines()?;
|
||||
let last_line = source_lines.last();
|
||||
assert_eq!(last_line.map(SourceLine::text).as_deref(), Some("]"));
|
||||
let last_line = last_line.unwrap();
|
||||
|
||||
let new_line = SourceLine {
|
||||
line: next_line.line.next(),
|
||||
line: last_line.line,
|
||||
path: source.path(),
|
||||
text: Arc::from(new_text),
|
||||
text: Arc::from(format!(
|
||||
" {option_name} = lib.mkOverride ({new_prio}) ({new_value});",
|
||||
)),
|
||||
};
|
||||
|
||||
Ok(new_line)
|
||||
}
|
||||
|
||||
pub fn write_next_prio(
|
||||
mut source: SourceFile,
|
||||
last_line_def: SourceLine,
|
||||
new_text: Arc<str>,
|
||||
) -> Result<(), BoxDynError> {
|
||||
//let lines = source.lines()?;
|
||||
|
||||
let open_brace_line = source.line(last_line_def.line.prev())?.text();
|
||||
let close_brace_line = source.line(last_line_def.line.next())?.text();
|
||||
|
||||
pub fn write_next_prio(mut source: SourceFile, new_line: SourceLine) -> Result<(), BoxDynError> {
|
||||
let new_mod_start = SourceLine {
|
||||
line: last_line_def.line.next(),
|
||||
line: new_line.line.prev(),
|
||||
path: source.path(),
|
||||
text: open_brace_line,
|
||||
};
|
||||
let new_line = SourceLine {
|
||||
line: new_mod_start.line.next(),
|
||||
path: source.path(),
|
||||
text: Arc::from(new_text),
|
||||
text: Arc::from(" {"),
|
||||
};
|
||||
let new_mod_end = SourceLine {
|
||||
line: new_line.line.next(),
|
||||
path: source.path(),
|
||||
text: close_brace_line,
|
||||
text: Arc::from(" }"),
|
||||
};
|
||||
|
||||
dbg!(&new_mod_start.text());
|
||||
|
||||
source.insert_lines(&[
|
||||
new_mod_start,
|
||||
new_line,
|
||||
new_mod_end,
|
||||
])?;
|
||||
|
||||
//source.insert_line(new_line.line, new_line.text())?;
|
||||
source.insert_lines(&[new_mod_start, new_line, new_mod_end])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
52
src/main.rs
52
src/main.rs
|
|
@ -1,17 +1,16 @@
|
|||
use std::io::{self, IsTerminal};
|
||||
use std::path::Path;
|
||||
use std::process::ExitCode;
|
||||
use std::{error::Error as StdError, sync::Arc};
|
||||
|
||||
use append_override::source::SourceFile;
|
||||
use clap::{ColorChoice, Parser as _};
|
||||
use fs_err::File;
|
||||
use fs_err::os::unix::fs::OpenOptionsExt;
|
||||
use tracing_human_layer::HumanLayer;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::{EnvFilter, layer::SubscriberExt};
|
||||
|
||||
fn main_wrapped() -> Result<(), Box<dyn StdError + Send + Sync + 'static>> {
|
||||
let args = append_override::Parser::parse();
|
||||
let args = Arc::new(dynix::Args::parse());
|
||||
|
||||
let success = append_override::CLI_ENABLE_COLOR.set(match args.color {
|
||||
let success = dynix::_CLI_ENABLE_COLOR.set(match args.color {
|
||||
ColorChoice::Always => true,
|
||||
ColorChoice::Auto => io::stdin().is_terminal(),
|
||||
ColorChoice::Never => false,
|
||||
|
|
@ -20,35 +19,20 @@ fn main_wrapped() -> Result<(), Box<dyn StdError + Send + Sync + 'static>> {
|
|||
success.expect("logic error in CLI_ENABLE_COLOR");
|
||||
}
|
||||
|
||||
// FIXME: handle relative paths without leading ./
|
||||
let filepath = Path::new(&args.file);
|
||||
tracing_subscriber::registry()
|
||||
.with(HumanLayer::new().with_color_output(*dynix::SHOULD_COLOR))
|
||||
.with(EnvFilter::from_default_env())
|
||||
.init();
|
||||
|
||||
// Get what file that thing is defined in.
|
||||
let def_path = append_override::get_where(&args.name, filepath)?;
|
||||
let def_path = Arc::from(def_path);
|
||||
let mut opts = File::options();
|
||||
opts.read(true)
|
||||
.write(true)
|
||||
.create(false)
|
||||
.custom_flags(libc::O_CLOEXEC);
|
||||
let source_file = SourceFile::open_from(Arc::clone(&def_path), opts)?;
|
||||
tracing::debug!("Parsed command-line arguments: {args:?}");
|
||||
|
||||
let (pri, last_def_line) = append_override::get_highest_prio(&args.name, source_file.clone())?;
|
||||
let new_pri = pri - 1;
|
||||
|
||||
eprintln!("{last_def_line}");
|
||||
|
||||
let new_pri_line = append_override::get_next_prio_line(
|
||||
source_file.clone(),
|
||||
args.name.into(),
|
||||
last_def_line.clone(),
|
||||
new_pri,
|
||||
args.value.into(),
|
||||
)?;
|
||||
|
||||
eprintln!("new_pri_line={new_pri_line}");
|
||||
|
||||
append_override::write_next_prio(source_file, last_def_line, new_pri_line.text())?;
|
||||
{
|
||||
use dynix::args::Subcommand::*;
|
||||
match &args.subcommand {
|
||||
Append(append_args) => dynix::do_append(args.clone(), append_args.clone())?,
|
||||
Delta(delta_args) => dynix::do_delta(args.clone(), delta_args.clone())?,
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -57,7 +41,7 @@ fn main() -> ExitCode {
|
|||
match main_wrapped() {
|
||||
Ok(_) => ExitCode::SUCCESS,
|
||||
Err(e) => {
|
||||
eprintln!("append-override: error: {}", e);
|
||||
eprintln!("dynix: error: {}", e);
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
|
|
|
|||
172
src/source.rs
172
src/source.rs
|
|
@ -1,10 +1,10 @@
|
|||
use std::{
|
||||
cell::{Cell, Ref, RefCell},
|
||||
cell::{Ref, RefCell},
|
||||
hash::Hash,
|
||||
io::{BufRead, BufReader, BufWriter},
|
||||
iter, mem,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::{Arc, Mutex, MutexGuard, OnceLock, PoisonError, RwLock},
|
||||
ops::Deref,
|
||||
ptr,
|
||||
sync::{Arc, Mutex, OnceLock},
|
||||
};
|
||||
|
||||
use crate::Line;
|
||||
|
|
@ -13,6 +13,33 @@ use crate::color::{ANSI_CYAN, ANSI_GREEN, ANSI_MAGENTA, ANSI_RESET};
|
|||
use crate::prelude::*;
|
||||
|
||||
use fs_err::OpenOptions;
|
||||
use itertools::Itertools;
|
||||
|
||||
pub fn replace_file<'a>(
|
||||
path: &Path,
|
||||
contents: impl IntoIterator<Item = &'a [u8]>,
|
||||
) -> Result<(), IoError> {
|
||||
let tmp_path = path.with_added_extension(".tmp");
|
||||
let tmp_file = File::options()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.custom_flags(libc::O_EXCL | libc::O_CLOEXEC)
|
||||
.open(&tmp_path)?;
|
||||
|
||||
let mut writer = BufWriter::new(tmp_file);
|
||||
for slice in contents {
|
||||
writer.write_all(slice)?;
|
||||
}
|
||||
|
||||
writer.flush()?;
|
||||
drop(writer);
|
||||
|
||||
// Rename the temporary file to the new file, which is atomic (TODO: I think).
|
||||
fs_err::rename(&tmp_path, &path)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub struct SourceLine {
|
||||
|
|
@ -25,6 +52,30 @@ impl SourceLine {
|
|||
pub fn text(&self) -> Arc<str> {
|
||||
Arc::clone(&self.text)
|
||||
}
|
||||
|
||||
pub fn text_ref(&self) -> &str {
|
||||
&self.text
|
||||
}
|
||||
|
||||
pub fn text_bytes(&self) -> Arc<[u8]> {
|
||||
let len: usize = self.text.as_bytes().len();
|
||||
|
||||
// We need to consume an Arc, but we are &self.
|
||||
let text = Arc::clone(&self.text);
|
||||
let str_ptr: *const str = Arc::into_raw(text);
|
||||
let start: *const u8 = str_ptr.cast();
|
||||
let slice_ptr: *const [u8] = ptr::slice_from_raw_parts(start, len);
|
||||
|
||||
unsafe { Arc::<[u8]>::from_raw(slice_ptr) }
|
||||
}
|
||||
|
||||
pub fn text_bytes_ref(&self) -> &[u8] {
|
||||
self.text.as_bytes()
|
||||
}
|
||||
|
||||
pub fn path(&self) -> Arc<Path> {
|
||||
Arc::clone(&self.path)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SourceLine {
|
||||
|
|
@ -39,11 +90,6 @@ impl Display for SourceLine {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub struct SourcePath {
|
||||
path: Arc<Path>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SourceFile {
|
||||
path: Arc<Path>,
|
||||
|
|
@ -56,6 +102,11 @@ pub struct SourceFile {
|
|||
impl SourceFile {
|
||||
/// Panics if `path` is a directory path instead of a file path.
|
||||
pub fn open_from(path: Arc<Path>, options: OpenOptions) -> Result<Self, IoError> {
|
||||
trace!(
|
||||
"SourceFile::open_from(path={:?}, options={:?})",
|
||||
path,
|
||||
options.options(),
|
||||
);
|
||||
assert!(path.file_name().is_some());
|
||||
|
||||
let file = Arc::new(Mutex::new(options.open(&*path)?));
|
||||
|
|
@ -126,6 +177,7 @@ impl SourceFile {
|
|||
if new_lines.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let num_lines_before_new = new_lines.last().unwrap().line.prev().index() as usize;
|
||||
|
||||
debug_assert!(new_lines.is_sorted_by(|lhs, rhs| lhs.line.next() == rhs.line));
|
||||
|
||||
|
|
@ -133,12 +185,12 @@ impl SourceFile {
|
|||
let cur_lines = self.lines()?;
|
||||
let first_half = cur_lines
|
||||
.iter()
|
||||
.take(new_lines.last().unwrap().line.prev().index() as usize)
|
||||
.take(num_lines_before_new)
|
||||
.map(SourceLine::text);
|
||||
let middle = new_lines.iter().map(SourceLine::text);
|
||||
let second_half = cur_lines
|
||||
.iter()
|
||||
.skip(new_lines.last().unwrap().line.prev().index() as usize)
|
||||
.skip(num_lines_before_new)
|
||||
.map(SourceLine::text);
|
||||
|
||||
let final_lines: Vec<SourceLine> = first_half
|
||||
|
|
@ -158,31 +210,11 @@ impl SourceFile {
|
|||
|
||||
drop(cur_lines);
|
||||
|
||||
// Write it to a file in the same directory.
|
||||
let new_name: OsString = [
|
||||
// foo
|
||||
path.file_name().unwrap(),
|
||||
OsStr::new(".tmp"),
|
||||
]
|
||||
.into_iter()
|
||||
.collect::<OsString>();
|
||||
let tmp_path = path.with_file_name(&new_name);
|
||||
let tmp_file = File::options()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.custom_flags(libc::O_EXCL | libc::O_CLOEXEC)
|
||||
.open(&tmp_path)?;
|
||||
|
||||
let mut writer = BufWriter::new(tmp_file);
|
||||
for line in final_lines.iter() {
|
||||
writer.write_all(line.text().as_bytes())?;
|
||||
writer.write_all(b"\n")?;
|
||||
}
|
||||
writer.flush()?;
|
||||
drop(writer);
|
||||
// Rename the temporary file to the new file, which is atomic (TODO: I think).
|
||||
fs_err::rename(&tmp_path, &path)?;
|
||||
let data = final_lines
|
||||
.iter()
|
||||
.map(SourceLine::text_bytes_ref)
|
||||
.pipe(|iterator| Itertools::intersperse(iterator, b"\n"));
|
||||
replace_file(&path, data)?;
|
||||
|
||||
// Finally, update state.
|
||||
self.lines.get().unwrap().replace(final_lines);
|
||||
|
|
@ -190,74 +222,6 @@ impl SourceFile {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn insert_line(&mut self, at: Line, text: Arc<str>) -> Result<(), IoError> {
|
||||
self.lines()?;
|
||||
let path = self.path();
|
||||
|
||||
//let lines = Arc::get_mut(&mut self.lines).unwrap().get_mut().unwrap();
|
||||
let lines_guard = self.lines.get().unwrap().borrow();
|
||||
let lines = &*lines_guard;
|
||||
let first_half = lines.iter().take(at.index() as usize).map(SourceLine::text);
|
||||
let second_half = lines.iter().skip(at.index() as usize).map(SourceLine::text);
|
||||
|
||||
let new_lines: Vec<SourceLine> = first_half
|
||||
.chain(iter::once(Arc::clone(&text)))
|
||||
.chain(second_half)
|
||||
.enumerate()
|
||||
.map(|(idx, text)| SourceLine {
|
||||
line: Line::from_index(idx as u64),
|
||||
text,
|
||||
path: Arc::clone(&path),
|
||||
})
|
||||
.collect();
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
assert_eq!(new_lines.len(), lines.len() + 1);
|
||||
let newly = new_lines.get(at.index() as usize);
|
||||
assert_eq!(newly.map(SourceLine::text), Some(text));
|
||||
|
||||
// Assert lines are continuous.
|
||||
let linenrs: Vec<Line> = new_lines
|
||||
.iter()
|
||||
.map(|source_line| source_line.line)
|
||||
.collect();
|
||||
assert!(linenrs.is_sorted());
|
||||
}
|
||||
|
||||
// Write it to a file in the same directory.
|
||||
let new_name: OsString = [
|
||||
// foo
|
||||
path.file_name().unwrap(),
|
||||
OsStr::new(".tmp"),
|
||||
]
|
||||
.into_iter()
|
||||
.collect::<OsString>();
|
||||
let tmp_path = path.with_file_name(&new_name);
|
||||
let tmp_file = File::options()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.custom_flags(libc::O_EXCL | libc::O_CLOEXEC)
|
||||
.open(&tmp_path)?;
|
||||
|
||||
let mut writer = BufWriter::new(tmp_file);
|
||||
for line in new_lines.iter() {
|
||||
writer.write_all(line.text().as_bytes())?;
|
||||
writer.write_all(b"\n")?;
|
||||
}
|
||||
writer.flush()?;
|
||||
drop(writer);
|
||||
// Rename the temporary file to the new file, which is atomic (TODO: I think).
|
||||
fs_err::rename(&tmp_path, &path)?;
|
||||
|
||||
drop(lines_guard);
|
||||
let mut lines_guard = self.lines.get().unwrap().borrow_mut();
|
||||
// Finally, update state.
|
||||
let _old_lines = mem::replace(&mut *lines_guard, new_lines);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn path(&self) -> Arc<Path> {
|
||||
Arc::clone(&self.path)
|
||||
}
|
||||
|
|
|
|||
9
tests/basic/configuration-package.nix
Normal file
9
tests/basic/configuration-package.nix
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
runCommand,
|
||||
}: runCommand "tests-basic-configuration-dot-nix" {
|
||||
} ''
|
||||
set -euo pipefail
|
||||
mkdir -vp "$out/share/nixos"
|
||||
cp -rv ${./configuration.nix} "$out/share/nixos/configuration.nix"
|
||||
cp -rv ${../../modules/dynamicism} "$out/share/nixos/dynamicism"
|
||||
''
|
||||
69
tests/basic/configuration.nix
Normal file
69
tests/basic/configuration.nix
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
{ pkgs, lib, config, modulesPath, ... }:
|
||||
let
|
||||
name = config.networking.hostName;
|
||||
nixosLibPath = (modulesPath + "/../lib");
|
||||
moduleList = import (modulesPath + "/module-list.nix");
|
||||
|
||||
optionalPath = p: lib.optional (builtins.pathExists p) p;
|
||||
in
|
||||
assert builtins.pathExists nixosLibPath;
|
||||
builtins.seq lib
|
||||
builtins.seq modulesPath
|
||||
builtins.seq moduleList
|
||||
{
|
||||
imports = moduleList ++ [
|
||||
(modulesPath + "/testing/test-instrumentation.nix")
|
||||
] ++ lib.concatLists [
|
||||
(optionalPath ./hardware-configuration.nix)
|
||||
(optionalPath ./dynamicism)
|
||||
(optionalPath ../../modules/dynamicism)
|
||||
];
|
||||
|
||||
system.switch.enable = true;
|
||||
documentation.enable = false;
|
||||
|
||||
networking.hostName = "machine";
|
||||
|
||||
boot.loader.grub = {
|
||||
enable = true;
|
||||
device = "/dev/vda";
|
||||
forceInstall = true;
|
||||
};
|
||||
|
||||
nix = {
|
||||
package = pkgs.lixPackageSets.latest.lix;
|
||||
nixPath = [ "nixpkgs=${pkgs.path}" ];
|
||||
|
||||
settings = {
|
||||
experimental-features = [ "nix-command" "pipe-operator" ];
|
||||
substituters = lib.mkForce [ ];
|
||||
hashed-mirrors = null;
|
||||
connect-timeout = 1;
|
||||
};
|
||||
};
|
||||
|
||||
services.gotosocial = {
|
||||
enable = true;
|
||||
setupPostgresqlDB = true;
|
||||
settings = {
|
||||
application-name = "gotosocial-for-${name}";
|
||||
host = "${name}.local";
|
||||
};
|
||||
};
|
||||
|
||||
dynamicism.for.gotosocial.enable = true;
|
||||
|
||||
environment.pathsToLink = [ "/share" ];
|
||||
environment.variables = {
|
||||
"NIXOS_CONFIG" = "/etc/nixos/configuration.nix";
|
||||
};
|
||||
|
||||
environment.shellAliases = {
|
||||
ls = "eza --long --header --group --group-directories-first --classify --binary";
|
||||
};
|
||||
environment.systemPackages = with pkgs; [
|
||||
eza
|
||||
fd
|
||||
ripgrep
|
||||
];
|
||||
}
|
||||
7
tests/basic/default.nix
Normal file
7
tests/basic/default.nix
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* Convenience shortcut for running this test from the command-line.
|
||||
* Normally this test is initialized from /tests/default.nix.
|
||||
*/
|
||||
{
|
||||
pkgs ? import <nixpkgs> { },
|
||||
}: pkgs.testers.runNixOSTest ./test.nix
|
||||
84
tests/basic/test-script.py
Normal file
84
tests/basic/test-script.py
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
from pathlib import Path
|
||||
import shlex
|
||||
import textwrap
|
||||
from typing import cast, TYPE_CHECKING
|
||||
|
||||
from beartype import beartype
|
||||
|
||||
from test_driver.machine import Machine
|
||||
from test_driver.errors import RequestedAssertionFailed
|
||||
|
||||
if TYPE_CHECKING:
|
||||
global machine
|
||||
machine = cast(Machine, ...)
|
||||
assert machine.shell is not None
|
||||
|
||||
ls = "eza -lah --color=always --group-directories-first"
|
||||
|
||||
@beartype
|
||||
def run_log(machine: Machine, *commands: str, timeout: int | None = 60) -> str:
|
||||
output = ""
|
||||
for command in commands:
|
||||
with machine.nested(f"must succeed: {command}"):
|
||||
(status, out) = machine.execute(f"{command} | tee /dev/stderr", timeout=timeout)
|
||||
if status != 0:
|
||||
machine.log(f"output: {out}")
|
||||
raise RequestedAssertionFailed(
|
||||
f"command `{command}` failed (exit code {status})",
|
||||
)
|
||||
output += out
|
||||
|
||||
return output
|
||||
|
||||
@beartype
|
||||
def get_config_file() -> str:
|
||||
machine.wait_for_unit("gotosocial.service")
|
||||
gotosocial_pid = int(machine.get_unit_property("gotosocial.service", "MainPID"))
|
||||
print(f"{gotosocial_pid=}")
|
||||
|
||||
cmdline = machine.succeed(f"cat /proc/{gotosocial_pid}/cmdline")
|
||||
cmdline_args = cmdline.split("\0")
|
||||
|
||||
config_file_idx = cmdline_args.index("--config-path") + 1
|
||||
config_file = Path(cmdline_args[config_file_idx])
|
||||
|
||||
machine.log(f"copying from VM: {config_file=}")
|
||||
machine.copy_from_vm(config_file.as_posix())
|
||||
|
||||
config_file_path = machine.out_dir / config_file.name
|
||||
with open(config_file_path, "r") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
machine.wait_for_unit("default.target")
|
||||
assert "lix" in machine.succeed("nix --version").lower()
|
||||
|
||||
run_log(machine, "nixos-generate-config")
|
||||
machine.succeed("mkdir -vp /etc/nixos")
|
||||
machine.succeed("cp -rv /run/current-system/sw/share/nixos/* /etc/nixos/")
|
||||
machine.succeed("env PAGER= nixos-rebuild switch --log-format raw-with-logs -v --fallback >&2")
|
||||
|
||||
config_text = get_config_file()
|
||||
lines = config_text.splitlines()
|
||||
application_name = next((line for line in lines if line.startswith("application-name:")), None)
|
||||
assert application_name is not None, f"no 'application-name:' found in config file: {textwrap.indent(config_text, "")}"
|
||||
assert "gotosocial-for-machine" in application_name, f"'gotosocial-for-machine' should be in {application_name=}"
|
||||
|
||||
new_app_name = "yay!"
|
||||
expr = textwrap.dedent(f"""
|
||||
let
|
||||
nixos = import <nixpkgs/nixos> {{ }};
|
||||
in nixos.config.dynamicism.doChange {{
|
||||
option = "services.gotosocial.settings.application-name";
|
||||
value = "{new_app_name}";
|
||||
}}
|
||||
""").strip()
|
||||
machine.succeed(rf"""
|
||||
nix run --show-trace --log-format raw-with-logs --impure -E {shlex.quote(expr)}
|
||||
""".strip())
|
||||
|
||||
config_file_new = get_config_file()
|
||||
lines = config_file_new.splitlines()
|
||||
|
||||
application_name = next(line for line in lines if line.startswith("application-name:"))
|
||||
assert new_app_name in application_name, f"'{new_app_name}' should be in {application_name=}"
|
||||
43
tests/basic/test.nix
Normal file
43
tests/basic/test.nix
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
{
|
||||
name = "nixos-test-dynamicism-main";
|
||||
|
||||
defaults = { ... }: { };
|
||||
|
||||
#node.pkgsReadOnly = false;
|
||||
|
||||
extraPythonPackages = p: [
|
||||
p.beartype
|
||||
];
|
||||
|
||||
nodes.machine = { pkgs, config, ... }: {
|
||||
imports = [ ./configuration.nix ];
|
||||
|
||||
system.includeBuildDependencies = true;
|
||||
system.switch.enable = true;
|
||||
|
||||
virtualisation.additionalPaths = [ config.system.build.toplevel ];
|
||||
virtualisation = {
|
||||
memorySize = 4096;
|
||||
cores = 4;
|
||||
writableStore = true;
|
||||
mountHostNixStore = true;
|
||||
installBootLoader = true;
|
||||
};
|
||||
|
||||
environment.systemPackages = let
|
||||
configFileTree = pkgs.callPackage ./configuration-package.nix { };
|
||||
in [
|
||||
configFileTree
|
||||
];
|
||||
};
|
||||
|
||||
# What's a little IFD between friends?
|
||||
testScript = ./test-script.py
|
||||
|> builtins.readFile;
|
||||
}
|
||||
8
tests/default.nix
Normal file
8
tests/default.nix
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
pkgs ? import <nixpkgs> { },
|
||||
lib ? pkgs.lib,
|
||||
}: lib.makeScope lib.callPackageWith (self: let
|
||||
inherit (pkgs.testers) runNixOSTest;
|
||||
in {
|
||||
basic = runNixOSTest ./basic/test.nix;
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue