diff --git a/Cargo.lock b/Cargo.lock index 76c1240..677f698 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -136,6 +136,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "2.9.0" @@ -182,6 +188,15 @@ version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +dependencies = [ + "serde", +] + [[package]] name = "camino" version = "1.1.9" @@ -212,7 +227,8 @@ dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", - "windows-link", + "serde", + "windows-link 0.1.1", ] [[package]] @@ -312,6 +328,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -362,6 +387,47 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + [[package]] name = "deps-infer" version = "0.1.0" @@ -382,6 +448,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", + "serde", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", ] [[package]] @@ -400,6 +490,12 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "either" version = "1.15.0" @@ -418,6 +514,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" +[[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.10" @@ -438,6 +540,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "generic-array" version = "0.14.7" @@ -507,12 +615,78 @@ dependencies = [ "walkdir", ] +[[package]] +name = "harmonia-store-core" +version = "0.0.0-alpha.0" +source = "git+https://github.com/nix-community/harmonia.git#ee85c47fd04b333d73289a972f9f88033697e9d3" +dependencies = [ + "bytes", + "data-encoding", + "derive_more", + "harmonia-utils-base-encoding", + "harmonia-utils-hash", + "num_enum", + "ring", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.12", + "tokio", + "tracing", + "zerocopy", +] + +[[package]] +name = "harmonia-utils-base-encoding" +version = "0.0.0-alpha.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1e8a868920d6ab01dfa2c5abce20ecd2f6d7e1f3c6a9e1941426883ce15a52c" +dependencies = [ + "data-encoding", + "derive_more", + "serde", +] + +[[package]] +name = "harmonia-utils-hash" +version = "0.0.0-alpha.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301feeb6f34b380ab13df6bb264c461704dbbb7f81b45b46c406c0d8c2d8d7f4" +dependencies = [ + "data-encoding", + "derive_more", + "harmonia-utils-base-encoding", + "md5", + "ring", + "serde", + "thiserror 2.0.12", + "tokio", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "humansize" version = "2.1.3" @@ -545,6 +719,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "ignore" version = "0.4.23" @@ -596,6 +776,39 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921" +dependencies = [ + "equivalent", + "hashbrown 0.15.5", + "serde", +] + +[[package]] +name = "io-uring" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd7bddefd0a8833b88a4b68f90dae22c7450d11b354198baee3874fd811b344" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -669,6 +882,12 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "md5" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae960838283323069879657ca3de837e9f7bbb4c7bf6ea7f1b290d5e9476d2e0" + [[package]] name = "memchr" version = "2.7.4" @@ -690,6 +909,17 @@ dependencies = [ "adler", ] +[[package]] +name = "mio" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.61.2", +] + [[package]] name = "n2" version = "0.1.0" @@ -702,23 +932,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "nix-base32" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2628953ed836273ee4262e3708a8ef63ca38bd8a922070626eef7f9e5d8d536" - -[[package]] -name = "nix-libstore" -version = "0.1.0" -dependencies = [ - "anyhow", - "nix-base32", - "serde", - "serde_json", - "sha2", -] - [[package]] name = "nix-ninja" version = "0.1.0" @@ -726,9 +939,10 @@ dependencies = [ "anyhow", "clap", "deps-infer", + "harmonia-store-core", + "harmonia-utils-hash", "include-graph", "n2", - "nix-libstore", "nix-ninja-task", "nix-tool", "regex", @@ -747,7 +961,7 @@ dependencies = [ "anyhow", "clap", "elf", - "nix-libstore", + "harmonia-store-core", ] [[package]] @@ -755,7 +969,7 @@ name = "nix-tool" version = "0.1.0" dependencies = [ "anyhow", - "nix-libstore", + "harmonia-store-core", "serde_json", ] @@ -807,6 +1021,28 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "object" version = "0.32.2" @@ -953,6 +1189,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.94" @@ -1007,6 +1252,26 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "regex" version = "1.11.1" @@ -1051,6 +1316,20 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -1063,6 +1342,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.44" @@ -1082,12 +1370,6 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - [[package]] name = "same-file" version = "1.0.6" @@ -1097,20 +1379,60 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -1119,14 +1441,46 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_with" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.11.1", + "schemars 0.9.0", + "schemars 1.1.0", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1161,12 +1515,27 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +dependencies = [ + "libc", +] + [[package]] name = "siphasher" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + [[package]] name = "slug" version = "0.1.6" @@ -1183,6 +1552,16 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "strsim" version = "0.11.1" @@ -1303,6 +1682,66 @@ dependencies = [ "time-core", ] +[[package]] +name = "tokio" +version = "1.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7211ff1b8f0d3adae1663b7da9ffe396eabe1ca25f0b0bee42b0da29a9ddce93" +dependencies = [ + "indexmap 2.11.1", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow", +] + [[package]] name = "tracing" version = "0.1.41" @@ -1462,12 +1901,30 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-width" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "utf8parse" version = "0.2.2" @@ -1637,6 +2094,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" version = "0.48.0" @@ -1646,6 +2109,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -1655,6 +2127,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -1776,6 +2257,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + [[package]] name = "winsafe" version = "0.0.19" @@ -1810,3 +2300,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index c57398a..1b8c0a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,7 @@ edition = "2021" [workspace.metadata.crane] name = "nix-ninja" + +[workspace.dependencies] +harmonia-store-core = { git = "https://github.com/nix-community/harmonia.git" } +harmonia-utils-hash = "0.0.0-alpha.0" diff --git a/crates/nix-libstore/Cargo.toml b/crates/nix-libstore/Cargo.toml deleted file mode 100644 index 76699df..0000000 --- a/crates/nix-libstore/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "nix-libstore" -version.workspace = true -edition.workspace = true -description = "Core Nix datastructures" -license = "MIT" - -[dependencies] -anyhow = "1.0" -nix-base32 = "0.2.0" -serde = { version = "1", features = ["derive"] } -serde_json = "1" -sha2 = "0.10" diff --git a/crates/nix-libstore/src/derivation.rs b/crates/nix-libstore/src/derivation.rs deleted file mode 100644 index c34da70..0000000 --- a/crates/nix-libstore/src/derivation.rs +++ /dev/null @@ -1,474 +0,0 @@ -use crate::derived_path::{SingleDerivedPath, SingleDerivedPathBuilt}; -use crate::store_path::StorePath; -use anyhow::Result; -use serde::{Deserialize, Serialize, Serializer}; -use std::collections::{HashMap, HashSet}; - -/// A Nix derivation, matching Nix's JSON derivation format -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Derivation { - /// The name of the derivation - pub name: String, - - /// The system type (e.g., "x86_64-linux") - pub system: String, - - /// The builder executable path - pub builder: String, - - /// Arguments to pass to the builder - #[serde(default)] - pub args: Vec, - - /// Environment variables for the build - #[serde(default, serialize_with = "serialize_hashmap_sorted")] - pub env: HashMap, - - /// Input derivations - #[serde( - default, - rename = "inputDrvs", - serialize_with = "serialize_hashmap_sorted" - )] - pub input_drvs: HashMap, - - /// Input sources (store paths) - #[serde( - default, - rename = "inputSrcs", - serialize_with = "serialize_hashset_as_vec" - )] - pub input_srcs: HashSet, - - /// Output specifications - #[serde(serialize_with = "serialize_hashmap_sorted")] - pub outputs: HashMap, -} - -/// Input derivation specification -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct InputDrv { - /// Outputs of the input derivation - pub outputs: Vec, - - /// Dynamic outputs for dynamic derivations - #[serde( - default, - rename = "dynamicOutputs", - serialize_with = "serialize_hashmap_sorted" - )] - pub dynamic_outputs: HashMap, -} - -/// Dynamic output specification for dynamic derivations -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DynamicOutput { - /// Outputs of the dynamic derivation - pub outputs: Vec, - - /// Nested dynamic outputs - #[serde( - default, - rename = "dynamicOutputs", - serialize_with = "serialize_hashmap_sorted" - )] - pub dynamic_outputs: HashMap, -} - -/// Output specification -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Output { - /// Hash algorithm for content-addressed derivations - #[serde(skip_serializing_if = "Option::is_none", rename = "hashAlgo")] - pub hash_algo: Option, - - /// Output hash mode for content-addressed derivations - #[serde(skip_serializing_if = "Option::is_none")] - pub method: Option, - - /// Output hash for fixed-output derivations - #[serde(skip_serializing_if = "Option::is_none")] - pub hash: Option, -} - -/// Hash algorithm used for Nix operations -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum HashAlgorithm { - #[serde(rename = "sha256")] - Sha256, - #[serde(rename = "sha512")] - Sha512, -} - -/// Output hash mode for derivations -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] -pub enum OutputHashMode { - #[serde(rename = "flat")] - Flat, - #[serde(rename = "nar")] - Nar, - #[serde(rename = "text")] - Text, -} - -impl Derivation { - /// Create a new derivation - pub fn new(name: &str, system: &str, builder: &str) -> Self { - Self { - name: name.to_string(), - system: system.to_string(), - builder: builder.to_string(), - args: Vec::new(), - env: HashMap::new(), - input_drvs: HashMap::new(), - input_srcs: HashSet::new(), - outputs: HashMap::new(), - } - } - - /// Add an argument to the builder - pub fn add_arg(&mut self, arg: &str) -> &mut Self { - self.args.push(arg.to_string()); - self - } - - /// Set an environment variable - pub fn set_env(&mut self, key: &str, value: &str) -> &mut Self { - self.env.insert(key.to_string(), value.to_string()); - self - } - - /// Add an output - pub fn add_output( - &mut self, - name: &str, - hash_algo: Option, - method: Option, - hash: Option, - ) -> &mut Self { - self.outputs.insert( - name.to_string(), - Output { - hash_algo, - method, - hash, - }, - ); - self - } - - /// Add a content-addressed output - pub fn add_ca_output( - &mut self, - name: &str, - hash_algo: HashAlgorithm, - method: OutputHashMode, - ) -> &mut Self { - self.outputs.insert( - name.to_string(), - Output { - hash_algo: Some(hash_algo), - method: Some(method), - hash: None, - }, - ); - self - } - - /// Serialize to JSON - pub fn to_json(&self) -> Result { - Ok(serde_json::to_string(self)?) - } - - /// Serialize to pretty-printed JSON - pub fn to_json_pretty(&self) -> Result { - Ok(serde_json::to_string_pretty(self)?) - } - - /// Deserialize from JSON - pub fn from_json(json: &str) -> Result { - Ok(serde_json::from_str(json)?) - } - - /// Add an input source - pub fn add_input_src(&mut self, store_path: &StorePath) -> &mut Self { - self.input_srcs.insert(store_path.to_string()); - self - } - - /// Add a derived path as input (either source or derivation) - pub fn add_derived_path(&mut self, derived_path: &SingleDerivedPath) -> &mut Self { - match derived_path { - SingleDerivedPath::Opaque(store_path) => { - // For opaque paths, add as input source - self.add_input_src(store_path); - } - SingleDerivedPath::Built(built) => { - self.add_input_built(built); - } - } - self - } - - /// Add a built derivation path as an input to this derivation - fn add_input_built(&mut self, built: &SingleDerivedPathBuilt) { - let drv_store_path = built.derived_path.store_path().to_string(); - let input_drv = self - .input_drvs - .entry(drv_store_path) - .or_insert_with(|| InputDrv { - outputs: vec![], - dynamic_outputs: HashMap::new(), - }); - - Self::add_built_nested( - &mut input_drv.outputs, - &mut input_drv.dynamic_outputs, - built, - ); - } - - /// Add a built path with potentially nested dynamic derivation structure - fn add_built_nested( - outputs: &mut Vec, - dynamic_outputs: &mut HashMap, - built: &SingleDerivedPathBuilt, - ) { - // Extract chain of output names from outermost to innermost - let mut chain = Vec::new(); - let mut current = built; - - loop { - chain.push(current.output.clone()); - match current.derived_path.as_ref() { - SingleDerivedPath::Opaque(_) => break, - SingleDerivedPath::Built(inner) => current = inner, - } - } - - // Reverse to process innermost to outermost - chain.reverse(); - - // Split into intermediate levels and final output - let Some((final_output, intermediate_levels)) = chain.split_last() else { - return; - }; - - // Navigate through intermediate levels, creating dynamic outputs - let mut current_outputs = outputs; - let mut current_dynamics = dynamic_outputs; - - for level in intermediate_levels { - let dynamic_output = - current_dynamics - .entry(level.clone()) - .or_insert_with(|| DynamicOutput { - outputs: vec![], - dynamic_outputs: HashMap::new(), - }); - current_outputs = &mut dynamic_output.outputs; - current_dynamics = &mut dynamic_output.dynamic_outputs; - } - - // Add final output - if !current_outputs.contains(final_output) { - current_outputs.push(final_output.clone()); - } - } -} - -fn serialize_hashset_as_vec(set: &HashSet, serializer: S) -> Result -where - S: Serializer, - T: Serialize + Clone + Ord, -{ - let mut vec: Vec = set.iter().cloned().collect(); - vec.sort(); - vec.serialize(serializer) -} - -fn serialize_hashmap_sorted(map: &HashMap, serializer: S) -> Result -where - S: Serializer, - K: Serialize + Clone + Ord + std::hash::Hash, - V: Serialize + Clone, -{ - use serde::ser::SerializeMap; - let mut sorted_keys: Vec<&K> = map.keys().collect(); - sorted_keys.sort(); - - let mut map_serializer = serializer.serialize_map(Some(map.len()))?; - for key in sorted_keys { - let value = &map[key]; - map_serializer.serialize_entry(key, value)?; - } - map_serializer.end() -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::derived_path::SingleDerivedPathBuilt; - use crate::store_path::StorePath; - - #[test] - fn derivation_serialization() { - // Create a basic derivation - let mut drv = Derivation::new( - "hello", - "x86_64-linux", - "/nix/store/w7jl0h7mwrrrcy2kgvk9c9h9142f1ca0-bash/bin/bash", - ); - - // Add some basic properties - drv.add_arg("-c") - .add_arg("echo Hello > $out") - .set_env( - "PATH", - "/nix/store/d1pzgj1pj3nk97vhm5x6n8szy4w3xhx7-coreutils/bin", - ) - .add_output("out", None, None, None); - - // Serialize to JSON - let json = drv.to_json().unwrap(); - - // Deserialize back - let drv2 = Derivation::from_json(&json).unwrap(); - - // Check that they match - assert_eq!(drv.name, drv2.name); - assert_eq!(drv.system, drv2.system); - assert_eq!(drv.builder, drv2.builder); - assert_eq!(drv.args, drv2.args); - assert_eq!(drv.outputs.len(), drv2.outputs.len()); - } - - #[test] - fn ca_derivation() { - let mut drv = Derivation::new( - "ca-example", - "x86_64-linux", - "/nix/store/w7jl0h7mwrrrcy2kgvk9c9h9142f1ca0-bash/bin/bash", - ); - - drv.add_ca_output("out", HashAlgorithm::Sha256, OutputHashMode::Nar); - - let output = drv.outputs.get("out").unwrap(); - assert_eq!(output.hash_algo, Some(HashAlgorithm::Sha256)); - assert_eq!(output.method, Some(OutputHashMode::Nar)); - assert_eq!(output.hash, None); - } - - #[test] - fn add_opaque_path() { - let mut drv = Derivation::new("test", "x86_64-linux", "/bin/bash"); - let store_path1 = sample_store_path(); - let store_path2 = - StorePath::new("/nix/store/zyxwvutsrqponmlkjihgfedcba987654-other").unwrap(); - let path1 = SingleDerivedPath::Opaque(store_path1.clone()); - let path2 = SingleDerivedPath::Opaque(store_path2.clone()); - - drv.add_derived_path(&path1); - drv.add_derived_path(&path2); - - assert!(drv.input_srcs.contains(&store_path1.to_string())); - assert!(drv.input_srcs.contains(&store_path2.to_string())); - assert!(drv.input_drvs.is_empty()); - } - - #[test] - fn add_built_path() { - let mut drv = Derivation::new("test", "x86_64-linux", "/bin/bash"); - let store_path = sample_store_path(); - let built1 = SingleDerivedPathBuilt::new(store_path.clone(), "out".to_string()); - let built2 = SingleDerivedPathBuilt::new(store_path.clone(), "dev".to_string()); - let path1 = SingleDerivedPath::Built(built1); - let path2 = SingleDerivedPath::Built(built2); - - drv.add_derived_path(&path1); - drv.add_derived_path(&path2); - - assert!(drv.input_srcs.is_empty()); - let input_drv = drv.input_drvs.get(&store_path.to_string()).unwrap(); - let mut outputs = input_drv.outputs.clone(); - outputs.sort(); - assert_eq!(outputs, vec!["dev", "out"]); - assert!(input_drv.dynamic_outputs.is_empty()); - } - - #[test] - fn add_multiple_dynamic_outputs() { - let mut drv = Derivation::new("test", "x86_64-linux", "/bin/bash"); - let store_path = sample_store_path(); - - // Add first dynamic derivation: store_path^inner^output1 - let inner1 = SingleDerivedPathBuilt::new(store_path.clone(), "inner".to_string()); - let inner_path1 = SingleDerivedPath::Built(inner1); - let outer1 = SingleDerivedPathBuilt::from_derived_path(inner_path1, "output1".to_string()); - let path1 = SingleDerivedPath::Built(outer1); - - drv.add_derived_path(&path1); - - // Check first dynamic path was added correctly - assert!(drv.input_srcs.is_empty()); - let input_drv = drv.input_drvs.get(&store_path.to_string()).unwrap(); - assert!(input_drv.outputs.is_empty()); - let dynamic_output = input_drv.dynamic_outputs.get("inner").unwrap(); - assert_eq!(dynamic_output.outputs, vec!["output1"]); - - // Add second dynamic derivation with same inner output: store_path^inner^output2 - let inner2 = SingleDerivedPathBuilt::new(store_path.clone(), "inner".to_string()); - let inner_path2 = SingleDerivedPath::Built(inner2); - let outer2 = SingleDerivedPathBuilt::from_derived_path(inner_path2, "output2".to_string()); - let path2 = SingleDerivedPath::Built(outer2); - - drv.add_derived_path(&path2); - - // Check aggregation: both outputs under same dynamic output - let input_drv = drv.input_drvs.get(&store_path.to_string()).unwrap(); - let dynamic_output = input_drv.dynamic_outputs.get("inner").unwrap(); - let mut outputs = dynamic_output.outputs.clone(); - outputs.sort(); - assert_eq!(outputs, vec!["output1", "output2"]); - assert!(dynamic_output.dynamic_outputs.is_empty()); - } - - #[test] - fn add_nested_dynamic_output() { - let mut drv = Derivation::new("test", "x86_64-linux", "/bin/bash"); - let store_path = sample_store_path(); - - // Create deeply nested structure: store_path^level1^level2^level3^output - let level1 = SingleDerivedPathBuilt::new(store_path.clone(), "level1".to_string()); - let level1_path = SingleDerivedPath::Built(level1); - let level2 = SingleDerivedPathBuilt::from_derived_path(level1_path, "level2".to_string()); - let level2_path = SingleDerivedPath::Built(level2); - let level3 = SingleDerivedPathBuilt::from_derived_path(level2_path, "level3".to_string()); - let level3_path = SingleDerivedPath::Built(level3); - let final_output = - SingleDerivedPathBuilt::from_derived_path(level3_path, "output".to_string()); - let path = SingleDerivedPath::Built(final_output); - - drv.add_derived_path(&path); - - // Should handle arbitrarily deep nesting - assert!(drv.input_srcs.is_empty()); - let input_drv = drv.input_drvs.get(&store_path.to_string()).unwrap(); - assert!(input_drv.outputs.is_empty()); - - // Navigate the nested structure - let level1_dynamic = input_drv.dynamic_outputs.get("level1").unwrap(); - assert!(level1_dynamic.outputs.is_empty()); - - let level2_dynamic = level1_dynamic.dynamic_outputs.get("level2").unwrap(); - assert!(level2_dynamic.outputs.is_empty()); - - let level3_dynamic = level2_dynamic.dynamic_outputs.get("level3").unwrap(); - assert_eq!(level3_dynamic.outputs, vec!["output"]); - assert!(level3_dynamic.dynamic_outputs.is_empty()); - } - - fn sample_store_path() -> StorePath { - StorePath::new("/nix/store/abcdefghijklmnopqrstuvwxyz123456-test").unwrap() - } -} diff --git a/crates/nix-libstore/src/derived_path.rs b/crates/nix-libstore/src/derived_path.rs deleted file mode 100644 index a1f2091..0000000 --- a/crates/nix-libstore/src/derived_path.rs +++ /dev/null @@ -1,133 +0,0 @@ -use std::fmt; -use std::path::PathBuf; - -use crate::placeholder::Placeholder; -use crate::store_path::StorePath; - -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum SingleDerivedPath { - Opaque(StorePath), - Built(SingleDerivedPathBuilt), -} - -impl SingleDerivedPath { - pub fn store_path(&self) -> StorePath { - match self { - SingleDerivedPath::Opaque(store_path) => store_path.clone(), - SingleDerivedPath::Built(built_path) => built_path.derived_path.store_path(), - } - } -} - -impl fmt::Display for SingleDerivedPath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - SingleDerivedPath::Opaque(store_path) => write!(f, "{store_path}"), - SingleDerivedPath::Built(built_path) => write!(f, "{built_path}"), - } - } -} - -/// A single derived path that is built from a derivation. -/// Built derived paths are a pair of a derivation and an output name. -/// -/// The derivation itself can be either a store path (Opaque) or another built derivation (Built), -/// allowing for higher-order/nested dynamic derivations. -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct SingleDerivedPathBuilt { - pub derived_path: Box, - pub output: String, -} - -impl SingleDerivedPathBuilt { - /// Create a new SingleDerivedPathBuilt from a store path and output name - pub fn new(drv_path: StorePath, output: String) -> Self { - Self { - derived_path: Box::new(SingleDerivedPath::Opaque(drv_path)), - output, - } - } - - /// Create a new SingleDerivedPathBuilt from another SingleDerivedPath and output name - pub fn from_derived_path(drv_path: SingleDerivedPath, output: String) -> Self { - Self { - derived_path: Box::new(drv_path), - output, - } - } - - pub fn placeholder(&self) -> PathBuf { - self.placeholder_recursive().render() - } - - fn placeholder_recursive(&self) -> Placeholder { - match self.derived_path.as_ref() { - SingleDerivedPath::Opaque(store_path) => { - // Base case: regular ca_output placeholder - Placeholder::ca_output(store_path, &self.output) - } - SingleDerivedPath::Built(inner_built) => { - // Recursive case: create dynamic_output placeholder - let inner_placeholder = inner_built.placeholder_recursive(); - Placeholder::dynamic_output(&inner_placeholder, &self.output) - } - } - } -} - -impl fmt::Display for SingleDerivedPathBuilt { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}^{}", &self.derived_path, &self.output) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn sample_store_path() -> StorePath { - StorePath::new("/nix/store/abcdefghijklmnopqrstuvwxyz123456-test").unwrap() - } - - #[test] - fn opaque_path() { - let store_path = sample_store_path(); - let path = SingleDerivedPath::Opaque(store_path.clone()); - - assert_eq!(path.store_path(), store_path); - assert_eq!(format!("{path}"), store_path.to_string()); - } - - #[test] - fn built_path() { - let store_path = sample_store_path(); - let built = SingleDerivedPathBuilt::new(store_path.clone(), "out".to_string()); - let path = SingleDerivedPath::Built(built); - - assert_eq!(path.store_path(), store_path); - assert_eq!(format!("{path}"), format!("{}^out", store_path)); - } - - #[test] - fn nested_path() { - let store_path = sample_store_path(); - - // Create inner derivation: store-path^inner - let inner_built = SingleDerivedPathBuilt::new(store_path.clone(), "inner".to_string()); - let inner_path = SingleDerivedPath::Built(inner_built); - - // Create outer derivation: (store-path^inner)^outer - let outer_built = - SingleDerivedPathBuilt::from_derived_path(inner_path, "outer".to_string()); - let outer_path = SingleDerivedPath::Built(outer_built); - - // The store_path should resolve to the innermost store path - assert_eq!(outer_path.store_path(), store_path); - - // The display should show the full nested structure - assert_eq!( - format!("{outer_path}"), - format!("{}^inner^outer", store_path) - ); - } -} diff --git a/crates/nix-libstore/src/lib.rs b/crates/nix-libstore/src/lib.rs deleted file mode 100644 index 61df16f..0000000 --- a/crates/nix-libstore/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod derivation; -pub mod derived_path; -pub mod placeholder; -pub mod prelude; -pub mod store_path; diff --git a/crates/nix-libstore/src/placeholder.rs b/crates/nix-libstore/src/placeholder.rs deleted file mode 100644 index b06609d..0000000 --- a/crates/nix-libstore/src/placeholder.rs +++ /dev/null @@ -1,175 +0,0 @@ -use std::path::PathBuf; - -use crate::store_path::StorePath; -use anyhow::anyhow; -use nix_base32; - -/// A placeholder for a Nix store path -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Placeholder { - /// The hash of the placeholder - hash: Vec, -} - -impl Placeholder { - /// Create a new placeholder from a hash - fn new(hash: Vec) -> Self { - Self { hash } - } - - /// Render the placeholder as a string - pub fn render(&self) -> PathBuf { - PathBuf::from(format!("/{}", nix_base32::to_nix_base32(&self.hash))) - } - - /// Generate a placeholder for a standard output - pub fn standard_output(output_name: &str) -> Self { - let clear_text = format!("nix-output:{output_name}"); - let hash = sha256_hash(clear_text.as_bytes()); - Self::new(hash) - } - - /// Generate a placeholder for a content-addressed derivation output - pub fn ca_output(drv_path: &StorePath, output_name: &str) -> Self { - let drv_name = drv_path.name(); - let drv_name = if drv_name.ends_with(".drv") { - &drv_name[0..drv_name.len() - 4] - } else { - drv_name - }; - - // Format the output path name according to Nix conventions - let output_path_name = output_path_name(drv_name, output_name); - - let clear_text = format!( - "nix-upstream-output:{}:{}", - drv_path.hash_part(), - output_path_name - ); - - let hash = sha256_hash(clear_text.as_bytes()); - Self::new(hash) - } - - /// Generate a placeholder for a dynamic derivation output - pub fn dynamic_output(placeholder: &Placeholder, output_name: &str) -> Self { - // Compress the hash according to Nix's implementation - let compressed = compress_hash(&placeholder.hash, 20); - - let compressed_str = nix_base32::to_nix_base32(&compressed); - let clear_text = format!("nix-computed-output:{compressed_str}:{output_name}"); - - let hash = sha256_hash(clear_text.as_bytes()); - Self::new(hash) - } -} - -impl TryFrom for Placeholder { - type Error = anyhow::Error; - - fn try_from(str: String) -> Result { - let hash = match nix_base32::from_nix_base32(&str) { - Some(h) => h, - None => { - return Err(anyhow!("Not valid nix base32 string: {str}")); - } - }; - - Ok(Placeholder::new(hash)) - } -} - -/// Format an output path name according to Nix conventions -pub fn output_path_name(drv_name: &str, output_name: &str) -> String { - if output_name == "out" { - drv_name.to_string() - } else { - format!("{drv_name}-{output_name}") - } -} - -/// Compress a hash to a smaller size by XORing bytes -fn compress_hash(hash: &[u8], new_size: usize) -> Vec { - if hash.is_empty() { - return vec![]; - } - - let mut result = vec![0u8; new_size]; - - for (i, &byte) in hash.iter().enumerate() { - result[i % new_size] ^= byte; - } - - result -} - -/// Calculate SHA-256 hash of data -fn sha256_hash(data: &[u8]) -> Vec { - use sha2::{Digest, Sha256}; - - let mut hasher = Sha256::new(); - hasher.update(data); - hasher.finalize().to_vec() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_standard_placeholder() { - let placeholder = Placeholder::standard_output("out"); - assert_eq!( - placeholder.render(), - PathBuf::from("/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9") - ); - } - - #[test] - fn test_ca_placeholder() { - let store_path = - StorePath::new("/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv").unwrap(); - let placeholder = Placeholder::ca_output(&store_path, "out"); - assert_eq!( - placeholder.render(), - PathBuf::from("/0c6rn30q4frawknapgwq386zq358m8r6msvywcvc89n6m5p2dgbz") - ); - } - - #[test] - fn test_dynamic_placeholder() { - let store_path = - StorePath::new("/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-foo.drv.drv").unwrap(); - let placeholder = Placeholder::ca_output(&store_path, "out"); - let dynamic = Placeholder::dynamic_output(&placeholder, "out"); - assert_eq!( - dynamic.render(), - PathBuf::from("/0gn6agqxjyyalf0dpihgyf49xq5hqxgw100f0wydnj6yqrhqsb3w"), - ) - } - - #[test] - fn test_store_path_parsing() { - let path = StorePath::new("/nix/store/ac8da0sqpg4pyhzyr0qgl26d5dnpn7qp-hello-2.10.tar.gz") - .unwrap(); - assert_eq!(path.hash_part(), "ac8da0sqpg4pyhzyr0qgl26d5dnpn7qp"); - assert_eq!(path.name(), "hello-2.10.tar.gz"); - - // Test with a derivation path - let drv_path = - StorePath::new("/nix/store/q3lv9bi7r4di3kxdjhy7kvwgvpmanfza-hello-2.10.drv").unwrap(); - assert_eq!(drv_path.hash_part(), "q3lv9bi7r4di3kxdjhy7kvwgvpmanfza"); - assert_eq!(drv_path.name(), "hello-2.10.drv"); - assert!(drv_path.is_derivation()); - } - - #[test] - fn test_output_path_name() { - // Test with "out" output - assert_eq!(output_path_name("hello-2.10", "out"), "hello-2.10"); - - // Test with non-"out" output - assert_eq!(output_path_name("hello-2.10", "bin"), "hello-2.10-bin"); - assert_eq!(output_path_name("hello-2.10", "dev"), "hello-2.10-dev"); - } -} diff --git a/crates/nix-libstore/src/prelude.rs b/crates/nix-libstore/src/prelude.rs deleted file mode 100644 index 998f810..0000000 --- a/crates/nix-libstore/src/prelude.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub use crate::derivation::{Derivation, HashAlgorithm, OutputHashMode}; -pub use crate::derived_path::{SingleDerivedPath, SingleDerivedPathBuilt}; -pub use crate::placeholder::Placeholder; -pub use crate::store_path::StorePath; diff --git a/crates/nix-libstore/src/store_path.rs b/crates/nix-libstore/src/store_path.rs deleted file mode 100644 index e9c7bb6..0000000 --- a/crates/nix-libstore/src/store_path.rs +++ /dev/null @@ -1,71 +0,0 @@ -use anyhow::{anyhow, Result}; -use std::fmt; -use std::path::PathBuf; - -/// A Nix store path -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct StorePath { - /// The full path including the store directory - path: PathBuf, -} - -impl StorePath { - /// Create a new store path, validating that it follows Nix path conventions - pub fn new>(path: P) -> Result { - let path_buf = path.as_ref().to_path_buf(); - - // Validate the path has a filename - let filename = path_buf - .file_name() - .and_then(|name| name.to_str()) - .ok_or_else(|| anyhow!("Invalid store path: missing filename"))?; - - // Validate the filename has the expected format with a 32-character hash - if filename.len() <= 33 || filename.chars().nth(32) != Some('-') { - return Err(anyhow!( - "Invalid store path: expected 32-character hash followed by dash: {}", - filename - )); - } - - Ok(Self { path: path_buf }) - } - - /// Get the hash part of the store path (always 32 characters) - pub fn hash_part(&self) -> &str { - let filename = self - .path - .file_name() - .and_then(|name| name.to_str()) - .expect("StorePath was validated at construction"); - - &filename[0..32] - } - - /// Get the name part of the store path (after the hash and dash) - pub fn name(&self) -> &str { - let filename = self - .path - .file_name() - .and_then(|name| name.to_str()) - .expect("StorePath was validated at construction"); - - &filename[33..] - } - - /// Get the full path - pub fn path(&self) -> &PathBuf { - &self.path - } - - /// Check if this is a derivation path - pub fn is_derivation(&self) -> bool { - self.name().ends_with(".drv") - } -} - -impl fmt::Display for StorePath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.path.to_string_lossy()) - } -} diff --git a/crates/nix-ninja-task/Cargo.toml b/crates/nix-ninja-task/Cargo.toml index e4ad1df..d4ec691 100644 --- a/crates/nix-ninja-task/Cargo.toml +++ b/crates/nix-ninja-task/Cargo.toml @@ -9,4 +9,4 @@ license = "MIT" anyhow = "1.0" clap = { version = "4.4", features = ["derive", "env"] } elf = "0.8" -nix-libstore = { path = "../nix-libstore" } +harmonia-store-core = { workspace = true } diff --git a/crates/nix-ninja-task/src/derived_file.rs b/crates/nix-ninja-task/src/derived_file.rs index 7039ac9..e36046b 100644 --- a/crates/nix-ninja-task/src/derived_file.rs +++ b/crates/nix-ninja-task/src/derived_file.rs @@ -1,6 +1,9 @@ +use anyhow::Context; use anyhow::{anyhow, Result}; -use nix_libstore::derived_path::SingleDerivedPath; -use nix_libstore::store_path::StorePath; +use harmonia_store_core::derived_path::SingleDerivedPath; +use harmonia_store_core::placeholder::StorePathOrPlaceholder; +use harmonia_store_core::store_path::StoreDir; +use harmonia_store_core::store_path::StorePath; use std::fmt; use std::fs; use std::os::unix::fs::symlink; @@ -23,15 +26,12 @@ impl DerivedFile { /// Encodes this DerivedFile for passing from nix-ninja to nix-ninja-task. /// /// Format: `"::"` - pub fn to_encoded(&self) -> String { - let path_str = match &self.derived_path { - SingleDerivedPath::Opaque(store_path) => { - store_path.path().to_string_lossy().to_string() - } - SingleDerivedPath::Built(built_path) => { - built_path.placeholder().to_string_lossy().to_string() - } - }; + /// + /// where `` is *without* the store dir. (That is known from context.) + pub fn to_encoded(&self, store_dir: &StoreDir) -> String { + let path_str = store_dir + .display(&StorePathOrPlaceholder::from(&self.derived_path)) + .to_string(); let rel_path_str = self .rel_path .as_ref() @@ -47,12 +47,14 @@ impl DerivedFile { /// Decodes a DerivedFile from the string format created by `to_encoded()`. /// Used by nix-ninja-task to recreate build directory symlinks. - pub fn from_encoded(encoded: &str) -> Result { + pub fn from_encoded(store_dir: &StoreDir, encoded: &str) -> Result { let mut parts = encoded.split(':'); - let store_path = - StorePath::new(parts.next().ok_or_else(|| { - anyhow!("Missing store path in encoded derived file: {encoded}") - })?)?; + let store_path_str = parts + .next() + .ok_or_else(|| anyhow!("Missing store path in encoded derived file: {encoded}"))?; + let store_path: StorePath = store_dir + .parse(store_path_str) + .context("Parsing encoded store path")?; let derived_path = SingleDerivedPath::Opaque(store_path); let build_path = PathBuf::from( parts @@ -67,32 +69,33 @@ impl DerivedFile { rel_path, }) } -} -impl fmt::Display for DerivedFile { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let base_path = match &self.derived_path { - SingleDerivedPath::Opaque(store_path) => store_path.path().clone(), - SingleDerivedPath::Built(built_path) => built_path.placeholder(), - }; + pub fn absolute_path(&self, store_dir: &StoreDir) -> PathBuf { + let base_path = PathBuf::from( + store_dir + .display(&StorePathOrPlaceholder::from(&self.derived_path)) + .to_string(), + ); if let Some(rel_path) = &self.rel_path { - write!(f, "{}", base_path.join(rel_path).to_string_lossy()) + base_path.join(rel_path) } else { - write!(f, "{}", base_path.to_string_lossy()) + base_path } } } -impl From<&DerivedFile> for PathBuf { - fn from(df: &DerivedFile) -> Self { - let base_path = match &df.derived_path { - SingleDerivedPath::Opaque(store_path) => store_path.path().clone(), - SingleDerivedPath::Built(built_path) => built_path.placeholder(), +impl fmt::Display for DerivedFile { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let base_path = match StorePathOrPlaceholder::from(&self.derived_path) { + StorePathOrPlaceholder::StorePath(store_path) => { + PathBuf::from(store_path.to_base_path()) + } + StorePathOrPlaceholder::Placeholder(placeholder) => placeholder.render(), }; - if let Some(rel_path) = &df.rel_path { - base_path.join(rel_path) + if let Some(rel_path) = &self.rel_path { + write!(f, "{}", base_path.join(rel_path).to_string_lossy()) } else { - base_path + write!(f, "{}", base_path.to_string_lossy()) } } } @@ -103,11 +106,12 @@ impl From<&DerivedFile> for PathBuf { /// pointing to the actual file at `derived_file.rel_path`. pub fn create_symlinks( prefix: &std::path::Path, + store_dir: &StoreDir, inputs: Vec, overwrite: bool, ) -> Result<()> { for input in inputs { - let source_path = input.to_string(); + let source_path = input.absolute_path(store_dir); let dest_path = prefix.join(&input.build_path); // Create parent directories if they don't exist @@ -115,9 +119,10 @@ pub fn create_symlinks( fs::create_dir_all(parent)?; } - if !std::path::Path::new(&source_path).exists() { + if !source_path.exists() { return Err(anyhow!( - "nix-ninja-task: symlink source does not exist: {source_path}" + "nix-ninja-task: symlink source does not exist: {}", + source_path.to_string_lossy() )); } @@ -128,7 +133,7 @@ pub fn create_symlinks( symlink(&source_path, &dest_path).map_err(|e| { anyhow!( "Failed to create symlink from {} to {}: {}", - source_path, + source_path.to_string_lossy(), dest_path.display(), e ) diff --git a/crates/nix-ninja-task/src/main.rs b/crates/nix-ninja-task/src/main.rs index 07dc945..acfc5ee 100644 --- a/crates/nix-ninja-task/src/main.rs +++ b/crates/nix-ninja-task/src/main.rs @@ -1,6 +1,6 @@ use anyhow::Result; -use clap::command; use clap::Parser; +use harmonia_store_core::store_path::StoreDir; use nix_ninja_task::derived_file::{create_symlinks, DerivedFile}; use nix_ninja_task::patchelf; use std::env; @@ -13,7 +13,7 @@ use std::process::{Command, Stdio}; pub struct Cli { /// Specify the Nix store directory. #[arg(long = "store-dir", env = "NIX_STORE", default_value = "/nix/store")] - pub store_dir: PathBuf, + pub store_dir: StoreDir, /// Directory prefix to recreate sources via symlinks. #[arg(long = "build-dir", default_value = "/build/source/build")] @@ -44,14 +44,14 @@ fn main() -> Result<()> { let mut inputs = Vec::new(); for encoded in cli.inputs.split_whitespace() { // println!("Processing input {}", encoded); - let input = DerivedFile::from_encoded(encoded)?; + let input = DerivedFile::from_encoded(&cli.store_dir, encoded)?; inputs.push(input); } let mut outputs = Vec::new(); for encoded in cli.outputs.split_whitespace() { // println!("Processing output {}", encoded); - let output = DerivedFile::from_encoded(encoded)?; + let output = DerivedFile::from_encoded(&cli.store_dir, encoded)?; outputs.push(output); } @@ -59,7 +59,7 @@ fn main() -> Result<()> { // symlinked while preserving the original directory hierarchy of the // sources. This ensures relative includes and other path-dependent // references remain valid. - create_symlinks(&cli.build_dir, inputs, false)?; + create_symlinks(&cli.build_dir, &cli.store_dir, inputs, false)?; println!( "nix-ninja-task: Setup source directory in {}", cli.build_dir.display() @@ -85,7 +85,7 @@ fn main() -> Result<()> { // Fix ELF RPATH to ensure it's linked against /nix/store paths rather // than relative path binaries in the build dir. - patchelf::fix_rpaths(&cli.store_dir, &outputs)?; + patchelf::fix_rpaths(cli.store_dir.to_path(), &outputs)?; // Outputs must be created in build directory and then copied out because // ninja build rules can have implicit outputs that we have no way of @@ -96,7 +96,7 @@ fn main() -> Result<()> { outputs.len(), ); for output in &outputs { - let target_path = PathBuf::from(&output.to_string()); + let target_path = output.absolute_path(&cli.store_dir); if let Some(parent) = target_path.parent() { fs::create_dir_all(parent)?; } diff --git a/crates/nix-ninja/Cargo.toml b/crates/nix-ninja/Cargo.toml index 23a4578..7e9b8bc 100644 --- a/crates/nix-ninja/Cargo.toml +++ b/crates/nix-ninja/Cargo.toml @@ -9,9 +9,10 @@ license = "MIT" anyhow = { version = "1.0", features = ["backtrace"] } clap = { version = "4.4", features = ["derive", "env"] } deps-infer = { path = "../deps-infer" } +harmonia-store-core = { workspace = true } +harmonia-utils-hash = { workspace = true } include-graph = { git = "https://github.com/hinshun/igraph", branch = "performance-improvements" } n2 = { git = "https://github.com/hinshun/n2", branch = "feature/minimal-pub", default-features = false } -nix-libstore = { path = "../nix-libstore" } nix-ninja-task = { path = "../nix-ninja-task" } nix-tool = { path = "../nix-tool" } regex = "1" diff --git a/crates/nix-ninja/src/build.rs b/crates/nix-ninja/src/build.rs index 5933029..c22ee55 100644 --- a/crates/nix-ninja/src/build.rs +++ b/crates/nix-ninja/src/build.rs @@ -1,6 +1,7 @@ use crate::task; use anyhow::bail; use anyhow::{anyhow, Result}; +use harmonia_store_core::store_path::StoreDir; use n2::densemap::DenseMap; use n2::graph::{Build, BuildId, FileId, Graph}; use n2::{canon, load, scanner}; @@ -12,7 +13,7 @@ use std::path::PathBuf; pub struct BuildConfig { pub build_dir: PathBuf, - pub store_dir: PathBuf, + pub store_dir: StoreDir, pub nix_tool: String, pub is_output_derivation: bool, } @@ -29,7 +30,7 @@ pub fn build( extra_args: Vec::new(), }); - let tools = task::Tools::new(nix)?; + let tools = task::Tools::new(&config.store_dir, nix)?; let mut runner = task::Runner::new( tools, diff --git a/crates/nix-ninja/src/cli.rs b/crates/nix-ninja/src/cli.rs index c4d0e61..dd513be 100644 --- a/crates/nix-ninja/src/cli.rs +++ b/crates/nix-ninja/src/cli.rs @@ -3,6 +3,7 @@ use crate::local; use crate::subtool::dynamic_task; use anyhow::{anyhow, Result}; use clap::Parser; +use harmonia_store_core::store_path::StoreDir; use nix_ninja_task::derived_file::DerivedFile; use nix_tool::{NixTool, StoreConfig}; use std::{ @@ -48,7 +49,7 @@ pub struct Cli { /// Specify the Nix store directory #[arg(long = "store-dir", default_value = "/nix/store", env = "NIX_STORE")] - pub store_dir: PathBuf, + pub store_dir: StoreDir, /// Specify the Nix tool #[arg(long = "nix-tool", default_value = "nix", env = "NIX_TOOL")] @@ -87,7 +88,7 @@ pub fn run() -> Result { return subtool( nix_tool, &build_dir, - cli.store_dir.as_path(), + &cli.store_dir, &tool, cli.targets.clone(), ); @@ -97,9 +98,20 @@ pub fn run() -> Result { Ok(derived_file) => { if cli.is_output_derivation { let out = env::var("out").map_err(|_| anyhow!("Expected $out to be set"))?; - fs::copy(derived_file.derived_path.store_path().path(), out)?; + fs::copy( + derived_file + .derived_path + .root_path() + .to_absolute_path(&cli.store_dir), + out, + )?; } else { - local::symlink_derived_files(&nix_tool, &build_dir, &[derived_file])?; + local::symlink_derived_files( + &nix_tool, + &cli.store_dir, + &build_dir, + &[derived_file], + )?; } Ok(0) } @@ -128,7 +140,7 @@ fn build(cli: &Cli, build_dir: &Path) -> Result { fn subtool( nix_tool: NixTool, build_dir: &Path, - store_dir: &Path, + store_dir: &StoreDir, subtool_name: &str, targets: Vec, ) -> Result { @@ -141,7 +153,8 @@ fn subtool( "drv" => { let cli = Cli::parse(); let derived_file = build(&cli, build_dir)?; - let output = nix_tool.derivation_show(&derived_file.derived_path.store_path())?; + let output = + nix_tool.derivation_show(store_dir, derived_file.derived_path.root_path())?; let stdout = str::from_utf8(&output.stdout)?; println!("{stdout}"); } @@ -163,7 +176,7 @@ fn subtool( Ok(0) } -fn dynamic_task_subtool(nix_tool: NixTool, store_dir: &Path, targets: Vec) -> i32 { +fn dynamic_task_subtool(nix_tool: NixTool, store_dir: &StoreDir, targets: Vec) -> i32 { match dynamic_task::run(nix_tool, store_dir, targets) { Ok(code) => code, Err(err) => { diff --git a/crates/nix-ninja/src/local.rs b/crates/nix-ninja/src/local.rs index e959cf7..1c13987 100644 --- a/crates/nix-ninja/src/local.rs +++ b/crates/nix-ninja/src/local.rs @@ -1,5 +1,6 @@ use anyhow::Result; -use nix_libstore::prelude::SingleDerivedPath; +use harmonia_store_core::derived_path::SingleDerivedPath; +use harmonia_store_core::store_path::StoreDir; use nix_ninja_task::derived_file::{create_symlinks, DerivedFile}; use nix_tool::NixTool; use std::collections::HashMap; @@ -7,6 +8,7 @@ use std::path::{Path, PathBuf}; pub fn build_derived_files( nix_tool: &NixTool, + store_dir: &StoreDir, derived_files: &[DerivedFile], ) -> Result> { let derived_paths: Vec<_> = derived_files @@ -15,7 +17,7 @@ pub fn build_derived_files( .collect(); // Build derived paths so the Nix store paths exist on the host. - let store_paths = nix_tool.build(&derived_paths)?; + let store_paths = nix_tool.build(store_dir, &derived_paths)?; // Create mapping from build_path to actual store path let built_paths: HashMap = derived_files @@ -23,9 +25,9 @@ pub fn build_derived_files( .zip(store_paths.iter()) .map(|(df, store_path)| { let actual_path = if let Some(rel_path) = &df.rel_path { - store_path.path().join(rel_path) + store_path.to_absolute_path(store_dir).join(rel_path) } else { - store_path.path().to_path_buf() + store_path.to_absolute_path(store_dir) }; (df.build_path.clone(), actual_path) }) @@ -36,6 +38,7 @@ pub fn build_derived_files( pub fn symlink_derived_files( nix_tool: &NixTool, + store_dir: &StoreDir, prefix: &Path, derived_files: &[DerivedFile], ) -> Result<()> { @@ -43,7 +46,7 @@ pub fn symlink_derived_files( .iter() .map(|df| df.derived_path.clone()) .collect(); - let store_paths = nix_tool.build(&derived_paths)?; + let store_paths = nix_tool.build(store_dir, &derived_paths)?; // Create new DerivedFiles with opaque store paths instead of placeholders let opaque_files: Vec = derived_files @@ -56,7 +59,7 @@ pub fn symlink_derived_files( }) .collect(); - create_symlinks(prefix, opaque_files, true)?; + create_symlinks(prefix, store_dir, opaque_files, true)?; Ok(()) } diff --git a/crates/nix-ninja/src/subtool/dynamic_task.rs b/crates/nix-ninja/src/subtool/dynamic_task.rs index 6cadb3d..2a98169 100644 --- a/crates/nix-ninja/src/subtool/dynamic_task.rs +++ b/crates/nix-ninja/src/subtool/dynamic_task.rs @@ -1,5 +1,7 @@ use anyhow::{anyhow, Result}; -use nix_libstore::prelude::{Derivation, StorePath}; +use harmonia_store_core::derivation::Derivation; +use harmonia_store_core::derived_path::SingleDerivedPath; +use harmonia_store_core::store_path::{StoreDir, StorePath}; use nix_ninja_task::derived_file::DerivedFile; use nix_tool::NixTool; use std::{ @@ -10,41 +12,45 @@ use std::{ use crate::task::discover_c_includes; -pub fn run(nix_tool: NixTool, store_dir: &Path, targets: Vec) -> Result { +pub fn run(nix_tool: NixTool, store_dir: &StoreDir, targets: Vec) -> Result { let input_drv = targets .first() .ok_or_else(|| anyhow!("Expected derivation path as argument"))?; let drv_json = fs::read_to_string(input_drv)?; - let mut drv = Derivation::from_json(&drv_json)?; + let mut drv: Derivation = serde_json::from_str(&drv_json)?; println!("nix-ninja-dynamic-task: Processing derivation {}", drv.name); // Stage 1: Prepare build environment - let (build_dir, built_paths) = prepare_build_environment()?; + let (build_dir, built_paths) = prepare_build_environment(store_dir)?; // Stage 2: Discover dynamic dependencies let (discovered_deps, discovered_store_paths) = discover_dynamic_dependencies(&nix_tool, store_dir, &build_dir, &drv, built_paths)?; // Stage 3: Update derivation with discovered dependencies - let new_deps = - update_derivation_with_discoveries(&mut drv, discovered_deps, discovered_store_paths)?; + let new_deps = update_derivation_with_discoveries( + &mut drv, + discovered_deps, + discovered_store_paths, + store_dir, + )?; // Print discovery results if !new_deps.is_empty() { for dep in &new_deps { println!( "nix-ninja-dynamic-task: Discovered dependency: {}", - dep.derived_path.store_path() + dep.derived_path.root_path() ); } } else { println!("nix-ninja-dynamic-task: No new dependencies discovered"); } - let drv_path = nix_tool.derivation_add(&drv)?; + let drv_path = nix_tool.derivation_add(store_dir, &drv)?; let out = env::var("out").map_err(|_| anyhow!("Expected $out to be set"))?; - fs::copy(drv_path.path(), out)?; + fs::copy(drv_path.to_absolute_path(store_dir), out)?; println!("nix-ninja-dynamic-task: Added derivation to store: {drv_path}"); Ok(0) @@ -52,7 +58,7 @@ pub fn run(nix_tool: NixTool, store_dir: &Path, targets: Vec) -> Result< /// Stage 1: Prepare build environment by setting up directories, copying source, /// and building derived files -fn prepare_build_environment() -> Result<(PathBuf, HashMap)> { +fn prepare_build_environment(store_dir: &StoreDir) -> Result<(PathBuf, HashMap)> { // Set up build directory using NIX_BUILD_TOP let build_top = env::var("NIX_BUILD_TOP").map_err(|_| anyhow!("Expected $NIX_BUILD_TOP to be set"))?; @@ -74,14 +80,14 @@ fn prepare_build_environment() -> Result<(PathBuf, HashMap)> { // Get built inputs for dynamic dependency discovery let derived_files: Vec = inputs .split_whitespace() - .filter_map(|encoded| DerivedFile::from_encoded(encoded).ok()) + .filter_map(|encoded| DerivedFile::from_encoded(store_dir, encoded).ok()) .collect(); // In derivation mode, built files are already available as store paths // Create the virtual paths mapping from the derived files let built_paths: HashMap = derived_files .iter() - .map(|df| (df.build_path.clone(), PathBuf::from(df))) + .map(|df| (df.build_path.clone(), df.absolute_path(store_dir))) .collect(); Ok((build_dir, built_paths)) @@ -90,15 +96,16 @@ fn prepare_build_environment() -> Result<(PathBuf, HashMap)> { /// Stage 2: Discover dynamic dependencies by analyzing built files for includes pub fn discover_dynamic_dependencies( nix_tool: &NixTool, - store_dir: &Path, + store_dir: &StoreDir, build_dir: &Path, drv: &Derivation, built_paths: HashMap, ) -> Result<(Vec, Vec)> { - let cmdline = drv + let cmdline_bytes = drv .args .first() .ok_or_else(|| anyhow!("No command line found in derivation"))?; + let cmdline = std::str::from_utf8(cmdline_bytes)?; let files: Vec = built_paths.keys().cloned().collect(); @@ -118,14 +125,21 @@ pub fn update_derivation_with_discoveries( drv: &mut Derivation, discovered_deps: Vec, discovered_store_paths: Vec, + store_dir: &StoreDir, ) -> Result> { for store_path in &discovered_store_paths { - drv.add_input_src(store_path); + drv.inputs + .insert(SingleDerivedPath::Opaque(store_path.clone())); } // Get NIX_NINJA_INPUTS from derivation environment, these are the existing // inputs of the derivation without the discovered inputs. - let drv_inputs = drv.env.get("NIX_NINJA_INPUTS").map_or("", |v| v); + let key = b"NIX_NINJA_INPUTS"; + let drv_inputs = drv + .env + .iter() + .find(|(k, _)| k.as_ref() == key) + .map_or("", |(_, v)| std::str::from_utf8(v).unwrap()); // Parse existing derivation inputs into a HashSet for deduplication let mut input_set: HashSet = drv_inputs @@ -135,7 +149,7 @@ pub fn update_derivation_with_discoveries( let mut new_deps = Vec::new(); for derived_file in discovered_deps { - let encoded = derived_file.to_encoded(); + let encoded = derived_file.to_encoded(store_dir); // Skip if already in input set if input_set.contains(&encoded) { @@ -144,14 +158,17 @@ pub fn update_derivation_with_discoveries( new_deps.push(derived_file.clone()); input_set.insert(encoded); - drv.add_derived_path(&derived_file.derived_path); + drv.inputs.insert(derived_file.derived_path.clone()); } if !new_deps.is_empty() { // Update NIX_NINJA_INPUTS with sorted list let mut inputs: Vec = input_set.into_iter().collect(); inputs.sort(); - drv.set_env("NIX_NINJA_INPUTS", &inputs.join(" ")); + drv.env.insert( + b"NIX_NINJA_INPUTS"[..].into(), + inputs.join(" ").into_bytes().into(), + ); } Ok(new_deps) diff --git a/crates/nix-ninja/src/task.rs b/crates/nix-ninja/src/task.rs index dc1d240..e13dd42 100644 --- a/crates/nix-ninja/src/task.rs +++ b/crates/nix-ninja/src/task.rs @@ -1,17 +1,21 @@ use crate::local; use crate::relative_from::relative_from; use crate::subtool::dynamic_task; -use anyhow::{anyhow, Error, Result}; +use anyhow::{anyhow, Context, Error, Result}; use deps_infer::c_include_parser; +use harmonia_store_core::derivation::{Derivation, DerivationOutput}; +use harmonia_store_core::derived_path::{OutputName, SingleDerivedPath}; +use harmonia_store_core::placeholder::Placeholder; +use harmonia_store_core::store_path::{ContentAddressMethodAlgorithm, StoreDir, StorePath}; use n2::{ canon, graph::{self, Build, BuildDependencies, BuildId, File, FileId}, }; -use nix_libstore::prelude::*; use nix_ninja_task::derived_file::DerivedFile; use nix_tool::NixTool; use regex::Regex; use sha2::{Digest, Sha256}; +use std::str::FromStr; use std::{ collections::{HashMap, HashSet}, env, fs, @@ -34,15 +38,15 @@ pub struct Tools { } impl Tools { - pub fn new(nix_tool: NixTool) -> Result { + pub fn new(store_dir: &StoreDir, nix_tool: NixTool) -> Result { Ok(Tools { - cc: which_store_path("cc")?, - coreutils: which_store_path("coreutils")?, - nix: which_store_path("nix")?, + cc: which_store_path(store_dir, "cc")?, + coreutils: which_store_path(store_dir, "coreutils")?, + nix: which_store_path(store_dir, "nix")?, nix_tool, - nix_ninja: which_store_path("nix-ninja")?, - nix_ninja_task: which_store_path("nix-ninja-task")?, - patchelf: which_store_path("patchelf")?, + nix_ninja: which_store_path(store_dir, "nix-ninja")?, + nix_ninja_task: which_store_path(store_dir, "nix-ninja-task")?, + patchelf: which_store_path(store_dir, "patchelf")?, }) } } @@ -60,7 +64,7 @@ struct Task { build_dir: PathBuf, build_deps: BuildDependencies, - store_dir: PathBuf, + store_dir: StoreDir, cmdline: Option, desc: Option, @@ -91,7 +95,7 @@ pub struct BuildResult { pub struct RunnerConfig { pub system: String, pub build_dir: PathBuf, - pub store_dir: PathBuf, + pub store_dir: StoreDir, pub is_output_derivation: bool, } @@ -112,7 +116,7 @@ pub struct Runner { impl Runner { pub fn new(tools: Tools, config: RunnerConfig) -> Result { - let store_dir_str = config.store_dir.to_string_lossy(); + let store_dir_str = config.store_dir.to_string(); let pattern = format!( r"{}\/[a-z0-9]{{32}}-[0-9a-zA-Z\+\-\._\?=]+", regex::escape(&store_dir_str) @@ -139,7 +143,7 @@ impl Runner { // Extract store paths from wrapper variables once let mut wrapper_store_paths = Vec::new(); for value in wrapper_vars.values() { - let found_store_paths = extract_store_paths(&store_regex, value)?; + let found_store_paths = extract_store_paths(&config.store_dir, &store_regex, value)?; wrapper_store_paths.extend(found_store_paths); } @@ -176,8 +180,12 @@ impl Runner { } let path = entry.into_path(); - let derived_file = - new_opaque_file(&self.tools.nix_tool, &self.config.build_dir, path.clone())?; + let derived_file = new_opaque_file( + &self.tools.nix_tool, + &self.config.store_dir, + &self.config.build_dir, + path.clone(), + )?; let fid = self.add_derived_file(files, derived_file.clone()); self.build_dir_inputs.insert(fid, derived_file); } @@ -208,7 +216,7 @@ impl Runner { nix_build_lock, ) { Ok(final_derived_path) => (Some(final_derived_path), None), - Err(err) => (None, Some(err.context(format!("Failed to handle derivation result for task '{}' (derivation: {})\nDerivation JSON:\n{}", task.name, drv.name, drv.to_json_pretty().unwrap_or_else(|_| "Failed to serialize derivation".to_string()))))), + Err(err) => (None, Some(err.context(format!("Failed to handle derivation result for task '{}' (derivation: {})\nDerivation JSON:\n{}", task.name, drv.name, serde_json::to_string_pretty(&drv).unwrap_or_else(|_| "Failed to serialize derivation".to_string()))))), }, Err(err) => (None, Some(err.context(format!("Failed to build task derivation for task '{}'", task.name)))), }; @@ -252,7 +260,10 @@ impl Runner { eprintln!("Backtrace: {}", err.backtrace()); let debug_info = if let Some(derived_path) = &result.derived_path { - format!("derivation: {derived_path}") + format!( + "derivation: {}", + self.config.store_dir.display(derived_path) + ) } else { format!("build_id: {:?}", result.bid) }; @@ -288,7 +299,7 @@ impl Runner { } fn new_task(&mut self, files: &mut graph::GraphFiles, build: &Build) -> Result { - let store_dir = self.config.store_dir.to_string_lossy().into_owned(); + let store_dir = self.config.store_dir.to_string(); // Provide the task access to all the original files for explicit // inputs and implicit/explicit outputs. @@ -320,6 +331,7 @@ impl Runner { let input = new_opaque_file( &self.tools.nix_tool, + &self.config.store_dir, &self.config.build_dir, file.name.clone().into(), )?; @@ -386,7 +398,8 @@ impl Runner { // Extract store paths from cmdline and add pre-extracted wrapper store paths let mut input_srcs = self.wrapper_store_paths.clone(); if let Some(cmdline) = &build.cmdline { - let found_store_paths = extract_store_paths(&self.store_regex, cmdline)?; + let found_store_paths = + extract_store_paths(&self.config.store_dir, &self.store_regex, cmdline)?; input_srcs.extend(found_store_paths); } @@ -417,14 +430,20 @@ fn build_task_derivation(tools: Tools, task: Task) -> Result { }; let mut drv = Derivation::new( - &task.name, - &task.system, - &format!("{}/bin/nix-ninja-task", tools.nix_ninja_task), + task.name.parse()?, + task.system.clone().into_bytes().into(), + format!( + "{}/bin/nix-ninja-task", + task.store_dir.display(&tools.nix_ninja_task) + ) + .into_bytes() + .into(), ); - drv.add_arg(cmdline); + drv.args.push(cmdline.to_string().into_bytes().into()); if let Some(desc) = &task.desc { - drv.add_arg(&format!("--description={desc}")); + drv.args + .push(format!("--description={desc}").into_bytes().into()); } // Propagate wrapper environment variables to the task. @@ -437,28 +456,36 @@ fn build_task_derivation(tools: Tools, task: Task) -> Result { } else { value.clone() }; - drv.set_env(key, &final_value); + drv.env.insert( + key.clone().into_bytes().into(), + final_value.into_bytes().into(), + ); } // Add pre-extracted store paths from cmdline and wrapper vars for store_path in &task.input_srcs { - drv.add_input_src(store_path); + drv.inputs + .insert(SingleDerivedPath::Opaque(store_path.clone())); } // Needed by all tasks. - drv.add_input_src(&tools.cc) - .add_input_src(&tools.coreutils) - .add_input_src(&tools.nix_ninja_task) - .add_input_src(&tools.patchelf); + drv.inputs + .insert(SingleDerivedPath::Opaque(tools.cc.clone())); + drv.inputs + .insert(SingleDerivedPath::Opaque(tools.coreutils.clone())); + drv.inputs + .insert(SingleDerivedPath::Opaque(tools.nix_ninja_task.clone())); + drv.inputs + .insert(SingleDerivedPath::Opaque(tools.patchelf.clone())); // Add all ninja build inputs. let mut input_set: HashSet = HashSet::new(); for input in &task.inputs { // Declare input for derivation. - drv.add_derived_path(&input.derived_path); + drv.inputs.insert(input.derived_path.clone()); // Encode input for nix-ninja-task. - let encoded = &input.to_encoded(); + let encoded = &input.to_encoded(&task.store_dir); input_set.insert(encoded.clone()); } @@ -473,7 +500,7 @@ fn build_task_derivation(tools: Tools, task: Task) -> Result { .iter() .filter_map(|input| match input.derived_path { SingleDerivedPath::Opaque(_) => Some(input.build_path.clone()), - SingleDerivedPath::Built(_) => None, // Will be filled in by dynamic task derivation + SingleDerivedPath::Built { .. } => None, // Will be filled in by dynamic task derivation }) .collect(); @@ -488,12 +515,13 @@ fn build_task_derivation(tools: Tools, task: Task) -> Result { // Add discovered store paths as input sources only for store_path in discovered_store_paths { - drv.add_input_src(&store_path); + drv.inputs + .insert(SingleDerivedPath::Opaque(store_path.clone())); } // Add discovered deps to NIX_NINJA_INPUTS and derivation for derived_file in discovered_deps { - let encoded = derived_file.to_encoded(); + let encoded = derived_file.to_encoded(&task.store_dir); // Skip if already in input_set if input_set.contains(&encoded) { @@ -501,7 +529,7 @@ fn build_task_derivation(tools: Tools, task: Task) -> Result { } input_set.insert(encoded); - drv.add_derived_path(&derived_file.derived_path); + drv.inputs.insert(derived_file.derived_path.clone()); discovered_inputs.push(derived_file); } } @@ -511,17 +539,27 @@ fn build_task_derivation(tools: Tools, task: Task) -> Result { let mut inputs: Vec = input_set.into_iter().collect(); inputs.sort(); - drv.set_env("NIX_NINJA_INPUTS", &inputs.join(" ")); + drv.env.insert( + b"NIX_NINJA_INPUTS"[..].into(), + inputs.join(" ").into_bytes().into(), + ); // Add all ninja build outputs. let mut outputs: Vec = Vec::new(); for output_path in &task.outputs { // Declare a content addressed output. let normalized_name = normalize_output(&output_path.to_string_lossy()); - drv.add_ca_output(&normalized_name, HashAlgorithm::Sha256, OutputHashMode::Nar); + drv.outputs.insert( + normalized_name.parse()?, + DerivationOutput::CAFloating(ContentAddressMethodAlgorithm::Recursive( + harmonia_utils_hash::Algorithm::SHA256, + )), + ); // Create a placeholder and encode output for nix-ninja-task. - let placeholder = Placeholder::standard_output(&normalized_name); + let placeholder = Placeholder::standard_output( + &OutputName::from_str(&normalized_name).context("While creating placeholder")?, + ); let encoded = format!( "{}:{}:{}", &placeholder.render().display(), @@ -530,14 +568,17 @@ fn build_task_derivation(tools: Tools, task: Task) -> Result { ); outputs.push(encoded); } - drv.set_env("NIX_NINJA_OUTPUTS", &outputs.join(" ")); + drv.env.insert( + b"NIX_NINJA_OUTPUTS"[..].into(), + outputs.join(" ").into_bytes().into(), + ); { // Prepare $PATH to have coreutils. let mut path: Vec = vec![ - format!("{}/bin", tools.cc), - format!("{}/bin", tools.coreutils), - format!("{}/bin", tools.patchelf), + format!("{}/bin", task.store_dir.display(&tools.cc)), + format!("{}/bin", task.store_dir.display(&tools.coreutils)), + format!("{}/bin", task.store_dir.display(&tools.patchelf)), ]; let cmdline_binary = cmdline @@ -546,15 +587,17 @@ fn build_task_derivation(tools: Tools, task: Task) -> Result { .ok_or_else(|| anyhow!("No command found in cmdline"))?; // TODO: If you don't find it it's ok, e.g. ./generated_binary - let cmdline_path = which_store_path(cmdline_binary)?; + let cmdline_path = which_store_path(&task.store_dir, cmdline_binary)?; - drv.add_input_src(&cmdline_path); - path.push(format!("{cmdline_path}/bin")); - drv.set_env("PATH", &path.join(":")); + drv.inputs + .insert(SingleDerivedPath::Opaque(cmdline_path.clone())); + path.push(format!("{}/bin", task.store_dir.display(&cmdline_path))); + drv.env + .insert(b"PATH"[..].into(), path.join(":").into_bytes().into()); } // For debugging purposes: - // let json = &drv.to_json_pretty()?; + // let json = serde_json::to_string_pretty(&drv)?; // println!("Derivation:\n{json}"); Ok(drv) @@ -570,69 +613,96 @@ fn build_task_derivation(tools: Tools, task: Task) -> Result { // and wrote that to its $deps output. fn build_dynamic_task_derivation( tools: Tools, + store_dir: &StoreDir, input_drv: Derivation, built_inputs: Vec, ) -> Result { let mut drv = Derivation::new( - &format!("{}.drv", input_drv.name), - &input_drv.system, - &format!("{}/bin/nix-ninja", tools.nix_ninja), + format!("{}.drv", input_drv.name).parse()?, + input_drv.platform.clone(), + format!("{}/bin/nix-ninja", store_dir.display(&tools.nix_ninja)) + .into_bytes() + .into(), ); - drv.add_input_src(&tools.nix_ninja) - .add_input_src(&tools.nix); + drv.inputs + .insert(SingleDerivedPath::Opaque(tools.nix_ninja.clone())); + drv.inputs + .insert(SingleDerivedPath::Opaque(tools.nix.clone())); // Add built inputs as dependencies so the dynamic task has access to them for scanning for built_input in &built_inputs { - drv.add_derived_path(&built_input.derived_path); + drv.inputs.insert(built_input.derived_path.clone()); } // Encode built inputs for NIX_NINJA_INPUTS so dynamic task can process them let mut inputs: Vec = built_inputs .iter() - .map(|input| input.to_encoded()) + .map(|input| input.to_encoded(store_dir)) .collect(); inputs.sort(); - drv.set_env("NIX_NINJA_INPUTS", &inputs.join(" ")); + drv.env.insert( + b"NIX_NINJA_INPUTS"[..].into(), + inputs.join(" ").into_bytes().into(), + ); - drv.add_ca_output("out", HashAlgorithm::Sha256, OutputHashMode::Text); - drv.set_env( - "out", - &Placeholder::standard_output("out") + drv.outputs.insert( + "out".parse()?, + DerivationOutput::CAFloating(ContentAddressMethodAlgorithm::Text), + ); + drv.env.insert( + b"out"[..].into(), + Placeholder::standard_output(&OutputName::from_str("out")?) .render() - .to_string_lossy(), + .to_string_lossy() + .to_string() + .into_bytes() + .into(), ); // Add the dynamic-task subtool argument - drv.add_arg("-t").add_arg("dynamic-task"); + drv.args.push(b"-t"[..].into()); + drv.args.push(b"dynamic-task"[..].into()); // Propagate sources to dynamic task for it discover inputs. let src = env::var("src").map_err(|_| anyhow!("Expected $src to be set"))?; - drv.set_env("src", &src); - let src_store_path = StorePath::new(src.clone())?; - drv.add_input_src(&src_store_path); + drv.env + .insert(b"src"[..].into(), src.clone().into_bytes().into()); + let src_store_path: StorePath = store_dir.parse(&src)?; + drv.inputs + .insert(SingleDerivedPath::Opaque(src_store_path.clone())); // Set up PATH to include nix binary - let path = format!("{}/bin", tools.nix); - drv.set_env("PATH", &path); + let path = format!("{}/bin", store_dir.display(&tools.nix)); + drv.env.insert(b"PATH"[..].into(), path.into_bytes().into()); // Requires extra experimental features to add our derivations. - drv.set_env( - "NIX_CONFIG", - "extra-experimental-features = nix-command ca-derivations dynamic-derivations", + drv.env.insert( + b"NIX_CONFIG"[..].into(), + b"extra-experimental-features = nix-command ca-derivations dynamic-derivations"[..].into(), ); // Require recursive-nix to allow nix commands inside the build - drv.set_env("requiredSystemFeatures", "recursive-nix"); + drv.env.insert( + b"requiredSystemFeatures"[..].into(), + b"recursive-nix"[..].into(), + ); // Serialize the derivation to a temporary file and add to nix store - let drv_json = input_drv.to_json()?; + let drv_json = serde_json::to_string(&input_drv)?; let temp_file = std::env::temp_dir().join(format!("drv-{}.json", input_drv.name)); fs::write(&temp_file, &drv_json)?; - let drv_json_path = tools.nix_tool.store_add(&temp_file)?; + let drv_json_path = tools.nix_tool.store_add(store_dir, &temp_file)?; // Add derivation.json as input dependency and argument - drv.add_input_src(&drv_json_path); - drv.add_arg(&drv_json_path.to_string()); + drv.inputs + .insert(SingleDerivedPath::Opaque(drv_json_path.clone())); + drv.args.push( + store_dir + .display(&drv_json_path) + .to_string() + .into_bytes() + .into(), + ); Ok(drv) } @@ -650,7 +720,7 @@ fn handle_derivation_result( let built_inputs: Vec = if task.deps.as_ref() == Some(&"gcc".to_string()) { task.inputs .iter() - .filter(|input| matches!(input.derived_path, SingleDerivedPath::Built(_))) + .filter(|input| matches!(input.derived_path, SingleDerivedPath::Built { .. })) .cloned() .collect() } else { @@ -661,12 +731,15 @@ fn handle_derivation_result( // If we're in Nix sandbox, create a dynamic derivation to handle // dynamic dependencies. if config.is_output_derivation { - let dynamic_drv = build_dynamic_task_derivation(tools.clone(), drv, built_inputs)?; - let dynamic_drv_path = tools.nix_tool.derivation_add(&dynamic_drv)?; - Ok(SingleDerivedPath::Built(SingleDerivedPathBuilt::new( - dynamic_drv_path, - "out".to_string(), - ))) + let dynamic_drv = + build_dynamic_task_derivation(tools.clone(), &config.store_dir, drv, built_inputs)?; + let dynamic_drv_path = tools + .nix_tool + .derivation_add(&config.store_dir, &dynamic_drv)?; + Ok(SingleDerivedPath::Built { + drv_path: Arc::new(SingleDerivedPath::Opaque(dynamic_drv_path)), + output: "out".parse().unwrap(), + }) } else { // Otherwise, symlink these built_inputs into build_dir and do // dependency discovery locally. @@ -678,7 +751,7 @@ fn handle_derivation_result( // TODO: This isn't ideal, perhaps we buffer the logs or emit // JSON events for log aggregation. let _lock = nix_build_lock.lock().unwrap(); - local::build_derived_files(&tools.nix_tool, &built_inputs)? + local::build_derived_files(&tools.nix_tool, &config.store_dir, &built_inputs)? }; let (discovered_deps, discovered_store_paths) = @@ -694,40 +767,47 @@ fn handle_derivation_result( &mut drv, discovered_deps, discovered_store_paths, + &config.store_dir, )?; - let drv_path = tools.nix_tool.derivation_add(&drv)?; + let drv_path = tools.nix_tool.derivation_add(&config.store_dir, &drv)?; Ok(SingleDerivedPath::Opaque(drv_path)) } } else { - let drv_path = tools.nix_tool.derivation_add(&drv)?; + let drv_path = tools.nix_tool.derivation_add(&config.store_dir, &drv)?; Ok(SingleDerivedPath::Opaque(drv_path)) } } -pub fn which_store_path(binary_name: &str) -> Result { +pub fn which_store_path(store_dir: &StoreDir, binary_name: &str) -> Result { let binary_path = which(binary_name).map_err(|err| anyhow!("Failed to find {}: {}", binary_name, err))?; // Canonicalize will resolve all symlinks and return an absolute path let canonical_path = std::fs::canonicalize(binary_path)?; - let store_path = canonical_path + let store_path_dir = canonical_path .parent() // Get bin/ directory .and_then(|p| p.parent()) // Get the store path ($out) .ok_or_else(|| anyhow!("Cannot determine store path from binary: {}", binary_name))?; - StorePath::new(store_path) + store_dir + .parse(&store_path_dir.to_string_lossy()) + .context("Failed to parse store path") } -fn extract_store_paths(store_regex: &Regex, s: &str) -> Result> { +fn extract_store_paths( + store_dir: &StoreDir, + store_regex: &Regex, + s: &str, +) -> Result> { let mut store_paths = Vec::new(); for cap in store_regex.find_iter(s) { - let store_path = StorePath::new(cap.as_str())?; + let store_path: StorePath = store_dir.parse(cap.as_str())?; if store_path.is_derivation() { continue; } - if !store_path.path().exists() { + if !store_path.to_absolute_path(store_dir).exists() { continue; } store_paths.push(store_path); @@ -737,6 +817,7 @@ fn extract_store_paths(store_regex: &Regex, s: &str) -> Result> { fn new_opaque_file( nix: &NixTool, + store_dir: &StoreDir, build_dir: &std::path::Path, path: PathBuf, ) -> Result { @@ -745,7 +826,7 @@ fn new_opaque_file( canon::canonicalize_path(&mut path); let canonical_path = fs::canonicalize(&path)?; - let store_path = nix.store_add(&canonical_path)?; + let store_path = nix.store_add(store_dir, &canonical_path)?; Ok(DerivedFile { derived_path: SingleDerivedPath::Opaque(store_path.clone()), build_path: relative_path, @@ -755,9 +836,11 @@ fn new_opaque_file( fn new_built_file(derived_path: SingleDerivedPath, build_path: PathBuf) -> DerivedFile { let output_name = normalize_output(&build_path.to_string_lossy()); - let derived_built = SingleDerivedPathBuilt::from_derived_path(derived_path, output_name); DerivedFile { - derived_path: SingleDerivedPath::Built(derived_built), + derived_path: SingleDerivedPath::Built { + drv_path: Arc::new(derived_path), + output: output_name.parse().expect("invalid output name"), + }, build_path: build_path.clone(), rel_path: Some(build_path), // For built files, rel_path same as build_path } @@ -775,7 +858,7 @@ fn normalize_output(output: &str) -> String { /// - discovered_store_paths: Store paths that only need to be added as input sources pub fn discover_c_includes( nix_tool: &NixTool, - store_dir: &Path, + store_dir: &StoreDir, build_dir: &Path, cmdline: &str, files: Vec, @@ -795,16 +878,17 @@ pub fn discover_c_includes( } // Check if include is from Nix store or a regular file - if let Ok(relative) = include.strip_prefix(store_dir) { + if let Ok(relative) = include.strip_prefix(AsRef::::as_ref(store_dir)) { if let Some(hash_path) = relative.components().next().map(|c| c.as_os_str()) { - let store_path = StorePath::new(store_dir.join(hash_path))?; + let full_path = AsRef::::as_ref(store_dir).join(hash_path); + let store_path: StorePath = store_dir.parse(&full_path.to_string_lossy())?; discovered_store_paths.push(store_path); continue; } } // Regular file, add to nix store and treat as derived dependency - let derived_file = new_opaque_file(nix_tool, build_dir, include)?; + let derived_file = new_opaque_file(nix_tool, store_dir, build_dir, include)?; discovered_deps.push(derived_file); } diff --git a/crates/nix-tool/Cargo.toml b/crates/nix-tool/Cargo.toml index 83d32b6..e771386 100644 --- a/crates/nix-tool/Cargo.toml +++ b/crates/nix-tool/Cargo.toml @@ -7,5 +7,5 @@ license = "MIT" [dependencies] anyhow = "1.0" -nix-libstore = { path = "../nix-libstore" } +harmonia-store-core = { workspace = true } serde_json = "1" diff --git a/crates/nix-tool/src/lib.rs b/crates/nix-tool/src/lib.rs index 85a8375..a6dd0dc 100644 --- a/crates/nix-tool/src/lib.rs +++ b/crates/nix-tool/src/lib.rs @@ -1,7 +1,8 @@ use anyhow::{anyhow, Context, Result}; -use nix_libstore::derivation::Derivation; -use nix_libstore::derived_path::SingleDerivedPath; -use nix_libstore::store_path::StorePath; +use harmonia_store_core::derivation::Derivation; +use harmonia_store_core::derived_path::SingleDerivedPath; +use harmonia_store_core::store_path::StoreDir; +use harmonia_store_core::store_path::StorePath; use std::ffi::OsStr; use std::io::Write; use std::process::{Command, Output}; @@ -36,8 +37,15 @@ impl NixTool { NixTool { config } } - pub fn build(&self, derived_paths: &[SingleDerivedPath]) -> Result> { - let installables: Vec = derived_paths.iter().map(|p| p.to_string()).collect(); + pub fn build( + &self, + store_dir: &StoreDir, + derived_paths: &[SingleDerivedPath], + ) -> Result> { + let installables: Vec = derived_paths + .iter() + .map(|p| store_dir.display(p).to_string()) + .collect(); let output = Command::new(&self.config.nix_tool) .args(&self.config.extra_args) .args(["build", "-L", "--no-link", "--print-out-paths"]) @@ -53,14 +61,14 @@ impl NixTool { let stdout = str::from_utf8(&output.stdout)?; let store_paths: Vec = stdout .lines() - .map(|line| StorePath::new(line.trim())) + .map(|line| store_dir.parse(line.trim())) .collect::, _>>()?; Ok(store_paths) } /// Add a file to the Nix store - pub fn store_add(&self, path: &std::path::Path) -> Result { + pub fn store_add(&self, store_dir: &StoreDir, path: &std::path::Path) -> Result { let output = self .run_nix_command(&["store", "add", &path.to_string_lossy()]) .map_err(|err| anyhow!("Failed to store add {}: {}", &path.to_string_lossy(), err))?; @@ -70,24 +78,21 @@ impl NixTool { .trim() .to_string(); - StorePath::new(store_path_str).context("Failed to parse store path") + store_dir + .parse(&store_path_str) + .context("Failed to parse store path") } - pub fn derivation_show(&self, drv_path: &StorePath) -> Result { - self.run_nix_command(&["derivation", "show", &drv_path.to_string()]) - .map_err(|err| { - anyhow!( - "Failed to derivation show {}: {}", - &drv_path.to_string(), - err - ) - }) + pub fn derivation_show(&self, store_dir: &StoreDir, drv_path: &StorePath) -> Result { + let full_path = store_dir.display(drv_path).to_string(); + self.run_nix_command(&["derivation", "show", &full_path]) + .map_err(|err| anyhow!("Failed to derivation show {}: {}", &full_path, err)) } /// Add a derivation to the Nix store - pub fn derivation_add(&self, drv: &Derivation) -> Result { + pub fn derivation_add(&self, store_dir: &StoreDir, drv: &Derivation) -> Result { // Serialize the drv to JSON - let json = drv.to_json()?; + let json = serde_json::to_string(drv)?; // Create a command with piped stdin/stdout/stderr let mut command = Command::new(&self.config.nix_tool); @@ -120,7 +125,9 @@ impl NixTool { .trim() .to_string(); - StorePath::new(store_path_str).context("Failed to parse store path") + store_dir + .parse(&store_path_str) + .context("Failed to parse store path") } /// Run a Nix command and return its output diff --git a/deny.toml b/deny.toml index 7ea2189..9e909d0 100644 --- a/deny.toml +++ b/deny.toml @@ -1,2 +1,2 @@ [licenses] -allow = ["MIT", "Apache-2.0", "BSD-3-Clause", "MPL-2.0", "Unicode-3.0"] +allow = ["MIT", "Apache-2.0", "BSD-3-Clause", "MPL-2.0", "Unicode-3.0", "ISC"] diff --git a/flake.lock b/flake.lock index ba88a45..328fd9d 100644 --- a/flake.lock +++ b/flake.lock @@ -71,15 +71,15 @@ "flake-compat_2": { "flake": false, "locked": { - "lastModified": 1733328505, - "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", - "owner": "edolstra", + "lastModified": 1767039857, + "narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=", + "owner": "NixOS", "repo": "flake-compat", - "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", + "rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab", "type": "github" }, "original": { - "owner": "edolstra", + "owner": "NixOS", "repo": "flake-compat", "type": "github" } @@ -181,85 +181,38 @@ "flake-compat": "flake-compat_2", "flake-parts": "flake-parts_2", "git-hooks-nix": "git-hooks-nix", - "nixpkgs": "nixpkgs", - "nixpkgs-23-11": "nixpkgs-23-11", - "nixpkgs-regression": "nixpkgs-regression" + "nixpkgs": [ + "nixpkgs" + ], + "nixpkgs-23-11": [], + "nixpkgs-regression": [] }, "locked": { - "lastModified": 1743180261, - "narHash": "sha256-9Qf3jXsEOwylipORivpNOo++31x6PWUDwGhCheKBCEo=", - "owner": "hinshun", + "lastModified": 1773272954, + "narHash": "sha256-mn4wy1IOr5La3jL5b+tHqKnIT65Xz8EF6MvOoVD4Eow=", + "owner": "NixOS", "repo": "nix", - "rev": "be772aa3cc9e85d9d54192e5eb98d26137a11671", + "rev": "6d1c28f7e88091244fbfaae2af29555492a0e743", "type": "github" }, "original": { - "owner": "hinshun", - "ref": "2.27.1-fix-nix-missing-includes", + "owner": "NixOS", "repo": "nix", "type": "github" } }, "nixpkgs": { "locked": { - "lastModified": 1734359947, - "narHash": "sha256-1Noao/H+N8nFB4Beoy8fgwrcOQLVm9o4zKW1ODaqK9E=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "48d12d5e70ee91fe8481378e540433a7303dbf6a", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "release-24.11", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs-23-11": { - "locked": { - "lastModified": 1717159533, - "narHash": "sha256-oamiKNfr2MS6yH64rUn99mIZjc45nGJlj9eGth/3Xuw=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446", - "type": "github" - }, - "original": { - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446", - "type": "github" - } - }, - "nixpkgs-regression": { - "locked": { - "lastModified": 1643052045, - "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", - "type": "github" - }, - "original": { - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", - "type": "github" - } - }, - "nixpkgs_2": { - "locked": { - "lastModified": 1755829505, - "narHash": "sha256-4/Jd+LkQ2ssw8luQVkqVs9spDBVE6h/u/hC/tzngsPo=", + "lastModified": 1773068389, + "narHash": "sha256-vMrm7Pk2hjBRPnCSjhq1pH0bg350Z+pXhqZ9ICiqqCs=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "f937f8ecd1c70efd7e9f90ba13dfb400cf559de4", + "rev": "44bae273f9f82d480273bab26f5c50de3724f52f", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixpkgs-unstable", + "ref": "nixos-25.11", "repo": "nixpkgs", "type": "github" } @@ -273,7 +226,7 @@ "flake-parts": "flake-parts", "globset": "globset", "nix": "nix", - "nixpkgs": "nixpkgs_2" + "nixpkgs": "nixpkgs" } } }, diff --git a/flake.nix b/flake.nix index a05bba1..eff5dfe 100644 --- a/flake.nix +++ b/flake.nix @@ -2,8 +2,13 @@ description = "Ninja compatible incremental C/C++ build system with Nix "; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; - nix.url = "github:hinshun/nix/2.27.1-fix-nix-missing-includes"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; + nix = { + url = "github:NixOS/nix"; + inputs.nixpkgs.follows = "nixpkgs"; + inputs.nixpkgs-23-11.follows = ""; + inputs.nixpkgs-regression.follows = ""; + }; globset = { url = "github:pdtpartners/globset"; inputs.nixpkgs-lib.follows = "nixpkgs"; diff --git a/modules/flake/examples/nix/default.nix b/modules/flake/examples/nix/default.nix index fb48c94..07f8f64 100644 --- a/modules/flake/examples/nix/default.nix +++ b/modules/flake/examples/nix/default.nix @@ -12,6 +12,7 @@ , editline , flex , gtest +, jsonschema , lib , libarchive , libblake3 @@ -50,6 +51,7 @@ mkMesonPackage { doxygen editline flex + jsonschema libarchive libblake3 libcpuid diff --git a/modules/flake/overlays.nix b/modules/flake/overlays.nix index 9860ee0..0ec8ef5 100644 --- a/modules/flake/overlays.nix +++ b/modules/flake/overlays.nix @@ -44,6 +44,7 @@ mkMesonPackage = self.callPackage ./pkgs/mkMesonPackage { inherit (self) nix-ninja nix-ninja-task; + nix = inputs.nix.packages.${self.system}.nix; }; # meson --internal symbolextractor depends on readelf. diff --git a/modules/nixos/tests/nix-build.nix b/modules/nixos/tests/nix-build.nix index cae93e7..8a24cdd 100644 --- a/modules/nixos/tests/nix-build.nix +++ b/modules/nixos/tests/nix-build.nix @@ -57,9 +57,10 @@ in { git nix-ninja nix-ninja-task - nixVersions.nix_2_30 ]; + nix.package = self.inputs.nix.packages.${pkgs.stdenv.hostPlatform.system}.nix; + nix.extraOptions = '' experimental-features = nix-command flakes dynamic-derivations ca-derivations recursive-nix '';