diff --git a/workshop-baker/Cargo.lock b/workshop-baker/Cargo.lock index d0e5c1b..36a40c4 100644 --- a/workshop-baker/Cargo.lock +++ b/workshop-baker/Cargo.lock @@ -259,6 +259,28 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "axum" version = "0.7.9" @@ -393,6 +415,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + [[package]] name = "block2" version = "0.6.2" @@ -527,6 +558,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -637,12 +670,31 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "colorchoice" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -672,6 +724,12 @@ dependencies = [ "yaml-rust2", ] +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "const-random" version = "0.1.18" @@ -726,6 +784,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "criterion" version = "0.5.1" @@ -803,6 +870,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "deranged" version = "0.5.5" @@ -819,8 +895,19 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "crypto-common", + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.0", + "const-oid", + "crypto-common 0.2.2", ] [[package]] @@ -853,6 +940,12 @@ dependencies = [ "const-random", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "duration-str" version = "0.21.0" @@ -1046,6 +1139,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures-channel" version = "0.3.31" @@ -1136,8 +1235,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1147,9 +1248,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasip2", + "wasm-bindgen", ] [[package]] @@ -1309,6 +1412,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hybrid-array" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.8.1" @@ -1382,6 +1494,7 @@ version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" dependencies = [ + "base64", "bytes", "futures-channel", "futures-core", @@ -1389,7 +1502,9 @@ dependencies = [ "http", "http-body", "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2", "tokio", @@ -1561,6 +1676,12 @@ dependencies = [ "serde_core", ] +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + [[package]] name = "is-terminal" version = "0.4.17" @@ -1626,6 +1747,65 @@ dependencies = [ "syn", ] +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" version = "0.3.83" @@ -1721,9 +1901,15 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "matchit" @@ -1737,6 +1923,16 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" +[[package]] +name = "md-5" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" +dependencies = [ + "cfg-if", + "digest 0.11.3", +] + [[package]] name = "memchr" version = "2.7.6" @@ -1769,9 +1965,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "wasi", @@ -2205,7 +2401,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" dependencies = [ "pest", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -2398,6 +2594,62 @@ dependencies = [ "prost", ] +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + [[package]] name = "quote" version = "1.0.42" @@ -2535,6 +2787,41 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "reqwest" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +dependencies = [ + "base64", + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "ring" version = "0.17.14" @@ -2583,6 +2870,21 @@ dependencies = [ "num-traits", ] +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[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 = "1.1.4" @@ -2602,6 +2904,7 @@ version = "0.23.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" dependencies = [ + "aws-lc-rs", "log", "once_cell", "ring", @@ -2629,15 +2932,44 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" dependencies = [ + "web-time", "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -2861,6 +3193,17 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + [[package]] name = "sha2" version = "0.10.9" @@ -2868,8 +3211,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -2894,6 +3248,22 @@ dependencies = [ "libc", ] +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "slab" version = "0.4.11" @@ -2908,12 +3278,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2956,6 +3326,9 @@ name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -3093,10 +3466,25 @@ dependencies = [ ] [[package]] -name = "tokio" -version = "1.48.0" +name = "tinyvec" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -3111,9 +3499,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -3262,6 +3650,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -3320,9 +3726,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "ucd-trie" @@ -3488,6 +3894,19 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.106" @@ -3530,6 +3949,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "1.0.4" @@ -3849,6 +4287,7 @@ dependencies = [ "which", "workshop-baker-params", "workshop-engine", + "workshop-getterurl", ] [[package]] @@ -3877,6 +4316,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "workshop-getterurl" +version = "0.1.0" +dependencies = [ + "log", + "md-5", + "reqwest", + "sha1", + "sha2 0.11.0", + "thiserror 2.0.18", + "tokio", + "url", +] + [[package]] name = "writeable" version = "0.6.2" diff --git a/workshop-baker/Cargo.toml b/workshop-baker/Cargo.toml index f336791..c223cf6 100644 --- a/workshop-baker/Cargo.toml +++ b/workshop-baker/Cargo.toml @@ -50,6 +50,7 @@ privdrop = "0.5.6" which = "8.0.2" globset = "0.4.18" axum = "0.7" +workshop-getterurl = { path = "../workshop-getterurl" } workshop-baker-params = { version = "0.1.0", path = "../workshop-baker-params" } [dev-dependencies] diff --git a/workshop-baker/src/bake.rs b/workshop-baker/src/bake.rs index ce891ae..e49c2f0 100644 --- a/workshop-baker/src/bake.rs +++ b/workshop-baker/src/bake.rs @@ -1,4 +1,5 @@ -use crate::bake::constant::{DEFAULT_WORKSPACE, TRIVIAL_SCRIPT_NAME}; +use crate::bake::builder::RenderMode; +use crate::bake::constant::DEFAULT_WORKSPACE; use crate::bake::error::BakeError; use crate::bake::parser::Function; use crate::cli::Cli; @@ -6,8 +7,12 @@ use crate::prebake; use crate::prebake::PrebakeConfig; use crate::prebake::security::get_drop_after; use crate::prebake::stage::PrebakeStage; +use std::future::Future; use std::path::{Path, PathBuf}; -use workshop_engine::{Engine, EventSender, ExecutionContext}; +use std::pin::Pin; +use std::time::Duration; +use tokio::sync::mpsc; +use workshop_engine::{Engine, EventSender, ExecutionContext, ExecutionError}; mod builder; pub mod constant; @@ -15,6 +20,13 @@ mod decorator; pub mod error; pub mod parser; mod schedule; +pub mod trivial; + +/// Completion event sent from a spawned task to the batch consumer loop. +struct TaskCompletion { + name: String, + result: Result<(), BakeError>, +} pub async fn bake( script_path: &Path, @@ -39,8 +51,8 @@ pub async fn bake( .iter() .any(|d| matches!(d, decorator::Decorator::Pipeline)) }); - let trivial = false; - let use_template = true; + // TODO: if PIP(pipeline implicit parameter).trivial is set, always use RenderMode::Off + let render_mode = RenderMode::Full; let engine = Engine::new(); if let Some(env) = &prebake.envvars @@ -49,31 +61,97 @@ pub async fn bake( ctx.env_vars.extend(prebake_env.clone()); } - if !has_pipeline || trivial { - let function = Function { - name: TRIVIAL_SCRIPT_NAME.to_string(), - decorators: vec![], - body: script_content, - }; - let script = - builder::build_script(&function, bake_base_path, &workspace, None, use_template)?; - let result = engine.execute_script(&script, ctx, &event_tx).await; - result?; + if !has_pipeline { + trivial::run_trivial( + &script_content, + bake_base_path, + &workspace, + &engine, + ctx, + &event_tx, + render_mode, + ) + .await?; } else { - let sorted_functions = schedule::sort_function(functions.pipeline_functions)?; let remaining = functions.remaining_code; - for func in sorted_functions { - let mut modified_func = func.clone(); - modified_func.body = remaining.clone() + &func.body; - let script = builder::build_script( - &modified_func, - bake_base_path, - &workspace, - None, - use_template, - )?; - let result = engine.execute_script(&script, ctx, &event_tx).await; - result?; + let (mut dag, func_map) = schedule::build_dag(functions.pipeline_functions)?; + + while !dag.is_empty() { + let ready: Vec = dag.peek_all().into_iter().cloned().collect(); + if ready.is_empty() { + let remaining_nodes: Vec = dag.clone().into_iter().collect(); + return Err(BakeError::CircularDependency(remaining_nodes)); + } + + // Separate @parallel from sequential (consume ready for owned names) + let (sequential, parallel): (Vec, Vec) = ready.into_iter() + .partition(|name| { + let f = &func_map[name.as_str()]; + !f.decorators + .iter() + .any(|d| matches!(d, decorator::Decorator::Parallel)) + }); + + // Execute sequential functions first + for name in &sequential { + execute_one( + &engine, + &func_map[name.as_str()], + ctx, + &event_tx, + &workspace, + &remaining, + bake_base_path, + ) + .await?; + } + + // Execute @parallel functions concurrently + if !parallel.is_empty() { + let handles: Vec<_> = parallel + .iter() + .map(|name| { + let func = func_map[name.as_str()].clone(); + let mut ctx_local = ctx.clone(); + let et = event_tx.clone(); + let ws = workspace.clone(); + let rem = remaining.clone(); + let bb = bake_base_path.to_path_buf(); + let n = name.clone(); + tokio::spawn(async move { + let eng = Engine::new(); + ( + n, + execute_one(&eng, &func, &mut ctx_local, &et, &ws, &rem, &bb) + .await, + ) + }) + }) + .collect(); + + for handle in handles { + let (name, result) = handle.await.map_err(|_| { + BakeError::ScriptExecutionError(ExecutionError::ExecutionFailed( + "parallel task panicked".to_string(), + )) + })?; + let func = &func_map[name.as_str()]; + let is_fallible = func + .decorators + .iter() + .any(|d| matches!(d, decorator::Decorator::Fallible)); + match result { + Err(e) if !is_fallible => return Err(e), + Err(e) => { + log::warn!("'{}' failed (fallible, non-fatal): {}", name, e); + } + Ok(_) => {} + } + } + } + + // Commit the entire batch — all nodes completed (or fallible-failed) + dag.pop_all(); } } @@ -105,16 +183,118 @@ fn privileged(prebake_config: &PrebakeConfig) -> bool { PrebakeStage::Never == drop_after } -type TaskResult = Result<(), BakeError>; +// ── Execution helpers ── -type TaskFn = Box TaskResult + Send>; +async fn execute_one( + engine: &Engine, + func: &Function, + ctx: &mut ExecutionContext, + event_tx: &EventSender, + workspace: &Path, + remaining: &str, + bake_base: &Path, +) -> Result<(), BakeError> { + // Extract combinators preserving original order + let combos: Vec<&decorator::Decorator> = func + .decorators + .iter() + .filter(|d| decorator::is_combinator(d)) + .collect(); -// TODO: Learn closure and continue -// async fn run(f:F) -> TaskFn { -// let engine = Engine::new(); -// engine.execute_script -// } + // Build the script + let mut modified = func.clone(); + modified.body = remaining.to_string() + &func.body; + let script = builder::build_script( + &modified, bake_base, workspace, None, RenderMode::Full, + )?; -// fn with_timeout(f: F, duration: i32) -> TaskFn { + // Execute with combinator chain + execute_with_combos(engine, &script, ctx, event_tx, &combos).await +} -// } +async fn execute_with_combos( + engine: &Engine, + script: &PathBuf, + ctx: &ExecutionContext, + event_tx: &EventSender, + combos: &[&decorator::Decorator], +) -> Result<(), BakeError> { + build_combinator_chain(engine, script, ctx, event_tx, combos, 0).await +} + +/// Builds a combinator execution chain using `Box::pin` to avoid infinite-sized async fn recursion. +/// Each combinator wraps the inner future: processed outermost (idx=0) → innermost (baseline). +fn build_combinator_chain<'a>( + engine: &'a Engine, + script: &'a PathBuf, + ctx: &'a ExecutionContext, + event_tx: &'a EventSender, + combos: &'a [&'a decorator::Decorator], + idx: usize, +) -> Pin> + Send + 'a>> { + use decorator::Decorator; + + // Baseline: no more combinators — execute the script once + if idx >= combos.len() { + return Box::pin(async move { + engine.execute_script(script, ctx, event_tx).await?; + Ok(()) + }); + } + + match combos[idx] { + Decorator::Loop(count) => Box::pin(async move { + for i in 0..*count { + log::debug!("Loop {}/{}", i + 1, count); + build_combinator_chain(engine, script, ctx, event_tx, combos, idx + 1).await?; + } + Ok(()) + }), + Decorator::Timeout(secs) => Box::pin(async move { + tokio::time::timeout( + Duration::from_secs(*secs as u64), + build_combinator_chain(engine, script, ctx, event_tx, combos, idx + 1), + ) + .await + .map_err(|_| BakeError::ScriptExecutionError(ExecutionError::Timeout))? + }), + Decorator::Retry(retries, delay_secs) => Box::pin(async move { + let max_attempts = *retries as usize + 1; + let delay = Duration::from_secs(*delay_secs as u64); + let mut last_err = None; + for attempt in 0..max_attempts { + match build_combinator_chain( + engine, script, ctx, event_tx, combos, idx + 1, + ) + .await + { + Ok(_) => return Ok(()), + Err(e) => { + // Timeout should not be retried + if matches!(&e, BakeError::ScriptExecutionError(ExecutionError::Timeout)) { + return Err(e); + } + if attempt < max_attempts - 1 { + last_err = Some(e); + log::warn!( + "Retry {}/{} after {}s", + attempt + 1, + retries, + delay_secs + ); + tokio::time::sleep(delay).await; + } else { + return Err(e); + } + } + } + } + Err(last_err.unwrap()) + }), + // Non-combinator decorators: skip (shouldn't reach here if filtered correctly) + _ => build_combinator_chain(engine, script, ctx, event_tx, combos, idx + 1), + } +} + +// ── AI Coding Agent marker ── +// Code in this module PARTIALLY or FULLY utilized AI Coding Agent diff --git a/workshop-baker/src/bake/builder.rs b/workshop-baker/src/bake/builder.rs index 16cf838..3f367ad 100644 --- a/workshop-baker/src/bake/builder.rs +++ b/workshop-baker/src/bake/builder.rs @@ -1,4 +1,4 @@ -use crate::bake::constant::{PIPELINE_SKIP_ERRORCODE, TRIVIAL_SCRIPT_NAME}; +use crate::bake::constant::PIPELINE_SKIP_ERRORCODE; use crate::bake::decorator::Decorator; use crate::bake::error::BakeError; use crate::bake::parser::Function; @@ -9,24 +9,45 @@ use std::path::Path; use std::path::PathBuf; use tempfile::NamedTempFile; -/// Template for use_template=false, which just executes the main body without any wrapping. +/// Three-level rendering control. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum RenderMode { + /// No template, hardcoded "{{ main }}". Debug escape hatch. + Off, + /// Uses bake_base.sh with essential execution skeleton only. + Minimal, + /// Uses bake_base.sh with helpers (logs, colors, etc.). + Full, +} + +impl RenderMode { + pub fn as_str(&self) -> &'static str { + match self { + RenderMode::Off => "off", + RenderMode::Minimal => "minimal", + RenderMode::Full => "full", + } + } +} + +/// Hardcoded template for RenderMode::Off. const TRIVIAL_TEMPLATE: &str = "{{ main }}"; /// Renders a `Function` into an executable shell script using a minijinja template. -/// If `use_template` is true, wraps the body in `bake_base`; otherwise uses a trivial template. +/// +/// `render_mode` controls the template source: +/// - `Off`: hardcoded `{{ main }}`, no bake_base file needed. +/// - `Minimal` / `Full`: reads bake_base.sh; the template distinguishes the two +/// via the `render_mode` context variable. pub fn build_script( function: &Function, bake_base: &Path, target_dir: &Path, temp_path: Option, - use_template: bool, + render_mode: RenderMode, ) -> Result { let name = &function.name; - let trivial = name == TRIVIAL_SCRIPT_NAME; let body = &function.body; - let export = function - .find_decorator(|d| matches!(d, Decorator::Export).then_some(())) - .is_some(); let condition = function.find_decorator(|d| { if let Decorator::If(content) = d { Some(content.clone()) @@ -38,13 +59,14 @@ pub fn build_script( let filename = format!("bakefn_{}.sh", name); let target = target_dir.join(filename); - let bake_base_content = if use_template { - std::fs::read_to_string(bake_base).map_err(|e| BakeError::ScriptGenerationFailed { - script: bake_base.display().to_string(), - reason: e.to_string(), - })? - } else { - TRIVIAL_TEMPLATE.to_string() + let template_content = match render_mode { + RenderMode::Off => TRIVIAL_TEMPLATE.to_string(), + RenderMode::Minimal | RenderMode::Full => { + std::fs::read_to_string(bake_base).map_err(|e| BakeError::ScriptGenerationFailed { + script: bake_base.display().to_string(), + reason: e.to_string(), + })? + } }; let condition_code = match condition { @@ -57,30 +79,22 @@ pub fn build_script( None => String::new(), }; - let mut preexport_code = String::new(); - let mut export_code = String::new(); - if !trivial { - if export { - preexport_code = format!("declare -xp > /dev/shm/bakeenv.before.{}\n", name); - export_code = format!( - "declare -xp > /dev/shm/bakeenv.after.{}\ndiff -w --new-line-format='%L' --old-line-format='' --unchanged-line-format='' /dev/shm/bakeenv.before.{} /dev/shm/bakeenv.after.{} > /dev/shm/bakeexport.{}\n", - name, name, name, name - ); - } - preexport_code.push_str(format!(". /dev/shm/bakeexport.{}\n", name).as_str()); - export_code.push_str(format!("rm -f /dev/shm/bakeenv.*.{}\n", name).as_str()); - } + // preexport/export: stubs for the future IPC-based @export. + let preexport_code = String::new(); + let export_code = String::new(); let mut env = Environment::new(); - env.add_template("script", bake_base_content.as_str())?; - let script = env.get_template("script")?; - let rendered = if use_template { - script.render( - context! {main => body,condition => condition_code, preexport => preexport_code, name => name, export => export_code}, - )? - } else { - script.render(context! {main => body})? - }; + env.add_template("script", &template_content)?; + let rendered = env.get_template("script")?.render( + context! { + main => body, + condition => condition_code, + preexport => preexport_code, + name => name, + export => export_code, + render_mode => render_mode.as_str(), + }, + )?; let mut temp_file = NamedTempFile::new_in(&temp_path).map_err(|e| BakeError::ScriptGenerationFailed { @@ -114,7 +128,7 @@ mod tests { use super::*; #[test] - fn test_build_script_with_template() { + fn test_build_script_full_mode() { let temp_dir = tempfile::tempdir().unwrap(); let bake_base = temp_dir.path().join("bake_base.sh"); @@ -125,7 +139,9 @@ mod tests { decorators: vec![], body: "echo 'hello world'".to_string(), }; - let result = build_script(&function, &bake_base, temp_dir.path(), None, true); + let result = build_script( + &function, &bake_base, temp_dir.path(), None, RenderMode::Full, + ); assert!(result.is_ok()); let script_path = result.unwrap(); @@ -134,10 +150,10 @@ mod tests { } #[test] - fn test_build_script_trivial_mode() { + fn test_build_script_off_mode() { let temp_dir = tempfile::tempdir().unwrap(); let function = Function { - name: "trivial".to_string(), + name: "test".to_string(), decorators: vec![], body: "echo 'direct output'".to_string(), }; @@ -147,7 +163,7 @@ mod tests { Path::new("/nonexistent"), temp_dir.path(), None, - false, + RenderMode::Off, ); assert!(result.is_ok()); diff --git a/workshop-baker/src/bake/decorator.rs b/workshop-baker/src/bake/decorator.rs index 79d4002..4d75cec 100644 --- a/workshop-baker/src/bake/decorator.rs +++ b/workshop-baker/src/bake/decorator.rs @@ -7,8 +7,7 @@ use crate::bake::error::BakeError; lazy_static! { // "# @decorator" or "# @decorator(parameters)" - static ref DECORATOR_REGEX: Regex = Regex::new(r"^\s*#\s+@(\w+)\s?(\((.*)\))?").unwrap() - ; + static ref DECORATOR_REGEX: Regex = Regex::new(r"^\s*#\s+@(\w+)\s?(\((.*)\))?").unwrap(); static ref UNEXPECTED_ARGUMENT: &'static str = "decorator does not accept arguments"; static ref MISSING_ARGUMENT: &'static str = "decorator requires an argument"; static ref UNKNOWN_DECORATOR: &'static str = "unknown decorator"; @@ -46,6 +45,31 @@ pub enum Decorator { Health(String), } +/// Returns true if the decorator is a flag (position-independent, consumed after execution). +pub fn is_flag(d: &Decorator) -> bool { + matches!( + d, + Decorator::Pipeline + | Decorator::Fallible + | Decorator::Export + | Decorator::If(_) + | Decorator::After(_) + | Decorator::Parallel + | Decorator::Health(_) + | Decorator::Unknown + ) +} + +/// Returns true if the decorator is a combinator (position-dependent, nestable). +pub fn is_combinator(d: &Decorator) -> bool { + matches!(d, Decorator::Loop(_) | Decorator::Timeout(_) | Decorator::Retry(..)) +} + +/// Returns true if the decorator is a mode (at most one, alters what gets executed). +pub fn is_mode(d: &Decorator) -> bool { + matches!(d, Decorator::Pipe(_) | Decorator::Daemon) +} + impl Display for Decorator { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { diff --git a/workshop-baker/src/bake/error.rs b/workshop-baker/src/bake/error.rs index 680a5be..1aad016 100644 --- a/workshop-baker/src/bake/error.rs +++ b/workshop-baker/src/bake/error.rs @@ -34,6 +34,9 @@ pub enum BakeError { #[error("Script execution error: {0}")] ScriptExecutionError(#[from] ExecutionError), + + #[error("Incompatible decorators on function '{function}': {reason}")] + IncompatibleDecorators { function: String, reason: String }, } impl HasExitCode for BakeError { @@ -47,6 +50,7 @@ impl HasExitCode for BakeError { BakeError::YamlParseError(_) => EXITCODE_PARSE_ERROR, BakeError::DecoratorParseError { .. } => EXITCODE_PARSE_ERROR, BakeError::ScriptExecutionError(e) => e.exit_code(), + BakeError::IncompatibleDecorators { .. } => EXITCODE_PARSE_ERROR, } } } diff --git a/workshop-baker/src/bake/schedule.rs b/workshop-baker/src/bake/schedule.rs index 1a28a10..9de16b5 100644 --- a/workshop-baker/src/bake/schedule.rs +++ b/workshop-baker/src/bake/schedule.rs @@ -3,10 +3,13 @@ use crate::bake::error::BakeError; use std::collections::HashMap; use topological_sort::TopologicalSort; -/// Topologically sorts functions by explicit `@after` dependencies and implicit ordering -/// for consecutive `@pipeline` functions (each pipeline function implicitly depends on -/// the previous one unless it has an explicit `@after`). -pub fn sort_function(functions: Vec) -> Result, BakeError> { +/// Builds a DAG from functions using `@after` dependencies and implicit ordering +/// for consecutive `@pipeline` functions. Returns the sorted DAG and a name→Function map. +/// The caller should use `peek_all()` (non-destructive) to get ready nodes, execute them, +/// then `pop()` each completed node to advance the DAG. +pub fn build_dag( + functions: Vec, +) -> Result<(TopologicalSort, HashMap), BakeError> { let function_mapped = functions .iter() .map(|f| (f.name.as_str(), f)) @@ -31,8 +34,10 @@ pub fn sort_function(functions: Vec) -> Result, BakeErro // Add implicit @after for pipeline functions that don't have explicit @after if is_pipeline { - if !has_after && let Some(ref last) = last_pipeline { - ts.add_dependency(last, function.name.clone()); + if !has_after { + if let Some(ref last) = last_pipeline { + ts.add_dependency(last, function.name.clone()); + } } last_pipeline = Some(function.name.clone()); } @@ -55,18 +60,12 @@ pub fn sort_function(functions: Vec) -> Result, BakeErro } } - let mut functions_sorted = Vec::::new(); - while let Some(name) = ts.pop() { - let function = function_mapped[name.as_str()]; - functions_sorted.push(function.clone()); - } + let owned_map: HashMap = functions + .into_iter() + .map(|f| (f.name.clone(), f)) + .collect(); - if !ts.is_empty() { - let remaining: Vec = ts.collect(); - return Err(BakeError::CircularDependency(remaining)); - } - - Ok(functions_sorted) + Ok((ts, owned_map)) } #[cfg(test)] @@ -81,10 +80,28 @@ mod tests { } } + // Helper: drain the DAG and return sorted order (for backward-compatible tests) + fn drain_dag(functions: Vec) -> Result, BakeError> { + let (mut dag, func_map) = build_dag(functions)?; + let mut result = Vec::new(); + while !dag.is_empty() { + let batch = dag.pop_all(); + if batch.is_empty() { + // Cycle detected: remaining nodes form a circular dependency + let remaining: Vec = dag.into_iter().collect(); + return Err(BakeError::CircularDependency(remaining)); + } + for name in batch { + result.push(func_map[&name].clone()); + } + } + Ok(result) + } + #[test] fn test_empty_functions() { let functions = vec![]; - let result = sort_function(functions).unwrap(); + let result = drain_dag(functions).unwrap(); assert!(result.is_empty()); } @@ -93,7 +110,7 @@ mod tests { let function = create_function("func1", vec![], "body1"); let functions = vec![function.clone()]; - let result = sort_function(functions).unwrap(); + let result = drain_dag(functions).unwrap(); assert_eq!(result.len(), 1); assert_eq!(result[0].name, "func1"); @@ -106,7 +123,7 @@ mod tests { let function3 = create_function("func3", vec![], "body3"); let functions = vec![function1.clone(), function2.clone(), function3.clone()]; - let result = sort_function(functions).unwrap(); + let result = drain_dag(functions).unwrap(); assert_eq!(result.len(), 3); let names: Vec = result.iter().map(|f| f.name.clone()).collect(); @@ -125,7 +142,7 @@ mod tests { ); let functions = vec![function1.clone(), function2.clone()]; - let result = sort_function(functions).unwrap(); + let result = drain_dag(functions).unwrap(); assert_eq!(result.len(), 2); assert_eq!(result[0].name, "func1"); @@ -147,7 +164,7 @@ mod tests { ); let functions = vec![function1.clone(), function2.clone(), function3.clone()]; - let result = sort_function(functions).unwrap(); + let result = drain_dag(functions).unwrap(); assert_eq!(result.len(), 3); assert_eq!(result[0].name, "func1"); @@ -179,7 +196,7 @@ mod tests { function4.clone(), ]; - let result = sort_function(functions).unwrap(); + let result = drain_dag(functions).unwrap(); assert_eq!(result.len(), 4); @@ -212,7 +229,7 @@ mod tests { ); let functions = vec![function1.clone(), function2.clone()]; - let result = sort_function(functions).unwrap(); + let result = drain_dag(functions).unwrap(); assert_eq!(result.len(), 2); assert_eq!(result[0].name, "func1"); @@ -233,7 +250,7 @@ mod tests { ); let functions = vec![function1.clone(), function2.clone(), function3.clone()]; - let result = sort_function(functions).unwrap(); + let result = drain_dag(functions).unwrap(); assert_eq!(result.len(), 3); @@ -270,7 +287,7 @@ mod tests { ); let functions = vec![function1.clone(), function2.clone()]; - let result = sort_function(functions).unwrap(); + let result = drain_dag(functions).unwrap(); assert_eq!(result.len(), 2); let names: Vec = result.iter().map(|f| f.name.clone()).collect(); @@ -285,7 +302,7 @@ mod tests { let func3 = create_function("func3", vec![Decorator::Pipeline], "body3"); let functions = vec![func1.clone(), func2.clone(), func3.clone()]; - let result = sort_function(functions).unwrap(); + let result = drain_dag(functions).unwrap(); assert_eq!(result.len(), 3); assert_eq!(result[0].name, "func1"); @@ -298,7 +315,7 @@ mod tests { let func1 = create_function("func1", vec![Decorator::Pipeline], "body1"); let functions = vec![func1.clone()]; - let result = sort_function(functions).unwrap(); + let result = drain_dag(functions).unwrap(); assert_eq!(result.len(), 1); assert_eq!(result[0].name, "func1"); @@ -315,7 +332,7 @@ mod tests { let func3 = create_function("func3", vec![Decorator::Pipeline], "body3"); let functions = vec![func1.clone(), func2.clone(), func3.clone()]; - let result = sort_function(functions).unwrap(); + let result = drain_dag(functions).unwrap(); assert_eq!(result.len(), 3); let positions: std::collections::HashMap = result @@ -336,7 +353,7 @@ mod tests { let func4 = create_function("func4", vec![Decorator::Pipeline], "body4"); let functions = vec![func1.clone(), func2.clone(), func3.clone(), func4.clone()]; - let result = sort_function(functions).unwrap(); + let result = drain_dag(functions).unwrap(); assert_eq!(result.len(), 4); let positions: std::collections::HashMap = result @@ -353,7 +370,7 @@ mod tests { let func1 = create_function("func1", vec![Decorator::Pipeline], "body1"); let functions = vec![func1.clone()]; - let result = sort_function(functions).unwrap(); + let result = drain_dag(functions).unwrap(); assert_eq!(result.len(), 1); assert_eq!(result[0].name, "func1"); } @@ -364,7 +381,7 @@ mod tests { let func2 = create_function("func2", vec![Decorator::Pipeline], "body2"); let functions = vec![func1.clone(), func2.clone()]; - let result = sort_function(functions).unwrap(); + let result = drain_dag(functions).unwrap(); assert_eq!(result.len(), 2); let positions: std::collections::HashMap = result .iter() @@ -385,7 +402,7 @@ mod tests { ); let functions = vec![func1.clone(), func2.clone(), func3.clone()]; - let result = sort_function(functions).unwrap(); + let result = drain_dag(functions).unwrap(); assert_eq!(result.len(), 3); let positions: std::collections::HashMap = result .iter() @@ -411,7 +428,7 @@ mod tests { ); let functions = vec![func1.clone(), func2.clone()]; - let result = sort_function(functions); + let result = drain_dag(functions); assert!(result.is_err()); assert!(matches!( result.unwrap_err(), @@ -428,7 +445,7 @@ mod tests { ); let functions = vec![func1.clone()]; - let result = sort_function(functions); + let result = drain_dag(functions); assert!(result.is_err()); assert!(matches!( result.unwrap_err(), @@ -458,7 +475,7 @@ mod tests { ); let functions = vec![func1.clone(), func2.clone()]; - let result = sort_function(functions).unwrap(); + let result = drain_dag(functions).unwrap(); assert_eq!(result.len(), 2); let positions: std::collections::HashMap = result .iter() @@ -483,7 +500,7 @@ mod tests { func5.clone(), ]; - let result = sort_function(functions).unwrap(); + let result = drain_dag(functions).unwrap(); assert_eq!(result.len(), 5); let positions: std::collections::HashMap = result .iter() @@ -503,7 +520,7 @@ mod tests { let func3 = create_function("func3", vec![Decorator::Pipeline], "body3"); let functions = vec![func1.clone(), func2.clone(), func3.clone()]; - let result = sort_function(functions).unwrap(); + let result = drain_dag(functions).unwrap(); assert_eq!(result.len(), 3); let positions: std::collections::HashMap = result .iter() diff --git a/workshop-baker/src/bake/trivial.rs b/workshop-baker/src/bake/trivial.rs new file mode 100644 index 0000000..9e04b91 --- /dev/null +++ b/workshop-baker/src/bake/trivial.rs @@ -0,0 +1,32 @@ +use crate::bake::builder::{build_script, RenderMode}; +use crate::bake::constant::TRIVIAL_SCRIPT_NAME; +use crate::bake::error::BakeError; +use crate::bake::parser::Function; +use std::path::Path; +use workshop_engine::{Engine, EventSender, ExecutionContext}; + +/// Executes `script_content` as a single function in trivial mode. +/// +/// Two entry paths lead here: +/// - **Auto-detection** (`!has_pipeline`): bake.sh has no `@pipeline` functions, +/// the entire content is treated as one trivial function. +/// - **Explicit PIP** (future): `trivial` parameter forces trivial mode even +/// when `@pipeline` functions exist, typically with `RenderMode::Off`. +pub async fn run_trivial( + script_content: &str, + bake_base_path: &Path, + workspace: &Path, + engine: &Engine, + ctx: &mut ExecutionContext, + event_tx: &EventSender, + render_mode: RenderMode, +) -> Result<(), BakeError> { + let function = Function { + name: TRIVIAL_SCRIPT_NAME.to_string(), + decorators: vec![], + body: script_content.to_string(), + }; + let script = build_script(&function, bake_base_path, workspace, None, render_mode)?; + engine.execute_script(&script, ctx, event_tx).await?; + Ok(()) +} diff --git a/workshop-baker/src/monitor/cgroups.rs b/workshop-baker/src/monitor/cgroups.rs deleted file mode 100644 index afcbe54..0000000 --- a/workshop-baker/src/monitor/cgroups.rs +++ /dev/null @@ -1,118 +0,0 @@ -use crate::monitor::UsageStats; -use cgroups_rs; -use cgroups_rs::fs::Cgroup; -use cgroups_rs::fs::{cpu::CpuController, memory::MemController, pid::PidController}; -use log; -use std::time::{Duration, Instant}; -#[cfg(target_os = "linux")] -pub fn get_usage( - name: &String, - usage_prev: crate::monitor::UsageStats, -) -> anyhow::Result { - let hierarchy = cgroups_rs::fs::hierarchies::auto(); - if !hierarchy.v2() { - return Err(anyhow::anyhow!( - "Unsupported cgroup hierarchy, cgroups v2 required." - )); - } - let cgroup = Cgroup::load(hierarchy, name); - let mut cpu_usage_us: u64 = 0; - let mut cpu_usage_percent: f64 = 0.0; - let mut memory_usage_bytes: u64 = 0; - let mut memory_limit_bytes: i64 = 0; - let mut process_count: u64 = 0; - - if let Some(cpu) = cgroup.controller_of::() { - let stats = parse_cpustat(cpu.cpu().stat.as_str())?; - cpu_usage_us = stats.usage_usec; - } else { - log::warn!("Failed to fetch CPU usage info!"); - } - - let current = Instant::now(); - let delta_usage = cpu_usage_us - usage_prev.cpu_usage_us; - let delta_wall = current - usage_prev._timestamp; - if usage_prev._timestamp > current { - log::warn!("Usage stats are from the future!"); - } else if usage_prev.cpu_usage_us == 0 { - log::info!("Previous CPU usage is zero, probably a fresh start."); - } else if delta_wall < Duration::from_millis(1) { - log::warn!("Usage stats are too close together!"); - } else { - cpu_usage_percent = delta_usage as f64 / delta_wall.as_micros() as f64 * 100.0; - // dbg!(&delta_usage, &delta_wall); - } - - if let Some(mem) = cgroup.controller_of::() { - let stats = mem.memory_stat(); - memory_usage_bytes = stats.usage_in_bytes; - memory_limit_bytes = stats.limit_in_bytes; - } - - if let Some(proc) = cgroup.controller_of::() { - process_count = proc.get_pid_current()?; - } - Ok(UsageStats { - cpu_usage_us, - cpu_usage_percent, - memory_usage_bytes, - // memory_usage_percent: 0.0, - memory_limit_bytes, - process_count, - _timestamp: Instant::now(), - }) - - // todo!(); -} - -#[derive(Debug, Default, Clone)] -pub struct CgroupCpuStat { - pub usage_usec: u64, - pub user_usec: u64, - pub system_usec: u64, - pub nice_usec: u64, - pub core_sched_force_idle_usec: u64, - pub nr_periods: u64, - pub nr_throttled: u64, - pub throttled_usec: u64, - pub nr_bursts: u64, - pub burst_usec: u64, -} - -fn parse_cpustat(stat: &str) -> anyhow::Result { - let mut cpu_stat = CgroupCpuStat::default(); - for line in stat.lines() { - let line = line.trim(); - if line.is_empty() { - continue; - } - - let (key, value_str) = line - .split_once(' ') - .ok_or_else(|| anyhow::anyhow!("Invalid format:{}", line))?; - - let value = value_str.parse::().map_err(|e| { - anyhow::anyhow!( - "Cannot convert value {}({}) into integer: {}", - key, - value_str, - e - ) - })?; - - match key { - "usage_usec" => cpu_stat.usage_usec = value, - "user_usec" => cpu_stat.user_usec = value, - "system_usec" => cpu_stat.system_usec = value, - "nice_usec" => cpu_stat.nice_usec = value, - "core_sched.force_idle_usec" => cpu_stat.core_sched_force_idle_usec = value, - "nr_periods" => cpu_stat.nr_periods = value, - "nr_throttled" => cpu_stat.nr_throttled = value, - "throttled_usec" => cpu_stat.throttled_usec = value, - "nr_bursts" => cpu_stat.nr_bursts = value, - "burst_usec" => cpu_stat.burst_usec = value, - _unknown_key => {} // Ignore unknown keys - } - } - Ok(cpu_stat) -} diff --git a/workshop-baker/src/monitor/jobobject.rs b/workshop-baker/src/monitor/jobobject.rs deleted file mode 100644 index d9eff88..0000000 --- a/workshop-baker/src/monitor/jobobject.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[cfg(target_os = "windows")] -pub fn get_usage() -> anyhow::Result { - unimplemented!("Not supporting windows JobObject at this time!"); -} diff --git a/workshop-baker/src/notify/template.rs b/workshop-baker/src/notify/template.rs index 922f311..e611919 100644 --- a/workshop-baker/src/notify/template.rs +++ b/workshop-baker/src/notify/template.rs @@ -139,17 +139,11 @@ pub fn resolve_template( _standalone: bool, server_templates: Option<&HashMap>, ) -> Result { - // 1. Try the initial schema directly - if let Some(schema_name) = schema.as_deref() { - if let Some(template) = - resolve_template_by_schema(schema_name, method_name, templates, server_templates) - { - return Ok(template); - } - } - - // 2. Walk the fallback chain - let mut schema_next = resolve_template_next_schema(schema); + let mut schema_next = if schema.is_none() { + resolve_template_next_schema(schema) + } else { + schema.clone() + }; while let Some(ref schema_name) = schema_next { if let Some(template) = resolve_template_by_schema(schema_name, method_name, templates, server_templates) diff --git a/workshop-baker/src/prebake/security.rs b/workshop-baker/src/prebake/security.rs index 4b154d7..7437528 100644 --- a/workshop-baker/src/prebake/security.rs +++ b/workshop-baker/src/prebake/security.rs @@ -113,7 +113,7 @@ pub fn lookup_uid_by_name(username: &str) -> Result { } } - // TODO: originally unsafe {pwent} here, maybe to check and remove + // NOTE: originally unsafe {pwent} here // let pwent = pwent ; if pwent.is_null() { if let Ok(uid) = username.parse::() { diff --git a/workshop-baker/src/prebake/stage/bootstrap.rs b/workshop-baker/src/prebake/stage/bootstrap.rs index d7a65bd..31b945b 100644 --- a/workshop-baker/src/prebake/stage/bootstrap.rs +++ b/workshop-baker/src/prebake/stage/bootstrap.rs @@ -54,52 +54,50 @@ pub async fn bootstrap( let bootstrap_config = config.bootstrap.clone().unwrap_or_default(); if let Some(raw) = bootstrap_config.custom { - match BootstrapSource::parse(&raw) { - BootstrapSource::Inline(script) => { - if ctx.dry_run { - log::info!( - "[DRY_RUN] User provided inline bootstrap script:\n{}", - script - ); - return Ok(()); + // Workshop-specific schemes requiring bakerd infrastructure + if let Some(rest) = raw.strip_prefix("workspace://") { + log::info!("Custom bootstrap from workspace: {}", rest); + unimplemented!("Workspace-relative bootstrap requires bakerd's workspace manager"); + } else if let Some(name) = raw.strip_prefix("server://") { + log::info!("Custom bootstrap from server: {}", name); + unimplemented!("Server-provided bootstrap requires bakerd's script registry"); + } + // Use getterURL for standard URL schemes: http, https, file, git, ... + else if workshop_getterurl::GetterUrl::parse(&raw).is_ok() { + log::info!("Fetching custom bootstrap via getterURL: {}", raw); + if ctx.standalone { + let temp_dir = tempfile::tempdir().map_err(BootstrapError::IOError)?; + let fetched = workshop_getterurl::fetch(&raw, temp_dir.path()) + .await + .map_err(|e| BootstrapError::InvalidConfig(e.to_string()))?; + if fetched.is_dir() { + return Err(BootstrapError::InvalidConfig( + format!("getterURL returned a directory, expected a single file: {}", fetched.display()) + )); } - log::debug!("Using inline bootstrap script ({} bytes)", script.len()); - if ctx.standalone { - write_bootstrap_content(&bootstrap_path, script)?; - } else { - // Requires PIP(Pipeline Implicit Parameters) to be implemented - unimplemented!("Permission check is not implemented, failing..."); - } - } - BootstrapSource::Workspace(relpath) => { - // Project-relative path, resolved by bakerd against the project root. - // Routing path relpath through bakerd's workspace manager ensures - // proper sandboxing and access control. - log::info!("Custom bootstrap from workspace: {}", relpath); - unimplemented!("Workspace-relative bootstrap requires bakerd's workspace manager"); - } - BootstrapSource::Server(name) => { - // Script provided by the bake server (bakerd), keyed by name. - // The server maintains a registry of trusted bootstrap scripts. - log::info!("Custom bootstrap from server: {}", name); - unimplemented!("Server-provided bootstrap requires bakerd's script registry"); - } - BootstrapSource::Http(url) => { - // HTTP download, similar to finalize::plugin::fetch::fetch_http_plugin. - // Should support: checksum verification, retry, timeout. - log::info!("Custom bootstrap from URL: {}", url); - log::info!(" Target: {}", bootstrap_path.display()); - unimplemented!( - "HTTP bootstrap fetching requires bakerd-level network infrastructure" - ); - } - BootstrapSource::File(path) => { - // Local file copy. This is the simplest case — we can implement - // this directly without bakerd infrastructure. - log::info!("Custom bootstrap from local file: {}", path); - tokio::fs::copy(&path, &bootstrap_path) + let content = tokio::fs::read_to_string(&fetched) .await .map_err(BootstrapError::IOError)?; + if ctx.dry_run { + log::info!("[DRY_RUN] Fetched bootstrap script:\n{}", content); + return Ok(()); + } + write_bootstrap_content(&bootstrap_path, content)?; + } else { + unimplemented!("Permission check is not implemented, failing..."); + } + } + // Inline content (not a valid getterURL pattern) + else { + if ctx.dry_run { + log::info!("[DRY_RUN] User provided inline bootstrap script:\n{}", raw); + return Ok(()); + } + log::debug!("Using inline bootstrap script ({} bytes)", raw.len()); + if ctx.standalone { + write_bootstrap_content(&bootstrap_path, raw.to_string())?; + } else { + unimplemented!("Permission check is not implemented, failing..."); } } } else {