Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
Please login to access the resource
home:ojkastl_buildservice:Branch_devel_tools_building
ambient-driver
ambient-driver-0.1.0~1727114145.0b40a58.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File ambient-driver-0.1.0~1727114145.0b40a58.obscpio of Package ambient-driver
07070100000000000081A400000000000000000000000166F1ABA100000020000000000000000000000000000000000000003300000000ambient-driver-0.1.0~1727114145.0b40a58/.gitignore/target *.html test.log test.py 07070100000001000081A400000000000000000000000166F1ABA10000E2C4000000000000000000000000000000000000003300000000ambient-driver-0.1.0~1727114145.0b40a58/Cargo.lock# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "ahash" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom", "once_cell", "version_check", ] [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "aligned" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "377e4c0ba83e4431b10df45c1d4666f178ea9c552cac93e60c3a88bf32785923" dependencies = [ "as-slice", ] [[package]] name = "ambient-driver" version = "0.1.0" dependencies = [ "anyhow", "byte-unit", "bytesize", "clap", "directories", "fehler", "log", "pretty_env_logger", "serde", "serde_yaml 0.9.34+deprecated", "subplot-build", "subplotlib", "tar", "tempfile", "thiserror", "walkdir", ] [[package]] name = "anstream" version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", ] [[package]] name = "anyhow" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "arrayvec" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "as-slice" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" dependencies = [ "stable_deref_trait", ] [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "base64" version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bitvec" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ "funty", "radium", "tap", "wyz", ] [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "borsh" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" dependencies = [ "borsh-derive", "cfg_aliases", ] [[package]] name = "borsh-derive" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" dependencies = [ "once_cell", "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.71", "syn_derive", ] [[package]] name = "bstr" version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", "serde", ] [[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byte-unit" version = "5.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ac19bdf0b2665407c39d82dbc937e951e7e2001609f0fb32edd0af45a2d63e" dependencies = [ "rust_decimal", "serde", "utf8-width", ] [[package]] name = "bytecheck" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" dependencies = [ "bytecheck_derive", "ptr_meta", "simdutf8", ] [[package]] name = "bytecheck_derive" version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "bytes" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" [[package]] name = "bytesize" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e368af43e418a04d52505cf3dbc23dda4e3407ae2fa99fd0e4f308ce546acc" [[package]] name = "cc" version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" dependencies = [ "heck", "proc-macro2", "quote", "syn 2.0.71", ] [[package]] name = "clap_lex" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "colorchoice" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "cpufeatures" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] [[package]] name = "crossbeam-deque" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "cvt" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2ae9bf77fbf2d39ef573205d554d87e86c12f1994e9ea335b0651b9b278bcf1" dependencies = [ "cfg-if", ] [[package]] name = "deranged" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", ] [[package]] name = "deunicode" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00" [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", ] [[package]] name = "directories" version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", "option-ext", "redox_users", "windows-sys 0.48.0", ] [[package]] name = "env_logger" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" dependencies = [ "humantime", "is-terminal", "log", "regex", "termcolor", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "fastrand" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fehler" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5729fe49ba028cd550747b6e62cd3d841beccab5390aa398538c31a2d983635" dependencies = [ "fehler-macros", ] [[package]] name = "fehler-macros" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccb5acb1045ebbfa222e2c50679e392a71dd77030b78fb0189f2d9c5974400f9" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "file_diff" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31a7a908b8f32538a2143e59a6e4e2508988832d5d4d6f7c156b3cbc762643a5" [[package]] name = "filetime" version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" dependencies = [ "cfg-if", "libc", "redox_syscall", "windows-sys 0.52.0", ] [[package]] name = "fs2" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" dependencies = [ "libc", "winapi", ] [[package]] name = "fs_at" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "982f82cc75107eef84f417ad6c53ae89bf65b561937ca4a3b3b0fd04d0aa2425" dependencies = [ "aligned", "cfg-if", "cvt", "libc", "nix", "windows-sys 0.48.0", ] [[package]] name = "funty" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "generator" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" dependencies = [ "cc", "libc", "log", "rustversion", "windows", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "getopts" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" dependencies = [ "unicode-width", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "git-testament" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "710c78d2b68e46e62f5ba63ba0a7a2986640f37f9ecc07903b9ad4e7b2dbfc8e" dependencies = [ "git-testament-derive", ] [[package]] name = "git-testament-derive" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b31494efbbe1a6730f6943759c21b92c8dc431cb4df177e6f2a6429c3c96842" dependencies = [ "log", "proc-macro2", "quote", "syn 2.0.71", "time", ] [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" dependencies = [ "aho-corasick", "bstr", "log", "regex-automata 0.4.7", "regex-syntax 0.8.4", ] [[package]] name = "globwalk" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ "bitflags 2.6.0", "ignore", "walkdir", ] [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[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.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "html-escape" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" dependencies = [ "utf8-width", ] [[package]] name = "humansize" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" dependencies = [ "libm", ] [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "ignore" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" dependencies = [ "crossbeam-deque", "globset", "log", "memchr", "regex-automata 0.4.7", "same-file", "walkdir", "winapi-util", ] [[package]] name = "indexmap" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", ] [[package]] name = "indexmap" version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.5", ] [[package]] name = "is-terminal" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ "hermit-abi", "libc", "windows-sys 0.52.0", ] [[package]] name = "is_terminal_polyfill" version = "1.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[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.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libm" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libredox" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", ] [[package]] name = "line-col" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e69cdf6b85b5c8dce514f694089a2cf8b1a702f6cd28607bcb3cf296c9778db" [[package]] name = "linked-hash-map" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "loom" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" dependencies = [ "cfg-if", "generator", "scoped-tls", "serde", "serde_json", "tracing", "tracing-subscriber", ] [[package]] name = "matchers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ "regex-automata 0.1.10", ] [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "nix" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", ] [[package]] name = "normpath" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5831952a9476f2fed74b77d74182fa5ddc4d21c72ec45a333b250e3ed0272804" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "nu-ansi-term" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ "overload", "winapi", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[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.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" dependencies = [ "memchr", "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" dependencies = [ "pest", "pest_generator", ] [[package]] name = "pest_generator" version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", "syn 2.0.71", ] [[package]] name = "pest_meta" version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" dependencies = [ "once_cell", "pest", "sha2", ] [[package]] name = "pikchr" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b430b470a0dfac4e22cd248210e3ef005346acd1ada670d74d6bdcdbab0dc96e" dependencies = [ "cc", "libc", ] [[package]] name = "pin-project-lite" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "pretty_env_logger" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" dependencies = [ "env_logger", "log", ] [[package]] name = "proc-macro-crate" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro-error" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", "version_check", ] [[package]] name = "proc-macro-error-attr" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", "quote", "version_check", ] [[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "ptr_meta" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" dependencies = [ "ptr_meta_derive", ] [[package]] name = "ptr_meta_derive" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "pulldown-cmark" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" dependencies = [ "bitflags 2.6.0", "getopts", "memchr", "unicase", ] [[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "radium" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "redox_syscall" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "redox_users" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ "getrandom", "libredox", "thiserror", ] [[package]] name = "regex" version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", "regex-automata 0.4.7", "regex-syntax 0.8.4", ] [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ "regex-syntax 0.6.29", ] [[package]] name = "regex-automata" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", "regex-syntax 0.8.4", ] [[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.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "remove_dir_all" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23895cfadc1917fed9c6ed76a8c2903615fa3704f7493ff82b364c6540acc02b" dependencies = [ "aligned", "cfg-if", "cvt", "fs_at", "lazy_static", "libc", "normpath", "windows-sys 0.45.0", ] [[package]] name = "rend" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" dependencies = [ "bytecheck", ] [[package]] name = "rkyv" version = "0.7.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0" dependencies = [ "bitvec", "bytecheck", "bytes", "hashbrown 0.12.3", "ptr_meta", "rend", "rkyv_derive", "seahash", "tinyvec", "uuid", ] [[package]] name = "rkyv_derive" version = "0.7.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "roadmap" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a129e44a647b309ed394a092e21eabcb58537802c6912920ef4ea76239421234" dependencies = [ "anyhow", "serde", "serde_yaml 0.8.26", "textwrap", "thiserror", ] [[package]] name = "rust_decimal" version = "1.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" dependencies = [ "arrayvec", "borsh", "bytes", "num-traits", "rand", "rkyv", "serde", "serde_json", ] [[package]] name = "rustix" version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "rustversion" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scoped-tls" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "seahash" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "serde" version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde-aux" version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d2e8bfba469d06512e11e3311d4d051a4a387a5b42d010404fecf3200321c95" dependencies = [ "serde", "serde_json", ] [[package]] name = "serde_derive" version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", "syn 2.0.71", ] [[package]] name = "serde_json" version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "serde_yaml" version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" dependencies = [ "indexmap 1.9.3", "ryu", "serde", "yaml-rust", ] [[package]] name = "serde_yaml" version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ "indexmap 2.2.6", "itoa", "ryu", "serde", "unsafe-libyaml", ] [[package]] name = "sha2" version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sharded-slab" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] [[package]] name = "shell-words" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" [[package]] name = "simdutf8" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" [[package]] name = "slug" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3bd94acec9c8da640005f8e135a39fc0372e74535e6b368b7a04b875f784c8c4" dependencies = [ "deunicode", "wasm-bindgen", ] [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smawk" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "state" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" dependencies = [ "loom", ] [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subplot" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2c5723f41235a3deefab3cfd6164a5b780802f596fa97eb40dfcf3c39c36b21" dependencies = [ "anyhow", "base64", "clap", "env_logger", "file_diff", "git-testament", "html-escape", "lazy_static", "line-col", "log", "pikchr", "pulldown-cmark", "regex", "roadmap", "serde", "serde-aux", "serde_json", "serde_yaml 0.9.34+deprecated", "tempfile", "tempfile-fast", "tera", "thiserror", "time", "walkdir", ] [[package]] name = "subplot-build" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd58ff7123e83e5a5ed5bcdcd9f6f23226eea5b08bc310e129cad5d24b18fabe" dependencies = [ "subplot", "tempfile", "tracing", ] [[package]] name = "subplotlib" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fc3c17998cc92ec00493a7d1c0d0256f8977c7baed553777ba347dd6d1c3ccf" dependencies = [ "base64", "fehler", "filetime", "fs2", "glob", "lazy_static", "regex", "remove_dir_all", "shell-words", "state", "subplot-build", "subplotlib-derive", "tempfile", "time", "unescape", ] [[package]] name = "subplotlib-derive" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0234a041a912954e3cc81230b9f64f6a471c4297e65053e6ad733bb3e473bc60" dependencies = [ "fehler", "proc-macro2", "quote", "syn 2.0.71", ] [[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.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn_derive" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" dependencies = [ "proc-macro-error", "proc-macro2", "quote", "syn 2.0.71", ] [[package]] name = "tap" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tar" version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" dependencies = [ "filetime", "libc", "xattr", ] [[package]] name = "tempfile" version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", "rustix", "windows-sys 0.52.0", ] [[package]] name = "tempfile-fast" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a74be8531b1a9d607004a32b8f50dd8093b09ec6b0a6af004e33051068e87af6" dependencies = [ "libc", "rand", "tempfile", ] [[package]] name = "tera" version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab9d851b45e865f178319da0abdbfe6acbc4328759ff18dafc3a41c16b4cd2ee" dependencies = [ "globwalk", "humansize", "lazy_static", "percent-encoding", "pest", "pest_derive", "rand", "regex", "serde", "serde_json", "slug", "unic-segment", ] [[package]] name = "termcolor" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] name = "textwrap" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d" dependencies = [ "smawk", "unicode-linebreak", "unicode-width", ] [[package]] name = "thiserror" version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", "syn 2.0.71", ] [[package]] name = "thread_local" version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", ] [[package]] name = "time" version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", ] [[package]] name = "tinyvec" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" 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 = "toml_datetime" version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" [[package]] name = "toml_edit" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ "indexmap 2.2.6", "toml_datetime", "winnow", ] [[package]] name = "tracing" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", "syn 2.0.71", ] [[package]] name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", ] [[package]] name = "tracing-log" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ "log", "once_cell", "tracing-core", ] [[package]] name = "tracing-subscriber" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term", "once_cell", "regex", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", ] [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" [[package]] name = "unescape" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccb97dac3243214f8d8507998906ca3e2e0b900bf9bf4870477f125b82e68f6e" [[package]] name = "unic-char-property" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" dependencies = [ "unic-char-range", ] [[package]] name = "unic-char-range" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" [[package]] name = "unic-common" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" [[package]] name = "unic-segment" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" dependencies = [ "unic-ucd-segment", ] [[package]] name = "unic-ucd-segment" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" dependencies = [ "unic-char-property", "unic-char-range", "unic-ucd-version", ] [[package]] name = "unic-ucd-version" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" dependencies = [ "unic-common", ] [[package]] name = "unicase" version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ "version_check", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-linebreak" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-width" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unsafe-libyaml" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "utf8-width" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn 2.0.71", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", "syn 2.0.71", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[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-util" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ "windows-sys 0.52.0", ] [[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" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ "windows-targets 0.42.2", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-targets" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[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.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" version = "0.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" dependencies = [ "memchr", ] [[package]] name = "wyz" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] [[package]] name = "xattr" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" dependencies = [ "libc", "linux-raw-sys", "rustix", ] [[package]] name = "yaml-rust" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" dependencies = [ "linked-hash-map", ] 07070100000002000081A400000000000000000000000166F1ABA1000002A6000000000000000000000000000000000000003300000000ambient-driver-0.1.0~1727114145.0b40a58/Cargo.toml[package] name = "ambient-driver" version = "0.1.0" edition = "2021" default-run = "ambient-driver" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] anyhow = "1.0.75" byte-unit = { version = "5.1.4", features = ["serde"] } bytesize = "1.3.0" clap = { version = "4.4.7", features = ["derive"] } directories = "5.0.1" log = "0.4.20" pretty_env_logger = "0.5.0" serde = { version = "1.0.190", features = ["derive"] } serde_yaml = "0.9.27" subplotlib = "0.9.0" tar = "0.4.40" tempfile = "3.8.1" thiserror = "1.0.50" walkdir = "2.4.0" [build-dependencies] subplot-build = "0.9.0" [dev-dependencies] fehler = "1.0.0" 07070100000003000081A400000000000000000000000166F1ABA100000301000000000000000000000000000000000000003200000000ambient-driver-0.1.0~1727114145.0b40a58/README.md# Ambient - a CI engine See <https://ambient.liw.fi/> for more information. ## Legalese Copyright 2023 Lars Wirzenius and others This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. 07070100000004000081A400000000000000000000000166F1ABA100001F50000000000000000000000000000000000000003A00000000ambient-driver-0.1.0~1727114145.0b40a58/ambient-driver.md# Introduction `ambient-driver` is a prototype for running CI on a project, using `ambient-run`. # Smoke test _Requirement:_ The `ambient-driver` tool can show it's runtime configuration. _Justification:_ If this doesn't work, there's no hope of anything else working, either. _Stakeholder:_ Lars ~~~scenario given an installed ambient-driver given file .config/ambient-driver/config.yaml from config.yaml when I run ambient-driver config then stdout contains "ambient.log" ~~~ ~~~{#config.yaml .file .yaml} log: ambient.log run_ci: run-ci ~~~ # Allows specifying a configuration file _Requirement:_ The `ambient-driver` tool allows the user to specify which configuration file to use. _Justification:_ This is very convenient when one wants to try things and temporarily not use the default configuration file. This happens both for experimentation and for testing. _Stakeholder:_ Lars ~~~scenario given an installed ambient-driver given file special.yaml when I run ambient-driver --config special.yaml config then stdout contains "special.log" ~~~ ~~~{#special.yaml .file .yaml} log: special.log run_ci: run-ci ~~~ # Capture build log when running _Requirement:_ The stdout/stderr output of the commands executed by the CI run must be captured. _Justification:_ This is the primary way for the user to know what happened during a run. _Stakeholder:_ Lars. Note that we can't use `.` for the current directory as the `source` field in the project file due to how Subplot sets up a temporary data directory for each scenario, and sets `TMPDIR` to that directory. This would mean that `.` as `source` would create a tar archive in the temporary directory that tries to add itself, which creates an infinitely large tar archive. ~~~scenario given an installed ambient-driver given an Ambient VM image ambient.qcow2 given file .config/ambient-driver/config.yaml from config.yaml given file hello.yaml given a directory srcdir when I run env AMBIENT_LOG=trace ambient-driver run hello.yaml then file ambient.log contains "well hello there, friend" ~~~ ~~~{#hello.yaml .file .yaml} projects: hello: image: ambient.qcow2 source: srcdir plan: - action: shell shell: | echo well hello there, friend ~~~ # Relative filenames in project files _Requirement:_ When a project file has a relative filename, it's relative to the directory containing the project file. _Justification:_ The user can then run `ambient-driver run` from anywhere, not just the source directory. _Stakeholder:_ Lars. ~~~scenario given an installed ambient-driver given file .config/ambient-driver/config.yaml from config.yaml given a directory path/to/project/srcdir given an Ambient VM image ambient.qcow2 when I run mv ambient.qcow2 path/to/project/ given file path/to/project/hello.yaml from hello.yaml when I run env AMBIENT_LOG=trace ambient-driver run path/to/project/hello.yaml then file ambient.log contains "well hello there, friend" ~~~ # Working directory in pre- and post-plan actions _Requirement:_ When a pre- or post-plan action is executed, the current working directory should be the source directory. _Justification:_ Many actions can only usefully be executed in the source directory. _Stakeholder:_ Lars. Note that we can't use `.` for the current directory as the `source` field in the project file due to how Subplot sets up a temporary data directory for each scenario, and sets `TMPDIR` to that directory. This would mean that `.` as `source` would create a tar archive in the temporary directory that tries to add itself, which creates an infinitely large tar archive. ~~~scenario given an installed ambient-driver given file .config/ambient-driver/config.yaml from config.yaml given an Ambient VM image ambient.qcow2 given file cwd.yaml given a directory path/to/project/srcdir when I run env AMBIENT_LOG=ambient_driver::action=trace ambient-driver run cwd.yaml then stderr matches regex INFO.*cwd:.*/path/to/project/srcdir ~~~ ~~~{#cwd.yaml .file .yaml} projects: pwd: image: ambient.qcow2 source: path/to/project/srcdir pre_plan: - action: pwd post_plan: - action: pwd plan: - action: shell shell: | pwd ~~~ # Run CI only for some projects _Requirement:_ When the projects file has more than one project, user can choose only specific ones to run CI for. _Justification:_ User may be only interested in one project right now. _Stakeholders:_ Lars ~~~scenario given an installed ambient-driver given file .config/ambient-driver/config.yaml from config.yaml given an Ambient VM image ambient.qcow2 given file multi.yaml given a directory foo given a directory bar when I run env AMBIENT_LOG=debug ambient-driver run --dry-run multi.yaml foo then stderr contains "project foo: NOT running CI" then stderr doesn't contain "project bar:" ~~~ ~~~{#multi.yaml .file .yaml} projects: foo: image: ambient.qcow2 source: foo plan: - action: shell shell: | echo foo project bar: image: ambient.qcow2 source: bar plan: - action: shell shell: | echo bar project ~~~ # List names of projects _Requirement:_ The user can list names of projects in a projects file. _Justification:_ This is handy for checking and scripting. _Stakeholders:_ Lars ~~~scenario given an installed ambient-driver given file .config/ambient-driver/config.yaml from config.yaml given an Ambient VM image ambient.qcow2 given file multi.yaml given a directory foo given a directory bar when I run ambient-driver projects multi.yaml then stdout is exactly "bar\nfoo\n" ~~~ # List names of actions _Requirement:_ The user can list names of projects in a projects file. _Justification:_ This is handy for checking and scripting. _Stakeholders:_ Lars ~~~scenario given an installed ambient-driver given file .config/ambient-driver/config.yaml from config.yaml given an Ambient VM image ambient.qcow2 when I run ambient-driver actions then stdout contains "pre_actions:\n" then stdout contains "\nactions:\n" then stdout contains "\npost_actions:\n" ~~~ # Cache persists between CI runs _Requirement:_ Cache data is persisted between CI runs. _Justification:_ This allows incrementally building a project after changes. _Stakeholders:_ Lars ~~~scenario given an installed ambient-driver given file .config/ambient-driver/config.yaml from config.yaml given an Ambient VM image ambient.qcow2 given file cache.yaml given a directory srcdir when I run ambient-driver run cache.yaml then file ambient.log contains "counter is now 1." when I run ambient-driver run cache.yaml then file ambient.log contains "counter is now 2." when I run ambient-driver run cache.yaml then file ambient.log contains "counter is now 3." ~~~ ~~~{#cache.yaml .file .yaml} projects: hello: image: ambient.qcow2 source: srcdir plan: - action: shell shell: | cache=/workspace/cache counter="$cache/counter" if [ -e "$counter" ]; then n="$(expr "$(cat "$counter")" + 1)" echo "$n" > "$counter" else echo 1 > "$counter" fi echo "counter is now $(cat "$counter")." find "$cache" -ls ~~~ # Publish files via rsync _Requirement:_ Artifacts can be published via rsync to a server. _Justification:_ This allows publishing many kinds of things. _Stakeholders:_ Lars ~~~scenario given an installed ambient-driver given file .config/ambient-driver/config.yaml from config.yaml given an Ambient VM image ambient.qcow2 given file rsync.yaml given a directory srcdir given file srcdir/data.dat from rsync.yaml given a directory serverdir when I run ambient-driver run rsync.yaml --target serverdir then file serverdir/hello.txt contains "hello, world" ~~~ ~~~{#rsync.yaml .file .yaml} projects: hello: image: ambient.qcow2 source: srcdir plan: - action: shell shell: | echo hello, world > /workspace/artifacts/hello.txt post_plan: - action: rsync ~~~ 07070100000005000081A400000000000000000000000166F1ABA1000003B5000000000000000000000000000000000000003A00000000ambient-driver-0.1.0~1727114145.0b40a58/ambient-driver.pyimport os def install_ambient_driver(ctx): runcmd_prepend_to_path = globals()["runcmd_prepend_to_path"] srcdir = globals()["srcdir"] # Add the directory with built Rust binaries to the path. default_target = os.path.join(srcdir, "target") target = os.environ.get("CARGO_TARGET_DIR", default_target) runcmd_prepend_to_path(ctx, dirname=os.path.join(target, "debug")) ctx["binary"] = os.path.join(target, "debug", "ambient-driver") ctx["run-ci"] = os.path.join(target, "debug", "run-ci") shutil.copy(ctx["run-ci"], "run-ci") assert os.path.exists("run-ci") def install_vm_image(ctx, filename=None): assert "/" not in filename image = os.environ.get("IMAGE") if image is None: raise Exception("set IMAGE in environment to path to Ambient image to use") if not os.path.exists(image): raise Exception("$IMAGE MUST point to an existing file") os.symlink(image, filename) 07070100000006000081A400000000000000000000000166F1ABA1000000FC000000000000000000000000000000000000003F00000000ambient-driver-0.1.0~1727114145.0b40a58/ambient-driver.subplottitle: "ambient-driver acceptance criteria" authors: - Lars Wirzenius markdowns: - ambient-driver.md bindings: - ambient-driver.yaml - lib/files.yaml - lib/runcmd.yaml impls: python: - ambient-driver.py - lib/files.py - lib/runcmd.py 07070100000007000081A400000000000000000000000166F1ABA1000000DC000000000000000000000000000000000000003C00000000ambient-driver-0.1.0~1727114145.0b40a58/ambient-driver.yaml- given: an installed ambient-driver impl: python: function: install_ambient_driver - given: "an Ambient VM image {filename}" impl: python: function: install_vm_image types: filename: path 07070100000008000081ED00000000000000000000000166F1ABA100000514000000000000000000000000000000000000002E00000000ambient-driver-0.1.0~1727114145.0b40a58/check#!/bin/sh # # Run the automated tests for the project. set -eu die() { echo "$@" >&1 exit 1 } hideok=chronic image='' if ! command -v chronic >/dev/null; then hideok= fi while [ "$#" -gt 0 ]; do case "$1" in -v | --verbose) hideok= shift ;; -i | --image) shift if [ "$#" = 0 ]; then die "--image MUST have an argument" fi image="$1" shift ;; *) break ;; esac done require_cmd() { if ! command -v "$1" >/dev/null; then echo "Need to have $1 installed, but can't find it" 1>&2 return 1 fi } got_cargo_cmd() { cargo "$1" --help >/dev/null } require_cmd rustc require_cmd cc require_cmd cargo require_cmd python3 require_cmd subplot $hideok cargo --version $hideok rustc --version got_cargo_cmd clippy && cargo clippy --all-targets -q -- -D warnings $hideok cargo build --all-targets got_cargo_cmd fmt && $hideok cargo fmt -- --check $hideok cargo test subplot docgen ambient-driver.subplot -o ambient-driver.html target="$(cargo metadata --format-version=1 | python3 -c 'import sys, json; o = json.load(sys.stdin); print(o["target_directory"])')" subplot codegen ambient-driver.subplot -o test.py rm -f test.log $hideok python3 test.py --log test.log --env "CARGO_TARGET_DIR=$target" --env "IMAGE=$image" "$@" echo "Everything seems to be in order." 07070100000009000041ED00000000000000000000000266F1ABA100000000000000000000000000000000000000000000002F00000000ambient-driver-0.1.0~1727114145.0b40a58/debian0707010000000A000081A400000000000000000000000166F1ABA100000032000000000000000000000000000000000000004300000000ambient-driver-0.1.0~1727114145.0b40a58/debian/cargo-checksum.json/* This file needs to exist, but can be empty. */ 0707010000000B000081A400000000000000000000000166F1ABA100000090000000000000000000000000000000000000003900000000ambient-driver-0.1.0~1727114145.0b40a58/debian/changelogambient-driver (0.1.0-1) unstable; urgency=medium * First package release. -- Lars Wirzenius <liw@liw.fi> Wed, 14 Feb 2024 08:23:31 +0200 0707010000000C000081A400000000000000000000000166F1ABA100000004000000000000000000000000000000000000003600000000ambient-driver-0.1.0~1727114145.0b40a58/debian/compat10 0707010000000D000081A400000000000000000000000166F1ABA1000001BD000000000000000000000000000000000000003700000000ambient-driver-0.1.0~1727114145.0b40a58/debian/controlSource: ambient-driver Maintainer: Lars Wirzenius <liw@liw.fi> Section: devel Priority: optional Standards-Version: 4.6.2 Build-Depends: debhelper (>= 10~) Homepage: https://ambient.liw.fi/ Package: ambient-driver Architecture: any Depends: ${misc:Depends}, qemu-system-x86 Description: local continuous integration engine Ambient is a continuous integration system in development. This package contains a tool to run CI locally on projects. 0707010000000E000081A400000000000000000000000166F1ABA100000127000000000000000000000000000000000000003900000000ambient-driver-0.1.0~1727114145.0b40a58/debian/copyrightFormat: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: ambient-driver Upstream-Contact: https://ambient.liw.fi/ Source: https://codeberg.org/ambient Files: * Copyright: 2023-2024 Lars Wirzenius License: GPL-3+ See /usr/share/common-licenses/GPL-3 for a copy. 0707010000000F000081A400000000000000000000000166F1ABA10000008E000000000000000000000000000000000000004100000000ambient-driver-0.1.0~1727114145.0b40a58/debian/lintian-overridesambient-driver binary: initial-upload-closes-no-bugs ambient-driver binary: no-manual-page ambient-driver: shared-library-lacks-prerequisites 07070100000010000081ED00000000000000000000000166F1ABA10000010F000000000000000000000000000000000000003500000000ambient-driver-0.1.0~1727114145.0b40a58/debian/rules#!/usr/bin/make -f %: dh $@ override_dh_auto_build: true override_dh_auto_install: cargo install --offline --target x86_64-unknown-linux-musl --path=. --root=debian/ambient-driver rm -f debian/*/.crates*.* override_dh_auto_test: echo tests are disabled, for now 07070100000011000041ED00000000000000000000000266F1ABA100000000000000000000000000000000000000000000003600000000ambient-driver-0.1.0~1727114145.0b40a58/debian/source07070100000012000081A400000000000000000000000166F1ABA10000000C000000000000000000000000000000000000003D00000000ambient-driver-0.1.0~1727114145.0b40a58/debian/source/format3.0 (quilt) 07070100000013000041ED00000000000000000000000266F1ABA100000000000000000000000000000000000000000000002C00000000ambient-driver-0.1.0~1727114145.0b40a58/src07070100000014000081A400000000000000000000000166F1ABA1000032E2000000000000000000000000000000000000003600000000ambient-driver-0.1.0~1727114145.0b40a58/src/action.rsuse std::{ path::{Path, PathBuf}, process::{Command, Stdio}, }; use log::{debug, info}; use serde::{Deserialize, Serialize}; use crate::{ config::RunCommand, project::{Project, State}, qemu, util::{changes_file, dput, rsync_server, UtilError}, vdrive::{VirtualDriveBuilder, VirtualDriveError}, }; const RUST_ENVS: &[(&str, &str)] = &[ ("CARGO_TARGET_DIR", qemu::CACHE_DIR), ("CARGO_HOME", qemu::DEPS_DIR), ]; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "action")] #[serde(rename_all = "snake_case")] pub enum TrustedAction { Dummy, Pwd, CargoFetch, Rsync, Dput, } impl TrustedAction { pub fn names() -> &'static [&'static str] { &["dummy", "pwd", "cargo_fetch", "rsync", "dput"] } pub fn execute( &self, project: &Project, state: &State, run: &RunCommand, ) -> Result<(), ActionError> { debug!("Plan::execute: {:#?}", self); match self { Self::Dummy => dummy(), Self::Pwd => pwd(project), Self::CargoFetch => cargo_fetch(project, state), Self::Rsync => rsync(state, run), Self::Dput => dput_action(state, run), } } } fn dummy() -> Result<(), ActionError> { println!("dummy action"); Ok(()) } fn pwd(project: &Project) -> Result<(), ActionError> { info!("cwd: {}", project.source().display()); Ok(()) } fn rsync(state: &State, run: &RunCommand) -> Result<(), ActionError> { if let Some(target) = &run.target() { rsync_server(&state.artifactsdir(), target)?; } else { return Err(ActionError::RsyncTargetMissing); } Ok(()) } fn dput_action(state: &State, run: &RunCommand) -> Result<(), ActionError> { if let Some(target) = &run.dput_target() { let changes = changes_file(&state.artifactsdir())?; dput(target, &changes)?; } else { return Err(ActionError::DputTargetMissing); } Ok(()) } fn cargo_fetch(project: &Project, state: &State) -> Result<(), ActionError> { let cargo_home = state.dependenciesdir(); spawn_str_in( &[ "env", &format!("CARGO_HOME={}", cargo_home.display()), "cargo", "fetch", "--locked", "-vvv", ], project.source(), ) } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "action")] #[serde(rename_all = "snake_case")] pub enum UnsafeAction { Mkdir { pathname: PathBuf, }, TarCreate { archive: PathBuf, directory: PathBuf, }, TarExtract { archive: PathBuf, directory: PathBuf, }, Spawn { argv: Vec<String>, }, Shell { shell: String, }, CargoFmt, CargoClippy, CargoBuild, CargoTest, CargoInstall, Deb, } impl UnsafeAction { pub fn names() -> &'static [&'static str] { &[ "mkdir", "tar_create", "tar_extract", "spawn", "shell", "cargo_fmt", "cargo_clippy", "cargo_build", "cargo_test", "cargo_install", "deb", ] } pub fn execute(&self) -> Result<(), ActionError> { debug!("Plan::execute: {:#?}", self); match self { Self::Mkdir { pathname } => mkdir(pathname), Self::TarCreate { archive, directory } => tar_create(archive, directory), Self::TarExtract { archive, directory } => tar_extract(archive, directory), Self::Spawn { argv } => spawn(argv, &[]), Self::Shell { shell: snippet } => shell(snippet), Self::CargoFmt => cargo_fmt(), Self::CargoClippy => cargo_clippy(), Self::CargoBuild => cargo_build(), Self::CargoTest => cargo_test(), Self::CargoInstall => cargo_install(), Self::Deb => deb(), } } pub fn mkdir<P: AsRef<Path>>(pathname: P) -> Self { Self::Mkdir { pathname: pathname.as_ref().into(), } } pub fn tar_create<P: AsRef<Path>>(archive: P, directory: P) -> Self { Self::TarCreate { archive: archive.as_ref().into(), directory: directory.as_ref().into(), } } pub fn tar_extract<P: AsRef<Path>>(archive: P, directory: P) -> Self { Self::TarExtract { archive: archive.as_ref().into(), directory: directory.as_ref().into(), } } pub fn spawn(argv: &[&str]) -> Self { Self::Spawn { argv: argv.iter().map(|s| s.to_string()).collect(), } } } fn mkdir(pathname: &Path) -> Result<(), ActionError> { std::fs::create_dir(pathname).map_err(|e| ActionError::Mkdir(pathname.into(), e)) } fn tar_create(archive: &Path, dirname: &Path) -> Result<(), ActionError> { VirtualDriveBuilder::default() .filename(archive) .root_directory(dirname) .create(None) .map_err(|e| ActionError::TarCreate(archive.into(), dirname.into(), e))?; Ok(()) } fn tar_extract(archive: &Path, dirname: &Path) -> Result<(), ActionError> { let tar = VirtualDriveBuilder::default() .filename(archive) .root_directory(dirname) .open() .map_err(|e| ActionError::TarOpen(archive.into(), e))?; tar.extract_to(dirname) .map_err(|e| ActionError::TarExtract(archive.into(), dirname.into(), e))?; Ok(()) } fn spawn_in(argv: &[String], cwd: &Path, extra_env: &[(&str, &str)]) -> Result<(), ActionError> { println!("SPAWN: argv={argv:?}"); println!(" cwd={}", cwd.display()); println!(" extra_env={extra_env:?}"); let argv0 = if let Some(argv0) = argv.first() { argv0 } else { return Err(ActionError::SpawnNoArgv0); }; let argv_str = format!("{:?}", argv); let mut cmd = Command::new(argv0) .args(&argv[1..]) .envs(extra_env.iter().copied()) .stdin(Stdio::null()) .current_dir(cwd) .spawn() .map_err(|e| ActionError::SpawnInvoke(argv_str.clone(), e))?; let exit = cmd.wait(); let exit = exit.map_err(|e| ActionError::SpawnInvoke(argv_str.clone(), e))?; match exit.code() { Some(exit) => { if exit != 0 { return Err(ActionError::SpawnFailed(argv_str, exit)); } } None => return Err(ActionError::SpawnKilledBySignal(argv_str)), } Ok(()) } fn spawn_str_in(argv: &[&str], cwd: &Path) -> Result<(), ActionError> { let argv: Vec<String> = argv.iter().map(|s| s.to_string()).collect(); spawn_in(&argv, cwd, &[]) } fn spawn(argv: &[String], extra_env: &[(&str, &str)]) -> Result<(), ActionError> { spawn_in(argv, Path::new(qemu::SOURCE_DIR), extra_env) } fn shell(snippet: &str) -> Result<(), ActionError> { let snippet = format!("set -xeuo pipefail\n{}", snippet); spawn(&["/bin/bash".into(), "-c".into(), snippet], &[]) } fn spawn_str(argv: &[&str], extra_env: &[(&str, &str)]) -> Result<(), ActionError> { let argv: Vec<String> = argv.iter().map(|s| s.to_string()).collect(); spawn(&argv, extra_env) } fn prepend_cargo_bin_dir_to_path() -> String { let path = std::env::var("PATH").unwrap(); format!("/root/.cargo/bin:{path}") } fn rust_envs(path: &str) -> Vec<(&str, &str)> { let mut env = Vec::from(RUST_ENVS); env.push(("PATH", path)); env } fn spawn_cargo(args: &[&'static str]) -> Result<(), ActionError> { let path = prepend_cargo_bin_dir_to_path(); spawn_str(args, &rust_envs(&path)) } fn cargo_fmt() -> Result<(), ActionError> { spawn_cargo(&["cargo", "fmt", "--check"]) } fn cargo_clippy() -> Result<(), ActionError> { spawn_cargo(&[ "cargo", "clippy", "--offline", "--locked", "--workspace", "--all-targets", "-vvv", "--", "--deny", "warning", ]) } fn cargo_build() -> Result<(), ActionError> { spawn_cargo(&[ "cargo", "build", "--offline", "--locked", "--workspace", "--all-targets", "-vvv", ]) } fn cargo_test() -> Result<(), ActionError> { spawn_cargo(&[ "cargo", "test", "--offline", "--locked", "--workspace", "-vvv", ]) } fn cargo_install() -> Result<(), ActionError> { spawn_cargo(&[ "cargo", "install", "-vvv", "--offline", "--locked", "--bins", "--path=.", "--root", qemu::ARTIFACTS_DIR, ]) } fn deb() -> Result<(), ActionError> { let shell = format!( r#"#!/bin/bash set -xeuo pipefail export PATH="/root/.cargo/bin:$PATH" export CARGO_HOME=/workspace/deps export DEBEMAIL=liw@liw.fi export DEBFULLNAME="Lars Wirzenius" env command -v cargo command -v rustc cargo --version rustc --version # Get name and version of source package. name="$(dpkg-parsechangelog -SSource)" version="$(dpkg-parsechangelog -SVersion)" # Get upstream version: everything before the last dash. uv="$(echo "$version" | sed 's/-[^-]*$//')" # Files that will be created. arch="$(dpkg --print-architecture)" orig="../${{name}}_${{uv}}.orig.tar.xz" deb="../${{name}}_${{version}}_${{arch}}.deb" changes="../${{name}}_${{version}}_${{arch}}.changes" # Create "upstream tarball". git archive HEAD | xz >"$orig" # Build package. dpkg-buildpackage -us -uc # Dump some information to make it easier to visually verify # everything looks OK. Also, test the package with the lintian tool. ls -l .. for x in ../*.deb; do dpkg -c "$x"; done # FIXME: disabled while this prevents radicle-native-ci deb from being built. # lintian -i --allow-root --fail-on warning ../*.changes # Move files to artifacts directory. mv ../*_* {} "#, qemu::ARTIFACTS_DIR ); spawn_str(&["bash", "-c", &shell], RUST_ENVS) } #[derive(Debug, thiserror::Error)] pub enum ActionError { #[error("failed to create directory {0}")] Mkdir(PathBuf, #[source] std::io::Error), #[error("failed to create tar archive {0} from {1}")] TarCreate(PathBuf, PathBuf, #[source] VirtualDriveError), #[error("failed to open tar archive {0}")] TarOpen(PathBuf, #[source] VirtualDriveError), #[error("failed to extract tar archive {0} into {1}")] TarExtract(PathBuf, PathBuf, #[source] VirtualDriveError), #[error("failed to invoke command: empty argv")] SpawnNoArgv0, #[error("failed to invoke command: {0}")] SpawnInvoke(String, #[source] std::io::Error), #[error("command failed was killed by signal")] SpawnKilledBySignal(String), #[error("command failed: {0}")] SpawnFailed(String, i32), #[error("failed to remove directory {0}")] RemoveDir(PathBuf, #[source] std::io::Error), #[error("failed to remove file {0}")] RemoveFile(PathBuf, #[source] std::io::Error), #[error("failed to create symlink {0} -> {1}")] Symlink(PathBuf, PathBuf, #[source] std::io::Error), #[error(transparent)] Util(#[from] UtilError), #[error("for the rsync action you must specify an rsync target (config or command line)")] RsyncTargetMissing, #[error("for the dput action you must specify a dput target (config or command line)")] DputTargetMissing, } #[cfg(test)] mod test { use super::*; use tempfile::tempdir; #[test] fn mkdir_action() { let tmp = tempdir().unwrap(); let path = tmp.path().join("testdir"); let action = UnsafeAction::mkdir(&path); assert!(!path.exists()); assert!(action.execute().is_ok()); assert!(path.exists()); } #[test] fn tar_create_action() { let tmp = tempdir().unwrap(); let src = tmp.path().join("src"); let tar = tmp.path().join("src.tar"); std::fs::create_dir(&src).unwrap(); let action = UnsafeAction::tar_create(&tar, &src); assert!(!tar.exists()); assert!(action.execute().is_ok()); assert!(tar.exists()); } #[test] fn tar_extract_action() { let tmp = tempdir().unwrap(); let src = tmp.path().join("src"); let tar = tmp.path().join("src.tar"); let extracted = tmp.path().join("extracted"); std::fs::create_dir(&src).unwrap(); std::fs::File::create(src.join("file.dat")).unwrap(); UnsafeAction::tar_create(&tar, &src).execute().unwrap(); let action = UnsafeAction::tar_extract(&tar, &extracted); assert!(action.execute().is_ok()); assert!(extracted.join("file.dat").exists()); } #[test] fn spawn_action() { // We can't use the Spawn action here, as it expects the // SOURCE_DIR to exist. However, spawn_in does the same thing, // except in a directory of our choosing. assert!(spawn_in(&["true".into()], Path::new("/"), &[]).is_ok()); } } 07070100000015000041ED00000000000000000000000266F1ABA100000000000000000000000000000000000000000000003000000000ambient-driver-0.1.0~1727114145.0b40a58/src/bin07070100000016000081A400000000000000000000000166F1ABA100000353000000000000000000000000000000000000004200000000ambient-driver-0.1.0~1727114145.0b40a58/src/bin/ambient-driver.rsuse clap::Parser; use log::{debug, info}; use ambient_driver::{ config::{Args, EffectiveConfig}, run::run, }; fn main() { if let Err(e) = fallible_main() { eprintln!("ERROR: {}", e); let mut source = e.source(); while let Some(src) = source { eprintln!("caused by: {}", src); source = src.source(); } std::process::exit(1); } } fn fallible_main() -> anyhow::Result<()> { let mut builder = pretty_env_logger::formatted_builder(); builder.filter_level(log::LevelFilter::Info); builder.parse_env("AMBIENT_LOG"); builder.init(); info!("ambient-driver starts"); let args = Args::parse(); let config = EffectiveConfig::try_from(&args)?; debug!("{:#?}", config); run(&config)?; info!("ambient-driver ends successfully"); Ok(()) } 07070100000017000081A400000000000000000000000166F1ABA100000170000000000000000000000000000000000000003A00000000ambient-driver-0.1.0~1727114145.0b40a58/src/bin/run-ci.rsuse std::path::Path; use ambient_driver::plan::Plan; const PLAN_FILENAME: &str = "plan.yaml"; fn main() -> anyhow::Result<()> { println!("run-ci starts"); let plan = Plan::from_file(Path::new(PLAN_FILENAME))?; for action in plan.iter() { println!("RUN: {:#?}", action); action.execute()?; } println!("run-ci ends"); Ok(()) } 07070100000018000081A400000000000000000000000166F1ABA100001F07000000000000000000000000000000000000003600000000ambient-driver-0.1.0~1727114145.0b40a58/src/config.rsuse std::{ fs::File, path::{Path, PathBuf}, }; use byte_unit::Byte; use directories::ProjectDirs; use log::debug; use serde::Deserialize; use clap::{Parser, Subcommand}; use crate::util::expand_tilde; const QUAL: &str = "liw.fi"; const ORG: &str = "Ambient CI"; const APP: &str = env!("CARGO_PKG_NAME"); const GIB: u64 = Byte::GIBIBYTE.as_u64(); const DEFAULT_TMP: &str = "/tmp"; const DEFAULT_MEMORY: u64 = GIB; const DEFAULT_ARTIFACTS_MAX_SIZE: u64 = 10 * GIB; const DEFAULT_CACHE_MAX_SIZE: u64 = 10 * GIB; #[derive(Debug, Parser)] #[clap(name = APP, version = env!("CARGO_PKG_VERSION"))] pub struct Args { /// Configuration file to use instead of the default one. #[clap(long)] config: Option<PathBuf>, /// Operation #[clap(subcommand)] cmd: Command, } impl Args { pub fn cmd(&self) -> &Command { &self.cmd } } #[derive(Debug, Subcommand)] pub enum Command { Run(RunCommand), Config(ConfigCommand), Projects(ProjectsCommand), Actions(ActionsCommand), } #[derive(Debug, Clone, Default, Parser)] pub struct RunCommand { /// Project specification file. May contain any number of projects. projects: PathBuf, /// Which projects to run CI for, from the ones in the PROJECTS /// file. Default is all of them. chosen: Option<Vec<String>>, /// Build log. #[clap(long)] log: Option<PathBuf>, /// rsync target for publishing ikiwiki output. #[clap(long, alias = "rsync")] target: Option<String>, /// dput target for publishing .deb package. #[clap(long, alias = "dput")] dput_target: Option<String>, /// Path to `run-ci` binary to use to run CI in VM. #[clap(long)] run_ci: Option<PathBuf>, /// Only pretend to run CI. #[clap(long)] dry_run: bool, /// Run even if repository hasn't changed. #[clap(long)] force: bool, } impl RunCommand { fn with_config(&mut self, config: &StoredConfig) { if self.log.is_none() { self.log.clone_from(&config.log); } if self.target.is_none() { self.target.clone_from(&config.target); } if self.dput_target.is_none() { self.dput_target.clone_from(&config.dput_target); } if self.run_ci.is_none() { self.run_ci.clone_from(&config.run_ci); } } pub fn chosen(&self) -> Option<&[String]> { self.chosen.as_deref() } pub fn projects(&self) -> &Path { self.projects.as_path() } pub fn log(&self) -> Option<&Path> { self.log.as_deref() } pub fn target(&self) -> Option<&str> { self.target.as_deref() } pub fn dput_target(&self) -> Option<&str> { self.dput_target.as_deref() } pub fn run_ci(&self) -> Option<&Path> { self.run_ci.as_deref() } pub fn dry_run(&self) -> bool { self.dry_run } pub fn force(&self) -> bool { self.force } } #[derive(Debug, Parser, Clone)] pub struct ConfigCommand {} #[derive(Debug, Default, Deserialize)] pub struct StoredConfig { tmpdir: Option<PathBuf>, log: Option<PathBuf>, state: Option<PathBuf>, target: Option<String>, dput_target: Option<String>, run_ci: Option<PathBuf>, cpus: Option<usize>, memory: Option<Byte>, artifacts_max_size: Option<Byte>, cache_max_size: Option<Byte>, } impl StoredConfig { fn from_file(path: &Path) -> Result<Self, ConfigError> { let f = File::open(path).map_err(|e| ConfigError::Read(path.into(), e))?; let config: Self = serde_yaml::from_reader(f).map_err(|e| ConfigError::Yaml(path.into(), e))?; Ok(config) } pub fn tmpdir(&self) -> PathBuf { if let Some(path) = &self.tmpdir { path.into() } else if let Ok(path) = std::env::var("TMPDIR") { PathBuf::from(&path) } else { PathBuf::from(DEFAULT_TMP) } } pub fn state(&self) -> Option<&Path> { self.state.as_deref() } } // Some of the fields are for the "config" subcommand to write, and // it's OK if they're not otherwise accessed. #[allow(dead_code)] #[derive(Debug)] pub struct EffectiveConfig { config_file: PathBuf, config_file_read: bool, stored_config: StoredConfig, cmd: Command, } impl EffectiveConfig { pub fn config_file(&self) -> &Path { &self.config_file } pub fn config_file_read(&self) -> bool { self.config_file_read } pub fn stored_config(&self) -> &StoredConfig { &self.stored_config } pub fn cpus(&self) -> usize { self.stored_config.cpus.unwrap() } pub fn memory(&self) -> u64 { self.stored_config .memory .map(|v| v.as_u64()) .unwrap_or(DEFAULT_MEMORY) } pub fn cmd(&self) -> &Command { &self.cmd } pub fn artfifacts_max_size(&self) -> u64 { self.stored_config .artifacts_max_size .map(|v| v.as_u64()) .unwrap_or(DEFAULT_ARTIFACTS_MAX_SIZE) } pub fn cache_max_size(&self) -> u64 { self.stored_config .cache_max_size .map(|v| v.as_u64()) .unwrap_or(DEFAULT_CACHE_MAX_SIZE) } } impl TryFrom<&Args> for EffectiveConfig { type Error = ConfigError; fn try_from(args: &Args) -> Result<Self, Self::Error> { let dirs = ProjectDirs::from(QUAL, ORG, APP).ok_or(ConfigError::ProjectDirs)?; let config_path = if let Some(config_path) = &args.config { config_path.to_path_buf() } else { dirs.config_dir().join("config.yaml") }; let mut config_file_read = false; let mut stored_config = StoredConfig::default(); if config_path.exists() { config_file_read = true; stored_config = StoredConfig::from_file(Path::new(&config_path))?; if let Some(path) = &stored_config.tmpdir { stored_config.tmpdir = Some(expand_tilde(path)); } if let Some(path) = &stored_config.log { stored_config.log = Some(expand_tilde(path)); } if let Some(path) = &stored_config.state { stored_config.state = Some(expand_tilde(path)); } else { stored_config.state = dirs.state_dir().map(|p| p.into()); } if let Some(tgt) = &stored_config.run_ci { stored_config.run_ci = Some(expand_tilde(tgt)); } stored_config.cpus = Some(stored_config.cpus.unwrap_or(1)); debug!("{:#?}", stored_config); }; let cmd = match &args.cmd { Command::Run(build) => { let mut build = build.clone(); build.with_config(&stored_config); Command::Run(build) } Command::Config(x) => Command::Config(x.clone()), Command::Projects(x) => Command::Projects(x.clone()), Command::Actions(x) => Command::Actions(x.clone()), }; Ok(Self { config_file: config_path, config_file_read, stored_config, cmd, }) } } #[derive(Debug, thiserror::Error)] pub enum ConfigError { #[error("failed to find home directory, while looking for configuration file")] ProjectDirs, #[error("failed to read configuration file {0}")] Read(PathBuf, #[source] std::io::Error), #[error("failed to parse configuration file as YAML: {0}")] Yaml(PathBuf, #[source] serde_yaml::Error), } #[derive(Debug, Parser, Clone)] pub struct ProjectsCommand { /// Name of YAML file with projects. projects: PathBuf, } impl ProjectsCommand { pub fn projects(&self) -> &Path { &self.projects } } #[derive(Debug, Parser, Clone)] pub struct ActionsCommand {} 07070100000019000081A400000000000000000000000166F1ABA1000006AE000000000000000000000000000000000000003300000000ambient-driver-0.1.0~1727114145.0b40a58/src/git.rs//! Utility functions for git operations. use std::{path::Path, process::Command}; use log::trace; const UNKNOWN_EXIT: i32 = -1; /// Is a directory a git checkout? pub fn is_git(dirname: &Path) -> bool { let dotgit = dirname.join(".git"); trace!(".git exists? {}", dotgit.exists()); dirname.exists() && git_head(dirname).is_ok() } /// Return commit id for HEAD in a git checkout. pub fn git_head(dirname: &Path) -> Result<String, GitError> { let stdout = git(&["rev-parse", "HEAD"], dirname)?; let head = stdout.trim().into(); trace!("git HEAD in {} is {}", dirname.display(), head); Ok(head) } /// Is the git checkout clean? pub fn git_is_clean(dirname: &Path) -> bool { if let Ok(stdout) = git(&["status", "--short"], dirname) { stdout.is_empty() } else { false // some error happened, assume checkout is not clean } } // Run git command in a directory; if successful, return stdout as text. fn git(args: &[&str], dirname: &Path) -> Result<String, GitError> { let output = Command::new("git") .args(args) .current_dir(dirname) .output() .map_err(GitError::Invoke)?; let exit = output.status.code().unwrap_or(UNKNOWN_EXIT); if exit == 0 { Ok(String::from_utf8_lossy(&output.stdout).into()) } else { Err(GitError::Failed( exit, String::from_utf8_lossy(&output.stdout).into(), )) } } /// Possible errors from git operations. #[derive(Debug, thiserror::Error)] pub enum GitError { #[error("failed to run git at all")] Invoke(#[source] std::io::Error), #[error("git failed with exit code {0}; stderr:\n{1}")] Failed(i32, String), } 0707010000001A000081A400000000000000000000000166F1ABA100000085000000000000000000000000000000000000003300000000ambient-driver-0.1.0~1727114145.0b40a58/src/lib.rspub mod action; pub mod config; pub mod git; pub mod plan; pub mod project; pub mod qemu; pub mod run; pub mod util; pub mod vdrive; 0707010000001B000081A400000000000000000000000166F1ABA1000005B6000000000000000000000000000000000000003400000000ambient-driver-0.1.0~1727114145.0b40a58/src/plan.rsuse std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize}; use crate::action::UnsafeAction; #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct Plan { steps: Vec<UnsafeAction>, } impl Plan { pub fn from_file(filename: &Path) -> Result<Self, PlanError> { let plan = std::fs::read(filename).map_err(|e| PlanError::PlanOpen(filename.into(), e))?; let plan = serde_yaml::from_slice(&plan).map_err(|e| PlanError::PlanParse(filename.into(), e))?; Ok(plan) } pub fn to_file(&self, filename: &Path) -> Result<(), PlanError> { let plan = serde_yaml::to_string(&self).map_err(PlanError::PlanSerialize)?; std::fs::write(filename, plan).map_err(|e| PlanError::PlanWrite(filename.into(), e))?; Ok(()) } pub fn push(&mut self, action: UnsafeAction) { self.steps.push(action); } pub fn iter(&self) -> impl Iterator<Item = &UnsafeAction> { self.steps.iter() } } #[derive(Debug, thiserror::Error)] pub enum PlanError { #[error("failed to read CI plan file: {0}")] PlanOpen(PathBuf, #[source] std::io::Error), #[error("failed to parse CI plan file as YAML: {0}")] PlanParse(PathBuf, #[source] serde_yaml::Error), #[error("failed to serialize CI plan as YAML")] PlanSerialize(#[source] serde_yaml::Error), #[error("failed to write CI plan file: {0}")] PlanWrite(PathBuf, #[source] std::io::Error), } 0707010000001C000081A400000000000000000000000166F1ABA100001BFA000000000000000000000000000000000000003700000000ambient-driver-0.1.0~1727114145.0b40a58/src/project.rsuse std::{ collections::HashMap, fs::{read, write}, path::{Path, PathBuf}, }; use log::debug; use serde::{Deserialize, Serialize}; use crate::{ action::{TrustedAction, UnsafeAction}, util::expand_tilde, }; #[derive(Debug, Deserialize, Clone)] #[serde(deny_unknown_fields)] pub struct Projects { projects: HashMap<String, Project>, } impl Projects { pub fn from_file(filename: &Path) -> Result<Self, ProjectError> { let dirname = if let Some(parent) = filename.parent() { parent.to_path_buf() } else { return Err(ProjectError::Parent(filename.into())); }; let yaml = read(filename).map_err(|e| ProjectError::Read(filename.into(), e))?; let mut projects: Self = serde_yaml::from_slice(&yaml).map_err(|e| ProjectError::Yaml(filename.into(), e))?; for (_, p) in projects.projects.iter_mut() { p.expand_tilde(&dirname)?; } debug!("projects from file {}: {:#?}", filename.display(), projects); Ok(projects) } pub fn iter(&self) -> impl Iterator<Item = (&str, &Project)> { self.projects.iter().map(|(k, v)| (k.as_str(), v)) } } #[derive(Debug, Deserialize, Clone)] #[serde(deny_unknown_fields)] pub struct Project { source: PathBuf, image: PathBuf, pre_plan: Option<Vec<TrustedAction>>, plan: Option<Vec<UnsafeAction>>, post_plan: Option<Vec<TrustedAction>>, artifact_max_size: Option<u64>, cache_max_size: Option<u64>, } impl Project { fn expand_tilde(&mut self, basedir: &Path) -> Result<(), ProjectError> { self.source = Self::abspath(basedir.join(expand_tilde(&self.source)))?; self.image = Self::abspath(basedir.join(expand_tilde(&self.image)))?; Ok(()) } pub fn from_file(filename: &Path) -> Result<Self, ProjectError> { let dirname = if let Some(parent) = filename.parent() { parent.to_path_buf() } else { return Err(ProjectError::Parent(filename.into())); }; let yaml = read(filename).map_err(|e| ProjectError::Read(filename.into(), e))?; let mut project: Project = serde_yaml::from_slice(&yaml).map_err(|e| ProjectError::Yaml(filename.into(), e))?; project.expand_tilde(&dirname)?; debug!("project from file {}: {:#?}", filename.display(), project); Ok(project) } fn abspath(path: PathBuf) -> Result<PathBuf, ProjectError> { path.canonicalize() .map_err(|e| ProjectError::Canonicalize(path, e)) } pub fn source(&self) -> &Path { &self.source } pub fn image(&self) -> &Path { &self.image } pub fn artifact_max_size(&self) -> Option<u64> { self.artifact_max_size } pub fn cache_max_size(&self) -> Option<u64> { self.cache_max_size } pub fn pre_plan(&self) -> &[TrustedAction] { if let Some(plan) = &self.pre_plan { plan.as_slice() } else { &[] } } pub fn plan(&self) -> &[UnsafeAction] { if let Some(plan) = &self.plan { plan.as_slice() } else { &[] } } pub fn post_plan(&self) -> &[TrustedAction] { if let Some(plan) = &self.post_plan { plan.as_slice() } else { &[] } } } /// Persistent project state. #[derive(Debug, Clone, Deserialize, Serialize)] #[allow(dead_code)] pub struct State { // File where this state is stored, if it's stored. #[serde(skip)] filename: PathBuf, // Where persistent state is stored for this project. #[serde(skip)] statedir: PathBuf, /// Latest commit that CI has run on, if any. latest_commit: Option<String>, } impl State { /// Load state for a project from a file, if it's present. If it's /// not present, return an empty state. pub fn from_file(statedir: &Path, project: &str) -> Result<Self, ProjectError> { let statedir = statedir.join(project); let filename = statedir.join("meta.yaml"); debug!("load project state from {}", filename.display()); if filename.exists() { let yaml = read(&filename).map_err(|e| ProjectError::ReadState(filename.clone(), e))?; let mut state: Self = serde_yaml::from_slice(&yaml) .map_err(|e| ProjectError::ParseState(filename.clone(), e))?; state.filename = filename; state.statedir = statedir; Ok(state) } else { Ok(Self { filename, statedir, latest_commit: None, }) } } /// Write project state. pub fn write_to_file(&self) -> Result<(), ProjectError> { debug!("write project state to {}", self.filename.display()); let yaml = serde_yaml::to_string(&self) .map_err(|e| ProjectError::SerializeState(self.clone(), e))?; if !self.statedir.exists() { std::fs::create_dir(&self.statedir) .map_err(|e| ProjectError::CreateState(self.statedir.clone(), e))?; } write(&self.filename, yaml) .map_err(|e| ProjectError::WriteState(self.filename.clone(), e))?; Ok(()) } /// Return state directory. pub fn statedir(&self) -> &Path { &self.statedir } /// Return artifacts directory for project. pub fn artifactsdir(&self) -> PathBuf { self.statedir.join("artifacts") } /// Return cache directory for project. pub fn cachedir(&self) -> PathBuf { self.statedir.join("cache") } /// Return dependencies directory for a project. pub fn dependenciesdir(&self) -> PathBuf { self.statedir.join("dependencies") } /// Return latest commit that CI has run on. pub fn latest_commit(&self) -> Option<&str> { self.latest_commit.as_deref() } /// Set latest commit. pub fn set_latest_commot(&mut self, commit: Option<&str>) { self.latest_commit = commit.map(|s| s.into()); } } #[derive(Debug, thiserror::Error)] pub enum ProjectError { #[error("failed to determine directory containing project file {0}")] Parent(PathBuf), #[error("failed to make filename absolute: {0}")] Canonicalize(PathBuf, #[source] std::io::Error), #[error("failed top read project file {0}")] Read(PathBuf, #[source] std::io::Error), #[error("failed to parse project file as YAML: {0}")] Yaml(PathBuf, #[source] serde_yaml::Error), #[error("failed to serialize project state as YAML: {0:#?}")] SerializeState(State, #[source] serde_yaml::Error), #[error("failed to write project state to file {0}")] WriteState(PathBuf, #[source] std::io::Error), #[error("failed to read project state from file {0}")] ReadState(PathBuf, #[source] std::io::Error), #[error("failed to parse project state file as YAML: {0}")] ParseState(PathBuf, #[source] serde_yaml::Error), #[error("failed to create project state directory {0}")] CreateState(PathBuf, #[source] std::io::Error), } 0707010000001D000081A400000000000000000000000166F1ABA100003146000000000000000000000000000000000000003400000000ambient-driver-0.1.0~1727114145.0b40a58/src/qemu.rs//! Run QEMU. use std::{ fs::{copy, File}, path::{Path, PathBuf}, process::Command, }; use bytesize::MIB; use log::{debug, error, trace}; use tempfile::tempdir_in; use crate::vdrive::{VirtualDrive, VirtualDriveError}; const OVMF_FD: &str = "/usr/share/ovmf/OVMF.fd"; pub const RUN_CI_DRIVE: &str = "/dev/vdb"; pub const SOURCE_DRIVE: &str = "/dev/vdc"; pub const ARTIFACT_DRIVE: &str = "/dev/vdd"; pub const CACHE_DRIVE: &str = "/dev/vde"; pub const DEPS_DRIVE: &str = "/dev/vdf"; pub const WORKSPACE_DIR: &str = "/workspace"; pub const SOURCE_DIR: &str = "/workspace/src"; pub const DEPS_DIR: &str = "/workspace/deps"; pub const CACHE_DIR: &str = "/workspace/cache"; pub const ARTIFACTS_DIR: &str = "/workspace/artifacts"; /// Build a QEMU runner. #[derive(Debug)] pub struct QemuBuilder<'a> { tmpdir: PathBuf, image: Option<PathBuf>, log: Option<PathBuf>, run_ci: Option<&'a VirtualDrive>, source: Option<&'a VirtualDrive>, artifact: Option<&'a VirtualDrive>, dependencies: Option<&'a VirtualDrive>, cache: Option<&'a VirtualDrive>, cpus: Option<usize>, memory: Option<u64>, } impl<'a> QemuBuilder<'a> { /// Create a new runner. pub fn new(tmpdir: &Path) -> Self { Self { image: None, tmpdir: tmpdir.into(), source: None, log: None, run_ci: None, artifact: None, dependencies: None, cache: None, cpus: None, memory: None, } } /// Set base image to use. pub fn with_image(mut self, image: &Path) -> Self { self.image = Some(image.into()); self } /// Set number of virtual CPUs to use. pub fn with_cpus(mut self, cpus: usize) -> Self { self.cpus = Some(cpus); self } /// Set amount of memory to use. pub fn with_memory(mut self, memory: u64) -> Self { self.memory = Some(memory); self } /// Set log file. pub fn with_log(mut self, log: &Path) -> Self { self.log = Some(log.into()); self } /// Set directory with run-ci program. pub fn with_run_ci(mut self, drive: &'a VirtualDrive) -> Self { self.run_ci = Some(drive); self } /// Set source directory. pub fn with_source(mut self, source: &'a VirtualDrive) -> Self { self.source = Some(source); self } /// Set output artifact filename. pub fn with_artifact(mut self, artifact: &'a VirtualDrive) -> Self { self.artifact = Some(artifact); self } /// Set directory where dependencies are. pub fn with_dependencies(mut self, drive: &'a VirtualDrive) -> Self { self.dependencies = Some(drive); self } /// Set directory where project build cache is. pub fn with_cache(mut self, drive: &'a VirtualDrive) -> Self { self.cache = Some(drive); self } /// Create a [`Qemu`], or panic if something is wrong. pub fn build(self) -> Result<Qemu<'a>, QemuError> { Ok(Qemu { image: self.image.ok_or_else(|| QemuError::Missing("image"))?, tmpdir: self.tmpdir, log: self.log.ok_or_else(|| QemuError::Missing("log"))?, run_ci: self.run_ci, source: self.source, artifact: self.artifact, dependencies: self.dependencies, cache: self.cache, cpus: self.cpus.unwrap(), memory: self.memory.unwrap(), }) } } /// A QEMU runner. #[derive(Debug)] pub struct Qemu<'a> { image: PathBuf, tmpdir: PathBuf, log: PathBuf, run_ci: Option<&'a VirtualDrive>, source: Option<&'a VirtualDrive>, artifact: Option<&'a VirtualDrive>, dependencies: Option<&'a VirtualDrive>, cache: Option<&'a VirtualDrive>, cpus: usize, memory: u64, } impl<'a> Qemu<'a> { /// Run QEMU in the specified way. pub fn run(&self) -> Result<i32, QemuError> { debug!("run QEMU"); trace!("{:#?}", self); let tmp = tempdir_in(&self.tmpdir).map_err(QemuError::TempDir)?; let image = tmp.path().join("vm.qcow2"); let vars = tmp.path().join("vars.fd"); let console_log = tmp.path().join("console.log"); debug!("create copy-on-write image and UEFI vars file"); create_cow_image(&self.image, &image)?; copy(OVMF_FD, &vars).map_err(|e| QemuError::Copy(OVMF_FD.into(), e))?; let source = self.source.unwrap(); let artifact_drive = self.artifact.unwrap(); let deps_drive = self.dependencies.unwrap(); let cache_drive = self.cache.unwrap(); let run_ci_drive = self.run_ci.unwrap(); debug!("set console log file"); Self::create_file(&console_log)?; debug!("set build log file"); let build_log = Self::create_file(&self.log)?; let cpus = format!("cpus={}", self.cpus); let memory = format!("{}", self.memory / MIB); let args = QemuArgs::default() .with_valued_arg("-m", &memory) .with_valued_arg("-smp", &cpus) .with_valued_arg("-display", "none") .with_valued_arg("-serial", &format!("file:/{}", console_log.display())) // ttyS0 .with_valued_arg("-serial", &format!("file:{}", build_log.display())) // ttyS1 .with_ipflash(0, OVMF_FD, true) .with_ipflash(1, vars.to_str().unwrap(), false) .with_qcow2(image.to_str().unwrap()) .with_raw(run_ci_drive.filename(), true) .with_raw(source.filename(), true) .with_raw(artifact_drive.filename(), false) .with_raw(cache_drive.filename(), false) .with_raw(deps_drive.filename(), true) .with_arg("-nodefaults"); debug!("spawn QEMU"); let child = Command::new("kvm") .args(args.iter()) .spawn() .map_err(QemuError::Invoke)?; debug!("wait for QEMU to finish"); let output = child.wait_with_output().map_err(QemuError::Run)?; if output.status.success() { debug!("QEMU OK"); let log = Self::build_log(&build_log)?; let exit = Self::exit_code(&log)?; Ok(exit) } else { let exit = output.status.code().unwrap_or(255); let out = String::from_utf8_lossy(&output.stdout).to_string(); let err = String::from_utf8_lossy(&output.stderr).to_string(); let log = std::fs::read(&console_log).map_err(QemuError::TemporaryLog)?; let log = String::from_utf8_lossy(&log).to_string(); error!( "QEMU failed: exit={}\nstdout: {:?}\nstderr: {:?}\nVM console: {:?}", exit, out, err, log ); Ok(exit) } } fn create_file(filename: &Path) -> Result<PathBuf, QemuError> { File::create(filename).map_err(|e| QemuError::Log(filename.into(), e))?; Ok(filename.into()) } fn build_log(filename: &Path) -> Result<String, QemuError> { const BEGIN: &str = "====================== BEGIN ======================"; let log = std::fs::read(filename).map_err(|e| QemuError::ReadLog(filename.into(), e))?; let log = String::from_utf8_lossy(&log); if let Some((_, log)) = log.split_once(BEGIN) { return Ok(log.into()); } Ok("".into()) } fn exit_code(log: &str) -> Result<i32, QemuError> { const EXIT: &str = "\nEXIT CODE: "; if let Some((_, rest)) = log.split_once(EXIT) { trace!("log has {:?}", EXIT); if let Some((exit, _)) = rest.split_once('\n') { let exit = exit.trim(); trace!("log has newline after exit: {:?}", exit); if let Ok(exit) = exit.parse::<i32>() { trace!("log exit coded parses ok: {}", exit); return Ok(exit); } debug!("log exit does not parse"); } } Err(QemuError::NoExit) } } fn create_cow_image(backing_file: &Path, new_file: &Path) -> Result<(), QemuError> { let output = Command::new("qemu-img") .arg("create") .arg("-b") .arg(backing_file) .args(["-F", "qcow2"]) .args(["-f", "qcow2"]) .arg(new_file) .output() .map_err(QemuError::Run)?; if !output.status.success() { let exit = output.status.code().unwrap_or(255); let out = String::from_utf8_lossy(&output.stdout).to_string(); let err = String::from_utf8_lossy(&output.stderr).to_string(); error!( "qemu-img failed: exit={}\nstdout: {:?}\nstderr: {:?}", exit, out, err, ); Err(QemuError::QemuImg(exit, err)) } else { Ok(()) } } #[derive(Debug, Default)] struct QemuArgs { args: Vec<String>, } impl QemuArgs { fn with_arg(mut self, arg: &str) -> Self { self.args.push(arg.into()); self } fn with_valued_arg(mut self, arg: &str, value: &str) -> Self { self.args.push(arg.into()); self.args.push(value.into()); self } fn with_ipflash(mut self, unit: usize, path: &str, readonly: bool) -> Self { self.args.push("-drive".into()); self.args.push(format!( "if=pflash,format=raw,unit={},file={}{}", unit, path, if readonly { ",readonly=on" } else { "" }, )); self } fn with_qcow2(mut self, path: &str) -> Self { self.args.push("-drive".into()); self.args .push(format!("format=qcow2,if=virtio,file={}", path)); self } fn with_raw(mut self, path: &Path, readonly: bool) -> Self { self.args.push("-drive".into()); self.args.push(format!( "format=raw,if=virtio,file={}{}", path.display(), if readonly { ",readonly=on" } else { "" }, )); self } fn iter(&self) -> impl Iterator<Item = &str> { self.args.iter().map(|s| s.as_str()) } } /// Possible errors from running Qemu. #[allow(missing_docs)] #[derive(Debug, thiserror::Error)] pub enum QemuError { #[error("missing field in QemuBuilder: {0}")] Missing(&'static str), #[error("failed to invoke QEMU")] Invoke(#[source] std::io::Error), #[error("QEMU failed")] Run(#[source] std::io::Error), #[error("qemu-img failed: exit code {0}: stderr:\n{1}")] QemuImg(i32, String), #[error("failed to run QEMU (kvm): exit code {0}, stderr:\n{1}\nconsole: {2}")] Kvm(i32, String, String), #[error("failed to create a temporary directory")] TempDir(#[source] std::io::Error), #[error("failed to copy to temporary directory: {0}")] Copy(PathBuf, #[source] std::io::Error), #[error("failed to write temporary file")] Write(#[source] std::io::Error), #[error("failed to read log file {0}")] ReadLog(PathBuf, #[source] std::io::Error), #[error("failed to open log file for writing")] Log(PathBuf, #[source] std::io::Error), #[error("failed to create a tar archive from {0}")] Tar(PathBuf, #[source] VirtualDriveError), #[error("failed to remove cache (so it can be replaced): {0}")] RemoveCache(PathBuf, #[source] std::io::Error), #[error("failed to extract cache drive to {0}")] ExtractCache(PathBuf, #[source] VirtualDriveError), #[error("failed to read temporary file for logging")] TemporaryLog(#[source] std::io::Error), #[error("build log lacks exit code of run")] NoExit, } #[cfg(test)] mod test { use super::QemuBuilder; use bytesize::GIB; use std::path::Path; #[test] fn sets_image() { let path = Path::new("/my/image.qcow2"); let log = Path::new("/tmp/log"); let tmp = Path::new("/tmp"); let qemu = QemuBuilder::new(tmp) .with_image(path) .with_log(log) .with_cpus(1) .with_memory(GIB) .build() .unwrap(); assert_eq!(&qemu.image, &path); } #[test] fn sets_log() { let path = Path::new("/my/image.qcow2"); let log = Path::new("/my/log"); let tmp = Path::new("/tmp"); let qemu = QemuBuilder::new(tmp) .with_image(path) .with_cpus(1) .with_memory(GIB) .with_log(log) .build() .unwrap(); assert_eq!(qemu.log, log); } } 0707010000001E000081A400000000000000000000000166F1ABA100005306000000000000000000000000000000000000003300000000ambient-driver-0.1.0~1727114145.0b40a58/src/run.rsuse std::{ collections::HashSet, path::{Path, PathBuf}, }; use log::{debug, info}; use serde::Serialize; use tempfile::tempdir_in; use crate::{ action::{ActionError, TrustedAction, UnsafeAction}, config::{Command, EffectiveConfig, ProjectsCommand, RunCommand}, git::{git_head, git_is_clean, is_git, GitError}, plan::{Plan, PlanError}, project::{Project, ProjectError, Projects, State}, qemu::{self, QemuBuilder, QemuError}, util::{mkdir, mkdir_child, recreate_dir, UtilError}, vdrive::{VirtualDrive, VirtualDriveBuilder, VirtualDriveError}, }; pub fn run(config: &EffectiveConfig) -> Result<(), RunError> { match config.cmd() { Command::Run(x) => cmd_run(config, x)?, Command::Config(_) => cmd_config(config)?, Command::Projects(x) => cmd_projects(x)?, Command::Actions(_) => cmd_actions(), } Ok(()) } fn cmd_run(config: &EffectiveConfig, run: &RunCommand) -> Result<(), RunError> { let projects = Projects::from_file(run.projects())?; let log = if let Some(log) = run.log() { log } else { Path::new("/dev/null") }; let statedir = config.stored_config().state().ok_or(RunError::NoState)?; if !statedir.exists() { debug!( "Create state directory shared by all projects {}", statedir.display() ); mkdir(statedir).map_err(|e| RunError::MkdirState(statedir.into(), e))?; } for (name, project) in chosen(run, &projects) { let (do_run, mut state) = should_run(run, statedir, name, project)?; if do_run { info!("project {}: running CI", name); let plan = construct_unsafe_plan(project)?; debug!("Executing pre-plan steps"); for action in project.pre_plan() { action.execute(project, &state, run)?; } let tmp = tempdir_in(config.stored_config().tmpdir()).map_err(RunError::TempDir)?; let source_drive = create_tar(tmp.path().join("src.tar"), project.source())?; let artifactsdir = state.artifactsdir(); recreate_dir(&artifactsdir)?; let artifact_drive = create_tar_with_size( tmp.path().join("artifacts.tar"), &artifactsdir, project .artifact_max_size() .unwrap_or(config.artfifacts_max_size()), )?; let dependencies = state.dependenciesdir(); if !dependencies.exists() { mkdir(&dependencies) .map_err(|e| RunError::MkdirProjectSubState(dependencies.clone(), e))?; } let deps_drive = create_tar(tmp.path().join("deps.tar"), &dependencies)?; let cachedir = state.cachedir(); if !cachedir.exists() { mkdir(&cachedir) .map_err(|e| RunError::MkdirProjectSubState(cachedir.clone(), e))?; } let cache_drive = create_tar_with_size( tmp.path().join("cache.tar"), &cachedir, project.cache_max_size().unwrap_or(config.cache_max_size()), )?; let run_ci_drive = { assert!(run.run_ci().is_some()); let bin = &run.run_ci().unwrap(); let dirname = mkdir_child(tmp.path(), "run-ci").map_err(RunError::MkdirProjectRunCi)?; let bin2 = dirname.join("run-ci"); debug!("copying {} to {}", bin.display(), bin2.display()); std::fs::copy(bin, &bin2).map_err(|e| RunError::Copy(bin.into(), bin2, e))?; let plan_filename = dirname.join("plan.yaml"); std::fs::write(&plan_filename, plan.as_bytes()) .map_err(|e| RunError::PlanWrite(plan_filename.clone(), e))?; create_tar(tmp.path().join("run-ci.tar"), &dirname)? }; QemuRunner::default() .config(config) .project(project) .run_ci(&run_ci_drive) .source(&source_drive) .cache(&cache_drive) .dependencies(&deps_drive) .artifacts(&artifact_drive) .log(log) .run()?; artifact_drive.extract_to(&artifactsdir)?; debug!("remove old cache"); std::fs::remove_dir_all(&cachedir) .map_err(|e| QemuError::RemoveCache(cachedir.clone(), e))?; debug!("extract cache"); cache_drive .extract_to(&cachedir) .map_err(|e| QemuError::ExtractCache(cachedir.clone(), e))?; debug!("Executing post-plan steps"); for action in project.post_plan() { action.execute(project, &state, run)?; } if is_git(project.source()) { let head = git_head(project.source())?; state.set_latest_commot(Some(&head)); } else { state.set_latest_commot(None); }; state.write_to_file()?; } else { info!("project {}: NOT running CI", name); } } Ok(()) } fn create_tar(tar_filename: PathBuf, dirname: &Path) -> Result<VirtualDrive, QemuError> { assert!(!tar_filename.starts_with(dirname)); debug!("create virtual drive {}", tar_filename.display()); let tar = VirtualDriveBuilder::default() .filename(&tar_filename) .root_directory(dirname) .create(None) .map_err(|e| QemuError::Tar(dirname.into(), e))?; Ok(tar) } fn create_tar_with_size( tar_filename: PathBuf, dirname: &Path, size: u64, ) -> Result<VirtualDrive, QemuError> { let tar = VirtualDriveBuilder::default() .filename(&tar_filename) .root_directory(dirname) .size(size) .create(None) .map_err(|e| QemuError::Tar(dirname.into(), e))?; Ok(tar) } #[derive(Default)] struct QemuRunner<'a> { config: Option<&'a EffectiveConfig>, project: Option<&'a Project>, run_ci: Option<&'a VirtualDrive>, source: Option<&'a VirtualDrive>, dependencies: Option<&'a VirtualDrive>, cache: Option<&'a VirtualDrive>, artifacts: Option<&'a VirtualDrive>, log: Option<&'a Path>, } impl<'a> QemuRunner<'a> { fn config(mut self, value: &'a EffectiveConfig) -> Self { self.config = Some(value); self } fn project(mut self, value: &'a Project) -> Self { self.project = Some(value); self } fn run_ci(mut self, value: &'a VirtualDrive) -> Self { self.run_ci = Some(value); self } fn source(mut self, value: &'a VirtualDrive) -> Self { self.source = Some(value); self } fn dependencies(mut self, value: &'a VirtualDrive) -> Self { self.dependencies = Some(value); self } fn cache(mut self, value: &'a VirtualDrive) -> Self { self.cache = Some(value); self } fn artifacts(mut self, value: &'a VirtualDrive) -> Self { self.artifacts = Some(value); self } fn log(mut self, value: &'a Path) -> Self { self.log = Some(value); self } fn run(&self) -> Result<(), RunError> { let config = self.config.ok_or(RunError::Missing("config"))?; let project = self.project.ok_or(RunError::Missing("project"))?; let run_ci_drive = self.run_ci.ok_or(RunError::Missing("run_ci_drive"))?; let source = self.source.ok_or(RunError::Missing("source"))?; let dependencies = self.dependencies.ok_or(RunError::Missing("dependencies"))?; let cache = self.cache.ok_or(RunError::Missing("cache"))?; let artifacts = self.artifacts.ok_or(RunError::Missing("artifacts"))?; let log = self.log.ok_or(RunError::Missing("log"))?; let qemu = QemuBuilder::new(&config.stored_config().tmpdir()) .with_image(project.image()) .with_cpus(config.cpus()) .with_memory(config.memory()) .with_run_ci(run_ci_drive) .with_source(source) .with_artifact(artifacts) .with_dependencies(dependencies) .with_cache(cache) .with_log(log) .build()?; let exit = qemu.run()?; debug!("QEMU exit code {}", exit); if exit != 0 { return Err(RunError::QemuFailed(exit)); } Ok(()) } } fn construct_unsafe_plan(project: &Project) -> Result<String, RunError> { let mut plan = Plan::default(); prelude(&mut plan); for action in project.plan().iter() { plan.push(action.clone()); } epilog(&mut plan); debug!("plan: {:#?}", plan); serde_yaml::to_string(&plan).map_err(RunError::PlanSerialize) } fn should_run( run: &RunCommand, statedir: &Path, name: &str, project: &Project, ) -> Result<(bool, State), RunError> { let mut decision = Decision::default(); let state = State::from_file(statedir, name)?; if let Some(latest_commit) = state.latest_commit() { debug!("latest commit: {:?}", latest_commit); decision.latest_commit(latest_commit); } else { debug!("no latest commit stored"); // No need to set latest commit to anything, the default is OK. } let is_git = is_git(project.source()); if is_git { debug!("is a git repository"); decision.is_git(); } else { debug!("is not a git repository"); decision.is_not_git(); } if git_is_clean(project.source()) { debug!("git repository is clean"); decision.is_clean(); } else { debug!("git repository is dirty"); decision.is_dirty(); } if is_git { let head = git_head(project.source())?; debug!("current (HEAD) commit: {head}"); decision.current_commit(&head); } else { debug!("no current commit due to not git repository"); // No need to set current commit to anything, the default is OK. } if run.dry_run() { debug!("dry run requested"); decision.dry_run(); } else { debug!("no dry run requested"); decision.no_dry_run(); } if run.force() { debug!("forced run requested"); decision.force(); } else { debug!("no forced run requested"); decision.no_force(); } let do_run = decision.should_run() == ShouldRun::Run; debug!("run? {do_run}"); Ok((do_run, state)) } fn chosen<'a>(run: &'a RunCommand, projects: &'a Projects) -> Vec<(&'a str, &'a Project)> { let set: HashSet<&str> = match run.chosen() { Some(v) if !v.is_empty() => v.iter().map(|s| s.as_str()).collect(), _ => projects.iter().map(|(k, _)| k).collect(), }; let mut projects: Vec<(&'a str, &'a Project)> = projects.iter().filter(|(k, _)| set.contains(k)).collect(); projects.sort_by(|(a_name, _), (b_name, _)| a_name.cmp(b_name)); projects } fn prelude(plan: &mut Plan) { plan.push(UnsafeAction::mkdir(Path::new(qemu::WORKSPACE_DIR))); plan.push(UnsafeAction::mkdir(Path::new(qemu::ARTIFACTS_DIR))); plan.push(UnsafeAction::tar_extract( Path::new(qemu::SOURCE_DRIVE), Path::new(qemu::SOURCE_DIR), )); plan.push(UnsafeAction::tar_extract( Path::new(qemu::DEPS_DRIVE), Path::new(qemu::DEPS_DIR), )); plan.push(UnsafeAction::tar_extract( Path::new(qemu::CACHE_DRIVE), Path::new(qemu::CACHE_DIR), )); plan.push(UnsafeAction::spawn(&[ "find", "/workspace", "-maxdepth", "2", "-ls", ])); } fn epilog(plan: &mut Plan) { plan.push(UnsafeAction::tar_create( Path::new(qemu::CACHE_DRIVE), Path::new(qemu::CACHE_DIR), )); plan.push(UnsafeAction::tar_create( Path::new(qemu::ARTIFACT_DRIVE), Path::new(qemu::ARTIFACTS_DIR), )); } fn cmd_config(config: &EffectiveConfig) -> Result<(), RunError> { println!("{:#?}", config); Ok(()) } fn cmd_projects(projcts: &ProjectsCommand) -> Result<(), RunError> { let projects = Projects::from_file(projcts.projects())?; let mut names: Vec<&str> = projects.iter().map(|(name, _)| name).collect(); names.sort(); for name in names.iter() { println!("{}", name); } Ok(()) } fn cmd_actions() { #[derive(Serialize)] struct Actions { pre_actions: Vec<&'static str>, actions: Vec<&'static str>, post_actions: Vec<&'static str>, } let mut actions = Actions { pre_actions: TrustedAction::names().to_vec(), actions: UnsafeAction::names().to_vec(), post_actions: TrustedAction::names().to_vec(), }; actions.pre_actions.sort(); actions.actions.sort(); actions.post_actions.sort(); print!("{}", serde_yaml::to_string(&actions).unwrap()); } #[derive(Debug, thiserror::Error)] pub enum RunError { #[error("programming error: field {0} is not set for QemuRunner")] Missing(&'static str), #[error(transparent)] Project(#[from] ProjectError), #[error(transparent)] Util(#[from] UtilError), #[error("failed to create temporary directory for running CI build")] TempDir(#[source] std::io::Error), #[error("failed to read ikikwiki setup file {0}")] ReadSetup(PathBuf, #[source] std::io::Error), #[error("failed to parse ikiwiki setup file as YAML: {0}")] SetupYaml(PathBuf, #[source] serde_yaml::Error), #[error("failed to figure out parent directory of {0}")] Parent(PathBuf), #[error("failed to create a general state directory")] MkdirState(PathBuf, #[source] UtilError), #[error("failed to create a project state directory")] MkdirProjectState(PathBuf, #[source] UtilError), #[error("failed to create a project state sub-directory")] MkdirProjectSubState(PathBuf, #[source] UtilError), #[error("failed to create a temporary directory for action runner")] MkdirProjectRunCi(#[source] UtilError), #[error("failed to make filename absolute: {0}")] Canonicalize(PathBuf, #[source] std::io::Error), #[error("failed to run ambient-run")] AmbientRunInvoke(#[source] std::io::Error), #[error("ambient-run failed:\n{0}")] AmbientRun(String), #[error("ambient-run didn't create the artifact tar archive {0}")] NoArtifact(PathBuf), #[error("failed to create temporary directory for unpacking artifacts")] CreateStaging(PathBuf, #[source] std::io::Error), #[error("failed to open artifact tar archive {0}")] OpenArtifacts(PathBuf, #[source] std::io::Error), #[error("failed to extract artifact archive {0} to {0}")] Extract(PathBuf, PathBuf, #[source] std::io::Error), #[error(transparent)] Qemu(#[from] QemuError), #[error("QEMU failed")] QemuFailed(i32), #[error("failed to copy {0} to {1}")] Copy(PathBuf, PathBuf, #[source] std::io::Error), #[error(transparent)] Plan(#[from] PlanError), #[error(transparent)] Action(#[from] ActionError), #[error(transparent)] Git(#[from] GitError), #[error(transparent)] VDrive(#[from] VirtualDriveError), #[error("no state directory specified")] NoState, #[error("failed to parse CI plan file as YAML: {0}")] PlanParse(PathBuf, #[source] serde_yaml::Error), #[error("failed to serialize CI plan as YAML")] PlanSerialize(#[source] serde_yaml::Error), #[error("failed to write CI plan file: {0}")] PlanWrite(PathBuf, #[source] std::io::Error), } #[derive(Debug, Default)] struct Decision { dry_run: Option<bool>, force_run: Option<bool>, is_git: Option<bool>, latest_commit: Option<String>, current_commit: Option<String>, source_is_dirty: Option<bool>, } impl Decision { fn dry_run(&mut self) { self.dry_run = Some(true); } fn no_dry_run(&mut self) { self.dry_run = Some(false); } fn force(&mut self) { self.force_run = Some(true); } fn no_force(&mut self) { self.force_run = Some(false); } fn is_git(&mut self) { self.is_git = Some(true); } fn is_not_git(&mut self) { self.is_git = Some(false); } fn latest_commit(&mut self, commit: &str) { self.latest_commit = Some(commit.into()); } fn current_commit(&mut self, commit: &str) { self.current_commit = Some(commit.into()); } fn is_clean(&mut self) { self.source_is_dirty = Some(false); } fn is_dirty(&mut self) { self.source_is_dirty = Some(true); } fn should_run(&self) -> ShouldRun { let dry_run = self.dry_run.unwrap(); let force = self.force_run.unwrap(); let is_git = self.is_git.unwrap(); let dirty = self.source_is_dirty.unwrap(); if dry_run { Self::log("dry run"); ShouldRun::DontRun } else if force { Self::log("force"); ShouldRun::Run } else if !is_git { Self::log("not git"); ShouldRun::Run } else if dirty { Self::log("dirty"); ShouldRun::Run } else if self.current_commit == self.latest_commit { Self::log("commits are equal"); ShouldRun::DontRun } else { Self::log("nothing prevents run"); ShouldRun::Run } } #[allow(unused_variables)] fn log(msg: &str) { #[cfg(test)] println!("{}", msg); } } // Use a custom enum to avoid confusing reader with interpreting // boolean values, specially in tests. #[derive(Debug, Eq, PartialEq, Copy, Clone)] enum ShouldRun { Run, DontRun, } #[cfg(test)] mod test_run_decision { use super::{Decision, ShouldRun}; #[test] fn is_not_git() { let mut d = Decision::default(); d.no_dry_run(); d.no_force(); d.is_not_git(); d.is_clean(); assert_eq!(d.should_run(), ShouldRun::Run); } #[test] fn unchanged() { let mut d = Decision::default(); d.no_dry_run(); d.no_force(); d.is_git(); d.is_clean(); d.latest_commit("abcd"); d.current_commit("abcd"); assert_eq!(d.should_run(), ShouldRun::DontRun); } #[test] fn unchanged_with_force() { let mut d = Decision::default(); d.no_dry_run(); d.force(); d.is_git(); d.is_clean(); d.latest_commit("abcd"); d.current_commit("abcd"); assert_eq!(d.should_run(), ShouldRun::Run); } #[test] fn unchanged_commit_but_dirty() { let mut d = Decision::default(); d.no_dry_run(); d.no_force(); d.is_git(); d.is_dirty(); d.latest_commit("abcd"); d.current_commit("abcd"); assert_eq!(d.should_run(), ShouldRun::Run); } #[test] fn commit_changed() { let mut d = Decision::default(); d.no_dry_run(); d.no_force(); d.is_git(); d.is_clean(); d.latest_commit("abcd"); d.current_commit("efgh"); assert_eq!(d.should_run(), ShouldRun::Run); } #[test] fn dry_run_for_unchanged() { let mut d = Decision::default(); d.dry_run(); d.no_force(); d.is_git(); d.is_clean(); d.latest_commit("abcd"); d.current_commit("abcd"); assert_eq!(d.should_run(), ShouldRun::DontRun); } #[test] fn dry_run_for_unchanged_but_dirty() { let mut d = Decision::default(); d.dry_run(); d.no_force(); d.is_git(); d.is_dirty(); d.latest_commit("abcd"); d.current_commit("efgh"); assert_eq!(d.should_run(), ShouldRun::DontRun); } #[test] fn dry_run_for_commit_changed() { let mut d = Decision::default(); d.dry_run(); d.no_force(); d.is_git(); d.is_clean(); d.latest_commit("abcd"); d.current_commit("efgh"); assert_eq!(d.should_run(), ShouldRun::DontRun); } #[test] fn dry_run_for_unchanged_with_force() { let mut d = Decision::default(); d.dry_run(); d.no_force(); d.is_git(); d.is_clean(); d.latest_commit("abcd"); d.current_commit("abcd"); assert_eq!(d.should_run(), ShouldRun::DontRun); } #[test] fn dry_run_for_unchanged_but_dirty_with_force() { let mut d = Decision::default(); d.dry_run(); d.no_force(); d.is_git(); d.is_dirty(); d.latest_commit("abcd"); d.current_commit("efgh"); assert_eq!(d.should_run(), ShouldRun::DontRun); } #[test] fn dry_run_for_commit_changed_with_force() { let mut d = Decision::default(); d.dry_run(); d.no_force(); d.is_git(); d.is_clean(); d.latest_commit("abcd"); d.current_commit("efgh"); assert_eq!(d.should_run(), ShouldRun::DontRun); } } 0707010000001F000081A400000000000000000000000166F1ABA100001485000000000000000000000000000000000000003400000000ambient-driver-0.1.0~1727114145.0b40a58/src/util.rsuse std::{ fs::create_dir_all, path::{Path, PathBuf}, process::Command, }; use log::{debug, info}; use walkdir::WalkDir; pub fn expand_tilde(path: &Path) -> PathBuf { if path.starts_with("~/") { if let Some(home) = std::env::var_os("HOME") { let mut expanded = PathBuf::from(home); for comp in path.components().skip(1) { expanded.push(comp); } expanded } else { path.to_path_buf() } } else { path.to_path_buf() } } pub fn mkdir(dirname: &Path) -> Result<(), UtilError> { create_dir_all(dirname).map_err(|e| UtilError::CreateDir(dirname.into(), e))?; Ok(()) } pub fn mkdir_child(parent: &Path, subdir: &str) -> Result<PathBuf, UtilError> { let pathname = parent.join(subdir); create_dir_all(&pathname).map_err(|e| UtilError::CreateDir(pathname.clone(), e))?; Ok(pathname) } pub fn recreate_dir(dirname: &Path) -> Result<(), UtilError> { if dirname.exists() { std::fs::remove_dir_all(dirname).map_err(|e| UtilError::RemoveDir(dirname.into(), e))?; } mkdir(dirname)?; Ok(()) } pub fn rsync_dir(src: &Path, dest: &Path) -> Result<(), UtilError> { let src = src.join("."); let dest = dest.join("."); info!("rsync {} -> {}", src.display(), dest.display()); let output = Command::new("rsync") .arg("-a") .arg("--del") .arg(&src) .arg(&dest) .output() .map_err(|e| UtilError::RsyncInvoke(src.clone(), dest.display().to_string(), e))?; if output.status.code() != Some(0) { let stderr = String::from_utf8_lossy(&output.stderr); return Err(UtilError::Rsync( src.clone(), dest.display().to_string(), stderr.into(), )); } Ok(()) } pub fn rsync_server(src: &Path, target: &str) -> Result<(), UtilError> { let src = src.join("."); let target = format!("{}/.", target); info!("rsync {} -> {}", src.display(), target); let output = Command::new("rsync") .arg("-a") .arg("--del") .arg(&src) .arg(&target) .output() .map_err(|e| UtilError::RsyncInvoke(src.clone(), target.clone(), e))?; if output.status.code() != Some(0) { let stderr = String::from_utf8_lossy(&output.stderr); return Err(UtilError::Rsync(src.clone(), target.clone(), stderr.into())); } Ok(()) } pub fn dput(dest: &str, changes: &Path) -> Result<(), UtilError> { info!("dput {} {}", dest, changes.display()); let output = Command::new("dput") .arg(dest) .arg(changes) .output() .map_err(|e| UtilError::DputInvoke(dest.into(), changes.to_path_buf(), e))?; if output.status.code() != Some(0) { let stderr = String::from_utf8_lossy(&output.stderr); return Err(UtilError::Dput( dest.into(), changes.to_path_buf(), stderr.into(), )); } Ok(()) } pub fn changes_file(dir: &Path) -> Result<PathBuf, UtilError> { let mut result = None; for entry in WalkDir::new(dir).into_iter() { let path = entry.map_err(UtilError::WalkDir)?.path().to_path_buf(); debug!("found {}", path.display()); if path.display().to_string().ends_with(".changes") { if result.is_some() { return Err(UtilError::ManyChanges); } result = Some(path); } } if let Some(path) = result { Ok(path) } else { Err(UtilError::NoChanges) } } #[derive(Debug, thiserror::Error)] pub enum UtilError { #[error("failed to create directory {0}")] CreateDir(PathBuf, #[source] std::io::Error), #[error("failed to run ambient-run")] AmbientRunInvoke(#[source] std::io::Error), #[error("ambient-run failed:\n{0}")] AmbientRun(String), #[error("failed to invoke dput to upload {1} to {0}")] DputInvoke(String, PathBuf, #[source] std::io::Error), #[error("dput failed to upload {1} to {0}:\n{2}")] Dput(String, PathBuf, String), #[error("failed to invoke rsync to synchronize {0} to {1}")] RsyncInvoke(PathBuf, String, #[source] std::io::Error), #[error("rsync failed to synchronize {0} to {1}:\n{2}")] Rsync(PathBuf, String, String), #[error("failed to create project YAML file {0}")] CreateProject(PathBuf, #[source] std::io::Error), #[error("failed to generate project YAML {0}")] Yaml(PathBuf, #[source] serde_yaml::Error), #[error("ambient-run didn't create the artifact tar archive {0}")] NoArtifact(PathBuf), #[error("failed to open artifact tar archive {0}")] OpenArtifacts(PathBuf, #[source] std::io::Error), #[error("failed to extract artifact archive {0} to {0}")] ExtractArtifacts(PathBuf, PathBuf, #[source] std::io::Error), // #[error("failed to list contents of upload directory")] WalkDir(#[source] walkdir::Error), #[error("no *.changes file built for deb project")] NoChanges, #[error("more than one *.changes file built for deb project")] ManyChanges, #[error("failed to remove directory {0}")] RemoveDir(PathBuf, #[source] std::io::Error), } 07070100000020000081A400000000000000000000000166F1ABA100001858000000000000000000000000000000000000003600000000ambient-driver-0.1.0~1727114145.0b40a58/src/vdrive.rs//! Virtual drive handling for ambient-run. use std::{ fs::File, path::{Path, PathBuf}, }; use log::{debug, trace}; /// A virtual drive. #[derive(Debug)] pub struct VirtualDrive { filename: PathBuf, } impl VirtualDrive { /// Path to file containing virtual drive. pub fn filename(&self) -> &Path { self.filename.as_path() } /// List files in the virtual drive. pub fn list(&self) -> Result<Vec<PathBuf>, VirtualDriveError> { let file = File::open(&self.filename) .map_err(|e| VirtualDriveError::Open(self.filename.clone(), e))?; let mut archive = tar::Archive::new(file); let entries = archive .entries() .map_err(|e| VirtualDriveError::List(self.filename.clone(), e))?; let mut filenames = vec![]; for maybe_entry in entries { let entry = maybe_entry.map_err(|e| VirtualDriveError::List(self.filename.clone(), e))?; let path = entry .path() .map_err(|e| VirtualDriveError::List(self.filename.clone(), e))?; let path = path.to_path_buf(); filenames.push(path); } Ok(filenames) } /// Extract files in the virtual drive into a directory. Create /// the directory if it doesn't exist. pub fn extract_to(&self, dirname: &Path) -> Result<(), VirtualDriveError> { debug!( "extracting {} to {}", self.filename.display(), dirname.display() ); if !dirname.exists() { std::fs::create_dir(dirname) .map_err(|e| VirtualDriveError::Extract(dirname.into(), e))?; } let file = File::open(&self.filename) .map_err(|e| VirtualDriveError::Open(self.filename.clone(), e))?; let mut archive = tar::Archive::new(file); let entries = archive .entries() .map_err(|e| VirtualDriveError::Open(self.filename.clone(), e))?; for entry in entries { let mut entry = entry.map_err(|e| { VirtualDriveError::ExtractEntry(self.filename.clone(), dirname.into(), e) })?; let path = entry.path().map_err(|e| { VirtualDriveError::ExtractEntry(self.filename.clone(), dirname.into(), e) })?; debug!("extract {}", path.display()); entry.unpack_in(dirname).map_err(|e| { VirtualDriveError::ExtractEntry(self.filename.clone(), dirname.into(), e) })?; } debug!("extraction OK"); Ok(()) } } /// Builder for [`VirtualDrive`]. #[derive(Debug, Default)] pub struct VirtualDriveBuilder { filename: Option<PathBuf>, root: Option<PathBuf>, size: Option<u64>, } impl VirtualDriveBuilder { /// Set filename for virtual drive. pub fn filename(mut self, filename: &Path) -> Self { self.filename = Some(filename.into()); self } /// Set directory of tree to copy into virtual drive. pub fn root_directory(mut self, dirname: &Path) -> Self { self.root = Some(dirname.into()); self } /// Set size of new drive. This is important when the build VM /// writes to the drive. pub fn size(mut self, size: u64) -> Self { self.size = Some(size); self } /// Create a virtual drive. pub fn create(self, extra: Option<(&Path, &str)>) -> Result<VirtualDrive, VirtualDriveError> { trace!("creating virtual drive (tar archive): {:#?}", self); let filename = self.filename.expect("filename has been set"); trace!( "tar archive to be created: {}; exists? {}", filename.display(), filename.exists() ); trace!("create archive file {}", filename.display()); let archive = File::create(&filename).map_err(|e| VirtualDriveError::Create(filename.clone(), e))?; if let Some(size) = self.size { trace!("restrict file {} to {} bytes", filename.display(), size); archive .set_len(size) .map_err(|e| VirtualDriveError::Create(filename.clone(), e))?; } let mut builder = tar::Builder::new(archive); if let Some(root) = self.root { trace!("directory {} exists? {}", root.display(), root.exists()); trace!("add contents of {} as .", root.display()); builder .append_dir_all(".", &root) .map_err(|e| VirtualDriveError::CreateTar(root.clone(), e))?; trace!("added {} to archive", root.display()); } if let Some((path, name)) = extra { trace!("add extra file {} as {}", path.display(), name); builder .append_path_with_name(path, name) .map_err(|e| VirtualDriveError::CreateTar(path.to_path_buf(), e))?; } trace!("finish creating {}", filename.display()); builder .finish() .map_err(|e| VirtualDriveError::CreateTar(filename.clone(), e))?; debug!("created virtual drive {}", filename.display()); Ok(VirtualDrive { filename }) } /// Open an existing virtual drive. pub fn open(self) -> Result<VirtualDrive, VirtualDriveError> { let filename = self.filename.expect("filename has been set"); Ok(VirtualDrive { filename }) } } /// Errors that may be returned from [`VirtualDrive`] use. #[allow(missing_docs)] #[derive(Debug, thiserror::Error)] pub enum VirtualDriveError { #[error("failed to create virtual drive {0}")] Create(PathBuf, #[source] std::io::Error), #[error("failed to create tar archive for virtual drive from {0}")] CreateTar(PathBuf, #[source] std::io::Error), #[error("failed to open virtual drive {0}")] Open(PathBuf, #[source] std::io::Error), #[error("failed to list files in virtual drive {0}")] List(PathBuf, #[source] std::io::Error), #[error("failed to create directory {0}")] Extract(PathBuf, #[source] std::io::Error), #[error("failed to extract {0} to {1}")] ExtractEntry(PathBuf, PathBuf, #[source] std::io::Error), } 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!304 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor