commit a525868969835082aba85e477c8c0d79aaedf22b Author: iretq Date: Fri May 22 18:48:11 2026 +0200 meta: initial rewrite (final) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..82448cc --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/build diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..38decb5 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1813 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocative" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fac2ce611db8b8cee9b2aa886ca03c924e9da5e5295d0dbd0526e5d0b0710f7" +dependencies = [ + "allocative_derive", + "bumpalo", + "ctor", + "hashbrown 0.14.5", + "num-bigint", +] + +[[package]] +name = "allocative_derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe233a377643e0fc1a56421d7c90acdec45c291b30345eb9f08e8d0ddce5a4ab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "annotate-snippets" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccaf7e9dfbb6ab22c82e473cd1a8a7bd313c19a5b7e40970f3d89ef5a5c9e81e" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "builder" +version = "0.1.0" +dependencies = [ + "allocative", + "anyhow", + "clap", + "petgraph 0.8.3", + "smallvec", + "starlark", + "starlark_derive", + "thiserror 2.0.18", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim 0.11.1", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + +[[package]] +name = "cmp_any" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9b18233253483ce2f65329a24072ec414db782531bdbb7d0bbc4bd2ce6b7e21" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "debugserver-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf6834a70ed14e8e4e41882df27190bea150f1f6ecf461f1033f8739cd8af4a" +dependencies = [ + "schemafy", + "serde", + "serde_json", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn 2.0.117", + "unicode-xid", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "display_container" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a110a75c96bedec8e65823dea00a1d710288b7a369d95fd8a0f5127639466fa" +dependencies = [ + "either", + "indenter", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dupe" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed2bc011db9c93fbc2b6cdb341a53737a55bafb46dbb74cf6764fc33a2fbf9c" +dependencies = [ + "dupe_derive", +] + +[[package]] +name = "dupe_derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e195b4945e88836d826124af44fdcb262ec01ef94d44f14f4fb5103f19892a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "ena" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabffdaee24bd1bf95c5ef7cec31260444317e72ea56c4c91750e8b7ee58d5f1" +dependencies = [ + "log", +] + +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" +dependencies = [ + "serde", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "error-code" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" + +[[package]] +name = "fd-lock" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" +dependencies = [ + "cfg-if", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "inventory" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "lalrpop" +version = "0.19.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a1cbf952127589f2851ab2046af368fd20645491bb4b376f04b7f94d7a9837b" +dependencies = [ + "ascii-canvas", + "bit-set", + "diff", + "ena", + "is-terminal", + "itertools 0.10.5", + "lalrpop-util", + "petgraph 0.6.5", + "regex", + "regex-syntax 0.6.29", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", +] + +[[package]] +name = "lalrpop-util" +version = "0.19.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3c48237b9604c5a4702de6b824e02006c3214327564636aef27c1028a8fa0ed" +dependencies = [ + "regex", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libredox" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +dependencies = [ + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "logos" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf8b031682c67a8e3d5446840f9573eb7fe26efe7ec8d195c9ac4c0647c502f1" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d849148dbaf9661a6151d1ca82b13bb4c4c128146a88d05253b38d4e2f496c" +dependencies = [ + "beef", + "fnv", + "proc-macro2", + "quote", + "regex-syntax 0.6.29", + "syn 1.0.109", +] + +[[package]] +name = "lsp-types" +version = "0.94.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66bfd44a06ae10647fe3f8214762e9369fd4248df1350924b4ef9e770a85ea1" +dependencies = [ + "bitflags 1.3.2", + "serde", + "serde_json", + "serde_repr", + "url", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +dependencies = [ + "bitflags 2.11.1", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset 0.4.2", + "indexmap", +] + +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset 0.5.7", + "hashbrown 0.15.5", + "indexmap", + "serde", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.1", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror 1.0.69", +] + +[[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 2.0.117", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax 0.8.10", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.10", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rustyline" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7803e8936da37efd9b6d4478277f4b2b9bb5cdb37a113e8d63222e58da647e63" +dependencies = [ + "bitflags 2.11.1", + "cfg-if", + "clipboard-win", + "fd-lock", + "home", + "libc", + "log", + "memchr", + "nix", + "radix_trie", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "windows-sys 0.52.0", +] + +[[package]] +name = "schemafy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aea5ba40287dae331f2c48b64dbc8138541f5e97ee8793caa7948c1f31d86d5" +dependencies = [ + "Inflector", + "schemafy_core", + "schemafy_lib", + "serde", + "serde_derive", + "serde_json", + "serde_repr", + "syn 1.0.109", +] + +[[package]] +name = "schemafy_core" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41781ae092f4fd52c9287efb74456aea0d3b90032d2ecad272bd14dbbcb0511b" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "schemafy_lib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e953db32579999ca98c451d80801b6f6a7ecba6127196c5387ec0774c528befa" +dependencies = [ + "Inflector", + "proc-macro2", + "quote", + "schemafy_core", + "serde", + "serde_derive", + "serde_json", + "syn 1.0.109", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "starlark" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f53849859f05d9db705b221bd92eede93877fd426c1b4a3c3061403a5912a8f" +dependencies = [ + "allocative", + "anyhow", + "bumpalo", + "cmp_any", + "debugserver-types", + "derivative", + "derive_more", + "display_container", + "dupe", + "either", + "erased-serde", + "hashbrown 0.14.5", + "inventory", + "itertools 0.13.0", + "maplit", + "memoffset", + "num-bigint", + "num-traits", + "once_cell", + "paste", + "ref-cast", + "regex", + "rustyline", + "serde", + "serde_json", + "starlark_derive", + "starlark_map", + "starlark_syntax", + "static_assertions", + "strsim 0.10.0", + "textwrap", + "thiserror 1.0.69", +] + +[[package]] +name = "starlark_derive" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe58bc6c8b7980a1fe4c9f8f48200c3212db42ebfe21ae6a0336385ab53f082a" +dependencies = [ + "dupe", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "starlark_map" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92659970f120df0cc1c0bb220b33587b7a9a90e80d4eecc5c5af5debb950173d" +dependencies = [ + "allocative", + "dupe", + "equivalent", + "fxhash", + "hashbrown 0.14.5", + "serde", +] + +[[package]] +name = "starlark_syntax" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe53b3690d776aafd7cb6b9fed62d94f83280e3b87d88e3719cc0024638461b3" +dependencies = [ + "allocative", + "annotate-snippets", + "anyhow", + "derivative", + "derive_more", + "dupe", + "lalrpop", + "lalrpop-util", + "logos", + "lsp-types", + "memchr", + "num-bigint", + "num-traits", + "once_cell", + "starlark_map", + "thiserror 1.0.69", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[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.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[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 new file mode 100644 index 0000000..1b20b46 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "builder" +version = "0.1.0" +edition = "2024" + +[dependencies] +allocative = "0.3.4" +anyhow = "1.0.102" +clap = { version = "4.6.1", features = ["derive"] } +petgraph = "0.8.3" +smallvec = "1.15.1" +starlark = "0.13.0" +starlark_derive = "0.13.0" +thiserror = "2.0.18" diff --git a/config.star b/config.star new file mode 100644 index 0000000..f3da73b --- /dev/null +++ b/config.star @@ -0,0 +1,64 @@ +arch = "x86_64" +libc = "glibc" + +if libc == "glibc": + env = "gnu" +elif libc == "musl": + env = "musl" +else: + fail(f"Unknown libc: {libc}") + +prefix = path("/usr") + +host_cflags = ["-O2", "-pipe"] +host_cxxflags = host_cflags + [] +host_ldflags = ["-Wl,-O1", "-Wl,--sort-common", "-Wl,--as-needed"] + +target_cflags = host_cflags + [] +target_cxxflags = host_cxxflags + [] +target_ldflags = host_ldflags + ["-Wl,-z,now"] + +if arch == "x86_64": + flags = [ + "-march=x86-64-v3", + "-mtune=generic", + "-fstack-clash-protection", + "-fstack-protector-strong", + "-fcf-protection", + ] + + target_cflags += flags + target_cxxflags += flags + target_ldflags += ["-Wl,-z,pack-relative-relocs"] + +config( + arch = arch, + recipes_dir = path("./recipes"), + host_recipes_dir = path("./host-recipes"), + container = podman( + image = "local/builder:latest", + dockerfile = path("./Dockerfile"), + ), + + target_arch = arch, + target_triple = f"{arch}-orchid-linux-{env}", + + libc = libc, + + prefix = prefix, + bindir = prefix / "bin", + sbindir = prefix / "bin", + libdir = prefix / "lib", + libexecdir = prefix / "libexec", + includedir = prefix / "include", + sysconfdir = path("/etc"), + localstatedir = path("/var"), + + host_cflags = " ".join(host_cflags), + host_cxxflags = " ".join(host_cxxflags), + host_ldflags = " ".join(host_ldflags), + + cflags = " ".join(target_cflags), + cxxflags = " ".join(target_cxxflags), + ldflags = " ".join(target_ldflags), +) diff --git a/host-recipes/binutils.star b/host-recipes/binutils.star new file mode 100644 index 0000000..f9a2d2c --- /dev/null +++ b/host-recipes/binutils.star @@ -0,0 +1,39 @@ +version = "2.46.0" +revision = 1 +metadata = meta( + description = "GNU binutils cross-compiled for the target triple", + license = "GPL-3.0-or-later", +) +source = tarball( + url = f"https://ftp.gnu.org/gnu/binutils/binutils-{version}.tar.xz", + sha256 = "d75a94f4d73e7a4086f7513e67e439e8fcdcbb726ffe63f4661744e6256b2cf2", + strip_components = 1, +) + +def configure(ctx): + ctx.run( + ctx.source_dir / "configure", + "--prefix=" + cfg.prefix, + "--target=" + cfg.target_triple, + "--with-sysroot=" + ctx.sysroot_dir, + "--with-pic", + "--enable-cet", + "--enable-default-execstack=no", + "--enable-deterministic-archives", + "--enable-ld=default", + "--enable-new-dtags", + "--enable-plugins", + "--enable-relro", + "--enable-separate-code", + "--enable-threads", + "--disable-nls", + "--disable-werror", + # gprofng's libcollector relies on glibc-specific internals. + "--disable-gprofng", + env = { + "CFLAGS": cfg.host_cflags, + "CXXFLAGS": cfg.host_cxxflags, + "LDFLAGS": cfg.host_ldflags, + }) + +_, build, install = autotools() diff --git a/recipes/linux-headers.star b/recipes/linux-headers.star new file mode 100644 index 0000000..9bac4d5 --- /dev/null +++ b/recipes/linux-headers.star @@ -0,0 +1,20 @@ +version = "7.0.9" +revision = 1 +metadata = meta( + description = "Linux kernel headers for userspace development", + license = "GPL-2.0-only", +) +source = tarball( + url = f"https://cdn.kernel.org/pub/linux/kernel/v7.x/linux-{version}.tar.xz", + sha256 = "ac07acdf76cf4621cc5187a2670270a1a699533c8a6b225e4878c416ad83f1c4", + strip_components = 1, +) + +def build(ctx): + ctx.run("cp", "-rp", ctx.source_dir / ".", ctx.build_dir) + ctx.run("make", "headers_install", "ARCH=" + cfg.target_arch) + ctx.run("find", ctx.build_dir / "usr" / "include", "-type", "f", "!", "-name", "*.h", "-delete") + +def install(ctx): + ctx.run("mkdir", "-p", ctx.dest_dir / options.prefix) + ctx.run("cp", "-rp", ctx.build_dir / "usr" / "include", ctx.dest_dir / options.prefix) diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..91ee681 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,99 @@ +use crate::{ + container::{ContainerManager, PodmanRuntime}, + eval::{Config, ContainerConfig, config_globals, eval_files, types_globals}, + recipe::RecipeSet, +}; + +use clap::{Parser, Subcommand}; +use starlark::environment::GlobalsBuilder; +use std::{cell::Cell, path::PathBuf, sync::Arc}; + +#[derive(Debug, Parser)] +struct Cli { + #[arg( + long, + short = 'C', + default_value = ".", + help = "Directory containing the configuration and recipe files" + )] + root: PathBuf, + #[command(subcommand)] + command: Command, +} + +#[derive(Debug, Parser)] +#[command(about = "Fetch sources for the given recipes")] +struct FetchCommand { + #[arg( + required = true, + help = "List of recipes to fetch, host recipes should be prefixed with `host:`" + )] + recipes: Vec, + #[arg(long, short = 'n', help = "Print what will be done and exit")] + dry_run: bool, +} + +#[derive(Debug, Parser)] +#[command(about = "Build the given recipes")] +struct BuildCommand { + #[arg( + required = true, + help = "List of recipes to build, host recipes should be prefixed with `host:`" + )] + recipes: Vec, + #[arg(long, short, help = "Perform a full rebuild of the given recipes")] + rebuild: bool, + #[arg(long, short = 'n', help = "Print what will be done and exit")] + dry_run: bool, +} + +#[derive(Debug, Subcommand)] +enum Command { + Fetch(FetchCommand), + Build(BuildCommand), +} + +pub fn run() -> anyhow::Result<()> { + let cli = Cli::parse(); + let root_path = cli.root.canonicalize().unwrap_or(cli.root); + + let config: Config = { + let cell = Cell::new(None); + let config_path = root_path.join("config.star"); + + eval_files( + &[&config_path], + &GlobalsBuilder::standard() + .with(types_globals) + .with(config_globals) + .build(), + None, + None, + Some(&cell), + )?; + + cell.take() + .ok_or_else(|| anyhow::anyhow!("`config` was not called"))? + }; + + let container_runtime = match config.container() { + ContainerConfig::Podman(_) => Arc::new(PodmanRuntime::new()?), + }; + + let mut container_manager = ContainerManager::new(container_runtime); + let mut recipes = RecipeSet::default(); + + recipes.load_recipes( + &root_path.join(config.recipes_dir()), + &root_path.join(config.host_recipes_dir()), + )?; + + println!("{:?}", recipes); + + match cli.command { + Command::Fetch(_) => {} + Command::Build(_) => {} + } + + Ok(()) +} diff --git a/src/container/mod.rs b/src/container/mod.rs new file mode 100644 index 0000000..7b97b10 --- /dev/null +++ b/src/container/mod.rs @@ -0,0 +1,88 @@ +use std::{collections::HashMap, path::Path, sync::Arc}; + +mod podman; + +pub use podman::PodmanRuntime; + +pub struct Container { + id: String, + runtime: Arc, +} + +impl Container { + fn new(id: String, runtime: Arc) -> Self { + Self { id, runtime } + } + + pub fn id(&self) -> &str { + &self.id + } + + pub fn exec<'a, 'e, 'c>( + &self, + argv: impl Into>, + env: impl Into>, + cwd: impl Into<&'c Path>, + ) -> anyhow::Result<()> { + self.runtime + .exec(self.id(), argv.into(), env.into(), cwd.into()) + } +} + +pub trait ContainerRuntime { + /// Starts a new container, returns the ID. + fn start_container( + &self, + image_name: &str, + mounts: &[(&Path, &str, bool)], + ) -> anyhow::Result; + + /// Stops a container. + fn stop_container(&self, container_id: &str); + + /// Executes a command in a container. + fn exec( + &self, + container_id: &str, + argv: Vec<&str>, + env: Vec<(&str, &str)>, + cwd: &Path, + ) -> anyhow::Result<()>; +} + +pub struct ContainerManager { + containers: HashMap, + runtime: Arc, +} + +impl ContainerManager { + pub fn new(runtime: Arc) -> Self { + Self { + containers: HashMap::new(), + runtime, + } + } + + pub fn container(&mut self, name: &str) -> anyhow::Result<&Container> { + if self.containers.get(name).is_none() { + let container_id = self.runtime.start_container("alpine:edge", &[])?; + + crate::log!("info", "Started new container ({container_id})"); + + self.containers.insert( + name.into(), + Container::new(container_id, self.runtime.clone()), + ); + } + + Ok(self.containers.get(name).unwrap()) + } +} + +impl Drop for ContainerManager { + fn drop(&mut self) { + for (_, container) in self.containers.iter() { + self.runtime.stop_container(container.id()); + } + } +} diff --git a/src/container/podman.rs b/src/container/podman.rs new file mode 100644 index 0000000..d34bc98 --- /dev/null +++ b/src/container/podman.rs @@ -0,0 +1,101 @@ +use crate::container::ContainerRuntime; + +use anyhow::Context; +use std::{ + path::Path, + process::{Command, Stdio}, +}; + +pub struct PodmanRuntime; + +impl PodmanRuntime { + pub fn new() -> anyhow::Result { + let output = Command::new("podman").arg("--version").output()?; + + if output.status.success() { + Ok(Self) + } else { + anyhow::bail!( + "Could not execute `podman --version` - make sure you have podman installed." + ); + } + } +} + +impl ContainerRuntime for PodmanRuntime { + fn start_container( + &self, + image_name: &str, + mounts: &[(&Path, &str, bool)], + ) -> anyhow::Result { + let mut cmd = Command::new("podman"); + + cmd.arg("run"); + cmd.arg("--detach"); + cmd.arg("--read-only"); + cmd.arg("--network=none"); + cmd.arg("--userns=keep-id"); + cmd.arg("--tmpfs").arg("/builder/dest"); + cmd.arg("--tmpfs").arg("/builder/sysroot"); + + for &(host, container, read_only) in mounts { + cmd.arg("--volume").arg(format!( + "{}:{}{}", + host.display(), + container, + if read_only { ":ro" } else { "" } + )); + } + + cmd.arg(image_name); + cmd.arg("sleep").arg("infinity"); + + let output = cmd.output()?; + + if output.status.success() { + Ok(String::from_utf8(output.stdout) + .context("container ID is not valid UTF-8")? + .trim() + .into()) + } else { + todo!() + } + } + + fn stop_container(&self, container_id: &str) { + Command::new("podman") + .arg("kill") + .arg(container_id) + .output() + .unwrap(); + } + + fn exec( + &self, + container_id: &str, + argv: Vec<&str>, + env: Vec<(&str, &str)>, + cwd: &Path, + ) -> anyhow::Result<()> { + let mut cmd = Command::new("podman"); + + cmd.arg("exec"); + cmd.arg("--workdir").arg(cwd); + + for (key, value) in env { + cmd.arg("--env").arg(format!("{key}={value}")); + } + + cmd.arg(container_id); + cmd.args(argv); + cmd.stdout(Stdio::inherit()).stderr(Stdio::inherit()); + + let output = cmd.output()?; + + if output.status.success() { + Ok(()) + } else { + anyhow::bail!("Failed to execute command"); + } + } +} diff --git a/src/eval/config.rs b/src/eval/config.rs new file mode 100644 index 0000000..169cbb2 --- /dev/null +++ b/src/eval/config.rs @@ -0,0 +1,154 @@ +use crate::eval::Path; + +use allocative::Allocative; +use starlark::{ + collections::SmallMap, + environment::GlobalsBuilder, + eval::Evaluator, + starlark_module, starlark_simple_value, + values::{StarlarkValue, Value, ValueLike, none::NoneType}, +}; +use starlark_derive::{NoSerialize, ProvidesStaticType, starlark_value}; +use std::{ + cell::Cell, + collections::HashMap, + path::{Path as StdPath, PathBuf}, +}; + +#[derive(Debug, Clone, Allocative, NoSerialize, ProvidesStaticType)] +pub struct PodmanConfig { + image: String, + dockerfile: PathBuf, +} + +impl std::fmt::Display for PodmanConfig { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "podman_config") + } +} + +starlark_simple_value!(PodmanConfig); + +#[starlark_value(type = "podman_config")] +impl<'v> StarlarkValue<'v> for PodmanConfig {} + +#[derive(Debug, Clone, Allocative, NoSerialize, ProvidesStaticType)] +pub enum ContainerConfig { + Podman(PodmanConfig), +} + +impl std::fmt::Display for ContainerConfig { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "container_config") + } +} + +starlark_simple_value!(ContainerConfig); + +#[starlark_value(type = "container_config")] +impl<'v> StarlarkValue<'v> for ContainerConfig {} + +#[derive(Debug, Clone, Allocative, ProvidesStaticType)] +pub enum ConfigValue { + String(String), + Integer(i32), + Bool(bool), + Path(Path), +} + +#[derive(Debug, Clone, Allocative, NoSerialize, ProvidesStaticType)] +pub struct Config { + arch: String, + container: ContainerConfig, + recipes_dir: Path, + host_recipes_dir: Path, + + options: HashMap, +} + +impl Config { + pub fn arch(&self) -> &str { + &self.arch + } + + pub fn container(&self) -> &ContainerConfig { + &self.container + } + + pub fn recipes_dir(&self) -> &StdPath { + &self.recipes_dir.path() + } + + pub fn host_recipes_dir(&self) -> &StdPath { + &self.host_recipes_dir.path() + } + + pub fn options(&self) -> &HashMap { + &self.options + } +} + +impl std::fmt::Display for Config { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "container_config") + } +} + +starlark_simple_value!(Config); + +#[starlark_value(type = "config")] +impl<'v> StarlarkValue<'v> for Config {} + +#[starlark_module] +pub fn config_globals(b: &mut GlobalsBuilder) { + fn podman( + #[starlark(require = named)] image: &str, + #[starlark(require = named)] dockerfile: &Path, + ) -> anyhow::Result { + Ok(ContainerConfig::Podman(PodmanConfig { + image: image.to_string(), + dockerfile: dockerfile.path().to_owned(), + })) + } + + fn config( + #[starlark(require = named)] arch: &str, + #[starlark(require = named)] container: &ContainerConfig, + #[starlark(require = named)] recipes_dir: &Path, + #[starlark(require = named)] host_recipes_dir: &Path, + #[starlark(kwargs)] kwargs: SmallMap<&str, Value>, + eval: &mut Evaluator, + ) -> anyhow::Result { + let config = eval + .extra + .and_then(|extra| extra.downcast_ref::>>()) + .ok_or_else(|| anyhow::anyhow!("`config` called outside of config.star"))?; + + config.set(Some(Config { + arch: arch.to_string(), + container: container.clone(), + recipes_dir: recipes_dir.clone(), + host_recipes_dir: host_recipes_dir.clone(), + options: kwargs + .iter() + .map(|(&k, v)| { + let value = if let Some(str) = v.unpack_str() { + ConfigValue::String(str.to_string()) + } else if let Some(num) = v.unpack_i32() { + ConfigValue::Integer(num) + } else if let Some(bool) = v.unpack_bool() { + ConfigValue::Bool(bool) + } else if let Some(path) = v.downcast_ref::() { + ConfigValue::Path(path.clone()) + } else { + anyhow::bail!("config option must be a `string`, `int`, `bool` or `path`"); + }; + + Ok((k.to_string(), value)) + }) + .collect::>()?, + })); + + Ok(NoneType) + } +} diff --git a/src/eval/mod.rs b/src/eval/mod.rs new file mode 100644 index 0000000..351e238 --- /dev/null +++ b/src/eval/mod.rs @@ -0,0 +1,78 @@ +use anyhow::Context; +use starlark::{ + any::AnyLifetime, + environment::{FrozenModule, Globals, Module}, + eval::Evaluator, + syntax::{AstModule, Dialect, DialectTypes}, +}; +use std::path::Path as StdPath; + +mod config; +mod types; + +#[allow(unused_imports)] +pub use config::*; +#[allow(unused_imports)] +pub use types::*; + +pub fn eval_files( + path: &[&StdPath], + globals: &Globals, + lib_module: Option<&FrozenModule>, + config: Option<&Config>, + extra: Option<&dyn AnyLifetime>, +) -> anyhow::Result { + let module = Module::new(); + + if let Some(lib_module) = lib_module { + module.import_public_symbols(lib_module); + } + + if let Some(config) = config { + module.set("cfg", module.heap().alloc(config.clone())); + } + + let mut paths = path.to_vec(); + + paths.sort(); + + let ast_modules = paths + .iter() + .map(|&path| { + let module = AstModule::parse_file(path, &default_dialect()) + .map_err(|err| anyhow::anyhow!("{err}")) + .context(format!("parsing file {:?}", path.display()))?; + + Ok((path, module)) + }) + .collect::>>()?; + + for (path, ast) in ast_modules { + let mut eval = Evaluator::new(&module); + + if let Some(extra) = extra { + eval.extra = Some(extra); + } + + eval.eval_module(ast, globals) + .map_err(|err| anyhow::anyhow!("{err}")) + .context(format!("evaluating file {:?}", path.display()))?; + } + + Ok(module) +} + +fn default_dialect() -> Dialect { + Dialect { + enable_def: true, + enable_lambda: true, + enable_load: false, + enable_keyword_only_arguments: false, + enable_positional_only_arguments: false, + enable_types: DialectTypes::Disable, + enable_load_reexport: false, + enable_top_level_stmt: true, + enable_f_strings: true, + ..Dialect::Standard + } +} diff --git a/src/eval/types.rs b/src/eval/types.rs new file mode 100644 index 0000000..39caa69 --- /dev/null +++ b/src/eval/types.rs @@ -0,0 +1,64 @@ +use allocative::Allocative; +use starlark::{ + environment::GlobalsBuilder, + starlark_module, starlark_simple_value, + values::{Heap, StarlarkValue, Value, ValueLike}, +}; +use starlark_derive::{NoSerialize, ProvidesStaticType, starlark_value}; +use std::path::{Path as StdPath, PathBuf}; + +#[derive(Debug, Clone, Allocative, NoSerialize, ProvidesStaticType)] +pub struct Path { + inner: PathBuf, +} + +impl Path { + pub fn new(value: impl Into) -> Self { + Self { + inner: value.into(), + } + } + + pub fn path(&self) -> &StdPath { + &self.inner + } +} + +impl std::fmt::Display for Path { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "path({:?})", self.inner) + } +} + +starlark_simple_value!(Path); + +#[starlark_value(type = "path")] +impl<'v> StarlarkValue<'v> for Path { + fn div(&self, other: Value<'v>, heap: &'v Heap) -> starlark::Result> { + let rhs = if let Some(str) = other.unpack_str() { + str.to_string() + } else if let Some(path) = other.downcast_ref::() { + path.inner.to_str().unwrap_or("").to_string() + } else { + return Err(starlark::Error::new_other(anyhow::anyhow!( + "expected a `string` or `path`, got `{}`", + other.get_type() + ))); + }; + + Ok(heap.alloc(Path { + inner: self + .inner + .join(rhs.trim_start_matches(std::path::MAIN_SEPARATOR_STR)), + })) + } +} + +#[starlark_module] +pub fn types_globals(b: &mut GlobalsBuilder) { + fn path(value: &str) -> anyhow::Result { + Ok(Path { + inner: PathBuf::from(value), + }) + } +} diff --git a/src/log.rs b/src/log.rs new file mode 100644 index 0000000..18af983 --- /dev/null +++ b/src/log.rs @@ -0,0 +1,23 @@ +use std::{io::IsTerminal, sync::LazyLock}; + +static IS_STDERR_TERMINAL: LazyLock = LazyLock::new(|| std::io::stderr().is_terminal()); + +pub fn __emit(color: &str, action: &str, args: std::fmt::Arguments) { + const ARROW: &str = "==>"; + + if *IS_STDERR_TERMINAL { + eprintln!("\x1b[{color}m{ARROW}\x1b[0m \x1b[1m{action}\x1b[0m {args}"); + } else { + eprintln!("{ARROW} {action} {args}"); + } +} + +#[macro_export] +macro_rules! log { + ($action:literal) => {{ + $crate::log::__emit("1;34", $action, format_args!()); + }}; + ($action:literal, $($arg:tt)*) => {{ + $crate::log::__emit("1;34", $action, format_args!($($arg)*)); + }}; +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..84dda56 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,136 @@ +use crate::{ + container::{ContainerManager, PodmanRuntime}, + plan::{Plan, PlanKey}, + recipe::{PackageRecipe, RecipeSet, SourceRecipe, ToolRecipe}, +}; + +use std::{path::Path, sync::Arc}; + +mod cli; +mod container; +mod eval; +mod log; +mod plan; +mod recipe; + +fn main() -> anyhow::Result<()> { + cli::run() + + // let podman_runtime = Arc::new(PodmanRuntime::new()?); + // let mut container_manager = ContainerManager::new(podman_runtime); + + // container_manager.container("example")?.exec( + // vec!["sh", "-c", "uname -a && id"], + // vec![], + // Path::new("/"), + // )?; + + // let mut recipes = RecipeSet::default(); + + // recipes.load_recipes(Path::new("./recipes"), Path::new("./host-recipes"))?; + + // recipes.add_source( + // "binutils-2.46", + // SourceRecipe { + // name: "binutils-2.46".into(), + // }, + // ); + + // recipes.add_source( + // "gcc-16.1.0", + // SourceRecipe { + // name: "gcc-16.1.0".into(), + // }, + // ); + + // recipes.add_source( + // "linux-7.0.9", + // SourceRecipe { + // name: "linux-7.0.9".into(), + // }, + // ); + + // recipes.add_source( + // "glibc-2.41", + // SourceRecipe { + // name: "glibc-2.41".into(), + // }, + // ); + + // recipes.add_source( + // "bash-5.3", + // SourceRecipe { + // name: "bash-5.3".into(), + // }, + // ); + + // recipes.add_tool( + // "binutils", + // ToolRecipe { + // name: "binutils".into(), + // sources: vec!["binutils-2.46".into()], + // tools_wanted: vec![], + // pkgs_wanted: vec![], + // }, + // ); + + // recipes.add_tool( + // "gcc-bootstrap", + // ToolRecipe { + // name: "gcc-bootstrap".into(), + // sources: vec!["gcc-16.1.0".into()], + // tools_wanted: vec!["binutils".into()], + // pkgs_wanted: vec![], + // }, + // ); + + // recipes.add_tool( + // "gcc", + // ToolRecipe { + // name: "gcc".into(), + // sources: vec!["gcc-16.1.0".into()], + // tools_wanted: vec!["binutils".into()], + // pkgs_wanted: vec!["glibc".into()], + // }, + // ); + + // recipes.add_package( + // "linux-headers", + // PackageRecipe { + // name: "linux-headers".into(), + // sources: vec!["linux-7.0.9".into()], + // tools_wanted: vec![], + // pkgs_wanted: vec![], + // }, + // ); + + // recipes.add_package( + // "glibc", + // PackageRecipe { + // name: "glibc".into(), + // sources: vec!["glibc-2.41".into()], + // tools_wanted: vec!["gcc-bootstrap".into()], + // pkgs_wanted: vec!["linux-headers".into()], + // }, + // ); + + // recipes.add_package( + // "bash", + // PackageRecipe { + // name: "bash".into(), + // sources: vec!["bash-5.3".into()], + // tools_wanted: vec!["gcc".into()], + // pkgs_wanted: vec!["glibc".into()], + // }, + // ); + + // let mut plan = Plan::new(&recipes); + + // plan.add_wanted(PlanKey::PkgPackage( + // recipes.package("bash").expect("back package"), + // )); + + // println!("{:#?}", plan.steps()?); + + // Ok(()) +} diff --git a/src/plan.rs b/src/plan.rs new file mode 100644 index 0000000..6ee0620 --- /dev/null +++ b/src/plan.rs @@ -0,0 +1,217 @@ +use crate::recipe::{PackageRecipe, RecipeSet, SourceRecipe, ToolRecipe}; + +use petgraph::{ + Direction, + graph::{DiGraph, NodeIndex}, +}; +use smallvec::{SmallVec, smallvec}; +use std::{ + cmp::Reverse, + collections::{BinaryHeap, HashMap, HashSet}, +}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum PlanError { + #[error("missing source recipe '{0}'")] + MissingSource(String), + #[error("missing tool recipe '{0}'")] + MissingTool(String), + #[error("missing package recipe '{0}'")] + MissingPackage(String), + #[error("plan cycle detected")] + CycleDetected, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum PlanKey<'a> { + SourceFetch(&'a SourceRecipe), + SourcePatch(&'a SourceRecipe), + SourcePrepare(&'a SourceRecipe), + + ToolConfigure(&'a ToolRecipe), + ToolBuild(&'a ToolRecipe), + ToolInstall(&'a ToolRecipe), + + PkgConfigure(&'a PackageRecipe), + PkgBuild(&'a PackageRecipe), + PkgInstall(&'a PackageRecipe), + PkgPackage(&'a PackageRecipe), +} + +impl<'a> PlanKey<'a> { + fn weight(&self) -> i8 { + match self { + PlanKey::SourceFetch(_) => 0, + PlanKey::SourcePatch(_) => 1, + PlanKey::SourcePrepare(_) => 2, + PlanKey::ToolConfigure(_) => 3, + PlanKey::ToolBuild(_) => 4, + PlanKey::ToolInstall(_) => 5, + PlanKey::PkgConfigure(_) => 6, + PlanKey::PkgBuild(_) => 7, + PlanKey::PkgInstall(_) => 8, + PlanKey::PkgPackage(_) => 9, + } + } + + fn dependencies( + &self, + recipes: &'a RecipeSet, + ) -> Result; 8]>, PlanError> { + match self { + PlanKey::SourceFetch(_) => Ok(smallvec![]), + PlanKey::SourcePatch(recipe) => Ok(smallvec![PlanKey::SourceFetch(recipe)]), + PlanKey::SourcePrepare(recipe) => Ok(smallvec![PlanKey::SourcePatch(recipe)]), + PlanKey::ToolConfigure(recipe) => { + let source_deps = recipe.sources.iter().map(|name| { + recipes + .source(name) + .map(PlanKey::SourcePrepare) + .ok_or(PlanError::MissingSource(name.clone())) + }); + + let tool_deps = recipe.tools_wanted.iter().map(|name| { + recipes + .tool(name) + .map(PlanKey::ToolInstall) + .ok_or(PlanError::MissingTool(name.clone())) + }); + + let pkg_deps = recipe.pkgs_wanted.iter().map(|name| { + recipes + .package(name) + .map(PlanKey::PkgPackage) + .ok_or(PlanError::MissingPackage(name.clone())) + }); + + source_deps.chain(tool_deps).chain(pkg_deps).collect() + } + PlanKey::ToolBuild(recipe) => Ok(smallvec![PlanKey::ToolConfigure(recipe)]), + PlanKey::ToolInstall(recipe) => Ok(smallvec![PlanKey::ToolBuild(recipe)]), + PlanKey::PkgConfigure(recipe) => { + let source_deps = recipe.sources.iter().map(|name| { + recipes + .source(name) + .map(PlanKey::SourcePrepare) + .ok_or(PlanError::MissingSource(name.clone())) + }); + + let tool_deps = recipe.tools_wanted.iter().map(|name| { + recipes + .tool(name) + .map(PlanKey::ToolInstall) + .ok_or(PlanError::MissingTool(name.clone())) + }); + + let pkg_deps = recipe.pkgs_wanted.iter().map(|name| { + recipes + .package(name) + .map(PlanKey::PkgPackage) + .ok_or(PlanError::MissingPackage(name.clone())) + }); + + source_deps.chain(tool_deps).chain(pkg_deps).collect() + } + PlanKey::PkgBuild(recipe) => Ok(smallvec![PlanKey::PkgConfigure(recipe)]), + PlanKey::PkgInstall(recipe) => Ok(smallvec![PlanKey::PkgBuild(recipe)]), + PlanKey::PkgPackage(recipe) => Ok(smallvec![PlanKey::PkgInstall(recipe)]), + } + } + + fn is_active(&self) -> bool { + true + } +} + +pub struct Plan<'a> { + recipes: &'a RecipeSet, + wanted: HashSet>, +} + +impl<'a> Plan<'a> { + pub fn new(recipes: &'a RecipeSet) -> Self { + Self { + recipes, + wanted: HashSet::new(), + } + } + + pub fn add_wanted(&mut self, key: PlanKey<'a>) { + self.wanted.insert(key); + } + + pub fn steps(&self) -> Result>, PlanError> { + let mut stack: Vec<_> = self.wanted.iter().copied().collect(); + let mut graph: DiGraph<_, ()> = DiGraph::new(); + let mut nodes = HashMap::new(); + + while let Some(node) = stack.pop() { + let node_idx = match nodes.get(&node) { + Some(&idx) => idx, + None => { + let idx = graph.add_node(node); + nodes.insert(node, idx); + idx + } + }; + + for dep in node.dependencies(self.recipes)?.iter().copied() { + let dep_idx = match nodes.get(&dep) { + Some(&idx) => idx, + None => { + let idx = graph.add_node(dep); + nodes.insert(dep, idx); + stack.push(dep); + idx + } + }; + + graph.update_edge(dep_idx, node_idx, ()); + } + } + + // petgraph::algo::toposort(&graph, None) + // .and_then(|nodes| { + // Ok(nodes + // .iter() + // .map(|&k| graph[k]) + // .filter(|node| node.is_active()) + // .collect()) + // }) + // .map_err(|_| PlanError::CycleDetected) + + let mut in_degree: HashMap = graph + .node_indices() + .map(|i| (i, graph.neighbors_directed(i, Direction::Incoming).count())) + .collect(); + + let mut heap: BinaryHeap<(Reverse, NodeIndex)> = in_degree + .iter() + .filter(|&(_, d)| *d == 0) + .map(|(&i, _)| (Reverse(graph[i].weight()), i)) + .collect(); + + let mut result = Vec::with_capacity(graph.node_count()); + + while let Some((_, idx)) = heap.pop() { + result.push(graph[idx]); + + for neighbor in graph.neighbors_directed(idx, Direction::Outgoing) { + let d = in_degree.get_mut(&neighbor).unwrap(); + *d -= 1; + if *d == 0 { + heap.push((Reverse(graph[neighbor].weight()), neighbor)); + } + } + } + + if result.len() != graph.node_count() { + Err(PlanError::CycleDetected) + } else { + result.retain(|node| node.is_active()); + + Ok(result) + } + } +} diff --git a/src/recipe.rs b/src/recipe.rs new file mode 100644 index 0000000..2e463c9 --- /dev/null +++ b/src/recipe.rs @@ -0,0 +1,145 @@ +use anyhow::Context; +use std::{ + collections::HashMap, + path::{Path, PathBuf}, +}; + +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct SourceRecipe { + pub name: String, +} + +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct ToolRecipe { + pub name: String, + pub sources: Vec, + pub tools_wanted: Vec, + pub pkgs_wanted: Vec, +} + +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct PackageRecipe { + pub name: String, + pub sources: Vec, + pub tools_wanted: Vec, + pub pkgs_wanted: Vec, +} + +impl std::fmt::Debug for SourceRecipe { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "source:{}", self.name) + } +} + +impl std::fmt::Debug for ToolRecipe { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "tool:{}", self.name) + } +} + +impl std::fmt::Debug for PackageRecipe { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "package:{}", self.name) + } +} + +#[derive(Default, Debug)] +pub struct RecipeSet { + sources: HashMap, + tools: HashMap, + packages: HashMap, +} + +impl RecipeSet { + fn add_source(&mut self, name: &str, recipe: SourceRecipe) -> anyhow::Result<()> { + if self.sources.insert(name.to_string(), recipe).is_some() { + anyhow::bail!("source '{name}' already exists"); + } + Ok(()) + } + + fn add_tool(&mut self, name: &str, recipe: ToolRecipe) -> anyhow::Result<()> { + if self.tools.insert(name.to_string(), recipe).is_some() { + anyhow::bail!("tool '{name}' already exists"); + } + Ok(()) + } + + fn add_package(&mut self, name: &str, recipe: PackageRecipe) -> anyhow::Result<()> { + if self.packages.insert(name.to_string(), recipe).is_some() { + anyhow::bail!("package '{name}' already exists"); + } + Ok(()) + } + + fn load_tool_recipe(&mut self, name: &str, path: &Path) -> anyhow::Result<()> { + Ok(()) + } + + fn load_package_recipe(&mut self, name: &str, path: &Path) -> anyhow::Result<()> { + Ok(()) + } + + pub fn load_recipes( + &mut self, + recipes_dir: &Path, + host_recipes_dir: &Path, + ) -> anyhow::Result<()> { + for (dir, tool_recipe) in [(recipes_dir, false), (host_recipes_dir, true)] { + for entry in std::fs::read_dir(dir)? { + let entry = entry.context("reading directory entry")?; + + if let Some((name, path)) = get_recipe_name_and_patch(&entry)? { + if tool_recipe { + self.load_tool_recipe(&name, &path)?; + } else { + self.load_package_recipe(&name, &path)?; + } + } + } + } + + Ok(()) + } + + pub fn source(&self, name: &str) -> Option<&SourceRecipe> { + self.sources.get(name) + } + + pub fn tool(&self, name: &str) -> Option<&ToolRecipe> { + self.tools.get(name) + } + + pub fn package(&self, name: &str) -> Option<&PackageRecipe> { + self.packages.get(name) + } +} + +fn get_recipe_name_and_patch( + entry: &std::fs::DirEntry, +) -> anyhow::Result> { + let file_type = entry.file_type()?; + let path = entry.path(); + + if file_type.is_dir() { + let recipe_path = path.join("recipe.star"); + + if recipe_path.exists() { + return Ok(Some(( + entry.file_name().to_str().unwrap_or("").to_string(), + recipe_path, + ))); + } + } else { + let name = path.file_stem().unwrap().to_str().unwrap_or("").to_string(); + let extension = path + .extension() + .ok_or(anyhow::anyhow!("File did not have an extension"))?; + + if extension == "star" { + return Ok(Some((name, path))); + } + } + + Ok(None) +}