buildlog-consultant-0.1.4/.cargo_vcs_info.json0000644000000001360000000000100150270ustar { "git": { "sha1": "b439c5bd62cad8db57b5d301867402a061f06236" }, "path_in_vcs": "" }buildlog-consultant-0.1.4/.codespellrc000064400000000000000000000001201046102023000161100ustar 00000000000000[codespell] ignore-words-list = crate,gir,atleast,seperately,runnning,swith,ser buildlog-consultant-0.1.4/AUTHORS000064400000000000000000000000431046102023000146640ustar 00000000000000Jelmer Vernooij buildlog-consultant-0.1.4/CODE_OF_CONDUCT.md000064400000000000000000000064221046102023000164220ustar 00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socioeconomic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at team@dulwich.io. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq buildlog-consultant-0.1.4/Cargo.lock0000644000001505130000000000100130070ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "aho-corasick" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anstream" version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", "windows-sys 0.60.2", ] [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "base64" version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "bit-set" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "boxcar" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36f64beae40a84da1b4b26ff2761a5b895c12adc41dc25aaee1c4f2bbfe97a6e" [[package]] name = "buildlog-consultant" version = "0.1.4" dependencies = [ "chatgpt_rs", "chrono", "clap", "debian-control", "debversion", "env_logger", "fancy-regex", "inventory", "lazy-regex", "lazy_static", "log", "maplit", "pep508_rs", "regex", "serde", "serde_json", "serde_yaml", "shlex", "text-size", "textwrap", "tokio", ] [[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cc" version = "1.2.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" dependencies = [ "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chatgpt_rs" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c8a252c161b36850bd202c735a7f224710a67758d91220d50a3bfd38dd1c7fc" dependencies = [ "derive_builder", "reqwest", "serde", "serde_json", "thiserror", "tokio", "url", ] [[package]] name = "chrono" version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", "windows-link", ] [[package]] name = "clap" version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim 0.11.1", ] [[package]] name = "clap_derive" version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", "quote", "syn 2.0.109", ] [[package]] name = "clap_lex" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "colorchoice" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "core-foundation" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "countme" version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" [[package]] name = "darling" version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" dependencies = [ "darling_core", "darling_macro", ] [[package]] name = "darling_core" version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim 0.10.0", "syn 1.0.109", ] [[package]] name = "darling_macro" version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ "darling_core", "quote", "syn 1.0.109", ] [[package]] name = "deb822-derive" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86bf2d0fa4ce2457e94bd7efb15aeadc115297f04b660bd0da706729e0d91442" dependencies = [ "proc-macro2", "quote", "syn 2.0.109", ] [[package]] name = "deb822-fast" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9ea8fe9a58604b036be1759540ab0dbc8da57474a67715459653f06a467e38c" dependencies = [ "deb822-derive", ] [[package]] name = "deb822-lossless" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5f75405879b25a0763e61bf9b4d29e1de0a512e70c616c27a305e7e54d9e185" dependencies = [ "regex", "rowan", "serde", ] [[package]] name = "debian-control" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6767abf628dd8eb77b0043208a3c3a2236dc508c4938885c890d3cd6344216" dependencies = [ "chrono", "deb822-fast", "deb822-lossless", "debversion", "regex", "rowan", "url", ] [[package]] name = "debversion" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4f5cc9ce1d5067bee8060dd75208525dd0133ffea0b2960fef64ab85d58c4c5" dependencies = [ "chrono", "lazy-regex", "num-bigint", "serde", ] [[package]] name = "derive_builder" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" dependencies = [ "derive_builder_macro", ] [[package]] name = "derive_builder_core" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" dependencies = [ "darling", "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "derive_builder_macro" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" dependencies = [ "derive_builder_core", "syn 1.0.109", ] [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn 2.0.109", ] [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] name = "env_filter" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ "log", "regex", ] [[package]] name = "env_logger" version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ "anstream", "anstyle", "env_filter", "jiff", "log", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "fancy-regex" version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f" dependencies = [ "bit-set", "regex-automata", "regex-syntax", ] [[package]] name = "find-msvc-tools" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-task", "pin-project-lite", "pin-utils", ] [[package]] name = "getrandom" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "h2" version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", "http", "indexmap", "slab", "tokio", "tokio-util", "tracing", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "http" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", "pin-project-lite", ] [[package]] name = "httparse" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" version = "0.14.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", "h2", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", "socket2 0.5.10", "tokio", "tower-service", "tracing", "want", ] [[package]] name = "hyper-rustls" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http", "hyper", "rustls", "tokio", "tokio-rustls", ] [[package]] name = "iana-time-zone" version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "log", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "icu_collections" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locale_core" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_normalizer" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" dependencies = [ "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" [[package]] name = "icu_provider" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", "writeable", "yoke", "zerofrom", "zerotrie", "zerovec", ] [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "indexmap" version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown 0.16.0", ] [[package]] name = "inventory" version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" dependencies = [ "rustversion", ] [[package]] name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "is_terminal_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" dependencies = [ "jiff-static", "log", "portable-atomic", "portable-atomic-util", "serde", ] [[package]] name = "jiff-static" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", "syn 2.0.109", ] [[package]] name = "js-sys" version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "lazy-regex" version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "191898e17ddee19e60bccb3945aa02339e81edd4a8c50e21fd4d48cdecda7b29" dependencies = [ "lazy-regex-proc_macros", "once_cell", "regex", ] [[package]] name = "lazy-regex-proc_macros" version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c35dc8b0da83d1a9507e12122c80dea71a9c7c613014347392483a83ea593e04" dependencies = [ "proc-macro2", "quote", "regex", "syn 2.0.109", ] [[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.177" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "litemap" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "log" version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "maplit" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mio" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", "wasi", "windows-sys 0.61.2", ] [[package]] name = "num-bigint" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", ] [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "pep440_rs" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31095ca1f396e3de32745f42b20deef7bc09077f918b085307e8eab6ddd8fb9c" dependencies = [ "once_cell", "serde", "unicode-width", "unscanny", "version-ranges", ] [[package]] name = "pep508_rs" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faee7227064121fcadcd2ff788ea26f0d8f2bd23a0574da11eca23bc935bcc05" dependencies = [ "boxcar", "indexmap", "itertools", "once_cell", "pep440_rs", "regex", "rustc-hash 2.1.1", "serde", "smallvec", "thiserror", "unicode-width", "url", "urlencoding", "version-ranges", ] [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "portable-atomic" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" dependencies = [ "portable-atomic", ] [[package]] name = "potential_utf" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ "zerovec", ] [[package]] name = "proc-macro2" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] [[package]] name = "regex" version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "base64", "bytes", "encoding_rs", "futures-core", "futures-util", "h2", "http", "http-body", "hyper", "hyper-rustls", "ipnet", "js-sys", "log", "mime", "once_cell", "percent-encoding", "pin-project-lite", "rustls", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "system-configuration", "tokio", "tokio-rustls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "webpki-roots", "winreg", ] [[package]] name = "ring" version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", "getrandom", "libc", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rowan" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "417a3a9f582e349834051b8a10c8d71ca88da4211e4093528e36b9845f6b5f21" dependencies = [ "countme", "hashbrown 0.14.5", "rustc-hash 1.1.0", "text-size", ] [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustls" version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring", "rustls-webpki", "sct", ] [[package]] name = "rustls-pemfile" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ "base64", ] [[package]] name = "rustls-webpki" version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ "ring", "untrusted", ] [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "sct" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ "ring", "untrusted", ] [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn 2.0.109", ] [[package]] name = "serde_json" version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", "serde_core", ] [[package]] name = "serde_urlencoded" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", "itoa", "ryu", "serde", ] [[package]] name = "serde_yaml" version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ "indexmap", "itoa", "ryu", "serde", "unsafe-libyaml", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "slab" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smawk" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "socket2" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", "windows-sys 0.60.2", ] [[package]] name = "stable_deref_trait" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sync_wrapper" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "synstructure" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", "syn 2.0.109", ] [[package]] name = "system-configuration" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "text-size" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" [[package]] name = "textwrap" version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" dependencies = [ "smawk", "unicode-linebreak", "unicode-width", ] [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn 2.0.109", ] [[package]] name = "tinystr" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tokio" version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ "bytes", "libc", "mio", "pin-project-lite", "socket2 0.6.1", "tokio-macros", "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", "syn 2.0.109", ] [[package]] name = "tokio-rustls" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ "rustls", "tokio", ] [[package]] name = "tokio-util" version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", ] [[package]] name = "tower-service" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", ] [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-linebreak" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-width" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unsafe-libyaml" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "unscanny" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9df2af067a7953e9c3831320f35c1cc0600c30d44d9f7a12b01db1cd88d6b47" [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", ] [[package]] name = "urlencoding" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "version-ranges" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8d079415ceb2be83fc355adbadafe401307d5c309c7e6ade6638e6f9f42f42d" dependencies = [ "smallvec", ] [[package]] name = "want" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ "try-lock", ] [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasm-bindgen" version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" dependencies = [ "cfg-if", "js-sys", "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ "bumpalo", "proc-macro2", "quote", "syn 2.0.109", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "webpki-roots" version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "windows-core" version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", "syn 2.0.109", ] [[package]] name = "windows-interface" version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", "syn 2.0.109", ] [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ "windows-link", ] [[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-sys" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ "windows-targets 0.53.5", ] [[package]] name = "windows-sys" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] [[package]] name = "windows-targets" version = "0.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 0.52.6", "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-targets" version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ "windows-link", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", "windows_i686_gnullvm 0.53.1", "windows_i686_msvc 0.53.1", "windows_x86_64_gnu 0.53.1", "windows_x86_64_gnullvm 0.53.1", "windows_x86_64_msvc 0.53.1", ] [[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_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[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_aarch64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" version = "0.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_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[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_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[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_i686_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" version = "0.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_gnu" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" version = "0.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_gnullvm" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" version = "0.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 = "windows_x86_64_msvc" version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winreg" version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ "cfg-if", "windows-sys 0.48.0", ] [[package]] name = "writeable" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "yoke" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", "syn 2.0.109", "synstructure", ] [[package]] name = "zerofrom" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", "syn 2.0.109", "synstructure", ] [[package]] name = "zerotrie" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", "zerofrom", ] [[package]] name = "zerovec" version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", "syn 2.0.109", ] buildlog-consultant-0.1.4/Cargo.toml0000644000000054170000000000100130340ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "buildlog-consultant" version = "0.1.4" authors = ["Jelmer Vernooij "] build = false exclude = [ ".github", "disperse.conf", ".gitignore", "MANIFEST", "MANIFEST.in", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "buildlog parser and analyser" homepage = "https://github.com/jelmer/buildlog-consultant" readme = "README.md" license = "GPL-2.0+" repository = "https://github.com/jelmer/buildlog-consultant.git" [features] chatgpt = ["dep:chatgpt_rs"] cli = [ "dep:clap", "dep:env_logger", ] default = ["cli"] [lib] name = "buildlog_consultant" path = "src/lib.rs" [[bin]] name = "analyze-apt-log" path = "src/bin/analyze-apt-log.rs" required-features = ["cli"] [[bin]] name = "analyze-autopkgtest-log" path = "src/bin/analyze-autopkgtest-log.rs" required-features = ["cli"] [[bin]] name = "analyze-build-log" path = "src/bin/analyze-build-log.rs" required-features = ["cli"] [[bin]] name = "analyze-sbuild-log" path = "src/bin/analyze-sbuild-log.rs" required-features = ["cli"] [[bin]] name = "chatgpt-analyze-log" path = "src/bin/chatgpt-analyze-log.rs" required-features = [ "chatgpt", "cli", "tokio", ] [dependencies.chatgpt_rs] version = "1" optional = true [dependencies.chrono] version = "0.4.31" [dependencies.clap] version = "4" features = ["derive"] optional = true [dependencies.debian-control] version = "0.2" [dependencies.debversion] version = ">=0.4.7,<0.6" features = ["serde"] [dependencies.env_logger] version = ">=0.10" optional = true [dependencies.fancy-regex] version = ">=0.11,< 0.17" [dependencies.inventory] version = "0.3" [dependencies.lazy-regex] version = "3.0.2" [dependencies.lazy_static] version = "1.4" [dependencies.log] version = "0.4.20" [dependencies.maplit] version = "1.0.2" [dependencies.pep508_rs] version = "0.9.1" [dependencies.regex] version = "1" [dependencies.serde] version = "1" features = ["derive"] [dependencies.serde_json] version = "1.0.118" [dependencies.serde_yaml] version = "0.9" [dependencies.shlex] version = "1" [dependencies.text-size] version = "1.1.1" [dependencies.textwrap] version = "0.16.0" [dependencies.tokio] version = "1" features = ["rt-multi-thread"] optional = true [dev-dependencies.maplit] version = "1.0.2" buildlog-consultant-0.1.4/Cargo.toml.orig000064400000000000000000000033201046102023000165040ustar 00000000000000[package] name = "buildlog-consultant" version = "0.1.4" authors = [ "Jelmer Vernooij ",] edition = "2021" license = "GPL-2.0+" description = "buildlog parser and analyser" repository = "https://github.com/jelmer/buildlog-consultant.git" homepage = "https://github.com/jelmer/buildlog-consultant" exclude = [".github", "disperse.conf", ".gitignore", "MANIFEST", "MANIFEST.in"] [features] default = ["cli"] chatgpt = ["dep:chatgpt_rs"] cli = ["dep:clap", "dep:env_logger"] [[bin]] name = "chatgpt-analyze-log" path = "src/bin/chatgpt-analyze-log.rs" required-features = ["chatgpt", "cli", "tokio"] [[bin]] name = "analyze-apt-log" path = "src/bin/analyze-apt-log.rs" required-features = ["cli"] [[bin]] name = "analyze-autopkgtest-log" path = "src/bin/analyze-autopkgtest-log.rs" required-features = ["cli"] [[bin]] name = "analyze-build-log" path = "src/bin/analyze-build-log.rs" required-features = ["cli"] [[bin]] name = "analyze-sbuild-log" path = "src/bin/analyze-sbuild-log.rs" required-features = ["cli"] [dependencies] inventory = "0.3" regex = "1" lazy_static = "1.4" serde_json = "1.0.118" serde = { version = "1", features = ["derive"] } shlex = "1" log = "0.4.20" text-size = "1.1.1" debversion = { version = ">=0.4.7,<0.6", features = ["serde"] } chrono = "0.4.31" fancy-regex = ">=0.11,< 0.17" lazy-regex = "3.0.2" textwrap = "0.16.0" chatgpt_rs = { version = "1", optional = true } env_logger = { version = ">=0.10", optional = true } clap = { version = "4", optional = true, features = ["derive"] } tokio = { version = "1", features = ["rt-multi-thread"], optional = true } serde_yaml = { version = "0.9" } debian-control = "0.2" maplit = "1.0.2" pep508_rs = "0.9.1" [dev-dependencies] maplit = "1.0.2" buildlog-consultant-0.1.4/LICENSE000064400000000000000000000432541046102023000146340ustar 00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. buildlog-consultant-0.1.4/README.md000064400000000000000000000032471046102023000151040ustar 00000000000000The build log consultant can parse and analyse build log files. Currently supported container formats: * sbuild * plain For a longer introduction, see the [blog post](https://www.jelmer.uk/buildlog-consultant.html). ## Example usage ```console $ analyze-sbuild-log < build.log Error: unsatisfied apt dependencies: librust-breezyshim+dirty-tracker-dev:amd64 (>= 0.1.138-\~\~) Issue found at lines 105-120: (I)Dose_deb: Parsing Packages file -... (I)Dose_common: total packages 71128 (I)Dose_applications: Cudf Universe: 71128 packages (I)Dose_applications: --checkonly specified, consider all packages as background packages (I)Dose_applications: Solving... > output-version: 1.2 > native-architecture: amd64 > report: > - > package: sbuild-build-depends-main-dummy > version: 0.invalid.0 > architecture: amd64 > status: broken > reasons: > - > missing: > pkg: > package: sbuild-build-depends-main-dummy > version: 0.invalid.0 > architecture: amd64 > unsat-dependency: librust-breezyshim+dirty-tracker-dev:amd64 (>= 0.1.138-~~) background-packages: 71127 foreground-packages: 1 total-packages: 71128 broken-packages: 1 Identified issue: unsatisfied apt dependencies: librust-breezyshim+dirty-tracker-dev:amd64 (>= 0.1.138-\~\~) ``` Or using the JSON output: ```console $ analyze-sbuild-log --json < build.log { "details": { "relations": "librust-breezyshim+dirty-tracker-dev:amd64 (>= 0.1.138-\~\~)" }, "line": " unsat-dependency: librust-breezyshim+dirty-tracker-dev:amd64 (>= 0.1.138-\~\~)\n", "lineno": 120, "problem": "unsatisfied-apt-dependencies" } ``` buildlog-consultant-0.1.4/SECURITY.md000064400000000000000000000004561046102023000154150ustar 00000000000000# Security Policy ## Supported Versions buildlog-consultant is still under heavy development. Only the latest version is security supported. ## Reporting a Vulnerability Please report security issues by e-mail to jelmer@jelmer.uk, ideally PGP encrypted to the key at https://jelmer.uk/D729A457.asc buildlog-consultant-0.1.4/disperse.toml000064400000000000000000000001361046102023000163320ustar 00000000000000name = "buildlog-consultant" tag-name = "v$VERSION" tarball-location = [] release-timeout = 5 buildlog-consultant-0.1.4/pyproject.toml000064400000000000000000000035551046102023000165430ustar 00000000000000[build-system] requires = ["setuptools>=61.2", "setuptools-rust"] build-backend = "setuptools.build_meta" [tool.mypy] warn_redundant_casts = true warn_unused_configs = true check_untyped_defs = true [[tool.mypy.overrides]] module = [ "requirements.*", "openai.*", ] ignore_missing_imports = true [project] name = "buildlog-consultant" authors = [{name = "Jelmer Vernooij", email = "jelmer@jelmer.uk"}] description = "buildlog parser and analyser" readme = "README.md" requires-python = ">=3.9" dependencies = [ "python_debian", "PyYAML", "requirements-parser", ] dynamic = ["version"] [project.urls] Homepage = "https://github.com/jelmer/buildlog-consultant" Repository = "https://github.com/jelmer/buildlog-consultant.git" [project.optional-dependencies] chatgpt = ["openai"] dev = ["ruff==0.14.3"] [tool.setuptools] include-package-data = false [tool.setuptools.packages.find] where = ["py"] include = ["buildlog_consultant*"] [tool.setuptools.package-data] buildlog_consultant = ["py.typed"] [tool.setuptools.dynamic] version = {attr = "buildlog_consultant.__version__"} [tool.ruff.lint] select = [ "ANN", "D", "E", "F", "I", "UP", ] ignore = [ "ANN001", "ANN002", "ANN003", "ANN201", "ANN202", "ANN204", "ANN206", "D100", "D101", "D102", "D103", "D104", "D105", "D107", "E501", ] [tool.ruff.lint.pydocstyle] convention = "google" [tool.cibuildwheel] skip = "*-win32 *musllinux*" before-build = "pip install -U setuptools-rust && rustup default stable && rustup show" environment = {PATH = "$HOME/.cargo/bin:$PATH"} [tool.cibuildwheel.linux] before-build = "pip install -U setuptools-rust && curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain=stable --profile=minimal -y && rustup show" [tool.cibuildwheel.macos] before-build = "rustup target add aarch64-apple-darwin" buildlog-consultant-0.1.4/src/apt.rs000064400000000000000000000572021046102023000155460ustar 00000000000000//! Module for parsing and analyzing APT package manager logs. //! //! This module contains functions for detecting and diagnosing common issues //! in APT package manager output, such as missing packages, disk space issues, //! and dependency problems. use crate::lines::Lines; use crate::problems::common::NoSpaceOnDevice; use crate::problems::debian::*; use crate::{Match, MultiLineMatch, Problem, SingleLineMatch}; use debian_control::lossless::relations::{Entry, Relations}; /// Type alias for APT failure result pub type AptFailureResult = (Option>, Option>); /// Type alias for APT dependency failure result pub type AptDependencyResult = ( Option, Option>, Option>, ); /// Analyzes APT output to identify failures and their causes. /// /// This function scans APT output for common error patterns and returns the matching line(s) /// along with a problem description. /// /// # Arguments /// * `lines` - Vector of lines from an APT log /// /// # Returns /// A tuple containing: /// * An optional match with the location of the error /// * An optional problem description pub fn find_apt_get_failure(lines: Vec<&str>) -> AptFailureResult { let mut ret: AptFailureResult = (None, None); for (lineno, line) in lines.enumerate_backward(Some(50)) { let line = line.trim_end_matches('\n'); if line.starts_with("E: Failed to fetch ") { if let Some((_, pkg, msg)) = lazy_regex::regex_captures!("^E: Failed to fetch ([^ ]+) (.*)", line) { let problem: Box = if msg.contains("No space left on device") { Box::new(NoSpaceOnDevice) } else { Box::new(AptFetchFailure { url: Some(pkg.to_string()), error: msg.to_string(), }) }; return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), Some(problem), ); } return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), None, ); } if line == "E: Broken packages" { let error = Some(Box::new(AptBrokenPackages { description: lines[lineno - 1].trim().to_string(), broken: None, }) as Box); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno - 1, Some("direct match"), )) as Box), error, ); } if line == "E: Unable to correct problems, you have held broken packages." { let mut offsets = vec![]; let mut broken = vec![]; for j in (0..(lineno - 1)).rev() { if let Some((_, pkg, _)) = lazy_regex::regex_captures!( r"\s*Depends: (.*) but it is not (going to be installed|installable)", lines[j] ) { offsets.push(j); broken.push(pkg.to_string()); continue; } if let Some((_, _, pkg, _)) = lazy_regex::regex_captures!( r"\s*(.*) : Depends: (.*) but it is not (going to be installed|installable)", lines[j] ) { offsets.push(j); broken.push(pkg.to_string()); continue; } break; } let error = Some(Box::new(AptBrokenPackages { description: lines[lineno].trim().to_string(), broken: Some(broken), }) as Box); offsets.push(lineno); let r#match = Some(Box::new(MultiLineMatch::from_lines( &lines, offsets, Some("direct match"), )) as Box); return (r#match, error); } if let Some((_, repo)) = lazy_regex::regex_captures!( "E: The repository '([^']+)' does not have a Release file.", line ) { return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), Some(Box::new(AptMissingReleaseFile(repo.to_string()))), ); } if let Some((_, _path)) = lazy_regex::regex_captures!( "dpkg-deb: error: unable to write file '(.*)': No space left on device", line ) { return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), Some(Box::new(NoSpaceOnDevice)), ); } if let Some((_, _path)) = lazy_regex::regex_captures!(r"E: You don't have enough free space in (.*)\.", line) { return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), Some(Box::new(NoSpaceOnDevice)), ); } if line.starts_with("E: ") && ret.0.is_none() { ret = ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), None, ); } if let Some((_, pkg)) = lazy_regex::regex_captures!(r"E: Unable to locate package (.*)", line) { return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), Some(Box::new(AptPackageUnknown(pkg.to_string()))), ); } if line == "E: Write error - write (28: No space left on device)" { return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), Some(Box::new(NoSpaceOnDevice)), ); } if let Some((_, msg)) = lazy_regex::regex_captures!(r"dpkg: error: (.*)", line) { if msg.ends_with(": No space left on device") { return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), Some(Box::new(NoSpaceOnDevice)), ); } return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), Some(Box::new(DpkgError(msg.to_string()))), ); } if let Some((_, pkg, msg)) = lazy_regex::regex_captures!(r"dpkg: error processing package (.*) \((.*)\):", line) { return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno + 1, Some("direct regex"), )) as Box), Some(Box::new(DpkgError(format!( "processing package {} ({})", pkg, msg )))), ); } } for (i, line) in lines.enumerate_forward(None) { if lazy_regex::regex_is_match!( r" cannot copy extracted data for '(.*)' to '(.*)': failed to write \(No space left on device\)", line, ) { return ( Some( Box::new(SingleLineMatch::from_lines(&lines, i, Some("direct regex"))) as Box, ), Some(Box::new(NoSpaceOnDevice)), ); } if lazy_regex::regex_is_match!(r" .*: No space left on device", line) { return ( Some( Box::new(SingleLineMatch::from_lines(&lines, i, Some("direct regex"))) as Box, ), Some(Box::new(NoSpaceOnDevice)), ); } } ret } /// Analyzes APT update failure in an sbuild log. /// /// This function extracts the "update chroot" section from an sbuild log /// and analyzes it for APT failures. /// /// # Arguments /// * `sbuildlog` - The sbuild log to analyze /// /// # Returns /// A tuple containing: /// * An optional section name where the failure was found /// * An optional match with the location of the error /// * An optional problem description pub fn find_apt_get_update_failure(sbuildlog: &crate::sbuild::SbuildLog) -> AptDependencyResult { let focus_section = "update chroot"; let lines = sbuildlog.get_section_lines(Some(focus_section)); let (match_, problem) = find_apt_get_failure(lines.unwrap()); (Some(focus_section.to_string()), match_, problem) } /// Finds and parses CUDF output in a log. /// /// This function searches for and extracts CUDF (Common Upgradeability Description Format) /// output from a log file. /// /// # Arguments /// * `lines` - Vector of lines to search in /// /// # Returns /// An optional tuple containing: /// * A vector of line offsets where the CUDF output was found /// * The parsed CUDF data pub(crate) fn find_cudf_output(lines: Vec<&str>) -> Option<(Vec, crate::cudf::Cudf)> { let mut offset = None; for (i, line) in lines.enumerate_backward(None) { if line.starts_with("output-version:") { offset = Some(i); } } let mut offset = offset?; let mut output = vec![]; let mut offsets = vec![]; while !lines[offset].trim().is_empty() { offsets.push(offset); output.push(lines[offset]); offset += 1; } Some((offsets, serde_yaml::from_str(&output.join("\n")).unwrap())) } /// Extracts error information from DOSE3 reports. /// /// This function analyzes DOSE3 reports to identify dependency problems /// and conflicts. /// /// # Arguments /// * `reports` - Slice of CUDF reports from DOSE3 /// /// # Returns /// An optional problem description extracted from the reports pub(crate) fn error_from_dose3_reports( reports: &[crate::cudf::Report], ) -> Option> { let packages = reports .iter() .map(|report| &report.package) .collect::>(); assert_eq!(packages, ["sbuild-build-depends-main-dummy"]); if reports[0].status != crate::cudf::Status::Broken { return None; } let mut missing = vec![]; let mut conflict = vec![]; for reason in &reports[0].reasons { if let Some(this_missing) = &reason.missing { let relation: Entry = this_missing .pkg .unsat_dependency .as_ref() .unwrap() .parse() .unwrap(); missing.push(relation); } if let Some(this_conflict) = &reason.conflict { let relation: Relations = this_conflict .pkg1 .unsat_conflict .as_ref() .unwrap() .parse() .unwrap(); conflict.extend(relation.entries()); } } if !missing.is_empty() { let missing: Relations = missing.into(); return Some(Box::new(UnsatisfiedAptDependencies(missing.to_string())) as Box); } if !conflict.is_empty() { let conflict: Relations = conflict.into(); return Some(Box::new(UnsatisfiedAptConflicts(conflict.to_string())) as Box); } None } #[cfg(test)] mod tests { use super::*; fn assert_just_match(lines: Vec<&str>, lineno: usize) { let (r#match, actual_err) = super::find_apt_get_failure(lines.clone()); assert!(actual_err.is_none()); if let Some(r#match) = r#match.as_ref() { assert_eq!(&r#match.line(), &lines[lineno - 1]); assert_eq!(lineno, r#match.lineno()); } else { assert!(r#match.is_none()); } } fn assert_match(lines: Vec<&str>, lineno: usize, mut expected: Option) { let (r#match, actual_err) = super::find_apt_get_failure(lines.clone()); if let Some(r#match) = r#match.as_ref() { assert_eq!(&r#match.line(), &lines[lineno - 1]); assert_eq!(lineno, r#match.lineno()); } else { assert!(r#match.is_none()); } if let Some(expected) = expected.take() { assert!( r#match.is_some(), "err ({:?}) provided but match missing", &expected ); assert_eq!( actual_err.as_ref().map(|x| x.as_ref()), Some(&expected as &dyn Problem) ); } else { assert!(actual_err.is_none()); } } #[test] fn test_make_missing_rule() { assert_match( vec![ "E: Failed to fetch http://janitor.debian.net/blah/Packages.xz File has unexpected size (3385796 != 3385720). Mirror sync in progress? [IP]" ], 1, Some(AptFetchFailure{ url: Some("http://janitor.debian.net/blah/Packages.xz".to_owned()), error: "File has unexpected size (3385796 != 3385720). Mirror sync in progress? [IP]".to_owned(), }), ); } #[test] fn test_missing_release_file() { assert_match( vec![ "E: The repository 'https://janitor.debian.net/ blah/ Release' does not have a Release file.", ], 1, Some(AptMissingReleaseFile("https://janitor.debian.net/ blah/ Release".to_owned())) ); } #[test] fn test_vague() { assert_just_match(vec!["E: Stuff is broken"], 1); } #[test] fn test_no_space_on_device() { assert_match( vec![ "E: Failed to fetch http://apt.example.com/pool/main/h/hello/hello_2.10.orig.tar.gz No space left on device" ], 1, Some(NoSpaceOnDevice {}), ); } #[test] fn test_dpkg_no_space_on_device() { assert_match( vec![ "dpkg-deb: error: unable to write file '/var/cache/apt/archives/hello_2.10-2_amd64.deb': No space left on device" ], 1, Some(NoSpaceOnDevice {}), ); } #[test] fn test_apt_no_space_error() { assert_match( vec!["E: You don't have enough free space in /var."], 1, Some(NoSpaceOnDevice {}), ); } #[test] fn test_write_error_no_space() { assert_match( vec!["E: Write error - write (28: No space left on device)"], 1, Some(NoSpaceOnDevice {}), ); } #[test] fn test_dpkg_error_no_space() { assert_match( vec!["dpkg: error: writing to '/var/lib/dpkg/status': No space left on device"], 1, Some(NoSpaceOnDevice {}), ); } #[test] fn test_dpkg_error_general() { assert_match( vec!["dpkg: error: some other error occurred"], 1, Some(DpkgError("some other error occurred".to_string())), ); } #[test] fn test_dpkg_error_processing_package_direct() { let lines = vec![ "dpkg: error processing package hello (--configure):", "subprocess installed post-installation script returned error exit status 1", ]; let (match_result, problem) = find_apt_get_failure(lines); assert!(match_result.is_some()); assert!(problem.is_some()); if let Some(problem) = problem { let dpkg_error = problem.as_any().downcast_ref::(); assert!(dpkg_error.is_some()); let dpkg_error = dpkg_error.unwrap(); assert_eq!(dpkg_error.0, "processing package hello (--configure)"); } } // Direct test of functions without using helper functions #[test] fn test_broken_packages_direct() { let lines = vec![ "The following packages have unmet dependencies:", "E: Broken packages", ]; let (match_result, problem) = find_apt_get_failure(lines); assert!(match_result.is_some()); assert!(problem.is_some()); if let Some(problem) = problem { let broken_packages = problem.as_any().downcast_ref::(); assert!(broken_packages.is_some()); let broken_packages = broken_packages.unwrap(); assert_eq!( broken_packages.description, "The following packages have unmet dependencies:" ); assert!(broken_packages.broken.is_none()); } } #[test] fn test_unable_to_locate_package_direct() { let lines = vec!["E: Unable to locate package nonexistent-package"]; let (match_result, problem) = find_apt_get_failure(lines); assert!(match_result.is_some()); assert!(problem.is_some()); if let Some(problem) = problem { let pkg_unknown = problem.as_any().downcast_ref::(); assert!(pkg_unknown.is_some()); let pkg_unknown = pkg_unknown.unwrap(); assert_eq!(pkg_unknown.0, "nonexistent-package"); } } #[test] fn test_copy_extracted_data_no_space_direct() { let lines = vec![ "some text before", " cannot copy extracted data for '/var/cache/apt/archives/hello_2.10-2_amd64.deb' to '/tmp/hello': failed to write (No space left on device)", "some text after" ]; let (match_result, problem) = find_apt_get_failure(lines); assert!(match_result.is_some()); assert!(problem.is_some()); if let Some(problem) = problem { assert!(problem.as_any().is::()); } } #[test] fn test_generic_no_space_error_direct() { let lines = vec![ "some text before", " /var/cache/apt/archives/hello_2.10-2_amd64.deb: No space left on device", "some text after", ]; let (match_result, problem) = find_apt_get_failure(lines); assert!(match_result.is_some()); assert!(problem.is_some()); if let Some(problem) = problem { assert!(problem.as_any().is::()); } } #[test] fn test_find_cudf_output() { use crate::cudf::*; let lines = include_str!("testdata/sbuild-cudf.log") .split_inclusive('\n') .collect::>(); let (offsets, report) = find_cudf_output(lines).unwrap(); assert_eq!(offsets, (104..=119).collect::>()); let expected = Cudf { output_version: (1, 2), native_architecture: "amd64".to_string(), report: vec![Report { package: "sbuild-build-depends-main-dummy".to_string(), version: "0.invalid.0".parse().unwrap(), architecture: "amd64".to_string(), status: Status::Broken, reasons: vec![Reason { missing: Some(Missing { pkg: Pkg { package: "sbuild-build-depends-main-dummy".to_string(), version: "0.invalid.0".parse().unwrap(), architecture: "amd64".to_string(), unsat_conflict: None, unsat_dependency: Some( "librust-breezyshim+dirty-tracker-dev:amd64 (>= 0.1.138-~~)" .to_string(), ), }, }), conflict: None, }], }], }; assert_eq!(report, expected); } #[test] fn test_error_from_dose3_reports() { use crate::cudf::*; // Test missing dependencies case let missing_reports = vec![Report { package: "sbuild-build-depends-main-dummy".to_string(), version: "0.invalid.0".parse().unwrap(), architecture: "amd64".to_string(), status: Status::Broken, reasons: vec![Reason { missing: Some(Missing { pkg: Pkg { package: "sbuild-build-depends-main-dummy".to_string(), version: "0.invalid.0".parse().unwrap(), architecture: "amd64".to_string(), unsat_conflict: None, unsat_dependency: Some("libfoo (>= 1.0)".to_string()), }, }), conflict: None, }], }]; let problem = error_from_dose3_reports(&missing_reports); assert!(problem.is_some()); let problem = problem.unwrap(); assert!(problem.as_any().is::()); // Test conflict case let conflict_reports = vec![Report { package: "sbuild-build-depends-main-dummy".to_string(), version: "0.invalid.0".parse().unwrap(), architecture: "amd64".to_string(), status: Status::Broken, reasons: vec![Reason { missing: None, conflict: Some(Conflict { pkg1: Pkg { package: "sbuild-build-depends-main-dummy".to_string(), version: "0.invalid.0".parse().unwrap(), architecture: "amd64".to_string(), unsat_conflict: Some("libbar (>= 2.0)".to_string()), unsat_dependency: None, }, pkg2: Pkg { package: "libbar".to_string(), version: "2.1".parse().unwrap(), architecture: "amd64".to_string(), unsat_conflict: None, unsat_dependency: None, }, }), }], }]; let problem = error_from_dose3_reports(&conflict_reports); assert!(problem.is_some()); let problem = problem.unwrap(); assert!(problem.as_any().is::()); // Test non-broken status - for this test we set empty reasons to simulate non-broken let ok_reports = vec![Report { package: "sbuild-build-depends-main-dummy".to_string(), version: "0.invalid.0".parse().unwrap(), architecture: "amd64".to_string(), status: Status::Broken, // The cudf.rs only has Broken enum variant reasons: vec![], // Empty reasons meaning it's actually "ok" for our test }]; let problem = error_from_dose3_reports(&ok_reports); assert!(problem.is_none()); } } buildlog-consultant-0.1.4/src/autopkgtest.rs000064400000000000000000001570651046102023000173440ustar 00000000000000//! Module for parsing and analyzing autopkgtest logs. //! //! This module provides functionality for parsing and analyzing logs from //! autopkgtest, the Debian automated testing framework. It can identify and //! extract test results, errors, and other information from autopkgtest logs. use crate::lines::Lines; use crate::problems::autopkgtest::*; use crate::{Match, Problem, SingleLineMatch}; use std::collections::HashMap; /// Type alias for autopkgtest failure result pub type AutopkgtestResult = (Option>, Option>); /// Type alias for autopkgtest detailed result with test info pub type AutopkgtestDetailedResult = ( Option>, Option, Option>, Option, ); /// Represents different types of log packets in autopkgtest output. /// /// Each variant corresponds to a specific type of log entry in autopkgtest output, /// such as test results, source information, or error messages. #[derive(Debug, PartialEq, Eq, Clone)] pub enum Packet<'a> { /// Source information marker in the log. Source, /// Summary section marker in the log. Summary, /// Beginning of test output for a specific test. TestBeginOutput(&'a str), /// End of test output for a specific test. TestEndOutput(&'a str), /// Test results section for a specific test. Results(&'a str), /// Standard error output from a specific test. Stderr(&'a str), /// Testbed setup information for a specific test. TestbedSetup(&'a str), /// General test output with test name and status. TestOutput(&'a str, &'a str), /// Error message in the log. Error(&'a str), /// Other unrecognized log entries. Other(&'a str), } /// Parses a single line from an autopkgtest log. /// /// This function extracts the timestamp and message content from a log line /// and categorizes it into a specific type of packet. /// /// # Arguments /// * `line` - The log line to parse /// /// # Returns /// An option containing a tuple of (timestamp, packet) if the line is a valid autopkgtest log line, /// or None if the line doesn't match the expected format fn parse_autopgktest_line(line: &str) -> Option<(&str, Packet<'_>)> { let (timestamp, message) = match lazy_regex::regex_captures!(r"autopkgtest \[([0-9:]+)\]: (.*)", line) { Some((_, timestamp, message)) => (timestamp, message), None => { return None; } }; if message.starts_with("@@@@@@@@@@@@@@@@@@@@ source ") { Some((timestamp, Packet::Source)) } else if message.starts_with("@@@@@@@@@@@@@@@@@@@@ summary") { return Some((timestamp, Packet::Summary)); } else if let Some(message) = message.strip_prefix("test ") { let (testname, test_status) = message.trim_end_matches('\n').split_once(": ").unwrap(); if test_status == "[-----------------------" { return Some((timestamp, Packet::TestBeginOutput(testname))); } else if test_status == "-----------------------]" { return Some((timestamp, Packet::TestEndOutput(testname))); } else if test_status == " - - - - - - - - - - results - - - - - - - - - -" { return Some((timestamp, Packet::Results(testname))); } else if test_status == " - - - - - - - - - - stderr - - - - - - - - - -" { return Some((timestamp, Packet::Stderr(testname))); } else if test_status == "preparing testbed" { return Some((timestamp, Packet::TestbedSetup(testname))); } else { return Some((timestamp, Packet::TestOutput(testname, test_status))); } } else if let Some(message) = message.strip_prefix("ERROR: ") { return Some((timestamp, Packet::Error(message))); } else { log::warn!("unhandled autopkgtest message: {}", message); return Some((timestamp, Packet::Other(message))); } } /// Represents the result of an individual autopkgtest test. /// /// This enum represents the possible outcomes of an autopkgtest test case. #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum TestResult { /// Test passed successfully. Pass, /// Test failed. Fail, /// Test was skipped. Skip, /// Test produced inconsistent results (sometimes passes, sometimes fails). Flaky, } impl std::str::FromStr for TestResult { type Err = (); fn from_str(s: &str) -> Result { match s { "PASS" => Ok(Self::Pass), "FAIL" => Ok(Self::Fail), "SKIP" => Ok(Self::Skip), "FLAKY" => Ok(Self::Flaky), _ => Err(()), } } } /// Summary of an autopkgtest test result. /// /// This structure represents a summary of an individual test result from an autopkgtest run, /// including the test name, result status, and additional information. #[derive(Debug, PartialEq, Eq, Clone)] pub struct Summary { /// The line offset of this summary in the log. pub offset: usize, /// The name of the test. pub name: String, /// The result of the test (pass, fail, skip, or flaky). pub result: TestResult, /// Optional reason for the result, especially useful for failures. pub reason: Option, /// Additional information about the test result. pub extra: Vec, } impl Summary { /// Gets the line offset of this summary in the log. /// /// # Returns /// The zero-based line offset pub fn offset(&self) -> usize { self.offset } /// Gets the line number of this summary in the log. /// /// # Returns /// The one-based line number pub fn lineno(&self) -> usize { self.offset + 1 } } /// Parses autopkgtest summary lines to extract test results. /// /// This function analyzes the summary section of an autopkgtest log /// and extracts information about individual test results. /// /// # Arguments /// * `lines` - Vector of log lines from the summary section /// /// # Returns /// A vector of `Summary` structures, one for each identified test result pub fn parse_autopkgtest_summary(lines: Vec<&str>) -> Vec { let mut i = 0; let mut ret = vec![]; while i < lines.len() { let line = lines[i]; if let Some((_, name)) = lazy_regex::regex_captures!("([^ ]+)(?:[ ]+)PASS", line) { ret.push(Summary { offset: i, name: name.to_string(), result: TestResult::Pass, reason: None, extra: vec![], }); i += 1; continue; } if let Some((_, testname, result, reason)) = lazy_regex::regex_captures!("([^ ]+)(?:[ ]+)(FAIL|PASS|SKIP|FLAKY) (.+)", line) { let offset = i; let mut extra = vec![]; if reason == "badpkg" { while i + 1 < lines.len() && (lines[i + 1].starts_with("badpkg:") || lines[i + 1].starts_with("blame:")) { extra.push(lines[i + 1]); i += 1; } } ret.push(Summary { offset, name: testname.to_string(), result: result.parse().unwrap(), reason: Some(reason.to_string()), extra: extra.iter().map(|x| x.to_string()).collect(), }); i += 1; } else { i += 1; continue; } } ret } /// Represents different field types in an autopkgtest log. /// /// This enum is used internally to categorize different sections of the log /// based on their content type and associated test. #[derive(Debug, PartialEq, Eq, Clone, std::hash::Hash)] enum Field { /// General output for a specific test. Output(String), /// Specific field output for a test. SpecificOutput(String, String), /// Standard error output for a test. Stderr(String), /// Results output for a test. Results(String), /// Testbed preparation output for a test. PrepareTestbed(String), /// Summary section. Summary, } impl std::fmt::Display for Field { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Field::Output(testname) => write!(f, "output for test {}", testname), Field::Stderr(testname) => write!(f, "stderr for test {}", testname), Field::Results(testname) => write!(f, "results for test {}", testname), Field::PrepareTestbed(testname) => write!(f, "testbed setup for test {}", testname), Field::SpecificOutput(testname, field) => { write!(f, "{} for test {}", field, testname) } Field::Summary => write!(f, "summary"), } } } impl Field { fn testname(&self) -> Option<&str> { match self { Field::Output(testname) => Some(testname), Field::Stderr(testname) => Some(testname), Field::Results(testname) => Some(testname), Field::PrepareTestbed(testname) => Some(testname), Field::SpecificOutput(testname, _) => Some(testname), Field::Summary => None, } } } /// Find the autopkgtest failure in output. pub fn find_autopkgtest_failure_description(mut lines: Vec<&str>) -> AutopkgtestDetailedResult { let mut test_output: HashMap, usize)> = HashMap::new(); let mut current_field: Option = None; let mut it = lines.iter().enumerate().peekable(); while let Some((i, line)) = it.next() { match parse_autopgktest_line(line) { Some((_, Packet::Source)) => {} Some((_, Packet::Other(_))) => {} Some((_, Packet::Error(msg))) => { let msg = if msg.starts_with('"') && msg.chars().filter(|x| *x == '"').count() == 1 { let mut sublines = vec![msg]; while i < lines.len() { let (_i, line) = it.next().unwrap(); sublines.push(line); if line.chars().filter(|x| x == &'"').count() == 1 { break; } } sublines.join("\n") } else { msg.to_string() }; let last_test = if let Some(current_field) = current_field.as_ref() { current_field.testname().map(|x| x.to_owned()) } else { None }; if let Some((_, _, stderr, _)) = lazy_regex::regex_captures!(r#""(.*)" failed with stderr "(.*)("?)"#, &msg) { if lazy_regex::regex_is_match!( "W: (.*): Failed to stat file: No such file or directory", stderr ) { let error = Some(Box::new(AutopkgtestDepChrootDisappeared) as Box); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, i, Some("direct regex"), )) as Box), last_test, error, Some(stderr.to_owned()), ); } } if let Some((_, testbed_failure_reason)) = lazy_regex::regex_captures!(r"testbed failure: (.*)", &msg) { if current_field.is_some() && testbed_failure_reason == "testbed auxverb failed with exit code 255" { let field = Field::Output( current_field .as_ref() .unwrap() .testname() .unwrap() .to_owned(), ); let (r#match, error) = crate::common::find_build_failure_description( test_output .get(&field) .map_or(vec![], |x| x.0.iter().map(|x| x.as_str()).collect()), ); if let (Some(m), Some(error)) = (r#match, error) { let description = m.line().to_string(); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, test_output.get(&field).unwrap().1 + m.offset(), Some("direct regex"), )) as Box), last_test.map(|x| x.to_owned()), Some(error), Some(description), ); } } if testbed_failure_reason == "sent `auxverb_debug_fail', got `copy-failed', expected `ok...'" { let (r#match, error) = crate::common::find_build_failure_description(lines.clone()); if let Some(error) = error { let description = r#match.as_ref().unwrap().line().to_string(); return (r#match, last_test, Some(error), Some(description)); } } if testbed_failure_reason == "cannot send to testbed: [Errno 32] Broken pipe" { let (r#match, error) = find_testbed_setup_failure(lines.clone()); if let (Some(m), Some(e)) = (r#match, error) { let description = m.line().to_string(); return (Some(m), last_test, Some(e), Some(description)); } } if testbed_failure_reason == "apt repeatedly failed to download packages" { let (r#match, error) = crate::apt::find_apt_get_failure(lines.clone()); if let (Some(m), Some(e)) = (r#match, error) { let description = m.line().to_string(); return (Some(m), last_test, Some(e), Some(description)); } return ( Some(Box::new(SingleLineMatch::from_lines( &lines, i, Some("direct regex"), )) as Box), last_test, Some(Box::new(crate::problems::debian::AptFetchFailure { url: None, error: testbed_failure_reason.to_owned(), }) as Box), None, ); } return ( Some( Box::new(SingleLineMatch::from_lines(&lines, i, Some("direct regex"))) as Box, ), last_test.map(|x| x.to_owned()), Some( Box::new(AutopkgtestTestbedFailure(testbed_failure_reason.to_owned())) as Box, ), None, ); } if let Some((_, pkg)) = lazy_regex::regex_captures!(r"erroneous package: (.*)", &msg) { let (r#match, error) = crate::common::find_build_failure_description(lines[..i].to_vec()); if let (Some(m), Some(e)) = (r#match, error) { let description = m.line().to_string(); return ( Some(m), last_test.map(|x| x.to_owned()), Some(e), Some(description), ); } return ( Some( Box::new(SingleLineMatch::from_lines(&lines, i, Some("direct regex"))) as Box, ), last_test.map(|x| x.to_owned()), Some(Box::new(AutopkgtestErroneousPackage(pkg.to_string())) as Box), None, ); } if msg == "unexpected error:" { let (r#match, error) = crate::common::find_build_failure_description(lines[(i + 1)..].to_vec()); if let (Some(m), Some(e)) = (r#match, error) { let description = m.line().to_string(); return ( Some(m), last_test.map(|x| x.to_owned()), Some(e), Some(description), ); } } if let Some(current_field) = current_field.as_ref() { let (r#match, error) = crate::apt::find_apt_get_failure( test_output .get(current_field) .unwrap() .0 .iter() .map(|x| x.as_str()) .collect(), ); if let (Some(m), Some(e)) = (r#match, error) { if let Some(test_output_entry) = test_output.get(current_field) { let description = m.line().to_string(); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, test_output_entry.1 + m.offset(), Some("direct regex"), )) as Box), last_test.map(|x| x.to_owned()), Some(e), Some(description), ); } } } if msg == "autopkgtest" && lines[i + 1].trim_end() == ": error cleaning up:" { let description = lines[i - 1].trim_end().to_owned(); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, test_output.get(current_field.as_ref().unwrap()).unwrap().1, Some("direct regex"), )) as Box), last_test.map(|x| x.to_owned()), Some(Box::new(AutopkgtestTimedOut) as Box), Some(description), ); } return ( Some( Box::new(SingleLineMatch::from_lines(&lines, i, Some("direct regex"))) as Box, ), last_test.map(|x| x.to_owned()), None, Some(msg), ); } Some((_, Packet::Summary)) => { current_field = Some(Field::Summary); test_output.insert(current_field.clone().unwrap(), (vec![], i + 1)); } Some(( _, p @ Packet::TestBeginOutput(..) | p @ Packet::TestEndOutput(..) | p @ Packet::Stderr(..) | p @ Packet::Results(..) | p @ Packet::TestbedSetup(..) | p @ Packet::TestOutput(..), )) => { match p { Packet::TestBeginOutput(testname) => { current_field = Some(Field::Output(testname.to_owned())); } Packet::TestEndOutput(testname) => { match ¤t_field { Some(Field::Output(current_testname)) => { if current_testname != testname { log::warn!( "unexpected test end output for {}, expected {}", current_testname, testname ); } } Some(f) => { log::warn!( "unexpected test end output for {} while in {}", testname, f ); } None => { log::warn!("unexpected test end output for {}", testname); } } current_field = None; continue; } Packet::Results(testname) => { current_field = Some(Field::Results(testname.to_owned())); } Packet::Stderr(testname) => { current_field = Some(Field::Stderr(testname.to_owned())); } Packet::TestbedSetup(testname) => { current_field = Some(Field::PrepareTestbed(testname.to_owned())); } Packet::TestOutput(testname, field) => { current_field = Some(Field::SpecificOutput(testname.to_owned(), field.to_owned())); } _ => {} } if test_output.contains_key(current_field.as_ref().unwrap()) { log::warn!( "duplicate output fields for {}", current_field.as_ref().unwrap() ); } test_output.insert(current_field.clone().unwrap(), (vec![], i + 1)); } None => { if let Some(current_field) = current_field.as_ref() { test_output .entry(current_field.clone()) .or_insert((vec![], i)) .0 .push(line.to_string()); } } } } let summary_field = Field::Summary; let (summary_lines, summary_offset) = match test_output.get(&summary_field) { Some((lines, _)) => (lines, test_output.get(&summary_field).unwrap().1), None => { while !lines.is_empty() && lines.last().unwrap().trim().is_empty() { lines.pop(); } if lines.is_empty() { return (None, None, None, None); } let offset = lines.len() - 1; let last_line = lines.last().map(|x| x.to_string()); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, offset, Some("direct regex"), )) as Box), last_line, None, None, ); } }; for packet in parse_autopkgtest_summary(summary_lines.iter().map(|x| x.as_str()).collect()) { if [TestResult::Pass, TestResult::Skip].contains(&packet.result) { continue; } assert!([TestResult::Fail, TestResult::Flaky].contains(&packet.result)); if packet.reason.as_deref() == Some("timed out") { let error = Some(Box::new(AutopkgtestTimedOut) as Box); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, summary_offset + packet.offset(), Some("direct regex"), )) as Box), Some(packet.name), error, packet.reason, ); } else if let Some(output) = packet .reason .as_ref() .and_then(|x| x.strip_prefix("stderr: ")) { let field = Field::Stderr(packet.name.to_string()); let (stderr_lines, stderr_offset) = test_output.get(&field).map_or_else( || (vec![], None), |x| (x.0.iter().map(|x| x.as_str()).collect(), Some(x.1)), ); let description; let mut offset = None; let r#match; let mut error; if !stderr_lines.is_empty() { (r#match, error) = crate::common::find_build_failure_description(stderr_lines.clone()); if let (Some(m), Some(so)) = (r#match.as_ref(), stderr_offset) { offset = Some(m.offset() + so); description = Some(m.line().to_string()); } else if stderr_lines.len() == 1 && lazy_regex::regex_is_match!( r"QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to \'(.*)\'", &stderr_lines[0], ) { error = Some(Box::new(XDGRunTimeNotSet) as Box); description = Some(stderr_lines[0].to_string()); offset = stderr_offset; } else { if let Some(stderr_offset) = stderr_offset { offset = Some(stderr_offset); } description = None; } } else { (r#match, error) = crate::common::find_build_failure_description(vec![output]); (offset, description) = if let Some(r#match) = r#match.as_ref() { ( Some(summary_offset + packet.offset() + r#match.offset()), Some(r#match.line().to_string()), ) } else { (None, None) }; } let offset = offset.unwrap_or_else(|| summary_offset + packet.offset()); let error = error.unwrap_or_else(|| Box::new(AutopkgtestStderrFailure(output.to_owned()))); let description = description.unwrap_or_else(|| { format!( "Test {} failed due to unauthorized stderr output: {}", packet.name, output ) }); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, offset, Some("direct regex"), )) as Box), Some(packet.name), Some(error), Some(description), ); } else if packet.reason.as_deref() == Some("badpkg") { let field = Field::Output(packet.name.to_string()); let (output_lines, output_offset) = test_output.get(&field).map_or_else( || (vec![], None), |x| (x.0.iter().map(|x| x.as_str()).collect(), Some(x.1)), ); if let (false, Some(offset)) = (output_lines.is_empty(), output_offset) { let (r#match, error) = crate::apt::find_apt_get_failure(output_lines); if let (Some(m), Some(e)) = (r#match, error) { return ( Some(Box::new(SingleLineMatch::from_lines( &lines, m.offset() + offset, Some("direct regex"), )) as Box), Some(packet.name), Some(e), None, ); } } let mut badpkg = None; let mut blame = None; let mut blame_offset = None; for (extra_offset, line) in packet.extra.iter().enumerate() { let extra_offset = extra_offset + 1; badpkg = line.strip_prefix("badpkg: "); if line.starts_with("blame: ") { blame = Some(line); blame_offset = Some(extra_offset); } } let description = if let Some(badpkg) = badpkg { format!( "Test {} failed: {}", packet.name, badpkg.trim_end_matches('\n') ) } else { format!("Test {} failed", packet.name) }; let error = blame.map(|blame| { Box::new(AutopkgtestDepsUnsatisfiable::from_blame_line(blame)) as Box }); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, summary_offset + packet.offset() + blame_offset.unwrap(), Some("direct regex"), )) as Box), Some(packet.name), error, Some(description), ); } else { let field = Field::Output(packet.name.to_string()); let (output_lines, output_offset) = test_output.get(&field).map_or_else( || (vec![], None), |x| (x.0.iter().map(|x| x.as_str()).collect(), Some(x.1)), ); let (r#match, error) = crate::common::find_build_failure_description(output_lines); let offset = match (r#match.as_ref(), output_offset) { (Some(m), Some(o)) => m.offset() + o, _ => summary_offset + packet.offset(), }; let description = if let Some(r#match) = r#match.as_ref() { r#match.line().to_string() } else if let Some(reason) = packet.reason { format!("Test {} failed: {}", packet.name, reason) } else { format!("Test {} failed", packet.name) }; return ( Some(Box::new(SingleLineMatch::from_lines( &lines, offset, Some("direct regex"), )) as Box), Some(packet.name), error, Some(description), ); } } (None, None, None, None) } /// Searches for testbed setup failures in autopkgtest logs. /// /// This function analyzes log lines to identify errors that occurred during /// the autopkgtest testbed setup phase, such as chroot not found errors. /// /// # Arguments /// * `lines` - Vector of log lines to analyze /// /// # Returns /// A tuple containing: /// * An optional match identifying the location of the error /// * An optional problem description pub fn find_testbed_setup_failure(lines: Vec<&str>) -> AutopkgtestResult { for (i, line) in lines.enumerate_backward(None) { if let Some((_, command, status_code, stderr)) = lazy_regex::regex_captures!( r"\[(.*)\] failed \(exit status ([0-9]+), stderr \'(.*)\'\)\n", line ) { if let Some((_, chroot)) = lazy_regex::regex_captures!(r"E: (.*): Chroot not found\\n", stderr) { return ( Some( Box::new(SingleLineMatch::from_lines(&lines, i, Some("direct regex"))) as Box, ), Some(Box::new(crate::problems::common::ChrootNotFound { chroot: chroot.to_owned(), }) as Box), ); } return ( Some( Box::new(SingleLineMatch::from_lines(&lines, i, Some("direct regex"))) as Box, ), Some(Box::new(AutopkgtestTestbedSetupFailure { command: command.to_string(), exit_status: status_code.parse().unwrap(), error: stderr.to_string(), }) as Box), ); } if let Some((_, command, stderr_group)) = lazy_regex::regex_captures!( r": failure: \['(.*)'\] unexpectedly produced stderr output `(.*)\n", line ) { if lazy_regex::regex_is_match!( r"W: /var/lib/schroot/session/(.*): Failed to stat file: No such file or directory", stderr_group ) { return ( Some( Box::new(SingleLineMatch::from_lines(&lines, i, Some("direct regex"))) as Box, ), Some(Box::new(AutopkgtestDepChrootDisappeared) as Box), ); } return ( Some( Box::new(SingleLineMatch::from_lines(&lines, i, Some("direct regex"))) as Box, ), Some(Box::new(AutopkgtestTestbedSetupFailure { command: command.to_string(), exit_status: 1, error: stderr_group.to_string(), }) as Box), ); } } (None, None) } #[cfg(test)] mod tests { use super::*; use crate::problems::common::*; fn assert_autopkgtest_match( lines: Vec<&str>, expected_offsets: Vec, expected_testname: Option<&str>, expected_error: Option>, expected_description: Option<&str>, ) { let (r#match, testname, error, description) = super::find_autopkgtest_failure_description(lines); if !expected_offsets.is_empty() { assert_eq!(r#match.as_ref().unwrap().offsets(), expected_offsets); } else { assert!(r#match.is_none()); } assert_eq!(testname, expected_testname.map(|x| x.to_string())); assert_eq!(error, expected_error); assert_eq!(description, expected_description.map(|x| x.to_string())); } #[test] fn test_empty() { assert_autopkgtest_match(vec![], vec![], None, None, None); } #[test] fn test_no_match() { let lines = vec!["blalblala\n"]; assert_autopkgtest_match(lines, vec![0], Some("blalblala\n"), None, None); } #[test] fn test_unknown_error() { assert_autopkgtest_match( vec![ "autopkgtest [07:58:03]: @@@@@@@@@@@@@@@@@@@@ summary\n", "python-bcolz FAIL some error\n", ], vec![1], Some("python-bcolz"), None, Some("Test python-bcolz failed: some error"), ); } #[test] fn test_timed_out() { let error = super::AutopkgtestTimedOut; let lines = vec![ "autopkgtest [07:58:03]: @@@@@@@@@@@@@@@@@@@@ summary\n", "unit-tests FAIL timed out\n", ]; assert_autopkgtest_match( lines, vec![1], Some("unit-tests"), Some(Box::new(error)), Some("timed out"), ); } #[test] fn test_deps() { let error = AutopkgtestDepsUnsatisfiable(vec![ ( Some("arg".to_string()), "/home/janitor/tmp/tmppvupofwl/build-area/bcolz-doc_1.2.1+ds2-4~jan+lint1_all.deb".to_string(), ), (Some("deb".to_string()), "bcolz-doc".to_string()), ( Some("arg".to_string()), "/home/janitor/tmp/tmppvupofwl/build-area/python-bcolz-dbgsym_1.2.1+ds2-4~jan+lint1_amd64.deb".to_string(), ), (Some("deb".to_string()), "python-bcolz-dbgsym".to_string()), ( Some("arg".to_string()), "/home/janitor/tmp/tmppvupofwl/build-area/python-bcolz_1.2.1+ds2-4~jan+lint1_amd64.deb".to_string(), ), (Some("deb".to_string()), "python-bcolz".to_string()), ( Some("arg".to_string()), "/home/janitor/tmp/tmppvupofwl/build-area/python3-bcolz-dbgsym_1.2.1+ds2-4~jan+lint1_amd64.deb".to_string(), ), (Some("deb".to_string()), "python3-bcolz-dbgsym".to_string()), ( Some("arg".to_string()), "/home/janitor/tmp/tmppvupofwl/build-area/python3-bcolz_1.2.1+ds2-4~jan+lint1_amd64.deb".to_string(), ), (Some("deb".to_string()), "python3-bcolz".to_string()), ( None, "/home/janitor/tmp/tmppvupofwl/build-area/bcolz_1.2.1+ds2-4~jan+lint1.dsc".to_string(), ), ] ); let lines = vec![ "autopkgtest [07:58:03]: @@@@@@@@@@@@@@@@@@@@ summary\n", "python-bcolz FAIL badpkg\n", "blame: arg:/home/janitor/tmp/tmppvupofwl/build-area/bcolz-doc_1.2.1+ds2-4~jan+lint1_all.deb deb:bcolz-doc arg:/home/janitor/tmp/tmppvupofwl/build-area/python-bcolz-dbgsym_1.2.1+ds2-4~jan+lint1_amd64.deb deb:python-bcolz-dbgsym arg:/home/janitor/tmp/tmppvupofwl/build-area/python-bcolz_1.2.1+ds2-4~jan+lint1_amd64.deb deb:python-bcolz arg:/home/janitor/tmp/tmppvupofwl/build-area/python3-bcolz-dbgsym_1.2.1+ds2-4~jan+lint1_amd64.deb deb:python3-bcolz-dbgsym arg:/home/janitor/tmp/tmppvupofwl/build-area/python3-bcolz_1.2.1+ds2-4~jan+lint1_amd64.deb deb:python3-bcolz /home/janitor/tmp/tmppvupofwl/build-area/bcolz_1.2.1+ds2-4~jan+lint1.dsc\n", "badpkg: Test dependencies are unsatisfiable. A common reason is that your testbed is out of date with respect to the archive, and you need to use a current testbed or run apt-get update or use -U.\n", ]; assert_autopkgtest_match(lines, vec![2], Some("python-bcolz"), Some(Box::new(error)), Some("Test python-bcolz failed: Test dependencies are unsatisfiable. A common reason is that your testbed is out of date with respect to the archive, and you need to use a current testbed or run apt-get update or use -U.")); let error = AutopkgtestDepsUnsatisfiable(vec![ ( Some("arg".to_string()), "/home/janitor/tmp/tmpgbn5jhou/build-area/cmake-extras_1.3+17.04.20170310-6~jan+unchanged1_all.deb".to_string(), ), (Some("deb".to_string()), "cmake-extras".to_string()), ( None, "/home/janitor/tmp/tmpgbn5jhou/build-area/cmake-extras_1.3+17.04.20170310-6~jan.dsc".to_string(), ), ] ); let lines = vec![ "autopkgtest [07:58:03]: @@@@@@@@@@@@@@@@@@@@ summary\n", "intltool FAIL badpkg", "blame: arg:/home/janitor/tmp/tmpgbn5jhou/build-area/cmake-extras_1.3+17.04.20170310-6~jan+unchanged1_all.deb deb:cmake-extras /home/janitor/tmp/tmpgbn5jhou/build-area/cmake-extras_1.3+17.04.20170310-6~jan.dsc", "badpkg: Test dependencies are unsatisfiable. A common reason is that your testbed is out of date with respect to the archive, and you need to use a current testbed or run apt-get update or use -U.", ]; assert_autopkgtest_match(lines, vec![2], Some("intltool"), Some(Box::new(error)), Some("Test intltool failed: Test dependencies are unsatisfiable. A common reason is that your testbed is out of date with respect to the archive, and you need to use a current testbed or run apt-get update or use -U.")); } #[test] fn test_session_disappeared() { let error = AutopkgtestDepChrootDisappeared; let lines = vec![ "autopkgtest [22:52:18]: starting date: 2021-04-01\n", "autopkgtest [22:52:18]: version 5.16\n", "autopkgtest [22:52:18]: host osuosl167-amd64; command line: /usr/bin/autopkgtest '/tmp/tmpb0o8ai2j/build-area/liquid-dsp_1.2.0+git20210131.9ae84d8-1~jan+deb1_amd64.changes' --no-auto-control -- schroot unstable-amd64-sbuild\n", ": failure: ['chmod', '1777', '/tmp/autopkgtest.JLqPpH'] unexpectedly produced stderr output `W: /var/lib/schroot/session/unstable-amd64-sbuild-dbcdb3f2-53ed-4f84-8f0d-2c53ebe71010: Failed to stat file: No such file or directory\n", "'\n", "autopkgtest [22:52:19]: ERROR: testbed failure: cannot send to testbed: [Errno 32] Broken pipe\n" ]; assert_autopkgtest_match(lines, vec![3], None, Some(Box::new(error)), Some(": failure: ['chmod', '1777', '/tmp/autopkgtest.JLqPpH'] unexpectedly produced stderr output `W: /var/lib/schroot/session/unstable-amd64-sbuild-dbcdb3f2-53ed-4f84-8f0d-2c53ebe71010: Failed to stat file: No such file or directory\n") ); } #[test] fn test_stderr() { let error = AutopkgtestStderrFailure("some output".to_string()); let lines = vec![ "intltool FAIL stderr: some output", "autopkgtest [20:49:00]: test intltool: - - - - - - - - - - stderr - - - - - - - - - -", "some output", "some more output", "autopkgtest [20:49:00]: @@@@@@@@@@@@@@@@@@@@ summary", "intltool FAIL stderr: some output", ]; assert_autopkgtest_match( lines, vec![2], Some("intltool"), Some(Box::new(error)), Some("Test intltool failed due to unauthorized stderr output: some output"), ); let lines = vec![ "autopkgtest [20:49:00]: test intltool: - - - - - - - - - - stderr - - - - - - - - - -", "/tmp/bla: 12: ss: not found", "some more output", "autopkgtest [20:49:00]: @@@@@@@@@@@@@@@@@@@@ summary", "intltool FAIL stderr: /tmp/bla: 12: ss: not found", ]; let error = MissingCommand("ss".to_owned()); assert_autopkgtest_match( lines, vec![1], Some("intltool"), Some(Box::new(error)), Some("/tmp/bla: 12: ss: not found"), ); let lines = vec![ "autopkgtest [07:58:03]: @@@@@@@@@@@@@@@@@@@@ summary\n", r#"command10 FAIL stderr: Can't exec "uptime": No such file or directory at /usr/lib/nagios/plugins/check_uptime line 529."#, ]; let error = MissingCommand("uptime".to_owned()); assert_autopkgtest_match( lines, vec![1], Some("command10"), Some(Box::new(error)), Some( r#"Can't exec "uptime": No such file or directory at /usr/lib/nagios/plugins/check_uptime line 529."#, ), ); } #[test] fn test_testbed_failure() { let error = AutopkgtestTestbedFailure( "sent `copyup /tmp/autopkgtest.9IStGJ/build.0Pm/src/ /tmp/autopkgtest.output.icg0g8e6/tests-tree/', got `timeout', expected `ok...'".to_owned() ); let lines = vec![ "autopkgtest [12:46:18]: ERROR: testbed failure: sent `copyup /tmp/autopkgtest.9IStGJ/build.0Pm/src/ /tmp/autopkgtest.output.icg0g8e6/tests-tree/', got `timeout', expected `ok...'\n" ]; assert_autopkgtest_match(lines, vec![0], None, Some(Box::new(error)), None); } #[test] fn test_testbed_failure_with_test() { let error = AutopkgtestTestbedFailure("testbed auxverb failed with exit code 255".to_owned()); let lines = vec!["Removing autopkgtest-satdep (0) ...\n", "autopkgtest [06:59:00]: test phpunit: [-----------------------\n", "PHP Fatal error: Declaration of Wicked_TestCase::setUp() must be compatible with PHPUnit\\Framework\\TestCase::setUp(): void in /tmp/autopkgtest.5ShOBp/build.ViG/src/wicked-2.0.8/test/Wicked/TestCase.php on line 31\n", "autopkgtest [06:59:01]: ERROR: testbed failure: testbed auxverb failed with exit code 255\n", "Exiting with 16\n" ]; assert_autopkgtest_match(lines, vec![3], Some("phpunit"), Some(Box::new(error)), None); } #[test] fn test_test_command_failure() { let lines = vec![ "Removing autopkgtest-satdep (0) ...\n", "autopkgtest [01:30:11]: test command2: phpunit --bootstrap /usr/autoload.php\n", "autopkgtest [01:30:11]: test command2: [-----------------------\n", "PHPUnit 8.5.2 by Sebastian Bergmann and contributors.\n", "\n", "Cannot open file \"/usr/share/php/Pimple/autoload.php\".\n", "\n", "autopkgtest [01:30:12]: test command2: -----------------------]\n", "autopkgtest [01:30:12]: test command2: - - - - - - - - - - results - - - - - - - - - -\n", "command2 FAIL non-zero exit status 1\n", "autopkgtest [01:30:12]: @@@@@@@@@@@@@@@@@@@@ summary\n", "command1 PASS\n", "command2 FAIL non-zero exit status 1\n", "Exiting with 4\n" ]; let error = MissingFile::new("/usr/share/php/Pimple/autoload.php".into()); assert_autopkgtest_match( lines, vec![5], Some("command2"), Some(Box::new(error)), Some("Cannot open file \"/usr/share/php/Pimple/autoload.php\".\n"), ); } #[test] fn test_dpkg_failure() { let lines = vec![ "autopkgtest [19:19:19]: test require: [-----------------------\n", "autopkgtest [19:19:20]: test require: -----------------------]\n", "autopkgtest [19:19:20]: test require: - - - - - - - - - - results - - - - - - - - - -\n", "require PASS\n", "autopkgtest [19:19:20]: test runtestsuite: preparing testbed\n", "Get:1 file:/tmp/autopkgtest.hdIETy/binaries InRelease\n", "Ign:1 file:/tmp/autopkgtest.hdIETy/binaries InRelease\n", "autopkgtest [19:19:23]: ERROR: \"dpkg --unpack /tmp/autopkgtest.hdIETy/4-autopkgtest-satdep.deb\" failed with stderr \"W: /var/lib/schroot/session/unstable-amd64-sbuild-7fb1b836-14f9-4709-8584-cbbae284db97: Failed to stat file: No such file or directory\n", ]; let error = AutopkgtestDepChrootDisappeared; assert_autopkgtest_match(lines, vec![7], Some("runtestsuite"), Some(Box::new(error)), Some("W: /var/lib/schroot/session/unstable-amd64-sbuild-7fb1b836-14f9-4709-8584-cbbae284db97: Failed to stat file: No such file or directory")); } #[test] fn test_last_stderr_line() { let lines = vec![ "autopkgtest [17:38:49]: test unmunge: [-----------------------\n", "munge: Error: Failed to access \"/run/munge/munge.socket.2\": No such file or directory\n", "unmunge: Error: No credential specified\n", "autopkgtest [17:38:50]: test unmunge: -----------------------]\n", "autopkgtest [17:38:50]: test unmunge: - - - - - - - - - - results - - - - - - - - - -\n", "unmunge FAIL non-zero exit status 2\n", "autopkgtest [17:38:50]: test unmunge: - - - - - - - - - - stderr - - - - - - - - - -\n", "munge: Error: Failed to access \"/run/munge/munge.socket.2\": No such file or directory\n", "unmunge: Error: No credential specified\n", "autopkgtest [17:38:50]: @@@@@@@@@@@@@@@@@@@@ summary\n", "unmunge FAIL non-zero exit status 2\n", "Exiting with 4\n" ]; assert_autopkgtest_match( lines, vec![10], Some("unmunge"), None, Some("Test unmunge failed: non-zero exit status 2"), ); } #[test] fn test_python_error_in_output() { let lines = vec![ "autopkgtest [14:55:35]: test unit-tests-3: [-----------------------", " File \"twisted/test/test_log.py\", line 511, in test_getTimezoneOffsetWithout", " self._getTimezoneOffsetTest(\"Africa/Johannesburg\", -7200, -7200)", " File \"twisted/test/test_log.py\", line 460, in _getTimezoneOffsetTest", " daylight = time.mktime(localDaylightTuple)", "builtins.OverflowError: mktime argument out of range", "-------------------------------------------------------------------------------", "Ran 12377 tests in 143.490s", "", "143.4904797077179 12377 12377 1 0 2352", "autopkgtest [14:58:01]: test unit-tests-3: -----------------------]", "autopkgtest [14:58:01]: test unit-tests-3: - - - - - - - - - - results - - - - - - - - - -", "unit-tests-3 FAIL non-zero exit status 1", "autopkgtest [14:58:01]: @@@@@@@@@@@@@@@@@@@@ summary", "unit-tests-3 FAIL non-zero exit status 1", "Exiting with 4" ]; assert_autopkgtest_match( lines, vec![5], Some("unit-tests-3"), None, Some("builtins.OverflowError: mktime argument out of range"), ); } mod parse_autopkgtest_summary { use super::*; #[test] fn test_empty() { assert_eq!(parse_autopkgtest_summary(vec![]), vec![]); } #[test] fn test_single_pass() { assert_eq!( parse_autopkgtest_summary(vec!["python-bcolz PASS"]), vec![Summary { offset: 0, name: "python-bcolz".to_string(), result: TestResult::Pass, reason: None, extra: vec![] }] ); } #[test] fn test_single_fail() { assert_eq!( parse_autopkgtest_summary(vec!["python-bcolz FAIL some error"]), vec![Summary { offset: 0, name: "python-bcolz".to_string(), result: TestResult::Fail, reason: Some("some error".to_string()), extra: vec![] }] ); } #[test] fn test_single_skip() { assert_eq!( parse_autopkgtest_summary(vec!["python-bcolz SKIP some reason"]), vec![Summary { offset: 0, name: "python-bcolz".to_string(), result: TestResult::Skip, reason: Some("some reason".to_string()), extra: vec![] }] ); } #[test] fn test_single_flaky() { assert_eq!( parse_autopkgtest_summary(vec!["python-bcolz FLAKY some reason"]), vec![Summary { offset: 0, name: "python-bcolz".to_string(), result: TestResult::Flaky, reason: Some("some reason".to_string()), extra: vec![] }] ); } #[test] fn test_multiple() { assert_eq!( parse_autopkgtest_summary(vec![ "python-bcolz PASS", "python-bcolz FAIL some error", "python-bcolz SKIP some reason", "python-bcolz FLAKY some reason" ]), vec![ Summary { offset: 0, name: "python-bcolz".to_string(), result: TestResult::Pass, reason: None, extra: vec![] }, Summary { offset: 1, name: "python-bcolz".to_string(), result: TestResult::Fail, reason: Some("some error".to_string()), extra: vec![] }, Summary { offset: 2, name: "python-bcolz".to_string(), result: TestResult::Skip, reason: Some("some reason".to_string()), extra: vec![] }, Summary { offset: 3, name: "python-bcolz".to_string(), result: TestResult::Flaky, reason: Some("some reason".to_string()), extra: vec![] } ] ); } } mod parse_autopkgtest_line { #[test] fn test_source() { assert_eq!( super::parse_autopgktest_line( "autopkgtest [07:58:03]: @@@@@@@@@@@@@@@@@@@@ source " ), Some(("07:58:03", super::Packet::Source)) ); } #[test] fn test_summary() { assert_eq!( super::parse_autopgktest_line( "autopkgtest [07:58:03]: @@@@@@@@@@@@@@@@@@@@ summary" ), Some(("07:58:03", super::Packet::Summary)) ); } #[test] fn test_test_begin_output() { assert_eq!( super::parse_autopgktest_line( "autopkgtest [07:58:03]: test unit-tests: [-----------------------" ), Some(("07:58:03", super::Packet::TestBeginOutput("unit-tests"))) ); } #[test] fn test_test_end_output() { assert_eq!( super::parse_autopgktest_line( "autopkgtest [07:58:03]: test unit-tests: -----------------------]" ), Some(("07:58:03", super::Packet::TestEndOutput("unit-tests"))) ); } #[test] fn test_results() { assert_eq!( super::parse_autopgktest_line("autopkgtest [07:58:03]: test unit-tests: - - - - - - - - - - results - - - - - - - - - -"), Some(("07:58:03", super::Packet::Results("unit-tests"))) ); } #[test] fn test_stderr() { assert_eq!( super::parse_autopgktest_line("autopkgtest [07:58:03]: test unit-tests: - - - - - - - - - - stderr - - - - - - - - - -"), Some(("07:58:03", super::Packet::Stderr("unit-tests"))) ); } #[test] fn test_testbed_setup() { assert_eq!( super::parse_autopgktest_line( "autopkgtest [07:58:03]: test unit-tests: preparing testbed" ), Some(("07:58:03", super::Packet::TestbedSetup("unit-tests"))) ); } #[test] fn test_test_output() { assert_eq!( super::parse_autopgktest_line( "autopkgtest [07:58:03]: test unit-tests: some output" ), Some(( "07:58:03", super::Packet::TestOutput("unit-tests", "some output") )) ); } #[test] fn test_error() { assert_eq!( super::parse_autopgktest_line("autopkgtest [07:58:03]: ERROR: some error"), Some(("07:58:03", super::Packet::Error("some error"))) ); } } } buildlog-consultant-0.1.4/src/bin/analyze-apt-log.rs000064400000000000000000000017321046102023000205330ustar 00000000000000use clap::Parser; #[derive(Parser)] struct Args { #[clap(short, long)] debug: bool, #[clap(short, long, default_value = "5")] context: usize, path: std::path::PathBuf, } fn main() { let args = Args::parse(); env_logger::Builder::from_default_env() .filter_level(if args.debug { log::LevelFilter::Debug } else { log::LevelFilter::Info }) .format_timestamp(None) .format_level(false) .format_target(false) .init(); let log = std::fs::read_to_string(&args.path).expect("Failed to read log file"); let lines = log.split_inclusive('\n').collect::>(); let (r#match, error) = buildlog_consultant::apt::find_apt_get_failure(lines.clone()); if let Some(error) = error.as_ref() { log::info!("Error: {}", error); } if let Some(r#match) = r#match { buildlog_consultant::highlight_lines(&lines, r#match.as_ref(), args.context); } } buildlog-consultant-0.1.4/src/bin/analyze-autopkgtest-log.rs000064400000000000000000000032601046102023000223170ustar 00000000000000use clap::Parser; use std::io::Write; #[derive(Parser)] struct Args { #[clap(short, long)] debug: bool, #[clap(short, long)] json: bool, #[clap(short, long, default_value = "5")] context: usize, path: std::path::PathBuf, } fn main() { let args = Args::parse(); // Honor debug env_logger::Builder::from_default_env() .filter_level(if args.debug { log::LevelFilter::Debug } else if args.json { log::LevelFilter::Warn } else { log::LevelFilter::Info }) .format_timestamp(None) .format_level(false) .format_target(false) .init(); let log = std::fs::read_to_string(&args.path).expect("Failed to read log file"); let lines = log.split('\n').collect::>(); let (r#match, testname, error, description) = buildlog_consultant::autopkgtest::find_autopkgtest_failure_description(lines.clone()); if args.json { let mut ret = serde_json::json!({ "testname": testname, "error": error, "description": description }); if let Some(ref r#match) = r#match { ret["offset"] = serde_json::value::Value::Number(r#match.offset().into()); } std::io::stdout() .write_all(serde_json::to_string_pretty(&ret).unwrap().as_bytes()) .unwrap(); } if let Some(testname) = testname { log::info!("Test name: {}", testname); } if let Some(error) = error { log::info!("Error: {}", error); } if let Some(r#match) = r#match { buildlog_consultant::highlight_lines(&lines, r#match.as_ref(), args.context); } } buildlog-consultant-0.1.4/src/bin/analyze-build-log.rs000064400000000000000000000067451046102023000210570ustar 00000000000000use buildlog_consultant::common::find_build_failure_description; use buildlog_consultant::{Match, Problem}; use clap::Parser; use std::cmp::min; use std::path::PathBuf; #[derive(Parser)] struct Args { #[clap(short, long, default_value = "5")] /// Number of lines of context to show. context: usize, #[clap(short, long)] /// Output JSON. json: bool, #[clap(short, long)] /// Enable debug output. debug: bool, /// The path to the build log to analyze. path: Option, } fn as_json(m: Option<&dyn Match>, problem: Option<&dyn Problem>) -> serde_json::Value { let mut ret = serde_json::Map::new(); if let Some(m) = m { ret.insert( "lineno".to_string(), serde_json::Value::Number(serde_json::Number::from(m.lineno())), ); ret.insert( "line".to_string(), serde_json::Value::String(m.line().to_string()), ); ret.insert( "origin".to_string(), serde_json::Value::String(m.origin().to_string()), ); } if let Some(problem) = problem { ret.insert( "problem".to_string(), serde_json::Value::String(problem.kind().to_string()), ); ret.insert("details".to_string(), problem.json()); } serde_json::Value::Object(ret) } pub fn main() -> Result<(), i8> { let args = Args::parse(); // Honor debug env_logger::Builder::from_default_env() .filter_level(if args.debug { log::LevelFilter::Debug } else if args.json { log::LevelFilter::Warn } else { log::LevelFilter::Info }) .format_timestamp(None) .format_level(false) .format_target(false) .init(); let log = if let Some(path) = args.path.as_deref() { std::fs::read_to_string(path).expect("Failed to read log file") } else { use std::io::Read; let mut log = String::new(); std::io::stdin() .read_to_string(&mut log) .expect("Failed to read log from stdin"); log }; let lines = log.split_inclusive('\n').collect::>(); let (m, problem) = find_build_failure_description(lines.clone()); if args.json { let ret = as_json( m.as_ref().map(|m| m.as_ref()), problem.as_ref().map(|p| p.as_ref()), ); serde_json::to_writer_pretty(std::io::stdout(), &ret).expect("Failed to write JSON"); } else { if let Some(m) = m { if m.linenos().len() == 1 { log::info!("Issue found at line {}:", m.lineno()); } else { log::info!( "Issue found at lines {}-{}:", m.linenos().first().unwrap(), m.linenos().last().unwrap() ); } let start = m.offsets()[0].saturating_sub(args.context); let end = min(lines.len(), m.offsets().last().unwrap() + args.context + 1); for (i, line) in lines.iter().enumerate().take(end).skip(start) { log::info!( " {} {}", if m.offsets().contains(&i) { ">" } else { " " }, line.trim_end_matches('\n') ); } } else { log::info!("No issues found"); } if let Some(problem) = problem { log::info!("Identified issue: {}: {}", problem.kind(), problem); } } Ok(()) } buildlog-consultant-0.1.4/src/bin/analyze-sbuild-log.rs000064400000000000000000000067251046102023000212400ustar 00000000000000use buildlog_consultant::sbuild::{worker_failure_from_sbuild_log, SbuildLog}; use buildlog_consultant::{Match, Problem}; use clap::Parser; use std::path::PathBuf; #[derive(Parser)] struct Args { #[clap(short, long, default_value = "5")] /// Number of lines of context to show. context: usize, #[clap(short, long)] /// Output JSON. json: bool, #[clap(short, long)] /// Enable debug output. debug: bool, #[clap(long)] /// Dump the build log to a file. dump: bool, /// The path to the build log to analyze. path: Option, } fn as_json(m: Option<&dyn Match>, problem: Option<&dyn Problem>) -> serde_json::Value { let mut ret = serde_json::Map::new(); if let Some(m) = m { ret.insert( "lineno".to_string(), serde_json::Value::Number(serde_json::Number::from(m.lineno())), ); ret.insert( "line".to_string(), serde_json::Value::String(m.line().to_string()), ); ret.insert( "origin".to_string(), serde_json::Value::String(m.origin().to_string()), ); } if let Some(problem) = problem { ret.insert( "problem".to_string(), serde_json::Value::String(problem.kind().to_string()), ); ret.insert("details".to_string(), problem.json()); } serde_json::Value::Object(ret) } pub fn main() -> Result<(), i8> { let args = Args::parse(); // Honor debug env_logger::Builder::from_default_env() .filter_level(if args.debug { log::LevelFilter::Debug } else if args.json { log::LevelFilter::Warn } else { log::LevelFilter::Info }) .format_timestamp(None) .format_level(false) .format_target(false) .init(); let sbuildlog: SbuildLog = if let Some(path) = args.path.as_deref() { std::fs::File::open(path) .expect("Failed to open log file") .try_into() .expect("Failed to parse log file") } else { std::io::BufReader::new(std::io::stdin().lock()) .try_into() .expect("Failed to parse log file") }; if args.debug { println!("{:?}", sbuildlog.summary()); } if args.dump { println!("{:?}", sbuildlog); } let failed_stage = sbuildlog.get_failed_stage(); let failure = worker_failure_from_sbuild_log(&sbuildlog); if args.json { let ret = as_json( failure.r#match.as_ref().map(|m| m.as_ref()), failure.error.as_ref().map(|p| p.as_ref()), ); serde_json::to_writer_pretty(std::io::stdout(), &ret).expect("Failed to write JSON"); } else { if let Some(failed_stage) = failed_stage { log::info!("Failed stage: {}", failed_stage); } else { log::info!("No failed stage found"); } if let Some(error) = failure.error.as_ref() { log::info!("Error: {}", error); } else { log::debug!("No error found"); } if let (Some(m), Some(s)) = (failure.r#match.as_ref(), failure.section.as_ref()) { buildlog_consultant::highlight_lines(&s.lines(), m.as_ref(), args.context); } else { log::info!("No specific issue found"); } if let Some(problem) = failure.error.as_ref() { log::info!("Identified issue: {}: {}", problem.kind(), problem); } } Ok(()) } buildlog-consultant-0.1.4/src/bin/chatgpt-analyze-log.rs000064400000000000000000000022211046102023000213730ustar 00000000000000use clap::Parser; use std::io::BufRead; #[derive(Parser)] struct Args { #[clap(short, long)] debug: bool, path: std::path::PathBuf, } fn main() { let args: Args = Args::parse(); env_logger::builder() .filter_level(if args.debug { log::LevelFilter::Debug } else { log::LevelFilter::Info }) .init(); let f = std::fs::File::open(&args.path).expect("Failed to open file"); let reader = std::io::BufReader::new(f); let lines = reader .lines() .map(|l| l.expect("Failed to read line")) .collect::>(); let openai_key = std::env::var("OPENAI_API_KEY").expect("OPENAI_KEY not set"); let runtime = tokio::runtime::Runtime::new().unwrap(); let m = runtime.block_on(buildlog_consultant::chatgpt::analyze( openai_key, lines.iter().map(|l| l.as_str()).collect::>(), )); match m { Ok(Some(m)) => log::info!("match: {}", m), Ok(None) => log::info!("No match found"), Err(e) => { log::error!("Failed to analyze log: {}", e); std::process::exit(1); } } } buildlog-consultant-0.1.4/src/brz.rs000064400000000000000000000246171046102023000155630ustar 00000000000000//! Module for parsing and analyzing Bazaar (brz) version control system logs. //! //! This module contains functions to identify and diagnose common issues in //! Bazaar/Breezy output, particularly in the context of Debian packaging. use crate::lines::Lines; use crate::problems::common::NoSpaceOnDevice; use crate::problems::debian::*; use crate::Problem; /// Type alias for brz error handler pub type BrzErrorHandler = Vec<( regex::Regex, fn(®ex::Captures, Vec<&str>) -> Option>, )>; /// Searches for Bazaar (brz) build errors in log lines. /// /// This function scans log lines for Bazaar errors and extracts problem information. /// /// # Arguments /// * `lines` - Vector of log lines to analyze /// /// # Returns /// An optional tuple containing: /// * An optional problem description /// * A string representation of the error pub fn find_brz_build_error(lines: Vec<&str>) -> Option<(Option>, String)> { for (i, line) in lines.enumerate_backward(None) { if let Some(suffix) = line.strip_prefix("brz: ERROR: ") { let mut rest = vec![suffix.to_string()]; rest.extend( lines[i + 1..] .iter() .filter(|n| n.starts_with(" ")) .map(|n| n.to_string()), ); let reflowed = rest.join("\n"); let (err, line) = parse_brz_error(&reflowed, lines[..i].to_vec()); return Some((err, line.to_string())); } } None } /// Extracts debcargo-specific failures from brz error output. /// /// This function parses error output specific to debcargo failures, /// looking for patterns like "Couldn't find any crate matching". /// /// # Arguments /// * `_` - The regex captures (unused) /// * `prior_lines` - Lines preceding the error to analyze for context /// /// # Returns /// An optional problem description fn parse_debcargo_failure(_: ®ex::Captures, prior_lines: Vec<&str>) -> Option> { const MORE_TAIL: &str = "\x1b[0m\n"; const MORE_HEAD1: &str = "\x1b[1;31mSomething failed: "; const MORE_HEAD2: &str = "\x1b[1;31mdebcargo failed: "; if let Some(extra) = prior_lines.last().unwrap().strip_suffix(MORE_TAIL) { let mut extra = vec![extra]; for line in prior_lines[..prior_lines.len() - 1].iter().rev() { if let Some(middle) = extra[0].strip_prefix(MORE_HEAD1) { extra[0] = middle; break; } if let Some(middle) = extra[0].strip_prefix(MORE_HEAD2) { extra[0] = middle; break; } extra.insert(0, line); } if extra.len() == 1 { extra = vec![]; } if extra .last() .and_then(|l| l.strip_prefix("Try `debcargo update` to update the crates.io index.")) .is_some() { if let Some((_, n)) = lazy_regex::regex_captures!( r"Couldn't find any crate matching (.*)", extra[extra.len() - 2].trim_end() ) { return Some(Box::new(MissingDebcargoCrate::from_string(n))); } else { return Some(Box::new(DpkgSourcePackFailed( extra[extra.len() - 2].to_owned(), ))); } } else if !extra.is_empty() { if let Some((_, d, p)) = lazy_regex::regex_captures!( r"Cannot represent prerelease part of dependency: (.*) Predicate \{ (.*) \}", extra[0] ) { return Some(Box::new(DebcargoUnacceptablePredicate { cratename: d.to_owned(), predicate: p.to_owned(), })); } else if let Some((_, d, c)) = lazy_regex::regex_captures!( r"Cannot represent prerelease part of dependency: (.*) Comparator \{ (.*) \}", extra[0] ) { return Some(Box::new(DebcargoUnacceptableComparator { cratename: d.to_owned(), comparator: c.to_owned(), })); } } else { return Some(Box::new(DebcargoFailure(extra.join("")))); } } Some(Box::new(DebcargoFailure( "Debcargo failed to run".to_string(), ))) } macro_rules! regex_line_matcher { ($re:expr, $f:expr) => { (regex::Regex::new($re).unwrap(), $f) }; } lazy_static::lazy_static! { static ref BRZ_ERRORS: BrzErrorHandler = vec![ regex_line_matcher!("Unable to find the needed upstream tarball for package (.*), version (.*)\\.", |m, _| Some(Box::new(UnableToFindUpstreamTarball{package: m.get(1).unwrap().as_str().to_string(), version: m.get(2).unwrap().as_str().parse().unwrap()}))), regex_line_matcher!("Unknown mercurial extra fields in (.*): b'(.*)'.", |m, _| Some(Box::new(UnknownMercurialExtraFields(m.get(2).unwrap().as_str().to_string())))), regex_line_matcher!("UScan failed to run: In watchfile (.*), reading webpage (.*) failed: 429 too many requests\\.", |m, _| Some(Box::new(UScanTooManyRequests(m.get(2).unwrap().as_str().to_string())))), regex_line_matcher!("UScan failed to run: OpenPGP signature did not verify..", |_, _| Some(Box::new(UpstreamPGPSignatureVerificationFailed))), regex_line_matcher!(r"Inconsistency between source format and version: version is( not)? native, format is( not)? native\.", |m, _| Some(Box::new(InconsistentSourceFormat{version: m.get(1).is_some(), source_format: m.get(2).is_some()}))), regex_line_matcher!(r"UScan failed to run: In (.*) no matching hrefs for version (.*) in watch line", |m, _| Some(Box::new(UScanRequestVersionMissing(m.get(2).unwrap().as_str().to_string())))), regex_line_matcher!(r"UScan failed to run: In directory ., downloading (.*) failed: (.*)", |m, _| Some(Box::new(UScanFailed{url: m.get(1).unwrap().as_str().to_string(), reason: m.get(2).unwrap().as_str().to_string()}))), regex_line_matcher!(r"UScan failed to run: In watchfile debian/watch, reading webpage\n (.*) failed: (.*)", |m, _| Some(Box::new(UScanFailed{url: m.get(1).unwrap().as_str().to_string(), reason: m.get(2).unwrap().as_str().to_string()}))), regex_line_matcher!(r"Unable to parse upstream metadata file (.*): (.*)", |m, _| Some(Box::new(UpstreamMetadataFileParseError{path: m.get(1).unwrap().as_str().to_string().into(), reason: m.get(2).unwrap().as_str().to_string()}))), regex_line_matcher!(r"Debcargo failed to run\.", parse_debcargo_failure), regex_line_matcher!(r"\[Errno 28\] No space left on device", |_, _| Some(Box::new(NoSpaceOnDevice))) ]; } /// Parses a brz error message to identify the specific problem. /// /// This function analyzes a Bazaar error message line and the preceding context /// to determine the specific problem type. /// /// # Arguments /// * `line` - The error message to parse /// * `prior_lines` - Vector of lines preceding the error message in the log /// /// # Returns /// A tuple containing: /// * An optional problem description if the error is recognized /// * A string representation of the error pub fn parse_brz_error<'a>( line: &'a str, prior_lines: Vec<&'a str>, ) -> (Option>, String) { let line = line.trim(); for (re, f) in BRZ_ERRORS.iter() { if let Some(m) = re.captures(line) { let err = f(&m, prior_lines); let description = err.as_ref().unwrap().to_string(); return (err, description); } } if let Some(suffix) = line.strip_prefix("UScan failed to run: ") { return ( Some(Box::new(UScanError(suffix.to_owned()))), line.to_string(), ); } if let Some(suffix) = line.strip_prefix("Unable to parse changelog: ") { return ( Some(Box::new(ChangelogParseError( suffix.to_string().to_string(), ))), line.to_string(), ); } (None, line.split_once('\n').unwrap().0.to_string()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_inconsistent_source_format() { let (err, line) = parse_brz_error( "Inconsistency between source format and version: version is not native, format is native.", vec![]); assert_eq!( line, "Inconsistent source format between version and source format", ); assert_eq!( Some(Box::new(InconsistentSourceFormat { version: true, source_format: false }) as Box), err ); } #[test] fn test_missing_debcargo_crate() { let lines = vec![ "Using crate name: version-check, version 0.9.2 Updating crates.io index\n", "\x1b[1;31mSomething failed: Couldn't find any crate matching version-check = 0.9.2\n", "Try `debcargo update` to update the crates.io index.\x1b[0m\n", "brz: ERROR: Debcargo failed to run.\n", ]; let (err, line) = find_brz_build_error(lines).unwrap(); assert_eq!( line, "debcargo can't find crate version-check (version: 0.9.2)" ); assert_eq!( err, Some(Box::new(MissingDebcargoCrate { cratename: "version-check".to_string(), version: Some("0.9.2".to_string()) }) as Box) ); } #[test] fn test_missing_debcargo_crate2() { let lines = vec![ "Running 'sbuild -A -s -v'\n", "Building using working tree\n", "Building package in merge mode\n", "Using crate name: utf8parse, version 0.10.1+git20220116.1.dfac57e\n", " Updating crates.io index\n", " Updating crates.io index\n", "\x1b[1;31mdebcargo failed: Couldn't find any crate matching utf8parse =0.10.1\n", "Try `debcargo update` to update the crates.io index.\x1b[0m\n", "brz: ERROR: Debcargo failed to run.\n", ]; let (err, line) = find_brz_build_error(lines).unwrap(); assert_eq!( line, "debcargo can't find crate utf8parse (version: 0.10.1)" ); assert_eq!( err, Some(Box::new(MissingDebcargoCrate { cratename: "utf8parse".to_owned(), version: Some("0.10.1".to_owned()) }) as Box) ); } } buildlog-consultant-0.1.4/src/chatgpt.rs000064400000000000000000000043651046102023000164160ustar 00000000000000//! Module for using ChatGPT to analyze build logs. //! //! This module provides functionality to use the ChatGPT API to identify //! the most likely error line in a build log. use crate::SingleLineMatch; use chatgpt::prelude::*; use chatgpt::types::CompletionResponse; /// Maximum number of tokens allowed in a ChatGPT request. pub const MAX_TOKENS: usize = 4096; /// Initial prompt used to ask ChatGPT to identify the error in a log file. pub const INITIAL_PROMPT: &str = "Which line in the log file below is the clearest explanation of a problem:\n\n"; /// Uses ChatGPT to analyze log lines and identify the most likely error. /// /// This function sends a portion of the log file to the ChatGPT API and asks /// it to identify the line that best explains the problem. It then tries to /// match the ChatGPT response back to an actual line in the log. /// /// # Arguments /// * `chatgpt_key` - API key for ChatGPT /// * `lines` - Vector of log lines to analyze /// /// # Returns /// A Result containing an optional `SingleLineMatch` pointing to the identified error line, /// or an error if the ChatGPT API call fails pub async fn analyze( chatgpt_key: String, lines: Vec<&str>, ) -> std::result::Result, Box> { let client = ChatGPT::new(chatgpt_key)?; // Select lines from the end, but not more than MAX_TOKENS - INITIAL_PROMPT.len() // Also, only include full lines let mut truncated: Vec<&str> = lines .iter() .rev() .take_while(|line| line.len() < MAX_TOKENS - INITIAL_PROMPT.len()) .copied() .collect(); // Reverse the lines back truncated.reverse(); let prompt = format!("{}{}", INITIAL_PROMPT, truncated.join("\n")); // Sending a message and getting the completion let response: CompletionResponse = client.send_message(&prompt).await?; let text = &response.message().content; for (i, line) in lines.iter().enumerate().rev() { if line.starts_with(text) { return Ok(Some(SingleLineMatch::from_lines( &lines, i, Some("chatgpt"), ))); } } log::debug!("Unable to find chatgpt answer in lines: {:?}", text); Ok(None) } buildlog-consultant-0.1.4/src/common.rs000064400000000000000000007774271046102023000162740ustar 00000000000000//! Common utilities for analyzing build logs across different environments. //! //! This module provides common functionality for analyzing build logs from various //! environments and identifying problems. It contains a wide range of pattern matchers //! and problem extraction logic that can be used by different log analyzers. //! //! NOTE: This module is currently a direct port from Python and needs refactoring. use crate::lines::Lines; use crate::problems::common::*; use crate::r#match::{Error, Matcher, MatcherGroup, RegexLineMatcher}; use crate::regex_line_matcher; use crate::regex_para_matcher; use crate::{Match, Problem}; use crate::{MultiLineMatch, Origin, SingleLineMatch}; use lazy_regex::{regex_captures, regex_is_match}; use regex::Captures; /// Type alias for the common return type of build failure functions pub type BuildFailureResult = (Option>, Option>); /// Type alias for matcher error result with match and problem pub type MatcherResult = Result, Option>)>, Error>; fn node_module_missing(c: &Captures) -> Result>, Error> { if c.get(1).unwrap().as_str().starts_with("/<>/") { return Ok(None); } if c.get(1).unwrap().as_str().starts_with("./") { return Ok(None); } Ok(Some(Box::new(MissingNodeModule( c.get(1).unwrap().as_str().to_string(), )))) } fn file_not_found(c: &Captures) -> Result>, Error> { let path = c.get(1).unwrap().as_str(); if path.starts_with('/') && !path.starts_with("/<>") { return Ok(Some(Box::new(MissingFile { path: std::path::PathBuf::from(path), }))); } if let Some(filename) = path.strip_prefix("/<>/") { return Ok(Some(Box::new(MissingBuildFile { filename: filename.to_string(), }))); } if path == ".git/HEAD" { return Ok(Some(Box::new(VcsControlDirectoryNeeded { vcs: vec!["git".to_string()], }))); } if path == "CVS/Root" { return Ok(Some(Box::new(VcsControlDirectoryNeeded { vcs: vec!["cvs".to_string()], }))); } if !path.contains('/') { // Maybe a missing command? return Ok(Some(Box::new(MissingBuildFile { filename: path.to_string(), }))); } Ok(None) } fn file_not_found_maybe_executable(p: &str) -> Result>, Error> { if p.starts_with('/') && !p.starts_with("/<>") { return Ok(Some(Box::new(MissingFile { path: std::path::PathBuf::from(p), }))); } if !p.contains('/') { // Maybe a missing command? return Ok(Some(Box::new(MissingCommandOrBuildFile { filename: p.to_string(), }))); } Ok(None) } fn interpreter_missing(c: &Captures) -> Result>, Error> { if c.get(1).unwrap().as_str().starts_with('/') { if c.get(1).unwrap().as_str().contains("PKGBUILDDIR") { return Ok(None); } return Ok(Some(Box::new(MissingFile { path: std::path::PathBuf::from(c.get(1).unwrap().as_str().to_string()), }))); } if c.get(1).unwrap().as_str().contains('/') { return Ok(None); } Ok(Some(Box::new(MissingCommand( c.get(1).unwrap().as_str().to_string(), )))) } fn pkg_config_missing(c: &Captures) -> Result>, Error> { let expr = c.get(1).unwrap().as_str().split('\t').next().unwrap(); if let Some((pkg, minimum)) = expr.split_once(">=") { return Ok(Some(Box::new(MissingPkgConfig { module: pkg.trim().to_string(), minimum_version: Some(minimum.trim().to_string()), }))); } if !expr.contains(' ') { return Ok(Some(Box::new(MissingPkgConfig { module: expr.to_string(), minimum_version: None, }))); } // Hmmm Ok(None) } fn command_missing(c: &Captures) -> Result>, Error> { let command = c.get(1).unwrap().as_str(); if command.contains("PKGBUILDDIR") { return Ok(None); } if command == "./configure" { return Ok(Some(Box::new(MissingConfigure))); } if command.starts_with("./") || command.starts_with("../") { return Ok(None); } if command == "debian/rules" { return Ok(None); } Ok(Some(Box::new(MissingCommand(command.to_string())))) } lazy_static::lazy_static! { static ref CONFIGURE_LINE_MATCHERS: MatcherGroup = MatcherGroup::new(vec![ regex_line_matcher!( r"^\s*Unable to find (.*) \(http(.*)\)", |m| Ok(Some(Box::new(MissingVagueDependency{ name: m.get(1).unwrap().as_str().to_string(), url: Some(m.get(2).unwrap().as_str().to_string()), minimum_version: None, current_version: None, }))) ), regex_line_matcher!( r"^\s*Unable to find (.*)\.", |m| Ok(Some(Box::new(MissingVagueDependency{ name: m.get(1).unwrap().as_str().to_string(), url: None, minimum_version: None, current_version: None, }))) ), ]); } #[derive(Debug, Clone)] struct MultiLineConfigureErrorMatcher; impl Matcher for MultiLineConfigureErrorMatcher { fn extract_from_lines( &self, lines: &[&str], offset: usize, ) -> Result, Option>)>, Error> { if lines[offset].trim_end_matches(['\r', '\n']) != "configure: error:" { return Ok(None); } let mut relevant_linenos = vec![]; for (j, line) in lines.enumerate_forward(None).skip(offset + 1) { if line.trim().is_empty() { continue; } relevant_linenos.push(j); let m = CONFIGURE_LINE_MATCHERS.extract_from_lines(lines, j)?; if let Some(m) = m { return Ok(Some(m)); } } let m = MultiLineMatch::new( Origin("configure".into()), relevant_linenos.clone(), lines .iter() .enumerate() .filter(|(i, _)| relevant_linenos.contains(i)) .map(|(_, l)| l.to_string()) .collect(), ); Ok(Some((Box::new(m), None))) } } #[derive(Debug, Clone)] struct HaskellMissingDependencyMatcher; impl Matcher for HaskellMissingDependencyMatcher { fn extract_from_lines( &self, lines: &[&str], offset: usize, ) -> Result, Option>)>, Error> { if !regex_is_match!( r"(.*): Encountered missing or private dependencies:", lines[offset].trim_end_matches('\n') ) { return Ok(None); } let mut deps = vec![]; let mut offsets = vec![offset]; for (offset, line) in lines.enumerate_forward(None).skip(offset + 1) { if line.trim().is_empty() { break; } if let Some((dep, _)) = line.trim().split_once(',') { deps.push(dep.to_string()); } offsets.push(offset); } let m = MultiLineMatch { origin: Origin("haskell dependencies".into()), offsets: offsets.clone(), lines: offsets.iter().map(|i| lines[*i].to_string()).collect(), }; let p = MissingHaskellDependencies(deps); Ok(Some((Box::new(m), Some(Box::new(p))))) } } #[derive(Debug, Clone)] struct SetupPyCommandMissingMatcher; impl Matcher for SetupPyCommandMissingMatcher { fn extract_from_lines( &self, lines: &[&str], offset: usize, ) -> Result, Option>)>, Error> { let first_offset = offset; let command = match regex_captures!(r"error: invalid command \'(.*)\'", lines[offset].trim()) { None => return Ok(None), Some((_, command)) => command, }; for j in 0..20 { let offset = offset - j; let line = lines[offset].trim_end_matches('\n'); if regex_is_match!( r"usage: setup.py \[global_opts\] cmd1 \[cmd1_opts\] \[cmd2 \[cmd2_opts\] \.\.\.\]", line, ) { let offsets: Vec = vec![first_offset]; let m = MultiLineMatch { origin: Origin("setup.py".into()), offsets, lines: vec![lines[first_offset].to_string()], }; let p = MissingSetupPyCommand(command.to_string()); return Ok(Some((Box::new(m), Some(Box::new(p))))); } } log::warn!("Unable to find setup.py usage line"); Ok(None) } } #[derive(Debug, Clone)] struct PythonFileNotFoundErrorMatcher; impl Matcher for PythonFileNotFoundErrorMatcher { fn extract_from_lines( &self, lines: &[&str], offset: usize, ) -> Result, Option>)>, Error> { if let Some((_, name)) = lazy_regex::regex_captures!( r"^(?:E +)?FileNotFoundError: \[Errno 2\] No such file or directory: \'(.*)\'", lines[offset].trim_end_matches('\n') ) { if offset > 2 && lines[offset - 2].contains("subprocess") { return Ok(Some(( Box::new(SingleLineMatch { origin: Origin("python".into()), offset, line: lines[offset].to_string(), }), Some(Box::new(MissingCommand(name.to_string()))), ))); } else { return Ok(Some(( Box::new(SingleLineMatch { origin: Origin("python".into()), offset, line: lines[offset].to_string(), }), file_not_found_maybe_executable(name)?, ))); } } Ok(None) } } #[derive(Debug, Clone)] struct MultiLinePerlMissingModulesErrorMatcher; impl Matcher for MultiLinePerlMissingModulesErrorMatcher { fn extract_from_lines( &self, lines: &[&str], offset: usize, ) -> Result, Option>)>, Error> { let line = lines[offset].trim_end_matches(['\r', '\n']); if line != "# The following modules are not available." { return Ok(None); } if lines[offset + 1].trim_end_matches(['\r', '\n']) != "# `perl Makefile.PL | cpanm` will install them:" { return Ok(None); } let relevant_linenos = vec![offset, offset + 1, offset + 2]; let m = MultiLineMatch::new( Origin("perl line match".into()), relevant_linenos.clone(), lines .iter() .enumerate() .filter(|(i, _)| relevant_linenos.contains(i)) .map(|(_, l)| l.to_string()) .collect(), ); let problem: Option> = Some(Box::new(MissingPerlModule::simple( lines[offset + 2].trim(), ))); Ok(Some((Box::new(m), problem))) } } lazy_static::lazy_static! { static ref VIGNETTE_LINE_MATCHERS: MatcherGroup = MatcherGroup::new(vec![ regex_line_matcher!(r"^([^ ]+) is not available", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!(r"^The package `(.*)` is required\.", |m| Ok(Some(Box::new(MissingRPackage::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!(r"^Package '(.*)' required.*", |m| Ok(Some(Box::new(MissingRPackage::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!(r"^The '(.*)' package must be installed.*", |m| Ok(Some(Box::new(MissingRPackage::simple(m.get(1).unwrap().as_str()))))), ]); } #[derive(Debug, Clone)] struct MultiLineVignetteErrorMatcher; impl Matcher for MultiLineVignetteErrorMatcher { fn extract_from_lines( &self, lines: &[&str], offset: usize, ) -> Result, Option>)>, Error> { let header_m = regex::Regex::new(r"^Error: processing vignette '(.*)' failed with diagnostics:") .unwrap(); if !header_m.is_match(lines[offset]) { return Ok(None); } if let Some((m, p)) = VIGNETTE_LINE_MATCHERS.extract_from_lines(lines, offset + 1)? { return Ok(Some((m, p))); } Ok(Some(( Box::new(SingleLineMatch { origin: Origin("vignette line match".into()), offset: offset + 1, line: lines[offset + 1].to_string(), }), None, ))) } } #[derive(Debug, Clone)] struct AutoconfUnexpectedMacroMatcher; impl Matcher for AutoconfUnexpectedMacroMatcher { fn extract_from_lines( &self, lines: &[&str], offset: usize, ) -> Result, Option>)>, Error> { if !regex_is_match!( r"\./configure: line [0-9]+: syntax error near unexpected token `.+'", lines[offset] ) { return Ok(None); } let m = MultiLineMatch::new( Origin("autoconf unexpected macro".into()), vec![offset, offset + 1], vec![lines[offset].to_string(), lines[offset + 1].to_string()], ); let problem = if let Some((_, r#macro)) = regex_captures!( r"^\./configure: line [0-9]+: `[\s\t]*([A-Z0-9_]+)\(.*", lines[offset + 1] ) { Some(Box::new(MissingAutoconfMacro { r#macro: r#macro.to_string(), need_rebuild: true, }) as Box) } else { None }; Ok(Some((Box::new(m), problem))) } } fn maven_missing_artifact(m: ®ex::Captures) -> Result>, Error> { let artifacts = m .get(1) .unwrap() .as_str() .split(',') .map(|s| s.trim().to_string()) .collect::>(); Ok(Some(Box::new(MissingMavenArtifacts(artifacts)))) } fn r_missing_package(m: ®ex::Captures) -> Result>, Error> { let fragment = m.get(1).unwrap().as_str(); let deps = fragment .split(",") .map(|dep| { dep.trim_matches('‘') .trim_matches('’') .trim_matches('\'') .to_string() }) .collect::>(); Ok(Some(Box::new(MissingRPackage::simple(&deps[0])))) } fn webpack_file_missing(m: ®ex::Captures) -> Result>, Error> { let path = std::path::Path::new(m.get(1).unwrap().as_str()); let container = std::path::Path::new(m.get(2).unwrap().as_str()); let path = container.join(path); if path.starts_with("/") && !path.as_path().starts_with("/<>") { return Ok(Some(Box::new(MissingFile { path }))); } Ok(None) } fn ruby_missing_gem(m: ®ex::Captures) -> Result>, Error> { let mut minimum_version = None; for grp in m.get(2).unwrap().as_str().split(",") { if let Some((cond, val)) = grp.trim().split_once(" ") { if cond == ">=" { minimum_version = Some(val.to_string()); break; } if cond == "~>" { minimum_version = Some(val.to_string()); } } } Ok(Some(Box::new(MissingRubyGem::new( m.get(1).unwrap().as_str().to_string(), minimum_version, )))) } const MAVEN_ERROR_PREFIX: &str = "(?:\\[ERROR\\]|\\[\x1b\\[1;31mERROR\x1b\\[m\\]) "; lazy_static::lazy_static! { static ref COMMON_MATCHERS: MatcherGroup = MatcherGroup::new(vec![ regex_line_matcher!(r"^[^:]+:\d+: (.*): No such file or directory$", |m| file_not_found_maybe_executable(m.get(1).unwrap().as_str())), regex_line_matcher!( r"^(distutils.errors.DistutilsError|error): Could not find suitable distribution for Requirement.parse\('([^']+)'\)$", |c| { let req = c.get(2).unwrap().as_str().split(';').next().unwrap(); Ok(Some(Box::new(MissingPythonDistribution::from_requirement_str(req, None)))) }), regex_line_matcher!( r"^We need the Python library (.*) to be installed. Try runnning: python -m ensurepip$", |c| Ok(Some(Box::new(MissingPythonDistribution { distribution: c.get(1).unwrap().as_str().to_string(), python_version: None, minimum_version: None })))), regex_line_matcher!( r"^pkg_resources.DistributionNotFound: The '([^']+)' distribution was not found and is required by the application$", |c| Ok(Some(Box::new(MissingPythonDistribution::from_requirement_str(c.get(1).unwrap().as_str(), None))))), regex_line_matcher!( r"^pkg_resources.DistributionNotFound: The '([^']+)' distribution was not found and is required by (.*)$", |c| Ok(Some(Box::new(MissingPythonDistribution::from_requirement_str(c.get(1).unwrap().as_str(), None))))), regex_line_matcher!( r"^Please install cmake version >= (.*) and re-run setup$", |_| Ok(Some(Box::new(MissingCommand("cmake".to_string()))))), regex_line_matcher!( r"^pluggy.manager.PluginValidationError: Plugin '.*' could not be loaded: \(.* \(/usr/lib/python2.[0-9]/dist-packages\), Requirement.parse\('(.*)'\)\)!$", |c| { let expr = c.get(1).unwrap().as_str(); let python_version = Some(2); if let Some((pkg, minimum)) = expr.split_once(">=") { Ok(Some(Box::new(MissingPythonModule { module: pkg.trim().to_string(), python_version, minimum_version: Some(minimum.trim().to_string()), }))) } else if !expr.contains(' ') { Ok(Some(Box::new(MissingPythonModule { module: expr.trim().to_string(), python_version, minimum_version: None, }))) } else { Ok(None) } }), regex_line_matcher!(r"^E ImportError: (.*) could not be imported\.$", |m| Ok(Some(Box::new(MissingPythonModule { module: m.get(1).unwrap().as_str().to_string(), python_version: None, minimum_version: None })))), regex_line_matcher!(r"^ImportError: could not find any library for ([^ ]+) .*$", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r"^ImportError: cannot import name (.*), introspection typelib not found$", |m| Ok(Some(Box::new(MissingIntrospectionTypelib(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r"^ValueError: Namespace (.*) not available$", |m| Ok(Some(Box::new(MissingIntrospectionTypelib(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r"^ namespace '(.*)' ([^ ]+) is being loaded, but >= ([^ ]+) is required$", |m| { let package = m.get(1).unwrap().as_str(); let min_version = m.get(3).unwrap().as_str(); Ok(Some(Box::new(MissingRPackage { package: package.to_string(), minimum_version: Some(min_version.to_string()), }))) }), regex_line_matcher!("^ImportError: cannot import name '(.*)' from '(.*)'$", |m| { let module = m.get(2).unwrap().as_str(); let name = m.get(1).unwrap().as_str(); // TODO(jelmer): This name won't always refer to a module let name = format!("{}.{}", module, name); Ok(Some(Box::new(MissingPythonModule { module: name, python_version: None, minimum_version: None, }))) }), regex_line_matcher!("^E fixture '(.*)' not found$", |m| Ok(Some(Box::new(MissingPytestFixture(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!("^pytest: error: unrecognized arguments: (.*)$", |m| { let args = shlex::split(m.get(1).unwrap().as_str()).unwrap(); Ok(Some(Box::new(UnsupportedPytestArguments(args)))) }), regex_line_matcher!( "^INTERNALERROR> pytest.PytestConfigWarning: Unknown config option: (.*)$", |m| Ok(Some(Box::new(UnsupportedPytestConfigOption(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!("^E ImportError: cannot import name '(.*)' from '(.*)'", |m| { let name = m.get(1).unwrap().as_str(); let module = m.get(2).unwrap().as_str(); Ok(Some(Box::new(MissingPythonModule { module: format!("{}.{}", module, name), python_version: None, minimum_version: None, }))) }), regex_line_matcher!("^E ImportError: cannot import name ([^']+)", |m| { Ok(Some(Box::new(MissingPythonModule { module: m.get(1).unwrap().as_str().to_string(), python_version: None, minimum_version: None, }))) }), regex_line_matcher!(r"^django.core.exceptions.ImproperlyConfigured: Error loading .* module: No module named '(.*)'", |m| { Ok(Some(Box::new(MissingPythonModule { module: m.get(1).unwrap().as_str().to_string(), python_version: None, minimum_version: None, }))) }), regex_line_matcher!("^E ImportError: No module named (.*)", |m| { Ok(Some(Box::new(MissingPythonModule { module: m.get(1).unwrap().as_str().to_string(), python_version: None, minimum_version: None, }))) }), regex_line_matcher!(r"^\s*ModuleNotFoundError: No module named '(.*)'",|m| { Ok(Some(Box::new(MissingPythonModule { module: m.get(1).unwrap().as_str().to_string(), python_version: Some(3), minimum_version: None, }))) }), regex_line_matcher!(r"^Could not import extension .* \(exception: No module named (.*)\)", |m| { Ok(Some(Box::new(MissingPythonModule { module: m.get(1).unwrap().as_str().trim().to_string(), python_version: None, minimum_version: None, }))) }), regex_line_matcher!(r"^Could not import (.*)\.", |m| { Ok(Some(Box::new(MissingPythonModule { module: m.get(1).unwrap().as_str().trim().to_string(), python_version: None, minimum_version: None, }))) }), regex_line_matcher!(r"^(.*): Error while finding module specification for '(.*)' \(ModuleNotFoundError: No module named '(.*)'\)", |m| { let exec = m.get(1).unwrap().as_str(); let python_version = if exec.ends_with("python3") { Some(3) } else if exec.ends_with("python2") { Some(2) } else { None }; Ok(Some(Box::new(MissingPythonModule { module: m.get(3).unwrap().as_str().trim().to_string(), python_version, minimum_version: None, })))}), regex_line_matcher!("^E ModuleNotFoundError: No module named '(.*)'", |m| { Ok(Some(Box::new(MissingPythonModule { module: m.get(1).unwrap().as_str().to_string(), python_version: Some(3), minimum_version: None }))) }), regex_line_matcher!(r"^/usr/bin/python3: No module named ([^ ]+).*", |m| { Ok(Some(Box::new(MissingPythonModule { module: m.get(1).unwrap().as_str().to_string(), python_version: Some(3), minimum_version: None, }))) }), regex_line_matcher!(r#"^(.*:[0-9]+|package .*): cannot find package "(.*)" in any of:"#, |m| Ok(Some(Box::new(MissingGoPackage { package: m.get(2).unwrap().as_str().to_string() })))), regex_line_matcher!(r#"^ImportError: Error importing plugin ".*": No module named (.*)"#, |m| { Ok(Some(Box::new(MissingPythonModule { module: m.get(1).unwrap().as_str().to_string(), python_version: None, minimum_version: None, }))) }), regex_line_matcher!(r"^ImportError: No module named (.*)", |m| { Ok(Some(Box::new(MissingPythonModule { module: m.get(1).unwrap().as_str().to_string(), python_version: None, minimum_version: None, }))) }), regex_line_matcher!(r"^[^:]+:\d+:\d+: fatal error: (.+\.h|.+\.hh|.+\.hpp): No such file or directory", |m| Ok(Some(Box::new(MissingCHeader { header: m.get(1).unwrap().as_str().to_string() })))), regex_line_matcher!(r"^[^:]+:\d+:\d+: fatal error: (.+\.xpm): No such file or directory", file_not_found), regex_line_matcher!(r".*fatal: not a git repository \(or any parent up to mount point /\)", |_| Ok(Some(Box::new(VcsControlDirectoryNeeded { vcs: vec!["git".to_string()] })))), regex_line_matcher!(r".*fatal: not a git repository \(or any of the parent directories\): \.git", |_| Ok(Some(Box::new(VcsControlDirectoryNeeded { vcs: vec!["git".to_string()] })))), regex_line_matcher!(r"[^:]+\.[ch]:\d+:\d+: fatal error: (.+): No such file or directory", |m| Ok(Some(Box::new(MissingCHeader { header: m.get(1).unwrap().as_str().to_string() })))), regex_line_matcher!("^.*␛\x1b\\[31mERROR:␛\x1b\\[39m Error: Cannot find module '(.*)'", node_module_missing), regex_line_matcher!("^\x1b\\[2mError: Cannot find module '(.*)'", node_module_missing), regex_line_matcher!("^\x1b\\[1m\x1b\\[31m\\[!\\] \x1b\\[1mError: Cannot find module '(.*)'", node_module_missing), regex_line_matcher!("^✖ \x1b\\[31mERROR:\x1b\\[39m Error: Cannot find module '(.*)'", node_module_missing), regex_line_matcher!("^\x1b\\[0;31m Error: To use the transpile option, you must have the '(.*)' module installed", node_module_missing), regex_line_matcher!(r#"^\[31mError: No test files found: "(.*)"\[39m"#), regex_line_matcher!(r#"^\x1b\[31mError: No test files found: "(.*)"\x1b\[39m"#), regex_line_matcher!(r"^\s*Error: Cannot find module '(.*)'", node_module_missing), regex_line_matcher!(r"^>> Error: Cannot find module '(.*)'", node_module_missing), regex_line_matcher!(r"^>> Error: Cannot find module '(.*)' from '.*'", node_module_missing), regex_line_matcher!(r"^Error: Failed to load parser '.*' declared in '.*': Cannot find module '(.*)'", |m| Ok(Some(Box::new(MissingNodeModule(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r"^ Cannot find module '(.*)' from '.*'", |m| Ok(Some(Box::new(MissingNodeModule(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r"^>> Error: Grunt attempted to load a \.coffee file but CoffeeScript was not installed\.", |_| Ok(Some(Box::new(MissingNodePackage("coffeescript".to_string()))))), regex_line_matcher!(r"^>> Got an unexpected exception from the coffee-script compiler. The original exception was: Error: Cannot find module '(.*)'", node_module_missing), regex_line_matcher!(r"^\s*Module not found: Error: Can't resolve '(.*)' in '(.*)'", node_module_missing), regex_line_matcher!(r"^ Module (.*) in the transform option was not found\.", node_module_missing), regex_line_matcher!( r"^libtool/glibtool not found!", |_| Ok(Some(Box::new(MissingVagueDependency::simple("libtool"))))), regex_line_matcher!(r"^qmake: could not find a Qt installation of ''", |_| Ok(Some(Box::new(MissingQt)))), regex_line_matcher!(r"^Cannot find X include files via .*", |_| Ok(Some(Box::new(MissingX11)))), regex_line_matcher!( r"^\*\*\* No X11! Install X-Windows development headers/libraries! \*\*\*", |_| Ok(Some(Box::new(MissingX11))) ), regex_line_matcher!( r"^configure: error: \*\*\* No X11! Install X-Windows development headers/libraries! \*\*\*", |_| Ok(Some(Box::new(MissingX11))) ), regex_line_matcher!( r"^configure: error: The Java compiler javac failed.*", |_| Ok(Some(Box::new(MissingCommand("javac".to_string())))) ), regex_line_matcher!( r"^configure: error: No ([^ ]+) command found", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"^ERROR: InvocationError for command could not find executable (.*)", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"^ \*\*\* The (.*) script could not be found\. .*", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r#"^(.*)" command could not be found. (.*)"#, |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"^configure: error: cannot find lib ([^ ]+)", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!(r#"^>> Local Npm module "(.*)" not found. Is it installed?"#, node_module_missing), regex_line_matcher!( r"^npm ERR! CLI for webpack must be installed.", |_| Ok(Some(Box::new(MissingNodePackage("webpack-cli".to_string())))) ), regex_line_matcher!(r"^npm ERR! \[!\] Error: Cannot find module '(.*)'", node_module_missing), regex_line_matcher!( r#"^npm ERR! >> Local Npm module "(.*)" not found. Is it installed\?"#, node_module_missing ), regex_line_matcher!(r"^npm ERR! Error: Cannot find module '(.*)'", node_module_missing), regex_line_matcher!( r"^npm ERR! ERROR in Entry module not found: Error: Can't resolve '(.*)' in '.*'", node_module_missing ), regex_line_matcher!(r"^npm ERR! sh: [0-9]+: (.*): not found", command_missing), regex_line_matcher!(r"^npm ERR! (.*\.ts)\([0-9]+,[0-9]+\): error TS[0-9]+: Cannot find module '(.*)' or its corresponding type declarations.", |m| Ok(Some(Box::new(MissingNodeModule(m.get(2).unwrap().as_str().to_string()))))), regex_line_matcher!(r"^npm ERR! Error: spawn (.*) ENOENT", command_missing), regex_line_matcher!( r"^(\./configure): line \d+: ([A-Z0-9_]+): command not found", |m| Ok(Some(Box::new(MissingAutoconfMacro::new(m.get(2).unwrap().as_str().to_string())))) ), regex_line_matcher!(r"^.*: line \d+: ([^ ]+): command not found", command_missing), regex_line_matcher!(r"^.*: line \d+: ([^ ]+): Permission denied"), regex_line_matcher!(r"^make\[[0-9]+\]: .*: Permission denied"), regex_line_matcher!(r"^/usr/bin/texi2dvi: TeX neither supports -recorder nor outputs \\openout lines in its log file"), regex_line_matcher!(r"^/bin/sh: \d+: ([^ ]+): not found", command_missing), regex_line_matcher!(r"^sh: \d+: ([^ ]+): not found", command_missing), regex_line_matcher!(r"^.*\.sh: \d+: ([^ ]+): not found", command_missing), regex_line_matcher!(r"^.*: 1: cd: can't cd to (.*)", |m| Ok(Some(Box::new(DirectoryNonExistant(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r"^/bin/bash: (.*): command not found", command_missing), regex_line_matcher!(r"^bash: ([^ ]+): command not found", command_missing), regex_line_matcher!(r"^env: ‘(.*)’: No such file or directory", interpreter_missing), regex_line_matcher!(r"^/bin/bash: .*: (.*): bad interpreter: No such file or directory", interpreter_missing), // SH Errors regex_line_matcher!(r"^.*: [0-9]+: exec: (.*): not found", command_missing), regex_line_matcher!(r"^.*: [0-9]+: (.*): not found", command_missing), regex_line_matcher!(r"^/usr/bin/env: [‘'](.*)['’]: No such file or directory", command_missing), regex_line_matcher!(r"^make\[[0-9]+\]: (.*): Command not found", command_missing), regex_line_matcher!(r"^make: (.*): Command not found", command_missing), regex_line_matcher!(r"^make: (.*): No such file or directory", command_missing), regex_line_matcher!(r"^xargs: (.*): No such file or directory", command_missing), regex_line_matcher!(r"^make\[[0-9]+\]: ([^/ :]+): No such file or directory", command_missing), regex_line_matcher!(r"^.*: failed to exec '(.*)': No such file or directory", command_missing), regex_line_matcher!(r"^No package '([^']+)' found", pkg_config_missing), regex_line_matcher!(r"^--\s* No package '([^']+)' found", pkg_config_missing), regex_line_matcher!( r"^\-\- Please install Git, make sure it is in your path, and then try again.", |_| Ok(Some(Box::new(MissingCommand("git".to_string())))) ), regex_line_matcher!( r#"^\+ERROR: could not access file "(.*)": No such file or directory"#, |m| Ok(Some(Box::new(MissingPostgresExtension(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r#"^configure: error: (Can't|Cannot) find "(.*)" in your PATH.*"#, |m| Ok(Some(Box::new(MissingCommand(m.get(2).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"^configure: error: Cannot find (.*) in your system path", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r#"^> Cannot run program "(.*)": error=2, No such file or directory"#, |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!(r"^(.*) binary '(.*)' not available .*", |m| Ok(Some(Box::new(MissingCommand(m.get(2).unwrap().as_str().to_string()))))), regex_line_matcher!(r"^An error has occurred: FatalError: git failed\. Is it installed, and are you in a Git repository directory\?", |_| Ok(Some(Box::new(MissingCommand("git".to_string()))))), regex_line_matcher!("^Please install '(.*)' seperately and try again.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"^> A problem occurred starting process 'command '(.*)''", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"^vcver.scm.git.GitCommandError: 'git .*' returned an error code 127", |_| Ok(Some(Box::new(MissingCommand("git".to_string())))) ), Box::new(MultiLineConfigureErrorMatcher), Box::new(MultiLinePerlMissingModulesErrorMatcher), Box::new(MultiLineVignetteErrorMatcher), regex_line_matcher!(r"^configure: error: No package '([^']+)' found", pkg_config_missing), regex_line_matcher!(r"^configure: error: (doxygen|asciidoc) is not available and maintainer mode is enabled", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r"^configure: error: Documentation enabled but rst2html not found.", |_| Ok(Some(Box::new(MissingCommand("rst2html".to_string()))))), regex_line_matcher!(r"^cannot run pkg-config to check .* version at (.*) line [0-9]+\.", |_| Ok(Some(Box::new(MissingCommand("pkg-config".to_string()))))), regex_line_matcher!(r"^Error: pkg-config not found!", |_| Ok(Some(Box::new(MissingCommand("pkg-config".to_string()))))), regex_line_matcher!(r"^\*\*\* pkg-config (.*) or newer\. You can download pkg-config", |m| Ok(Some(Box::new(MissingVagueDependency { name: "pkg-config".to_string(), minimum_version: Some(m.get(1).unwrap().as_str().to_string()), url: None, current_version: None })))), // Tox regex_line_matcher!(r"^ERROR: InterpreterNotFound: (.*)", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r"^ERROR: unable to find python", |_| Ok(Some(Box::new(MissingCommand("python".to_string()))))), regex_line_matcher!(r"^ ERROR: BLAS not found!", |_| Ok(Some(Box::new(MissingLibrary("blas".to_string()))))), Box::new(AutoconfUnexpectedMacroMatcher), regex_line_matcher!(r"^\./configure: [0-9]+: \.: Illegal option .*"), regex_line_matcher!(r"^Requested '(.*)' but version of ([^ ]+) is ([^ ]+)", pkg_config_missing), regex_line_matcher!(r"^.*configure: error: Package requirements \((.*)\) were not met:", pkg_config_missing), regex_line_matcher!(r"^configure: error: [a-z0-9_-]+-pkg-config (.*) couldn't be found", pkg_config_missing), regex_line_matcher!(r#"^configure: error: C preprocessor "/lib/cpp" fails sanity check"#), regex_line_matcher!(r"^configure: error: .*\. Please install (bison|flex)", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r"^configure: error: No C\# compiler found. You need to install either mono \(>=(.*)\) or \.Net", |_| Ok(Some(Box::new(MissingCSharpCompiler)))), regex_line_matcher!(r"^configure: error: No C\# compiler found", |_| Ok(Some(Box::new(MissingCSharpCompiler)))), regex_line_matcher!(r"^error: can't find Rust compiler", |_| Ok(Some(Box::new(MissingRustCompiler)))), regex_line_matcher!(r"^Found no assembler", |_| Ok(Some(Box::new(MissingAssembler)))), regex_line_matcher!(r"^error: failed to get `(.*)` as a dependency of package `(.*)`", |m| Ok(Some(Box::new(MissingCargoCrate::simple(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r"^configure: error: (.*) requires libkqueue \(or system kqueue\). .*", |_| Ok(Some(Box::new(MissingPkgConfig::simple("libkqueue".to_string()))))), regex_line_matcher!(r"^Did not find pkg-config by name 'pkg-config'", |_| Ok(Some(Box::new(MissingCommand("pkg-config".to_string()))))), regex_line_matcher!(r"^configure: error: Required (.*) binary is missing. Please install (.*).", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r#".*meson.build:([0-9]+):([0-9]+): ERROR: Dependency "(.*)" not found"#, |m| Ok(Some(Box::new(MissingPkgConfig::simple(m.get(3).unwrap().as_str().to_string()))))), regex_line_matcher!(r".*meson.build:([0-9]+):([0-9]+): ERROR: Problem encountered: No XSLT processor found, .*", |_| Ok(Some(Box::new(MissingVagueDependency::simple("xsltproc"))))), regex_line_matcher!(r".*meson.build:([0-9]+):([0-9]+): Unknown compiler\(s\): \[\['(.*)'.*\]", |m| Ok(Some(Box::new(MissingCommand(m.get(3).unwrap().as_str().to_string()))))), regex_line_matcher!(".*meson.build:([0-9]+):([0-9]+): ERROR: python3 \"(.*)\" missing", |m| Ok(Some(Box::new(MissingPythonModule { module: m.get(3).unwrap().as_str().to_string(), python_version: Some(3), minimum_version: None, })))), regex_line_matcher!(".*meson.build:([0-9]+):([0-9]+): ERROR: Program \'(.*)\' not found", |m| Ok(Some(Box::new(MissingCommand(m.get(3).unwrap().as_str().to_string()))))), regex_line_matcher!(".*meson.build:([0-9]+):([0-9]+): ERROR: Git program not found, .*", |_| Ok(Some(Box::new(MissingCommand("git".to_string()))))), regex_line_matcher!(".*meson.build:([0-9]+):([0-9]+): ERROR: C header \'(.*)\' not found", |m| Ok(Some(Box::new(MissingCHeader::new(m.get(3).unwrap().as_str().to_string()))))), regex_line_matcher!(r"^configure: error: (.+\.h) could not be found\. Please set CPPFLAGS\.", |m| Ok(Some(Box::new(MissingCHeader::new(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r".*meson.build:([0-9]+):([0-9]+): ERROR: Unknown compiler\(s\): \['(.*)'\]", |m| Ok(Some(Box::new(MissingCommand(m.get(3).unwrap().as_str().to_string()))))), regex_line_matcher!(".*meson.build:([0-9]+):([0-9]+): ERROR: Dependency \"(.*)\" not found, tried pkgconfig", |m| Ok(Some(Box::new(MissingPkgConfig::simple(m.get(3).unwrap().as_str().to_string()))))), regex_line_matcher!(r#".*meson.build:([0-9]+):([0-9]+): ERROR: Could not execute Vala compiler "(.*)""#, |m| Ok(Some(Box::new(MissingCommand(m.get(3).unwrap().as_str().to_string()))))), regex_line_matcher!(r".*meson.build:([0-9]+):([0-9]+): ERROR: python3 is missing modules: (.*)", |m| Ok(Some(Box::new(MissingPythonModule::simple(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r".*meson.build:([0-9]+):([0-9]+): ERROR: Invalid version of dependency, need '([^']+)' \['>=\s*([^']+)'\] found '([^']+)'\.", |m| Ok(Some(Box::new(MissingPkgConfig::new(m.get(3).unwrap().as_str().to_string(), Some(m.get(4).unwrap().as_str().to_string())))))), regex_line_matcher!(".*meson.build:([0-9]+):([0-9]+): ERROR: C shared or static library '(.*)' not found", |m| Ok(Some(Box::new(MissingLibrary(m.get(3).unwrap().as_str().to_string()))))), regex_line_matcher!(".*meson.build:([0-9]+):([0-9]+): ERROR: C\\+\\++ shared or static library '(.*)' not found", |m| Ok(Some(Box::new(MissingLibrary(m.get(3).unwrap().as_str().to_string()))))), regex_line_matcher!(".*meson.build:([0-9]+):([0-9]+): ERROR: Pkg-config binary for machine .* not found. Giving up.", |_| Ok(Some(Box::new(MissingCommand("pkg-config".to_string()))))), regex_line_matcher!(".*meson.build([0-9]+):([0-9]+): ERROR: Problem encountered: (.*) require (.*) >= (.*), (.*) which were not found.", |m| Ok(Some(Box::new(MissingVagueDependency{name: m.get(4).unwrap().as_str().to_string(), current_version: None, url: None, minimum_version: Some(m.get(5).unwrap().as_str().to_string())})))), regex_line_matcher!(".*meson.build([0-9]+):([0-9]+): ERROR: Problem encountered: (.*) is required to .*", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(4).unwrap().as_str()))))), regex_line_matcher!(r"^ERROR: (.*) is not installed\. Install at least (.*) version (.+) to continue\.", |m| Ok(Some(Box::new(MissingVagueDependency { name: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(3).unwrap().as_str().to_string()), current_version: None, url: None, })))), regex_line_matcher!(r"^configure: error: Library requirements \((.*)\) not met\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!(r"^configure: error: (.*) is missing -- (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!(r"^configure: error: Cannot find (.*), check (.*)", |m| Ok(Some(Box::new(MissingVagueDependency { name: m.get(1).unwrap().as_str().to_string(), url: Some(m.get(2).unwrap().as_str().to_string()), minimum_version: None, current_version: None })))), regex_line_matcher!(r"^configure: error: \*\*\* Unable to find (.* library)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!(r"^configure: error: unable to find (.*)\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!(r"^configure: error: Perl Module (.*) not available", |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!(r"(.*) was not found in your path\. Please install (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!(r"^configure: error: Please install (.*) >= (.*)", |m| Ok(Some(Box::new(MissingVagueDependency { name: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(2).unwrap().as_str().to_string()), current_version: None, url: None })))), regex_line_matcher!( r"^configure: error: the required package (.*) is not installed", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!(r"^configure: error: \*\*\* (.*) >= (.*) not installed.*", |m| Ok(Some(Box::new(MissingVagueDependency { name: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(2).unwrap().as_str().to_string()), current_version: None, url: None })))), regex_line_matcher!(r"^configure: error: you should install (.*) first", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!(r"^configure: error: cannot locate (.*) >= (.*)", |m| Ok(Some(Box::new(MissingVagueDependency { name: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(2).unwrap().as_str().to_string()), current_version: None, url: None })))), regex_line_matcher!(r"^configure: error: !!! Please install (.*) !!!", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!(r"^configure: error: (.*) version (.*) or higher is required", |m| Ok(Some(Box::new(MissingVagueDependency { name: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(2).unwrap().as_str().to_string()), current_version: None, url: None })))), regex_line_matcher!(r"^configure.(ac|in):[0-9]+: error: libtool version (.*) or higher is required", |m| Ok(Some(Box::new(MissingVagueDependency { name: m.get(2).unwrap().as_str().to_string(), minimum_version: Some(m.get(3).unwrap().as_str().to_string()), current_version: None, url: None })))), regex_line_matcher!(r"configure: error: ([^ ]+) ([^ ]+) or better is required.*", |m| Ok(Some(Box::new(MissingVagueDependency { name: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(2).unwrap().as_str().to_string()), current_version: None, url: None })))), regex_line_matcher!(r"configure: error: ([^ ]+) ([^ ]+) or greater is required.*", |m| Ok(Some(Box::new(MissingVagueDependency { name: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(2).unwrap().as_str().to_string()), current_version: None, url: None })))), regex_line_matcher!(r"configure: error: ([^ ]+) or greater is required.*", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"configure: error: (.*) library is required", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!( r"configure: error: (.*) library is not installed\.", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!( r"configure: error: OpenSSL developer library 'libssl-dev' or 'openssl-devel' not installed; cannot continue.", |_m| Ok(Some(Box::new(MissingLibrary("ssl".to_string()))))), regex_line_matcher!( r"configure: error: \*\*\* Cannot find (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"configure: error: (.*) is required to compile .*", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"\s*You must have (.*) installed to compile .*\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"You must install (.*) to compile (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"\*\*\* No (.*) found, please in(s?)tall it \*\*\*", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"configure: error: (.*) required, please in(s?)tall it", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"\*\* ERROR \*\* : You must have `(.*)' installed on your system\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"autogen\.sh: ERROR: You must have `(.*)' installed to compile this package\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"autogen\.sh: You must have (.*) installed\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"\s*Error! You need to have (.*) installed\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"(configure: error|\*\*Error\*\*): You must have (.*) installed.*", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(2).unwrap().as_str()))))), regex_line_matcher!( r"configure: error: (.*) is required for building this package.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"configure: error: (.*) is required to build (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"configure: error: (.*) is required", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"configure: error: (.*) is required for (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"configure: error: \*\*\* (.*) is required\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"configure: error: (.*) is required, please get it from (.*)", |m| Ok(Some(Box::new(MissingVagueDependency{ name: m.get(1).unwrap().as_str().to_string(), url: Some(m.get(2).unwrap().as_str().to_string()), minimum_version: None, current_version: None})))), regex_line_matcher!( r".*meson.build:\d+:\d+: ERROR: Assert failed: (.*) support explicitly required, but (.*) not found", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"configure: error: .*, (lib[^ ]+) is required", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"dh: Unknown sequence --(.*) \(options should not come before the sequence\)", |_| Ok(Some(Box::new(DhWithOrderIncorrect)))), regex_line_matcher!( r"(dh: |dh_.*: error: )Compatibility levels before ([0-9]+) are no longer supported \(level ([0-9]+) requested\)", |m| { let l1 = m.get(2).unwrap().as_str().parse().unwrap(); let l2 = m.get(3).unwrap().as_str().parse().unwrap(); Ok(Some(Box::new(UnsupportedDebhelperCompatLevel::new(l1, l2)))) } ), regex_line_matcher!(r"\{standard input\}: Error: (.*)"), regex_line_matcher!(r"dh: Unknown sequence (.*) \(choose from: .*\)"), regex_line_matcher!(r".*: .*: No space left on device", |_m| Ok(Some(Box::new(NoSpaceOnDevice)))), regex_line_matcher!(r"^No space left on device.", |_m| Ok(Some(Box::new(NoSpaceOnDevice)))), regex_line_matcher!( r".*Can't locate (.*).pm in @INC \(you may need to install the (.*) module\) \(@INC contains: (.*)\) at .* line [0-9]+\.", |m| { let path = format!("{}.pm", m.get(1).unwrap().as_str()); let inc = m.get(3).unwrap().as_str().split(' ').map(|s| s.to_string()).collect::>(); Ok(Some(Box::new(MissingPerlModule{ filename: Some(path), module: m.get(2).unwrap().as_str().to_string(), minimum_version: None, inc: Some(inc)}))) } ), regex_line_matcher!( r".*Can't locate (.*).pm in @INC \(you may need to install the (.*) module\) \(@INC contains: (.*)\)\.", |m| { let path = format!("{}.pm", m.get(1).unwrap().as_str()); let inc = m.get(3).unwrap().as_str().split(' ').map(|s| s.to_string()).collect::>(); Ok(Some(Box::new(MissingPerlModule{ filename: Some(path), module: m.get(2).unwrap().as_str().to_string(), inc: Some(inc), minimum_version: None }))) } ), regex_line_matcher!( r"\[DynamicPrereqs\] Can't locate (.*) at inline delegation in .*", |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r#"Can't locate object method "(.*)" via package "(.*)" \(perhaps you forgot to load "(.*)"\?\) at .*.pm line [0-9]+\."#, |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(2).unwrap().as_str())))) ), regex_line_matcher!( r">\(error\): Could not expand \[(.*)'", |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str().trim().trim_matches('\'')))))), regex_line_matcher!( r"\[DZ\] could not load class (.*) for license (.*)", |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"\- ([^\s]+)\s+\.\.\.missing. \(would need (.*)\)", |m| Ok(Some(Box::new(MissingPerlModule { filename: None, module: m.get(1).unwrap().as_str().to_string(), inc: None, minimum_version: Some(m.get(2).unwrap().as_str().to_string()), })))), regex_line_matcher!( r"Required plugin bundle ([^ ]+) isn't installed.", |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"Required plugin ([^ ]+) isn't installed.", |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r".*Can't locate (.*) in @INC \(@INC contains: (.*)\) at .* line .*.", |m| { let inc = m.get(2).unwrap().as_str().split(' ').map(|s| s.to_string()).collect::>(); Ok(Some(Box::new(MissingPerlFile::new(m.get(1).unwrap().as_str().to_string(), Some(inc))))) }), regex_line_matcher!( r"Can't find author dependency ([^ ]+) at (.*) line ([0-9]+).", |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"Can't find author dependency ([^ ]+) version (.*) at (.*) line ([0-9]+).", |m| Ok(Some(Box::new(MissingPerlModule { filename: None, module: m.get(1).unwrap().as_str().to_string(), inc: None, minimum_version: Some(m.get(2).unwrap().as_str().to_string()), })))), regex_line_matcher!( r"> Could not find (.*)\. Please check that (.*) contains a valid JDK installation.", |m| Ok(Some(Box::new(MissingJDKFile::new(m.get(2).unwrap().as_str().to_string(), m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!( r"> Could not find (.*)\. Please check that (.*) contains a valid \(and compatible\) JDK installation.", |m| Ok(Some(Box::new(MissingJDKFile::new(m.get(2).unwrap().as_str().to_string(), m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!( r"> Kotlin could not find the required JDK tools in the Java installation '(.*)' used by Gradle. Make sure Gradle is running on a JDK, not JRE.", |m| Ok(Some(Box::new(MissingJDK::new(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!( r"> JDK_5 environment variable is not defined. It must point to any JDK that is capable to compile with Java 5 target \((.*)\)", |m| Ok(Some(Box::new(MissingJDK::new(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!( r"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.", |_| Ok(Some(Box::new(MissingJRE)))), regex_line_matcher!( r#"Error: environment variable "JAVA_HOME" must be set to a JDK \(>= v(.*)\) installation directory"#, |m| Ok(Some(Box::new(MissingJDK::new(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!( r"(?:/usr/bin/)?install: cannot create regular file '(.*)': No such file or directory", file_not_found ), regex_line_matcher!( r"Cannot find source directory \((.*)\)", file_not_found ), regex_line_matcher!( r"python[0-9.]*: can't open file '(.*)': \[Errno 2\] No such file or directory", file_not_found ), regex_line_matcher!( r"^error: \[Errno 2\] No such file or directory: '(.*)'", |m| file_not_found_maybe_executable(m.get(1).unwrap().as_str()) ), regex_line_matcher!( r".*:[0-9]+:[0-9]+: ERROR: \['/usr/bin/python3'\]> is not a valid python or it is missing setuptools", |_| Ok(Some(Box::new(MissingPythonDistribution { distribution: "setuptools".to_string(), python_version: Some(3), minimum_version: None, }))) ), regex_line_matcher!(r"OSError: \[Errno 28\] No space left on device", |_| Ok(Some(Box::new(NoSpaceOnDevice)))), // python:setuptools_scm regex_line_matcher!( r"^LookupError: setuptools-scm was unable to detect version for '.*'\.", |_| Ok(Some(Box::new(SetuptoolScmVersionIssue))) ), regex_line_matcher!( r"^LookupError: setuptools-scm was unable to detect version for .*\.", |_| Ok(Some(Box::new(SetuptoolScmVersionIssue))) ), regex_line_matcher!(r"^OSError: 'git' was not found", |_| Ok(Some(Box::new(MissingCommand("git".to_string()))))), regex_line_matcher!(r"^OSError: No such file (.*)", |m| file_not_found_maybe_executable(m.get(1).unwrap().as_str())), regex_line_matcher!( r"^Could not open '(.*)': No such file or directory at /usr/share/perl/[0-9.]+/ExtUtils/MM_Unix.pm line [0-9]+.", |m| Ok(Some(Box::new(MissingPerlFile::new(m.get(1).unwrap().as_str().to_string(), None)))) ), regex_line_matcher!( r#"^Can't open perl script "(.*)": No such file or directory"#, |m| Ok(Some(Box::new(MissingPerlFile::new(m.get(1).unwrap().as_str().to_string(), None))))), // Maven regex_line_matcher!( format!("{}{}", MAVEN_ERROR_PREFIX, r"Failed to execute goal on project .*: \x1b\[1;31mCould not resolve dependencies for project .*: The following artifacts could not be resolved: (.*): Could not find artifact (.*) in (.*) \((.*)\)\x1b\[m -> \x1b\[1m\[Help 1\]\x1b\[m").as_str(), maven_missing_artifact), regex_line_matcher!( format!("{}{}", MAVEN_ERROR_PREFIX, r"Failed to execute goal on project .*: \x1b\[1;31mCould not resolve dependencies for project .*: Could not find artifact (.*)\x1b\[m .*").as_str(), maven_missing_artifact ), regex_line_matcher!( format!("{}{}", MAVEN_ERROR_PREFIX, r"Failed to execute goal on project .*: Could not resolve dependencies for project .*: The following artifacts could not be resolved: (.*): Cannot access central \(https://repo\.maven\.apache\.org/maven2\) in offline mode and the artifact .* has not been downloaded from it before..*").as_str(), maven_missing_artifact ), regex_line_matcher!( format!("{}{}", MAVEN_ERROR_PREFIX, r"Unresolveable build extension: Plugin (.*) or one of its dependencies could not be resolved: Cannot access central \(https://repo.maven.apache.org/maven2\) in offline mode and the artifact .* has not been downloaded from it before. @").as_str(), |m| Ok(Some(Box::new(MissingMavenArtifacts(vec![m.get(1).unwrap().as_str().to_string()]))))), regex_line_matcher!( format!("{}{}", MAVEN_ERROR_PREFIX, r"Non-resolvable import POM: Cannot access central \(https://repo.maven.apache.org/maven2\) in offline mode and the artifact (.*) has not been downloaded from it before. @ line [0-9]+, column [0-9]+").as_str(), maven_missing_artifact), regex_line_matcher!( r"\[FATAL\] Non-resolvable parent POM for .*: Cannot access central \(https://repo.maven.apache.org/maven2\) in offline mode and the artifact (.*) has not been downloaded from it before. .*", maven_missing_artifact), regex_line_matcher!( format!("{}{}", MAVEN_ERROR_PREFIX,r"Plugin (.*) or one of its dependencies could not be resolved: Cannot access central \(https://repo.maven.apache.org/maven2\) in offline mode and the artifact .* has not been downloaded from it before. -> \[Help 1\]").as_str(), |m| Ok(Some(Box::new(MissingMavenArtifacts(vec![m.get(1).unwrap().as_str().to_string()]))))), regex_line_matcher!( format!("{}{}", MAVEN_ERROR_PREFIX, r"Plugin (.+) or one of its dependencies could not be resolved: Failed to read artifact descriptor for (.*): (.*)").as_str(), |m| Ok(Some(Box::new(MissingMavenArtifacts(vec![m.get(1).unwrap().as_str().to_string()]))))), regex_line_matcher!( format!("{}{}", MAVEN_ERROR_PREFIX, r"Failed to execute goal on project .*: Could not resolve dependencies for project .*: Cannot access .* \([^\)]+\) in offline mode and the artifact (.*) has not been downloaded from it before. -> \[Help 1\]").as_str(), maven_missing_artifact), regex_line_matcher!( format!("{}{}", MAVEN_ERROR_PREFIX, r"Failed to execute goal on project .*: Could not resolve dependencies for project .*: Cannot access central \(https://repo.maven.apache.org/maven2\) in offline mode and the artifact (.*) has not been downloaded from it before..*").as_str(), maven_missing_artifact), regex_line_matcher!(format!("{}{}", MAVEN_ERROR_PREFIX, "Failed to execute goal (.*) on project (.*): (.*)").as_str(), |_| Ok(None)), regex_line_matcher!( format!("{}{}", MAVEN_ERROR_PREFIX, r"Error resolving version for plugin \'(.*)\' from the repositories \[.*\]: Plugin not found in any plugin repository -> \[Help 1\]").as_str(), |m| Ok(Some(Box::new(MissingMavenArtifacts(vec![m.get(1).unwrap().as_str().to_string()])))) ), regex_line_matcher!( r"E: eatmydata: unable to find '(.*)' in PATH", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"'(.*)' not found in PATH at (.*) line ([0-9]+)\.", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"/usr/bin/eatmydata: [0-9]+: exec: (.*): not found", command_missing ), regex_line_matcher!( r"/usr/bin/eatmydata: [0-9]+: exec: (.*): Permission denied", |m| Ok(Some(Box::new(NotExecutableFile(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r#"(.*): exec: "(.*)": executable file not found in \$PATH"#, |m| Ok(Some(Box::new(MissingCommand(m.get(2).unwrap().as_str().to_string())))) ), regex_line_matcher!( r#"Can't exec "(.*)": No such file or directory at (.*) line ([0-9]+)\."#, command_missing ), regex_line_matcher!( r"dh_missing: (warning: )?(.*) exists in debian/.* but is not installed to anywhere", |m| Ok(Some(Box::new(DhMissingUninstalled(m.get(2).unwrap().as_str().to_string())))) ), regex_line_matcher!(r"dh_link: link destination (.*) is a directory", |m| Ok(Some(Box::new(DhLinkDestinationIsDirectory(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r"I/O error : Attempt to load network entity (.*)", |m| Ok(Some(Box::new(MissingXmlEntity::new(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r"ccache: error: (.*)", |m| Ok(Some(Box::new(CcacheError(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!( r"dh: The --until option is not supported any longer \(#932537\). Use override targets instead.", |_| Ok(Some(Box::new(DhUntilUnsupported::new()))) ), regex_line_matcher!( r"dh: unable to load addon (.*): (.*) did not return a true value at \(eval 11\) line ([0-9]+).", |m| Ok(Some(Box::new(DhAddonLoadFailure::new(m.get(1).unwrap().as_str().to_string(), m.get(2).unwrap().as_str().to_string())))) ), regex_line_matcher!( "ERROR: dependencies (.*) are not available for package [‘'](.*)['’]", r_missing_package ), regex_line_matcher!( "ERROR: dependency [‘'](.*)['’] is not available for package [‘'](.*)[’']", r_missing_package ), regex_line_matcher!( r"Error in library\(.*\) : there is no package called \'(.*)\'", r_missing_package ), regex_line_matcher!(r"Error in .* : there is no package called \'(.*)\'", r_missing_package), regex_line_matcher!(r"there is no package called \'(.*)\'", r_missing_package), regex_line_matcher!( r" namespace ‘(.*)’ ([^ ]+) is being loaded, but >= ([^ ]+) is required", |m| Ok(Some(Box::new(MissingRPackage{ package: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(3).unwrap().as_str().to_string())}))) ), regex_line_matcher!( r" namespace ‘(.*)’ ([^ ]+) is already loaded, but >= ([^ ]+) is required", |m| Ok(Some(Box::new(MissingRPackage{package: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(3).unwrap().as_str().to_string())}))) ), regex_line_matcher!(r"b\'convert convert: Unable to read font \((.*)\) \[No such file or directory\]\.\\n\'", file_not_found), regex_line_matcher!(r"mv: cannot stat \'(.*)\': No such file or directory", file_not_found), regex_line_matcher!(r"mv: cannot move \'.*\' to \'(.*)\': No such file or directory", file_not_found), regex_line_matcher!( r"(/usr/bin/install|mv): will not overwrite just-created \'(.*)\' with \'(.*)\'", |_| Ok(None) ), regex_line_matcher!(r"^IOError: \[Errno 2\] No such file or directory: \'(.*)\'", |m| file_not_found_maybe_executable(m.get(1).unwrap().as_str())), regex_line_matcher!(r"^error: \[Errno 2\] No such file or directory: \'(.*)\'", |m| file_not_found_maybe_executable(m.get(1).unwrap().as_str())), regex_line_matcher!(r"^E IOError: \[Errno 2\] No such file or directory: \'(.*)\'", |m| file_not_found_maybe_executable(m.get(1).unwrap().as_str())), regex_line_matcher!("FAIL\t(.+\\/.+\\/.+)\t([0-9.]+)s", |_| Ok(None)), regex_line_matcher!( r#"dh_(.*): Cannot find \(any matches for\) "(.*)" \(tried in (.*)\)"#, |m| Ok(Some(Box::new(DebhelperPatternNotFound { pattern: m.get(2).unwrap().as_str().to_string(), tool: m.get(1).unwrap().as_str().to_string(), directories: m.get(3).unwrap().as_str().split(',').map(|s| s.trim().to_string()).collect(), }))) ), regex_line_matcher!( r#"Can't exec "(.*)": No such file or directory at /usr/share/perl5/Debian/Debhelper/Dh_Lib.pm line [0-9]+."#, command_missing ), regex_line_matcher!( r#"Can\'t exec "(.*)": Permission denied at (.*) line [0-9]+\."#, |m| Ok(Some(Box::new(NotExecutableFile(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"/usr/bin/fakeroot: [0-9]+: (.*): Permission denied", |m| Ok(Some(Box::new(NotExecutableFile(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!(r".*: error: (.*) command not found", command_missing), regex_line_matcher!(r"error: command '(.*)' failed: No such file or directory", command_missing), regex_line_matcher!( r"dh_install: Please use dh_missing --list-missing/--fail-missing instead", |_| Ok(None) ), regex_line_matcher!( r#"dh([^:]*): Please use the third-party "pybuild" build system instead of python-distutils"#, |_| Ok(None) ), // A Python error, but not likely to be actionable. The previous line will have the actual line that failed. regex_line_matcher!(r"ImportError: cannot import name (.*)", |_| Ok(None)), // Rust ? regex_line_matcher!(r"\s*= note: /usr/bin/ld: cannot find -l([^ ]+): .*", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r"\s*= note: /usr/bin/ld: cannot find -l([^ ]+)", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r"/usr/bin/ld: cannot find -l([^ ]+): .*", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r"/usr/bin/ld: cannot find -l([^ ]+)", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!( r"Could not find gem \'([^ ]+) \(([^)]+)\)\', which is required by gem.*", ruby_missing_gem ), regex_line_matcher!( r"Could not find gem \'([^ \']+)\', which is required by gem.*", |m| Ok(Some(Box::new(MissingRubyGem::simple(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"[^:]+:[0-9]+:in \`to_specs\': Could not find \'(.*)\' \(([^)]+)\) among [0-9]+ total gem\(s\) \(Gem::MissingSpecError\)", ruby_missing_gem ), regex_line_matcher!( r"[^:]+:[0-9]+:in \`to_specs\': Could not find \'(.*)\' \(([^)]+)\) - .* \(Gem::MissingSpecVersionError\)", ruby_missing_gem ), regex_line_matcher!( r"[^:]+:[0-9]+:in \`block in verify_gemfile_dependencies_are_found\!\': Could not find gem \'(.*)\' in any of the gem sources listed in your Gemfile\. \(Bundler::GemNotFound\)", |m| Ok(Some(Box::new(MissingRubyGem::simple(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"Exception: (.*) not in path[!.]*", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"Exception: Building sdist requires that ([^ ]+) be installed\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"[^:]+:[0-9]+:in \`find_spec_for_exe\': can\'t find gem (.*) \(([^)]+)\) with executable (.*) \(Gem::GemNotFoundException\)", ruby_missing_gem ), regex_line_matcher!( r".?PHP Fatal error: Uncaught Error: Class \'(.*)\' not found in (.*):([0-9]+)", |m| Ok(Some(Box::new(MissingPhpClass::simple(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!(r"Caused by: java.lang.ClassNotFoundException: (.*)", |m| Ok(Some(Box::new(MissingJavaClass::simple(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!( r"\[(.*)\] \t\t:: (.*)\#(.*);\$\{(.*)\}: not found", |m| Ok(Some(Box::new(MissingMavenArtifacts(vec![format!("{}:{}:jar:debian", m.get(2).unwrap().as_str(), m.get(3).unwrap().as_str())])))) ), regex_line_matcher!( r"Caused by: java.lang.IllegalArgumentException: Cannot find JAR \'(.*)\' required by module \'(.*)\' using classpath or distribution directory \'(.*)\'", |_| Ok(None) ), regex_line_matcher!( r".*\.xml:[0-9]+: Unable to find a javac compiler;", |_| Ok(Some(Box::new(MissingJavaClass::simple("com.sun.tools.javac.Main".to_string())))) ), regex_line_matcher!( r#"checking for (.*)\.\.\. configure: error: "Cannot check for existence of module (.*) without pkgconf""#, |_| Ok(Some(Box::new(MissingCommand("pkgconf".to_string())))) ), regex_line_matcher!( r"configure: error: Could not find '(.*)' in path\.", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"autoreconf was not found; .*", |_| Ok(Some(Box::new(MissingCommand("autoreconf".to_string())))) ), regex_line_matcher!(r"^g\+\+: error: (.*): No such file or directory", file_not_found), regex_line_matcher!(r"strip: \'(.*)\': No such file", file_not_found), regex_line_matcher!( r"Sprockets::FileNotFound: couldn\'t find file \'(.*)\' with type \'(.*)\'", |m| Ok(Some(Box::new(MissingSprocketsFile{ name: m.get(1).unwrap().as_str().to_string(), content_type: m.get(2).unwrap().as_str().to_string()}))) ), regex_line_matcher!( r#"xdt-autogen: You must have "(.*)" installed. You can get if from"#, |m| Ok(Some(Box::new(MissingXfceDependency::new(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"autogen.sh: You must have GNU autoconf installed.", |_| Ok(Some(Box::new(MissingCommand("autoconf".to_string())))) ), regex_line_matcher!( r"\s*You must have (autoconf|automake|aclocal|libtool|libtoolize) installed to compile (.*)\.", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"It appears that Autotools is not correctly installed on this system.", |_| Ok(Some(Box::new(MissingCommand("autoconf".to_string())))) ), regex_line_matcher!( r"\*\*\* No autoreconf found \*\*\*", |_| Ok(Some(Box::new(MissingCommand("autoreconf".to_string())))) ), regex_line_matcher!(r"You need to install gnome-common module and make.*", |_| Ok(Some(Box::new(GnomeCommonMissing)))), regex_line_matcher!(r"You need to install the gnome-common module and make.*", |_| Ok(Some(Box::new(GnomeCommonMissing)))), regex_line_matcher!( r"You need to install gnome-common from the GNOME (git|CVS|SVN)", |_| Ok(Some(Box::new(GnomeCommonMissing))) ), regex_line_matcher!( r"automake: error: cannot open < (.*): No such file or directory", |m| Ok(Some(Box::new(MissingAutomakeInput::new(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"configure(|\.in|\.ac):[0-9]+: error: possibly undefined macro: (.*)", |m| Ok(Some(Box::new(MissingAutoconfMacro::new(m.get(2).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"configure.(in|ac):[0-9]+: error: macro (.*) is not defined; is a m4 file missing\?", |m| Ok(Some(Box::new(MissingAutoconfMacro::new(m.get(2).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"config.status: error: cannot find input file: `(.*)\'", |m| Ok(Some(Box::new(MissingConfigStatusInput::new(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"\*\*\*Error\*\*\*: You must have glib-gettext >= (.*) installed.*", |m| Ok(Some(Box::new(MissingGnomeCommonDependency::new("glib-gettext".to_string(), Some(m.get(1).unwrap().as_str().to_string()))))) ), regex_line_matcher!( r"ERROR: JAVA_HOME is set to an invalid directory: /usr/lib/jvm/default-java/", |_| Ok(Some(Box::new(MissingJVM))) ), regex_line_matcher!( r#"Error: The file "MANIFEST" is missing from this distribution\. The MANIFEST lists all files included in the distribution\."#, |_| Ok(Some(Box::new(MissingPerlManifest))) ), regex_line_matcher!( r"dh_installdocs: --link-doc not allowed between (.*) and (.*) \(one is arch:all and the other not\)", |_| Ok(None) ), regex_line_matcher!( r"dh: unable to load addon systemd: dh: The systemd-sequence is no longer provided in compat >= 11, please rely on dh_installsystemd instead", |_| Ok(None) ), regex_line_matcher!( r"dh: The --before option is not supported any longer \(#932537\). Use override targets instead.", |_| Ok(None) ), regex_line_matcher!(r"\(.*\): undefined reference to `(.*)'", |_| Ok(None)), regex_line_matcher!("(.*):([0-9]+): undefined reference to `(.*)'", |_| Ok(None)), regex_line_matcher!("(.*):([0-9]+): error: undefined reference to '(.*)'", |_| Ok(None)), regex_line_matcher!( r"\/usr\/bin\/ld:(.*): multiple definition of `*.\'; (.*): first defined here", |_| Ok(None) ), regex_line_matcher!(r".+\.go:[0-9]+: undefined reference to `(.*)'", |_| Ok(None)), regex_line_matcher!(r"ar: libdeps specified more than once", |_| Ok(None)), regex_line_matcher!( r"\/usr\/bin\/ld: .*\(.*\):\(.*\): multiple definition of `*.\'; (.*):\((.*)\) first defined here", |_| Ok(None) ), regex_line_matcher!( r"\/usr\/bin\/ld:(.*): multiple definition of `*.\'; (.*):\((.*)\) first defined here", |_| Ok(None) ), regex_line_matcher!(r"\/usr\/bin\/ld: (.*): undefined reference to `(.*)\'", |_| Ok(None)), regex_line_matcher!(r"\/usr\/bin\/ld: (.*): undefined reference to symbol \'(.*)\'", |_| Ok(None)), regex_line_matcher!( r"\/usr\/bin\/ld: (.*): relocation (.*) against symbol `(.*)\' can not be used when making a shared object; recompile with -fPIC", |_| Ok(None) ), regex_line_matcher!( "(.*):([0-9]+): multiple definition of `(.*)'; (.*):([0-9]+): first defined here", |_| Ok(None) ), regex_line_matcher!( "(dh.*): debhelper compat level specified both in debian/compat and via build-dependency on debhelper-compat", |m| Ok(Some(Box::new(DuplicateDHCompatLevel::new(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( "(dh.*): (error: )?Please specify the compatibility level in debian/compat", |m| Ok(Some(Box::new(MissingDHCompatLevel::new(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( "dh_makeshlibs: The udeb (.*) does not contain any shared libraries but --add-udeb=(.*) was passed!?", |_| Ok(None) ), regex_line_matcher!( "dpkg-gensymbols: error: some symbols or patterns disappeared in the symbols file: see diff output below", |_| Ok(Some(Box::new(DisappearedSymbols))) ), regex_line_matcher!( r"Failed to copy \'(.*)\': No such file or directory at /usr/share/dh-exec/dh-exec-install-rename line [0-9]+.*", file_not_found ), regex_line_matcher!(r"Invalid gemspec in \[.*\]: No such file or directory - (.*)", command_missing), regex_line_matcher!( r".*meson.build:[0-9]+:[0-9]+: ERROR: Program\(s\) \[\'(.*)\'\] not found or not executable", command_missing ), regex_line_matcher!( r".*meson.build:[0-9]+:[0-9]: ERROR: Git program not found\.", |_| Ok(Some(Box::new(MissingCommand("git".to_string())))) ), regex_line_matcher!( r"Failed: [pytest] section in setup.cfg files is no longer supported, change to [tool:pytest] instead.", |_| Ok(None) ), regex_line_matcher!(r"cp: cannot stat \'(.*)\': No such file or directory", file_not_found), regex_line_matcher!(r"cp: \'(.*)\' and \'(.*)\' are the same file", |_| Ok(None)), regex_line_matcher!(r".?PHP Fatal error: (.*)", |_| Ok(None)), regex_line_matcher!(r"sed: no input files", |_| Ok(None)), regex_line_matcher!(r"sed: can\'t read (.*): No such file or directory", file_not_found), regex_line_matcher!( r"ERROR in Entry module not found: Error: Can\'t resolve \'(.*)\' in \'(.*)\'", webpack_file_missing ), regex_line_matcher!( r".*:([0-9]+): element include: XInclude error : could not load (.*), and no fallback was found", |_| Ok(None) ), regex_line_matcher!(r"E: Child terminated by signal ‘Terminated’", |_| Ok(Some(Box::new(Cancelled))) ), regex_line_matcher!(r"E: Caught signal ‘Terminated’", |_| Ok(Some(Box::new(Cancelled))) ), regex_line_matcher!(r"E: Failed to execute “(.*)”: No such file or directory", command_missing), regex_line_matcher!(r"E ImportError: Bad (.*) executable(\.?)", command_missing), regex_line_matcher!(r"E: The Debian version .* cannot be used as an ELPA version.", |_| Ok(None)), // ImageMagick regex_line_matcher!( r"convert convert: Image pixel limit exceeded \(see -limit Pixels\) \(-1\).", |_| Ok(None) ), regex_line_matcher!(r"convert convert: Improper image header \(.*\).", |_| Ok(None)), regex_line_matcher!(r"convert convert: invalid primitive argument \([0-9]+\).", |_| Ok(None)), regex_line_matcher!(r"convert convert: Unexpected end-of-file \(\)\.", |_| Ok(None)), regex_line_matcher!(r"convert convert: Unrecognized option \((.*)\)\.", |_| Ok(None)), regex_line_matcher!(r"convert convert: Unrecognized channel type \((.*)\)\.", |_| Ok(None)), regex_line_matcher!( r"convert convert: Unable to read font \((.*)\) \[No such file or directory\].", file_not_found ), regex_line_matcher!( r"convert convert: Unable to open file (.*) \[No such file or directory\]\.", file_not_found ), regex_line_matcher!( r"convert convert: No encode delegate for this image format \((.*)\) \[No such file or directory\].", |m| Ok(Some(Box::new(ImageMagickDelegateMissing::new(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!(r"ERROR: Sphinx requires at least Python (.*) to run.", |_| Ok(None)), regex_line_matcher!(r"Can\'t find (.*) directory in (.*)", |_| Ok(None)), regex_line_matcher!( r"/bin/sh: [0-9]: cannot create (.*): Directory nonexistent", |m| Ok(Some(Box::new(DirectoryNonExistant(std::path::Path::new(m.get(1).unwrap().as_str()).to_path_buf().parent().unwrap().display().to_string())))) ), regex_line_matcher!(r"dh: Unknown sequence (.*) \(choose from: .*\)", |_| Ok(None)), regex_line_matcher!(r".*\.vala:[0-9]+\.[0-9]+-[0-9]+.[0-9]+: error: (.*)", |_| Ok(None)), regex_line_matcher!( r"error: Package `(.*)\' not found in specified Vala API directories or GObject-Introspection GIR directories", |m| Ok(Some(Box::new(MissingValaPackage(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!(r".*.scala:[0-9]+: error: (.*)", |_| Ok(None)), // JavaScript regex_line_matcher!(r"error TS6053: File \'(.*)\' not found.", file_not_found), // Mocha regex_line_matcher!(r"Error \[ERR_MODULE_NOT_FOUND\]: Cannot find package '(.*)' imported from (.*)", |m| Ok(Some(Box::new(MissingNodePackage(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r"\s*Uncaught Error \[ERR_MODULE_NOT_FOUND\]: Cannot find package '(.*)' imported from (.*)", |m| Ok(Some(Box::new(MissingNodePackage(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r"(.*\.ts)\([0-9]+,[0-9]+\): error TS[0-9]+: (.*)", |_| Ok(None)), regex_line_matcher!(r"(.*.nim)\([0-9]+, [0-9]+\) Error: .*", |_| Ok(None)), regex_line_matcher!( r"dh_installinit: upstart jobs are no longer supported\! Please remove (.*) and check if you need to add a conffile removal", |m| Ok(Some(Box::new(UpstartFilePresent(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"dh_installinit: --no-restart-on-upgrade has been renamed to --no-stop-on-upgrade", |_| Ok(None) ), regex_line_matcher!(r"find: paths must precede expression: .*", |_| Ok(None)), regex_line_matcher!(r"find: ‘(.*)’: No such file or directory", file_not_found), regex_line_matcher!(r"ninja: fatal: posix_spawn: Argument list too long", |_| Ok(None)), regex_line_matcher!("ninja: fatal: chdir to '(.*)' - No such file or directory", |m| Ok(Some(Box::new(DirectoryNonExistant(m.get(1).unwrap().as_str().to_string()))))), // Java regex_line_matcher!(r"error: Source option [0-9] is no longer supported. Use [0-9] or later.", |_| Ok(None)), regex_line_matcher!( r"(dh.*|jh_build): -s/--same-arch has been removed; please use -a/--arch instead", |_| Ok(None) ), regex_line_matcher!( r"dh_systemd_start: dh_systemd_start is no longer used in compat >= 11, please use dh_installsystemd instead", |_| Ok(None) ), regex_line_matcher!(r"Trying patch (.*) at level 1 \.\.\. 0 \.\.\. 2 \.\.\. failure.", |_| Ok(None)), // QMake regex_line_matcher!(r"Project ERROR: (.*) development package not found", pkg_config_missing), regex_line_matcher!(r"Package \'(.*)\', required by \'(.*)\', not found\n", pkg_config_missing), regex_line_matcher!(r"pkg-config cannot find (.*)", pkg_config_missing), regex_line_matcher!( r"configure: error: .* not found: Package dependency requirement \'([^\']+)\' could not be satisfied.", pkg_config_missing ), regex_line_matcher!( r"configure: error: (.*) is required to build documentation", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!(r".*:[0-9]+: (.*) does not exist.", file_not_found), // uglifyjs regex_line_matcher!(r"ERROR: can\'t read file: (.*)", file_not_found), regex_line_matcher!(r#"jh_build: Cannot find \(any matches for\) "(.*)" \(tried in .*\)"#, |_| Ok(None)), regex_line_matcher!( r"-- Package \'(.*)\', required by \'(.*)\', not found", |m| Ok(Some(Box::new(MissingPkgConfig::simple(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r".*.rb:[0-9]+:in `require_relative\': cannot load such file -- (.*) \(LoadError\)", |_| Ok(None) ), regex_line_matcher!( r":[0-9]+:in `require': cannot load such file -- (.*) \(LoadError\)", |m| Ok(Some(Box::new(MissingRubyFile::new(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r".*.rb:[0-9]+:in `require\': cannot load such file -- (.*) \(LoadError\)", |m| Ok(Some(Box::new(MissingRubyFile::new(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!(r"LoadError: cannot load such file -- (.*)", |m| Ok(Some(Box::new(MissingRubyFile::new(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r" cannot load such file -- (.*)", |m| Ok(Some(Box::new(MissingRubyFile::new(m.get(1).unwrap().as_str().to_string()))))), // TODO(jelmer): This is a fairly generic string; perhaps combine with other checks for ruby? regex_line_matcher!(r"File does not exist: ([a-z/]+)$", |m| Ok(Some(Box::new(MissingRubyFile::new(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!( r".*:[0-9]+:in `do_check_dependencies\': E: dependency resolution check requested but no working gemspec available \(RuntimeError\)", |_| Ok(None) ), regex_line_matcher!(r"rm: cannot remove \'(.*)\': Is a directory", |_| Ok(None)), regex_line_matcher!(r"rm: cannot remove \'(.*)\': No such file or directory", file_not_found), // Invalid option from Python regex_line_matcher!(r"error: option .* not recognized", |_| Ok(None)), // Invalid option from go regex_line_matcher!(r"flag provided but not defined: .*", |_| Ok(None)), regex_line_matcher!(r#"CMake Error: The source directory "(.*)" does not exist."#, |m| Ok(Some(Box::new(DirectoryNonExistant(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r".*: [0-9]+: cd: can\'t cd to (.*)", |m| Ok(Some(Box::new(DirectoryNonExistant(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r"/bin/sh: 0: Can\'t open (.*)", |m| file_not_found_maybe_executable(m.get(1).unwrap().as_str())), regex_line_matcher!(r"/bin/sh: [0-9]+: cannot open (.*): No such file", |m| file_not_found_maybe_executable(m.get(1).unwrap().as_str())), regex_line_matcher!(r".*: line [0-9]+: (.*): No such file or directory", |m| file_not_found_maybe_executable(m.get(1).unwrap().as_str())), regex_line_matcher!(r"/bin/sh: [0-9]+: Syntax error: .*", |_| Ok(None)), regex_line_matcher!(r"error: No member named \$memberName", |_| Ok(None)), regex_line_matcher!( r"(?:/usr/bin/)?install: cannot create regular file \'(.*)\': Permission denied", |_| Ok(None) ), regex_line_matcher!(r"(?:/usr/bin/)?install: cannot create directory .(.*).: File exists", |_| Ok(None)), regex_line_matcher!(r"/usr/bin/install: missing destination file operand after .*", |_| Ok(None)), // Ruby regex_line_matcher!(r"rspec .*\.rb:[0-9]+ # (.*)", |_| Ok(None)), // help2man regex_line_matcher!(r"Addendum (.*) does NOT apply to (.*) \(translation discarded\).", |_| Ok(None)), regex_line_matcher!( r"dh_installchangelogs: copy\((.*), (.*)\): No such file or directory", file_not_found ), regex_line_matcher!(r"dh_installman: mv (.*) (.*): No such file or directory", file_not_found), regex_line_matcher!(r"dh_installman: Could not determine section for (.*)", |_| Ok(None)), regex_line_matcher!( r"failed to initialize build cache at (.*): mkdir (.*): permission denied", |_| Ok(None) ), regex_line_matcher!( r#"Can't exec "(.*)": No such file or directory at (.*) line ([0-9]+)."#, command_missing ), regex_line_matcher!( r#"E OSError: No command "(.*)" found on host .*"#, command_missing ), // PHPUnit regex_line_matcher!(r#"Cannot open file "(.*)"."#, file_not_found), regex_line_matcher!( r".*Could not find a JavaScript runtime\. See https://github.com/rails/execjs for a list of available runtimes\..*", |_| Ok(Some(Box::new(MissingJavaScriptRuntime))) ), Box::new(PythonFileNotFoundErrorMatcher), // ruby regex_line_matcher!(r"Errno::ENOENT: No such file or directory - (.*)", file_not_found), regex_line_matcher!(r"(.*.rb):[0-9]+:in `.*\': .* \(.*\) ", |_| Ok(None)), // JavaScript regex_line_matcher!(r".*: ENOENT: no such file or directory, open \'(.*)\'", file_not_found), regex_line_matcher!(r"\[Error: ENOENT: no such file or directory, stat \'(.*)\'\] \{", file_not_found), regex_line_matcher!( r"(.*):[0-9]+: error: Libtool library used but \'LIBTOOL\' is undefined", |_| Ok(Some(Box::new(MissingLibtool))) ), // libtoolize regex_line_matcher!(r"libtoolize: error: \'(.*)\' does not exist.", file_not_found), // Seen in python-cogent regex_line_matcher!( "(OSError|RuntimeError): (.*) required but not found.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(2).unwrap().as_str())))) ), regex_line_matcher!( r"RuntimeError: The (.*) executable cannot be found\. Please check if it is in the system path\.", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_lowercase())))) ), regex_line_matcher!( r".*: [0-9]+: cannot open (.*): No such file", file_not_found ), regex_line_matcher!( r"Cannot find Git. Git is required for .*", |_| Ok(Some(Box::new(MissingCommand("git".to_string())))) ), regex_line_matcher!( r"E ImportError: Bad (.*) executable\.", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( "RuntimeError: (.*) is missing", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"(OSError|RuntimeError): Could not find (.*) library\..*", |m| Ok(Some(Box::new(MissingLibrary(m.get(2).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"(OSError|RuntimeError): We need package (.*), but not importable", |m| Ok(Some(Box::new(MissingPythonDistribution{ distribution: m.get(2).unwrap().as_str().to_string(), minimum_version: None, python_version: None }))) ), regex_line_matcher!( r"(OSError|RuntimeError): No (.*) was found: .*", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(2).unwrap().as_str())))) ), regex_line_matcher!( r"(.*)meson.build:[0-9]+:[0-9]+: ERROR: Meson version is (.+) but project requires >=\s*(.+)", |m| Ok(Some(Box::new(MissingVagueDependency{ name: "meson".to_string(), url: None, minimum_version: Some(m.get(3).unwrap().as_str().trim_end_matches('.').to_string()), current_version: Some(m.get(2).unwrap().as_str().to_string())} ))) ), // Seen in cpl-plugin-giraf regex_line_matcher!( r"ImportError: Numpy version (.*) or later must be installed to use .*", |m| Ok(Some(Box::new(MissingPythonModule{ module: "numpy".to_string(), python_version: None, minimum_version: Some(m.get(1).unwrap().as_str().to_string())}))) ), // Seen in mayavi2 regex_line_matcher!(r"\w+Numpy is required to build.*", |_| Ok(Some(Box::new(MissingPythonModule::simple("numpy".to_string()))))), // autoconf regex_line_matcher!(r"configure.ac:[0-9]+: error: required file \'(.*)\' not found", file_not_found), regex_line_matcher!(r"/usr/bin/m4:(.*):([0-9]+): cannot open `(.*)\': No such file or directory", |m| Ok(Some(Box::new(MissingFile{path: std::path::PathBuf::from(m.get(3).unwrap().as_str().to_string())})))), // automake regex_line_matcher!(r"Makefile.am: error: required file \'(.*)\' not found", file_not_found), // sphinx regex_line_matcher!(r"config directory doesn\'t contain a conf.py file \((.*)\)", |_| Ok(None)), // vcversioner regex_line_matcher!( r"vcversioner: no VCS could be detected in \'/<>\' and \'/<>/version.txt\' isn\'t present.", |_| Ok(None) ), // rst2html (and other Python?) regex_line_matcher!(r" InputError: \[Errno 2\] No such file or directory: \'(.*)\'", file_not_found), // gpg regex_line_matcher!(r"gpg: can\'t connect to the agent: File name too long", |_| Ok(None)), regex_line_matcher!(r"(.*.lua):[0-9]+: assertion failed", |_| Ok(None)), regex_line_matcher!(r"\s+\^\-\-\-\-\^ SC[0-4][0-9][0-9][0-9]: .*", |_| Ok(None)), regex_line_matcher!( r"Error: (.*) needs updating from (.*)\. Run \'pg_buildext updatecontrol\'.", |m| Ok(Some(Box::new(NeedPgBuildExtUpdateControl::new(m.get(1).unwrap().as_str().to_string(), m.get(2).unwrap().as_str().to_string())))) ), regex_line_matcher!(r"Patch (.*) does not apply \(enforce with -f\)", |m| Ok(Some(Box::new(PatchApplicationFailed::new(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!( r"java.io.FileNotFoundException: ([^ ]+) \(No such file or directory\)", file_not_found ), // Pytest regex_line_matcher!(r"INTERNALERROR> PluginValidationError: (.*)", |_| Ok(None)), regex_line_matcher!(r"[0-9]+ out of [0-9]+ hunks FAILED -- saving rejects to file (.*\.rej)", |_| Ok(None)), regex_line_matcher!(r"pkg_resources.UnknownExtra: (.*) has no such extra feature \'(.*)\'", |_| Ok(None)), regex_line_matcher!( r"dh_auto_configure: invalid or non-existing path to the source directory: .*", |_| Ok(None) ), // Sphinx regex_line_matcher!( r"(.*) is no longer a hard dependency since version (.*). Please install it manually.\(pip install (.*)\)", |m| Ok(Some(Box::new(MissingPythonModule::simple(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!(r"There is a syntax error in your configuration file: (.*)", |_| Ok(None)), regex_line_matcher!( r"E: The Debian version (.*) cannot be used as an ELPA version.", |m| Ok(Some(Box::new(DebianVersionRejected::new(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!(r#""(.*)" is not exported by the ExtUtils::MakeMaker module"#, |_| Ok(None)), regex_line_matcher!( r"E: Please add appropriate interpreter package to Build-Depends, see pybuild\(1\) for details\..*", |_| Ok(Some(Box::new(DhAddonLoadFailure::new("pybuild".to_string(), "Debian/Debhelper/Buildsystem/pybuild.pm".to_string())))) ), regex_line_matcher!(r"dpkg: error: .*: No space left on device", |_| Ok(Some(Box::new(NoSpaceOnDevice)))), regex_line_matcher!( r"You need the GNU readline library\(ftp://ftp.gnu.org/gnu/readline/\s+\) to build", |_| Ok(Some(Box::new(MissingLibrary("readline".to_string())))) ), regex_line_matcher!( r"configure: error: Could not find lib(.*)", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r" Could not find module ‘(.*)’", |m| Ok(Some(Box::new(MissingHaskellModule::new(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!(r"E: session: (.*): Chroot not found", |m| Ok(Some(Box::new(ChrootNotFound::new(m.get(1).unwrap().as_str().to_string()))))), Box::new(HaskellMissingDependencyMatcher), Box::new(SetupPyCommandMissingMatcher), Box::new(CMakeErrorMatcher), regex_line_matcher! ( r"error: failed to select a version for the requirement `(.*)`", |m| { let (crate_name, requirement) = match m.get(1).unwrap().as_str().split_once(" ") { Some((cratename, requirement)) => (cratename.to_string(), Some(requirement.to_string())), None => (m.get(1).unwrap().as_str().to_string(), None), }; Ok(Some(Box::new(MissingCargoCrate { crate_name, requirement, })) ) } ), regex_line_matcher!(r"^Environment variable \$SOURCE_DATE_EPOCH: No digits were found: $"), regex_line_matcher!( r"\[ERROR\] LazyFont - Failed to read font file (.*) \java.io.FileNotFoundException: (.*) \(No such file or directory\)", |m| Ok(Some(Box::new(MissingFile::new(m.get(1).unwrap().as_str().into())))) ), regex_line_matcher!(r"qt.qpa.xcb: could not connect to display", |_m| Ok(Some(Box::new(MissingXDisplay)))), regex_line_matcher!( r"\(.*:[0-9]+\): Gtk-WARNING \*\*: [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}: cannot open display: ", |_m| Ok(Some(Box::new(MissingXDisplay))) ), regex_line_matcher!( r"\s*Package (.*) was not found in the pkg-config search path.", |m| Ok(Some(Box::new(MissingPkgConfig::simple(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"Can't open display", |_m| Ok(Some(Box::new(MissingXDisplay))) ), regex_line_matcher!( r"Can't open (.+): No such file or directory.*", file_not_found ), regex_line_matcher!( r"pkg-config does not know (.*) at .*\.", |m| Ok(Some(Box::new(MissingPkgConfig::simple(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"\*\*\* Please install (.*) \(atleast version (.*)\) or adjust", |m| Ok(Some(Box::new(MissingPkgConfig{ module: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(2).unwrap().as_str().to_string()) }))) ), regex_line_matcher!( r"go runtime is required: https://golang.org/doc/install", |_m| Ok(Some(Box::new(MissingGoRuntime))) ), regex_line_matcher!( r"\%Error: '(.*)' must be installed to build", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r#"configure: error: "Could not find (.*) in PATH"#, |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!(r"Could not find executable (.*)", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!( r#"go: .*: Get \"(.*)\": x509: certificate signed by unknown authority"#, |m| Ok(Some(Box::new(UnknownCertificateAuthority(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r#".*.go:[0-9]+:[0-9]+: .*: Get \"(.*)\": x509: certificate signed by unknown authority"#, |m| Ok(Some(Box::new(UnknownCertificateAuthority(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"fatal: unable to access '(.*)': server certificate verification failed. CAfile: none CRLfile: none", |m| Ok(Some(Box::new(UnknownCertificateAuthority(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"curl: \(77\) error setting certificate verify locations: CAfile: (.*) CApath: (.*)", |m| Ok(Some(Box::new(MissingFile::new(m.get(1).unwrap().as_str().to_string().into())))) ), regex_line_matcher!( r"\t\(Do you need to predeclare (.*)\?\)", |m| Ok(Some(Box::new(MissingPerlPredeclared(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r#"Bareword \"(.*)\" not allowed while \"strict subs\" in use at Makefile.PL line ([0-9]+)."#, |m| Ok(Some(Box::new(MissingPerlPredeclared(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r#"String found where operator expected at Makefile.PL line ([0-9]+), near "([a-z0-9_]+).*""#, |m| Ok(Some(Box::new(MissingPerlPredeclared(m.get(2).unwrap().as_str().to_string())))) ), regex_line_matcher!(r" vignette builder 'knitr' not found", |_| Ok(Some(Box::new(MissingRPackage::simple("knitr"))))), regex_line_matcher!( r"fatal: unable to auto-detect email address \(got \'.*\'\)", |_m| Ok(Some(Box::new(MissingGitIdentity))) ), regex_line_matcher!( r"E fatal: unable to auto-detect email address \(got \'.*\'\)", |_m| Ok(Some(Box::new(MissingGitIdentity))) ), regex_line_matcher!(r"gpg: no default secret key: No secret key", |_m| Ok(Some(Box::new(MissingSecretGpgKey)))), regex_line_matcher!( r"ERROR: FAILED--Further testing stopped: Test requires module \'(.*)\' but it\'s not found", |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r#"(subprocess.CalledProcessError|error): Command \'\[\'/usr/bin/python([0-9.]*)\', \'-m\', \'pip\', \'--disable-pip-version-check\', \'wheel\', \'--no-deps\', \'-w\', .*, \'([^-][^\']+)\'\]\' returned non-zero exit status 1."#, |m| { let python_version = m.get(2).filter(|x| !x.is_empty()).map(|pv| pv.as_str().split_once('.').map_or(pv.as_str(), |x| x.0).parse().unwrap()); Ok(Some(Box::new(MissingPythonDistribution::from_requirement_str( m.get(3).unwrap().as_str(), python_version )))) } ), regex_line_matcher!( r"vcversioner: \[\'git\', .*, \'describe\', \'--tags\', \'--long\'\] failed and \'(.*)/version.txt\' isn\'t present\.", |_m| Ok(Some(Box::new(MissingVcVersionerVersion))) ), regex_line_matcher!( r"vcversioner: no VCS could be detected in '(.*)' and '(.*)/version.txt' isn't present\.", |_m| Ok(Some(Box::new(MissingVcVersionerVersion))) ), regex_line_matcher!( r"You don't have a working TeX binary \(tex\) installed anywhere in", |_m| Ok(Some(Box::new(MissingCommand("tex".to_string())))) ), regex_line_matcher!( r"# Module \'(.*)\' is not installed", |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r#"Base class package "(.*)" is empty."#, |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r" \! (.*::.*) is not installed", |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"Cannot find (.*) in @INC at (.*) line ([0-9]+)\.", |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"(.*::.*) (.*) is required to configure our .* dependency, please install it manually or upgrade your CPAN/CPANPLUS", |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"configure: error: Missing lib(.*)\.", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"OSError: (.*): cannot open shared object file: No such file or directory", |m| Ok(Some(Box::new(MissingFile::new(m.get(1).unwrap().as_str().into())))) ), regex_line_matcher!( r#"The "(.*)" executable has not been found\."#, |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r" '\! LaTeX Error: File `(.*)' not found.'", |m| Ok(Some(Box::new(MissingLatexFile(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"\! LaTeX Error: File `(.*)\' not found\.", |m| Ok(Some(Box::new(MissingLatexFile(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r#"(\!|.*:[0-9]+:) Package fontspec Error: The font \"(.*)\" cannot be found\."#, |m| Ok(Some(Box::new(MissingFontspec(m.get(2).unwrap().as_str().to_string())))) ), regex_line_matcher!(r" vignette builder \'(.*)\' not found", |m| Ok(Some(Box::new(MissingRPackage::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"Error: package [‘'](.*)[’'] (.*) was found, but >= (.*) is required by [‘'](.*)[’']", |m| Ok(Some(Box::new(MissingRPackage { package: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(3).unwrap().as_str().to_string()), }))) ), regex_line_matcher!(r"\s*there is no package called \'(.*)\'", |m| Ok(Some(Box::new(MissingRPackage::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"Error in .*: there is no package called ‘(.*)’", |m| Ok(Some(Box::new(MissingRPackage::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"Exception: cannot execute command due to missing interpreter: (.*)", command_missing ), regex_line_matcher!( r"E: Build killed with signal TERM after ([0-9]+) minutes of inactivity", |m| Ok(Some(Box::new(InactiveKilled(m.get(1).unwrap().as_str().parse().unwrap())))) ), regex_line_matcher!( r#"\[.*Authority\] PAUSE credentials not found in "config.ini" or "dist.ini" or "~/.pause"\! Please set it or specify an authority for this plugin. at inline delegation in Dist::Zilla::Plugin::Authority for logger->log_fatal \(attribute declared in /usr/share/perl5/Dist/Zilla/Role/Plugin.pm at line [0-9]+\) line [0-9]+\."#, |_m| Ok(Some(Box::new(MissingPauseCredentials))) ), regex_line_matcher!( r"npm ERR\! ERROR: \[Errno 2\] No such file or directory: \'(.*)\'", file_not_found ), regex_line_matcher!( r"\*\*\* error: gettext infrastructure mismatch: using a Makefile\.in\.in from gettext version ([0-9.]+) but the autoconf macros are from gettext version ([0-9.]+)", |m| Ok(Some(Box::new(MismatchGettextVersions{ makefile_version: m.get(1).unwrap().as_str().to_string(), autoconf_version: m.get(2).unwrap().as_str().to_string(), }))) ), regex_line_matcher!( r"You need to install the (.*) package to use this program\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!(r"You need to install (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"configure: error: You don't seem to have the (.*) library installed\..*", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"configure: error: You need (.*) installed", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"open3: exec of cme (.*) failed: No such file or directory at .*/Dist/Zilla/Plugin/Run/Role/Runner.pm line [0-9]+\.", |m| Ok(Some(Box::new(MissingPerlModule::simple(&format!("App::Cme::Command::{}", m.get(1).unwrap().as_str()))))) ), regex_line_matcher!( r"pg_ctl: cannot be run as (.*)", |m| Ok(Some(Box::new(InvalidCurrentUser(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"([^ ]+) \(for section ([^ ]+)\) does not appear to be installed", |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"(.*) version (.*) required--this is only version (.*) at .*\.pm line [0-9]+\.", |m| Ok(Some(Box::new(MissingPerlModule { module: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(2).unwrap().as_str().to_string()), inc: None, filename: None, }))) ), regex_line_matcher!( r"Bailout called\. Further testing stopped: YOU ARE MISSING REQUIRED MODULES: \[ ([^,]+)(.*) \]:", |m| Ok(Some(Box::new(MissingPerlModule::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r#"CMake Error: CMake was unable to find a build program corresponding to "(.*)". CMAKE_MAKE_PROGRAM is not set\. You probably need to select a different build tool\."#, |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"Dist currently only works with Git or Mercurial repos", |_| Ok(Some(Box::new(VcsControlDirectoryNeeded::new(vec!["git", "hg"])))) ), regex_line_matcher!( r"GitHubMeta: need a .git\/config file, and you don\'t have one", |_| Ok(Some(Box::new(VcsControlDirectoryNeeded::new(vec!["git"])))) ), regex_line_matcher!( r"Exception: Versioning for this project requires either an sdist tarball, or access to an upstream git repository\. It's also possible that there is a mismatch between the package name in setup.cfg and the argument given to pbr\.version\.VersionInfo\. Project name .* was given, but was not able to be found\.", |_| Ok(Some(Box::new(VcsControlDirectoryNeeded::new(vec!["git"])))) ), regex_line_matcher!( r"configure: error: no suitable Python interpreter found", |_| Ok(Some(Box::new(MissingCommand("python".to_string())))) ), regex_line_matcher!(r#"Could not find external command "(.*)""#, |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!( r" Failed to find (.*) development headers\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"\*\*\* \Subdirectory \'(.*)\' does not yet exist. Use \'./gitsub.sh pull\' to create it, or set the environment variable GNULIB_SRCDIR\.", |m| Ok(Some(Box::new(MissingGnulibDirectory(m.get(1).unwrap().as_str().into())))) ), regex_line_matcher!( r"configure: error: Cap\'n Proto compiler \(capnp\) not found.", |_| Ok(Some(Box::new(MissingCommand("capnp".to_string())))) ), regex_line_matcher!( r"lua: (.*):(\d+): module \'(.*)\' not found:", |m| Ok(Some(Box::new(MissingLuaModule(m.get(3).unwrap().as_str().to_string())))) ), regex_line_matcher!(r"Unknown key\(s\) in sphinx_gallery_conf:"), regex_line_matcher!(r"(.+\.gir):In (.*): error: (.*)"), regex_line_matcher!(r"(.+\.gir):[0-9]+\.[0-9]+-[0-9]+\.[0-9]+: error: (.*)"), regex_line_matcher!(r"psql:.*\.sql:[0-9]+: ERROR: (.*)"), regex_line_matcher!(r"intltoolize: \'(.*)\' is out of date: use \'--force\' to overwrite"), regex_line_matcher!( r"E: pybuild pybuild:[0-9]+: cannot detect build system, please use --system option or set PYBUILD_SYSTEM env\. variable" ), regex_line_matcher!( r"-- Requested \'(.*) >= (.*)\' but version of (.*) is (.*)", |m| Ok(Some(Box::new(MissingPkgConfig{ module: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(2).unwrap().as_str().to_string()), }))) ), regex_line_matcher!( r".*Could not find (.*) lib/headers, please set .* or ensure (.*).pc is in PKG_CONFIG_PATH\.", |m| Ok(Some(Box::new(MissingPkgConfig::simple(m.get(2).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"go: go.mod file not found in current directory or any parent directory; see \'go help modules\'", |_| Ok(Some(Box::new(MissingGoModFile))) ), regex_line_matcher!( r"go: cannot find main module, but found Gopkg.lock in (.*)", |_| Ok(Some(Box::new(MissingGoModFile))) ), regex_line_matcher!(r"go: updates to go.mod needed; to update it:", |_| Ok(Some(Box::new(OutdatedGoModFile)))), regex_line_matcher!(r"(c\+\+|collect2|cc1|g\+\+): fatal error: .*"), regex_line_matcher!(r"fatal: making (.*): failed to create tests\/decode.trs"), // ocaml regex_line_matcher!(r"Please specify at most one of .*"), // Python lint regex_line_matcher!(r".*\.py:[0-9]+:[0-9]+: [A-Z][0-9][0-9][0-9] .*"), regex_line_matcher!( r#"PHPUnit requires the "(.*)" extension\."#, |m| Ok(Some(Box::new(MissingPHPExtension(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r#" \[exec\] PHPUnit requires the "(.*)" extension\."#, |m| Ok(Some(Box::new(MissingPHPExtension(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r".*/gnulib-tool: \*\*\* minimum supported autoconf version is (.*)\. ", |m| Ok(Some(Box::new(MinimumAutoconfTooOld(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"configure.(ac|in):[0-9]+: error: Autoconf version (.*) or higher is required", |m| Ok(Some(Box::new(MissingVagueDependency { name: "autoconf".to_string(), url: None, minimum_version: Some(m.get(2).unwrap().as_str().to_string()), current_version: None, }))) ), regex_line_matcher!( r#"# Error: The file "(MANIFEST|META.yml)" is missing from this distribution\\. .*"#, |m| Ok(Some(Box::new(MissingPerlDistributionFile(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!(r"^ ([^ ]+) does not exist$", file_not_found), regex_line_matcher!( r"\s*> Cannot find \'\.git\' directory", |_m| Ok(Some(Box::new(VcsControlDirectoryNeeded::new(vec!["git"])))) ), regex_line_matcher!( r"Unable to find the \'(.*)\' executable\. .*", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"\[@RSRCHBOY\/CopyrightYearFromGit\] - 412 No \.git subdirectory found", |_m| Ok(Some(Box::new(VcsControlDirectoryNeeded::new(vec!["git"])))) ), regex_line_matcher!( r"Couldn\'t find version control data \(git/hg/bzr/svn supported\)", |_m| Ok(Some(Box::new(VcsControlDirectoryNeeded::new(vec!["git", "hg", "bzr", "svn"])))) ), regex_line_matcher!( r"RuntimeError: Unable to determine package version. No local Git clone detected, and no version file found at .*", |_m| Ok(Some(Box::new(VcsControlDirectoryNeeded::new(vec!["git"])))) ), regex_line_matcher!( r#""(.*)" failed to start: "No such file or directory" at .*.pm line [0-9]+\."#, |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!(r"Can\'t find ([^ ]+)\.", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r"Error: spawn (.*) ENOENT", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!( r"E ImportError: Failed to initialize: Bad (.*) executable\.", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r#"ESLint couldn\'t find the config "(.*)" to extend from\. Please check that the name of the config is correct\."# ), regex_line_matcher!( r#"E OSError: no library called "cairo-2" was found"#, |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"ERROR: \[Errno 2\] No such file or directory: '(.*)'", |m| file_not_found_maybe_executable(m.get(1).unwrap().as_str()) ), regex_line_matcher!( r"error: \[Errno 2\] No such file or directory: '(.*)'", |m| file_not_found_maybe_executable(m.get(1).unwrap().as_str()) ), regex_line_matcher!( r"We need the Python library (.+) to be installed\. .*", |m| Ok(Some(Box::new(MissingPythonDistribution::simple(m.get(1).unwrap().as_str())))) ), // Waf regex_line_matcher!( r"Checking for header (.+\.h|.+\.hpp)\s+: not found ", |m| Ok(Some(Box::new(MissingCHeader::new(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"000: File does not exist (.*)", file_not_found ), regex_line_matcher!( r"ERROR: Coverage for lines \(([0-9.]+)%\) does not meet global threshold \(([0-9]+)%\)", |m| Ok(Some(Box::new(CodeCoverageTooLow{ actual: m.get(1).unwrap().as_str().parse().unwrap(), required: m.get(2).unwrap().as_str().parse().unwrap()}))) ), regex_line_matcher!( r"Error \[ERR_REQUIRE_ESM\]: Must use import to load ES Module: (.*)", |m| Ok(Some(Box::new(ESModuleMustUseImport(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!(r".* (/<>/.*): No such file or directory", file_not_found), regex_line_matcher!( r"Cannot open file `(.*)' in mode `(.*)' \(No such file or directory\)", file_not_found ), regex_line_matcher!(r"[^:]+: cannot stat \'(.*)\': No such file or directory", file_not_found), regex_line_matcher!(r"cat: (.*): No such file or directory", file_not_found), regex_line_matcher!(r"ls: cannot access \'(.*)\': No such file or directory", file_not_found), regex_line_matcher!( r"Problem opening (.*): No such file or directory at (.*) line ([0-9]+)\.", file_not_found ), regex_line_matcher!(r"/bin/bash: (.*): No such file or directory", file_not_found), regex_line_matcher!( r#"\(The package "(.*)" was not found when loaded as a Node module from the directory ".*"\.\)"#, |m| Ok(Some(Box::new(MissingNodePackage(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!(r"\+\-\- UNMET DEPENDENCY (.*)", |m| Ok(Some(Box::new(MissingNodePackage(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!( r"Project ERROR: Unknown module\(s\) in QT: (.*)", |m| Ok(Some(Box::new(MissingQtModules(m.get(1).unwrap().as_str().split_whitespace().map(|s| s.to_string()).collect())))) ), regex_line_matcher!( r"(.*):(\d+):(\d+): ERROR: Vala compiler \'.*\' can not compile programs", |_| Ok(Some(Box::new(ValaCompilerCannotCompile))) ), regex_line_matcher!( r"(.*):(\d+):(\d+): ERROR: Problem encountered: Cannot load ([^ ]+) library\. (.*)", |m| Ok(Some(Box::new(MissingLibrary(m.get(4).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"go: (.*)@(.*): missing go.sum entry; to add it:", |m| Ok(Some(Box::new(MissingGoSumEntry { package: m.get(1).unwrap().as_str().to_string(), version: m.get(2).unwrap().as_str().to_string(), }))) ), regex_line_matcher!( r"E: pybuild pybuild:(.*): configure: plugin (.*) failed with: PEP517 plugin dependencies are not available\. Please Build-Depend on (.*)\.", |m| Ok(Some(Box::new(MissingDebianBuildDep(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"^make\[[0-9]+\]: \*\*\* No rule to make target '(.*)', needed by '(.*)'\. Stop\.$", |m| Ok(Some(Box::new(MissingMakeTarget::new(m.get(1).unwrap().as_str(), Some(m.get(2).unwrap().as_str()))))) ), regex_line_matcher!(r#"make: \*\*\* No rule to make target \'(.*)\'\. Stop\."#, |m| Ok(Some(Box::new(MissingMakeTarget::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"make\[[0-9]+\]: \*\*\* No rule to make target \'(.*)\'\. Stop\.", |m| Ok(Some(Box::new(MissingMakeTarget::simple(m.get(1).unwrap().as_str()))))), // ADD NEW REGEXES ABOVE THIS LINE regex_line_matcher!( r#"configure: error: Can not find "(.*)" .* in your PATH"#, |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), // Intentionally at the bottom of the list. regex_line_matcher!( r"([^ ]+) package not found\. Please install from (https://[^ ]+)", |m| Ok(Some(Box::new(MissingVagueDependency {name:m.get(1).unwrap().as_str().to_string(),url:Some(m.get(2).unwrap().as_str().to_string()), minimum_version: None, current_version: None }))) ), regex_line_matcher!( r"([^ ]+) package not found\. Please use \'pip install .*\' first", |m| Ok(Some(Box::new(MissingPythonDistribution::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!(r".*: No space left on device", |_m| Ok(Some(Box::new(NoSpaceOnDevice)))), regex_line_matcher!(r".*(No space left on device).*", |_m| Ok(Some(Box::new(NoSpaceOnDevice)))), regex_line_matcher!( r"ocamlfind: Package `(.*)\' not found", |m| Ok(Some(Box::new(MissingOCamlPackage(m.get(1).unwrap().as_str().to_string())))) ), // Not a very unique ocaml-specific pattern :( regex_line_matcher!(r#"Error: Library "(.*)" not found."#, |m| Ok(Some(Box::new(MissingOCamlPackage(m.get(1).unwrap().as_str().to_string()))))), // ADD NEW REGEXES ABOVE THIS LINE // Intentionally at the bottom of the list, since they're quite broad. regex_line_matcher!( r"configure: error: ([^ ]+) development files not found", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"Exception: ([^ ]+) development files not found\..*", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"Exception: Couldn\'t find (.*) source libs\!", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( "configure: error: '(.*)' command was not found", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"configure: error: (.*) not present.*", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"configure: error: (.*) >= (.*) not found", |m| Ok(Some(Box::new(MissingVagueDependency { name: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(2).unwrap().as_str().to_string()), url: None, current_version: None }))) ), regex_line_matcher!( r"configure: error: (.*) headers (could )?not (be )?found", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"configure: error: (.*) ([0-9].*) (could )?not (be )?found", |m| Ok(Some(Box::new(MissingVagueDependency { name: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(2).unwrap().as_str().to_string()), url: None, current_version: None }))) ), regex_line_matcher!( r"configure: error: (.*) (could )?not (be )?found", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"configure: error: (.*) ([0-9.]+) is required to build.*", |m| Ok(Some(Box::new(MissingVagueDependency {name:m.get(1).unwrap().as_str().to_string(),minimum_version:Some(m.get(2).unwrap().as_str().to_string()),url:None, current_version: None }))) ), regex_line_matcher!( ".*meson.build:([0-9]+):([0-9]+): ERROR: Problem encountered: (.*) (.*) or later required", |m| Ok(Some(Box::new(MissingVagueDependency { name: m.get(3).unwrap().as_str().to_string(), minimum_version: Some(m.get(4).unwrap().as_str().to_string()), url: None, current_version: None }))) ), regex_line_matcher!( r"configure: error: Please install (.*) from (http:\/\/[^ ]+)", |m| Ok(Some(Box::new(MissingVagueDependency { name: m.get(1).unwrap().as_str().to_string(), url: Some(m.get(2).unwrap().as_str().to_string()), minimum_version: None, current_version: None }))) ), regex_line_matcher!( r"configure: error: Required package (.*) (is ?)not available\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"Error\! You need to have (.*) \((.*)\) around.", |m| Ok(Some(Box::new(MissingVagueDependency { name: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(2).unwrap().as_str().to_string()), url: None, current_version: None }))) ), regex_line_matcher!( r"configure: error: You don\'t have (.*) installed", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"configure: error: Could not find a recent version of (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"configure: error: Unable to locate (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"configure: error: Missing the (.* library)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"configure: error: (.*) requires (.* libraries), .*", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(2).unwrap().as_str())))) ), regex_line_matcher!( r"configure: error: (.*) requires ([^ ]+)\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(2).unwrap().as_str())))) ), regex_line_matcher!( r"(.*) cannot be discovered in ([^ ]+)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"configure: error: Missing required program '(.*)'.*", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"configure: error: Missing (.*)\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"configure: error: Unable to find (.*), please install (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(2).unwrap().as_str())))) ), regex_line_matcher!(r"configure: error: (.*) Not found", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"configure: error: You need to install (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"configure: error: (.*) \((.*)\) not found\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(2).unwrap().as_str())))) ), regex_line_matcher!( r"configure: error: (.*) libraries are required for compilation", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"configure: error: .*Make sure you have (.*) installed\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"error: Cannot find (.*) in the usual places. .*", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r#"Makefile:[0-9]+: \*\*\* "(.*) was not found"\. Stop\."#, |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r#"Makefile:[0-9]+: \*\*\* \"At least (.*) version (.*) is needed to build (.*)\.". Stop\."#, |m| Ok(Some(Box::new(MissingVagueDependency { name: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(2).unwrap().as_str().to_string()), url: None, current_version: None }))) ), regex_line_matcher!(r"([a-z0-9A-Z]+) not found", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!(r"ERROR: Unable to locate (.*)\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( "\x1b\\[1;31merror: (.*) not found\x1b\\[0;32m", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"You do not have (.*) correctly installed\. .*", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"Error: (.*) is not available on your system", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"ERROR: (.*) (.*) or later is required", |m| Ok(Some(Box::new(MissingVagueDependency { name: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(2).unwrap().as_str().to_string()), url: None, current_version: None }))) ), regex_line_matcher!( r"configure: error: .*Please install the \'(.*)\' package\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"Error: Please install ([^ ]+) package", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!(r"configure: error: <(.*\.h)> is required", |m| Ok(Some(Box::new(MissingCHeader::new(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!( r"configure: error: ([^ ]+) is required", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"configure: error: you should install ([^ ]+) first", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"configure: error: .*You need (.*) installed.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!(r"To build (.*) you need (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!(r".*Can\'t ([^\. ]+)\. (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"([^ ]+) >= (.*) is required", |m| Ok(Some(Box::new(MissingVagueDependency { name: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(1).unwrap().as_str().to_string()), current_version: None, url: None }))) ), regex_line_matcher!( r".*: ERROR: (.*) needs to be installed to run these tests", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!(r"ERROR: Unable to locate (.*)\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"ERROR: Cannot find command \'(.*)\' - do you have \'(.*)\' installed and in your PATH\?", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"ValueError: no ([^ ]+) installed, .*", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"This project needs (.*) in order to build\. .*", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!(r"ValueError: Unable to find (.+)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!(r"([^ ]+) executable not found\. .*", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!( r"ERROR: InvocationError for command could not find executable (.*)", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"E ImportError: Unable to find ([^ ]+) shared library", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"\s*([^ ]+) library not found on the system", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!(r"\s*([^ ]+) library not found(\.?)", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!( r".*Please install ([^ ]+) libraries\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"Error: Please install (.*) package", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"Please get ([^ ]+) from (www\..*)\.", |m| Ok(Some(Box::new(MissingVagueDependency { name: m.get(1).unwrap().as_str().to_string(), url: Some(m.get(2).unwrap().as_str().to_string()), minimum_version: None, current_version: None }))) ), regex_line_matcher!( r"Please install ([^ ]+) so that it is on the PATH and try again\.", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!( r"configure: error: No (.*) binary found in (.*)", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!(r"Could not find ([A-Za-z-]+)$", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"No ([^ ]+) includes and libraries found", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"Required library (.*) not found\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!(r"Missing ([^ ]+) boost library, .*", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!( r"configure: error: ([^ ]+) needed\!", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"\*\*\* (.*) not found, please install it \*\*\*", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"configure: error: could not find ([^ ]+)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"([^ ]+) is required for ([^ ]+)\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"configure: error: \*\*\* No ([^.])\! Install (.*) development headers/libraries! \*\*\*", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"configure: error: \'(.*)\' cannot be found", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"No (.*) includes and libraries found", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"\s*No (.*) version could be found in your system\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!(r"You need (.+)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_line_matcher!( r"configure: error: ([^ ]+) is needed", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"configure: error: Cannot find ([^ ]+)\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"configure: error: ([^ ]+) requested but not installed\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"We need the Python library (.+) to be installed\..*", |m| Ok(Some(Box::new(MissingPythonDistribution::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"(.*) uses (.*) \(.*\) for installation but (.*) was not found", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_line_matcher!( r"ERROR: could not locate the \'([^ ]+)\' utility", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_line_matcher!(r"Can\'t find (.*) libs. Exiting", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), ]); } lazy_static::lazy_static! { static ref CMAKE_ERROR_MATCHERS: MatcherGroup = MatcherGroup::new(vec![ regex_para_matcher!(r"Could NOT find (.*) \(missing:\s(.*)\)\s\(found\ssuitable\sversion\s.*", |m| Ok(Some(Box::new(MissingCMakeComponents{ name: m.get(1).unwrap().as_str().to_string(), components: m.get(2).unwrap().as_str().split_whitespace().map(|s| s.to_string()).collect()}))) ), regex_para_matcher!(r"\s*--\s+Package \'(.*)\', required by \'(.*)\', not found", |m| Ok(Some(Box::new(MissingPkgConfig::simple(m.get(1).unwrap().as_str().to_string())))) ), regex_para_matcher!(r#"Could not find a package configuration file provided by\s"(.*)" \(requested\sversion\s(.*)\)\swith\sany\s+of\s+the\s+following\snames:\n\n( .*\n)+\n.*$"#, |m| { let package = m.get(1).unwrap().as_str().to_string(); let version = m.get(2).unwrap().as_str().to_string(); let _names = m.get(3).unwrap().as_str().split_whitespace().map(|s| s.to_string()).collect::>(); Ok(Some(Box::new(MissingCMakeConfig{ name: package, version: Some(version), }))) } ), regex_para_matcher!( r"Could NOT find (.*) \(missing: (.*)\)", |m| { let name = m.get(1).unwrap().as_str().to_string(); let components = m.get(2).unwrap().as_str().split_whitespace().map(|s| s.to_string()).collect(); Ok(Some(Box::new(MissingCMakeComponents { name, components, }))) } ), regex_para_matcher!( r#"The (.+) compiler\n\n "(.*)"\n\nis not able to compile a simple test program\.\n\nIt fails with the following output:\n\n(.*)\n\nCMake will not be able to correctly generate this project.\n$"#, |m| { let compiler_output = textwrap::dedent(m.get(3).unwrap().as_str()); let (_match, error) = find_build_failure_description(compiler_output.split_inclusive('\n').collect()); Ok(error) } ), regex_para_matcher!( r#"Could NOT find (.*): Found unsuitable version \"(.*)\",\sbut\srequired\sis\sexact version \"(.*)\" \(found\s(.*)\)"#, |m| { let package = m.get(1).unwrap().as_str().to_string(); let version_found = m.get(2).unwrap().as_str().to_string(); let exact_version_needed = m.get(3).unwrap().as_str().to_string(); let path = m.get(4).unwrap().as_str().to_string(); Ok(Some(Box::new(CMakeNeedExactVersion { package, version_found, exact_version_needed, path: std::path::PathBuf::from(path), }))) } ), regex_para_matcher!( r"(.*) couldn't be found \(missing: .*_LIBRARIES .*_INCLUDE_DIR\)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_para_matcher!( r#"Could NOT find (.*): Found unsuitable version \"(.*)\",\sbut\srequired\sis\sat\sleast\s\"(.*)\" \(found\s(.*)\)"#, |m| Ok(Some(Box::new(MissingPkgConfig{ module: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(3).unwrap().as_str().to_string())}))) ), regex_para_matcher!( r#"The imported target \"(.*)\" references the file\n\n\s*"(.*)"\n\nbut this file does not exist\.(.*)"#, |m| Ok(Some(Box::new(MissingFile::new(m.get(2).unwrap().as_str().to_string().into())))) ), regex_para_matcher!( r#"Could not find a configuration file for package "(.*)"\sthat\sis\scompatible\swith\srequested\sversion\s"(.*)"\."#, |m| Ok(Some(Box::new(MissingCMakeConfig { name: m.get(1).unwrap().as_str().to_string(), version: Some(m.get(2).unwrap().as_str().to_string())}))) ), regex_para_matcher!( r#".*Could not find a package configuration file provided by "(.*)"\s+with\s+any\s+of\s+the\s+following\s+names:\n\n( .*\n)+\n.*$"#, |m| Ok(Some(Box::new(CMakeFilesMissing{ filenames: m.get(2).unwrap().as_str().split_whitespace().map(|s| s.to_string()).collect(), version: None }))) ), regex_para_matcher!( r#".*Could not find a package configuration file provided by "(.*)"\s\(requested\sversion\s(.+\))\swith\sany\sof\sthe\sfollowing\snames:\n\n( .*\n)+\n.*$"#, |m| { let package = m.get(1).unwrap().as_str().to_string(); let versions = m.get(2).unwrap().as_str().to_string(); let _names = m.get(3).unwrap().as_str().split_whitespace().map(|s| s.to_string()).collect::>(); Ok(Some(Box::new(MissingCMakeConfig { name: package, version: Some(versions), }))) } ), regex_para_matcher!( r#"No CMAKE_(.*)_COMPILER could be found.\n\nTell CMake where to find the compiler by setting either\sthe\senvironment\svariable\s"(.*)"\sor\sthe\sCMake\scache\sentry\sCMAKE_(.*)_COMPILER\sto\sthe\sfull\spath\sto\sthe\scompiler,\sor\sto\sthe\scompiler\sname\sif\sit\sis\sin\sthe\sPATH.\n"#, |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_lowercase())))) ), regex_para_matcher!(r#"file INSTALL cannot find\s"(.*)".\n"#, |m| Ok(Some(Box::new(MissingFile::new(m.get(1).unwrap().as_str().into()))))), regex_para_matcher!( r#"file INSTALL cannot copy file\n"(.*)"\sto\s"(.*)":\sNo space left on device.\n"#, |_m| Ok(Some(Box::new(NoSpaceOnDevice))) ), regex_para_matcher!( r"patch: \*\*\*\* write error : No space left on device", |_| Ok(Some(Box::new(NoSpaceOnDevice))) ), regex_para_matcher!( r".*\(No space left on device\)", |_| Ok(Some(Box::new(NoSpaceOnDevice))) ), regex_para_matcher!(r#"file INSTALL cannot copy file\n"(.*)"\nto\n"(.*)"\.\n"#), regex_para_matcher!( r#"Missing (.*)\. Either your\nlib(.*) version is too old, or lib(.*) wasn\'t found in the place you\nsaid."#, |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string())))) ), regex_para_matcher!( r"need (.*) of version (.*)", |m| Ok(Some(Box::new(MissingVagueDependency{ name: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(2).unwrap().as_str().to_string()), url: None, current_version: None }))) ), regex_para_matcher!( r"\*\*\* (.*) is required to build (.*)\n", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_para_matcher!(r"\[([^ ]+)\] not found", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_para_matcher!(r"([^ ]+) not found", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_para_matcher!(r"error: could not find git .*", |_m| Ok(Some(Box::new(MissingCommand("git".to_string()))))), regex_para_matcher!( r"Could not find \'(.*)\' executable[\!,].*", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_para_matcher!( r"Could not find (.*)_STATIC_LIBRARIES using the following names: ([a-zA-z0-9_.]+)", |m| Ok(Some(Box::new(MissingStaticLibrary{ library: m.get(1).unwrap().as_str().to_string(), filename: m.get(2).unwrap().as_str().to_string()}))) ), regex_para_matcher!( "include could not find (requested|load) file:\n\n (.*)\n", |m| { let mut path = m.get(2).unwrap().as_str().to_string(); if !path.ends_with(".cmake") { path += ".cmake"; } Ok(Some(Box::new(CMakeFilesMissing{filenames:vec![path], version: None }))) } ), regex_para_matcher!(r"(.*) and (.*) are required", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_para_matcher!( r"Please check your (.*) installation", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_para_matcher!(r"Python module (.*) not found\!", |m| Ok(Some(Box::new(MissingPythonModule::simple(m.get(1).unwrap().as_str().to_string()))))), regex_para_matcher!(r"\s*could not find ([^\s]+)$", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_para_matcher!( r"Please install (.*) before installing (.*)\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_para_matcher!( r"Please get (.*) from (www\..*)", |m| Ok(Some(Box::new(MissingVagueDependency { name: m.get(1).unwrap().as_str().to_string(), url: Some(m.get(2).unwrap().as_str().to_string()), minimum_version: None, current_version: None }))) ), regex_para_matcher!( r#"Found unsuitable Qt version "" from NOTFOUND, this code requires Qt 4.x"#, |_| Ok(Some(Box::new(MissingQt))) ), regex_para_matcher!( r"(.*) executable not found\! Please install (.*)\.", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string())))) ), regex_para_matcher!(r"(.*) tool not found", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))), regex_para_matcher!( r"-- Requested \'(.*) >= (.*)\' but version of (.*) is (.*)", |m| Ok(Some(Box::new(MissingPkgConfig{ module: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(2).unwrap().as_str().to_string()) }))) ), regex_para_matcher!(r"-- No package \'(.*)\' found", |m| Ok(Some(Box::new(MissingPkgConfig{minimum_version: None, module: m.get(1).unwrap().as_str().to_string()})))), regex_para_matcher!(r"([^ ]+) library not found\.", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))), regex_para_matcher!( r"Please install (.*) so that it is on the PATH and try again\.", command_missing ), regex_para_matcher!( r"-- Unable to find git\. Setting git revision to \'unknown\'\.", |_| Ok(Some(Box::new(MissingCommand("git".to_string())))) ), regex_para_matcher!( r"(.*) must be installed before configuration \& building can proceed", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_para_matcher!( r"(.*) development files not found\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_para_matcher!( r".* but no (.*) dev libraries found", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_para_matcher!( r"Failed to find (.*) \(missing: .*\)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_para_matcher!( r"Couldn\'t find ([^ ]+) development files\..*", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_para_matcher!( r"Could not find required (.*) package\!", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_para_matcher!( r"Cannot find (.*), giving up\. .*", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_para_matcher!( r"Cannot find (.*)\. (.*) is required for (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_para_matcher!( r"The development\sfiles\sfor\s(.*)\sare\srequired\sto\sbuild (.*)\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_para_matcher!( r"Required library (.*) not found\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_para_matcher!( r"(.*) required to compile (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_para_matcher!( r"(.*) requires (.*) ([0-9].*) or newer. See (https://.*)\s*", |m| Ok(Some(Box::new(MissingVagueDependency { name: m.get(2).unwrap().as_str().to_string(), minimum_version: Some(m.get(3).unwrap().as_str().to_string()), url: Some(m.get(4).unwrap().as_str().to_string()), current_version: None }))) ), regex_para_matcher!( r"(.*) requires (.*) ([0-9].*) or newer.\s*", |m| Ok(Some(Box::new(MissingVagueDependency{ name: m.get(2).unwrap().as_str().to_string(), minimum_version: Some(m.get(3).unwrap().as_str().to_string()), url: None, current_version: None }))) ), regex_para_matcher!(r"(.*) requires (.*) to build", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(2).unwrap().as_str()))))), regex_para_matcher!(r"(.*) library missing", |m| Ok(Some(Box::new(MissingLibrary(m.get(1).unwrap().as_str().to_string()))))), regex_para_matcher!(r"(.*) requires (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(2).unwrap().as_str()))))), regex_para_matcher!(r"Could not find ([A-Za-z-]+)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_para_matcher!(r"(.+) is required for (.*)\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_para_matcher!( r"No (.+) version could be found in your system\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_para_matcher!( r"([^ ]+) >= (.*) is required", |m| Ok(Some(Box::new(MissingVagueDependency { name: m.get(1).unwrap().as_str().to_string(), minimum_version: Some(m.get(2).unwrap().as_str().to_string()), current_version: None, url: None }))) ), regex_para_matcher!(r"\s*([^ ]+) is required", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_para_matcher!(r"([^ ]+) binary not found\!", |m| Ok(Some(Box::new(MissingCommand(m.get(1).unwrap().as_str().to_string()))))), regex_para_matcher!(r"error: could not find git for clone of .*", |_m| Ok(Some(Box::new(MissingCommand("git".to_string()))))), regex_para_matcher!(r"Did not find ([^\s]+)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), regex_para_matcher!( r"Could not find the ([^ ]+) external dependency\.", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str())))) ), regex_para_matcher!(r"Couldn\'t find (.*)", |m| Ok(Some(Box::new(MissingVagueDependency::simple(m.get(1).unwrap().as_str()))))), ]); } #[derive(Debug, Clone)] struct CMakeErrorMatcher; // Function to extract error lines and corresponding line numbers fn extract_cmake_error_lines<'a>(lines: &'a [&'a str], i: usize) -> (Vec, String) { let mut linenos = vec![i]; let mut error_lines = vec![]; // Iterate over the lines starting from index i + 1 for (j, line) in lines.iter().enumerate().skip(i + 1) { let trimmed = line.trim_end_matches('\n'); if !trimmed.is_empty() && !line.starts_with(' ') { break; } error_lines.push(*line); linenos.push(j); } // Remove trailing empty lines from error_lines and linenos while let Some(last_line) = error_lines.last() { if last_line.trim_end_matches('\n').is_empty() { error_lines.pop(); linenos.pop(); } else { break; } } // Dedent the error_lines using textwrap::dedent let dedented_string = textwrap::dedent(&error_lines.join("")); (linenos, dedented_string) } impl Matcher for CMakeErrorMatcher { fn extract_from_lines( &self, lines: &[&str], offset: usize, ) -> Result, Option>)>, Error> { let (_path, _start_linenos) = if let Some((_, _, path, start_lineno, _)) = lazy_regex::regex_captures!( r"CMake (Error|Warning) at (.+):([0-9]+) \((.*)\):", lines[offset].trim_end_matches('\n') ) { (path, start_lineno.parse::().unwrap()) } else { return Ok(None); }; let (linenos, error_string) = extract_cmake_error_lines(lines, offset); let mut actual_lines: Vec<_> = vec![]; for lineno in &linenos { actual_lines.push(lines[*lineno].to_string()); } let r#match = Box::new(MultiLineMatch::new( Origin("CMake".to_string()), linenos, actual_lines, )); if let Some((_match, problem)) = CMAKE_ERROR_MATCHERS.extract_from_lines(&[&error_string], 0)? { Ok(Some((r#match, problem))) } else { Ok(Some((r#match, None))) } } } /// Attempts to match and identify problems in the given lines. /// /// This function applies common matchers to the specified lines to identify /// known problems in build logs. /// /// # Arguments /// * `lines` - The log lines to analyze /// * `offset` - The offset at which to start analyzing /// /// # Returns /// A result containing either: /// * `Ok(Some((match, problem)))` - A match was found, possibly with a problem /// * `Ok(None)` - No match was found /// * `Err(error)` - An error occurred during matching pub fn match_lines(lines: &[&str], offset: usize) -> MatcherResult { COMMON_MATCHERS.extract_from_lines(lines, offset) } macro_rules! secondary_matcher { ($re:expr) => { fancy_regex::Regex::new($re).unwrap() }; } lazy_static::lazy_static! { /// Regexps that hint at an error of some sort, but not the error itself. static ref SECONDARY_MATCHERS: Vec = vec![ secondary_matcher!(r"E: pybuild pybuild:[0-9]+: test: plugin [^ ]+ failed with:"), secondary_matcher!(r"[^:]+: error: (.*)"), secondary_matcher!(r"[^:]+:[0-9]+: error: (.*)"), secondary_matcher!(r"[^:]+:[0-9]+:[0-9]+: error: (.*)"), secondary_matcher!(r"error TS[0-9]+: (.*)"), secondary_matcher!(r"mount: .*: mount failed: Operation not permitted\."), secondary_matcher!(r" [0-9]+:[0-9]+\s+error\s+.+"), secondary_matcher!(r"fontmake: Error: In '(.*)': (.*)"), secondary_matcher!(r"# Failed test at t\/.*\.t line [0-9]+\."), secondary_matcher!(r"Gradle build daemon disappeared unexpectedly \(it may have been killed or may have crashed\)"), // ocaml secondary_matcher!(r"\*\*\* omake error:"), secondary_matcher!(r".*ocamlc.*: OCam has been configured with -force-safe-string: -unsafe-string is not available\."), // latex secondary_matcher!(r"\! LaTeX Error: .*"), secondary_matcher!(r"Killed"), // Java secondary_matcher!(r#"Exception in thread "(.*)" (.*): (.*);"#), secondary_matcher!(r"error: Unrecognized option: \'.*\'"), secondary_matcher!(r"Segmentation fault"), secondary_matcher!(r"\[ERROR\] (.*\.java):\[[0-9]+,[0-9]+\] (.*)"), secondary_matcher!(r"make: \*\*\* No targets specified and no makefile found\. Stop\."), secondary_matcher!(r"make\[[0-9]+\]: \*\*\* No targets specified and no makefile found\. Stop\."), secondary_matcher!(r"make\[[0-9]+\]: (.*): No such file or directory"), secondary_matcher!(r"make\[[0-9]+\]: \*\*\* \[.*:[0-9]+: .*\] Segmentation fault"), secondary_matcher!( r".*:[0-9]+: \*\*\* empty variable name. Stop."), secondary_matcher!( r"error: can't copy '(.*)': doesn't exist or not a regular file"), secondary_matcher!( r"error: ([0-9]+) test executed, ([0-9]+) fatal tests failed, "), secondary_matcher!( r"([0-9]+) nonfatal test failed\."), secondary_matcher!( r".*\.rst:toctree contains ref to nonexisting file \'.*\'"), secondary_matcher!( r".*\.rst:[0-9]+:term not in glossary: .*"), secondary_matcher!( r"Try adding AC_PREREQ\(\[(.*)\]\) to your configure\.ac\."), // Erlang secondary_matcher!( r" (.*_test): (.+)\.\.\.\*failed\*"), secondary_matcher!( r"(.*\.erl):[0-9]+:[0-9]+: erlang:.*"), // Clojure secondary_matcher!( r"Could not locate (.*) or (.*) on classpath\."), // QMake secondary_matcher!( r"Project ERROR: .*"), // pdflatex secondary_matcher!( r"\! ==> Fatal error occurred, no output PDF file produced\!"), // latex secondary_matcher!( r"\! Undefined control sequence\."), secondary_matcher!( r"\! Emergency stop\."), secondary_matcher!(r"\!pdfTeX error: pdflatex: fwrite\(\) failed"), // inkscape secondary_matcher!(r"Unknown option (?!.*ignoring.*)"), // CTest secondary_matcher!( r"not ok [0-9]+ .*"), secondary_matcher!( r"Errors while running CTest"), secondary_matcher!( r"dh_auto_install: error: .*"), secondary_matcher!( r"dh_quilt_patch: error: (.*)"), secondary_matcher!( r"dh.*: Aborting due to earlier error"), secondary_matcher!( r"dh.*: unknown option or error during option parsing; aborting"), secondary_matcher!( r"Could not import extension .* \(exception: .*\)"), secondary_matcher!( r"configure.ac:[0-9]+: error: (.*)"), secondary_matcher!( r"Reconfigure the source tree (via './config' or 'perl Configure'), please."), secondary_matcher!( r"dwz: Too few files for multifile optimization"), secondary_matcher!( r"\[CJM/MatchManifest\] Aborted because of MANIFEST mismatch"), secondary_matcher!( r"dh_dwz: dwz -q -- .* returned exit code [0-9]+"), secondary_matcher!( r"help2man: can\'t get `-?-help\' info from .*"), secondary_matcher!( r"[^:]+: line [0-9]+:\s+[0-9]+ Segmentation fault.*"), secondary_matcher!( r"dpkg-gencontrol: error: (.*)"), secondary_matcher!( r".*:[0-9]+:[0-9]+: (error|ERROR): (.*)"), secondary_matcher!( r".*[.]+FAILED .*"), secondary_matcher!( r"FAIL: (.*)"), secondary_matcher!( r"FAIL\! : (.*)"), secondary_matcher!( r"\s*FAIL (.*) \(.*\)"), secondary_matcher!( r"FAIL\s+(.*) \[.*\] ?"), secondary_matcher!( r"([0-9]+)% tests passed, ([0-9]+) tests failed out of ([0-9]+)"), secondary_matcher!( r"TEST FAILURE"), secondary_matcher!( r"make\[[0-9]+\]: \*\*\* \[.*\] Error [0-9]+"), secondary_matcher!( r"make\[[0-9]+\]: \*\*\* \[.*\] Aborted"), secondary_matcher!( r"exit code=[0-9]+: .*"), secondary_matcher!( r"chmod: cannot access \'.*\': .*"), secondary_matcher!( r"dh_autoreconf: autoreconf .* returned exit code [0-9]+"), secondary_matcher!( r"make: \*\*\* \[.*\] Error [0-9]+"), secondary_matcher!( r".*:[0-9]+: \*\*\* missing separator\. Stop\."), secondary_matcher!( r"[0-9]+ tests: [0-9]+ ok, [0-9]+ failure\(s\), [0-9]+ test\(s\) skipped"), secondary_matcher!( r"\*\*Error:\*\* (.*)"), secondary_matcher!( r"^Error: (.*)"), secondary_matcher!( r"Failed [0-9]+ tests? out of [0-9]+, [0-9.]+% okay."), secondary_matcher!( r"Failed [0-9]+\/[0-9]+ test programs. [0-9]+/[0-9]+ subtests failed."), secondary_matcher!( r"Original error was: (.*)"), secondary_matcher!( r"-- Error \(.*\.R:[0-9]+:[0-9]+\): \(.*\) [-]*"), secondary_matcher!( r"^Error \[ERR_.*\]: .*"), secondary_matcher!( r"^FAILED \(.*\)"), secondary_matcher!( r"FAILED .*"), // Random Python errors secondary_matcher!( "^(E +)?(SyntaxError|TypeError|ValueError|AttributeError|NameError|django.core.exceptions..*|RuntimeError|subprocess.CalledProcessError|testtools.matchers._impl.MismatchError|PermissionError|IndexError|TypeError|AssertionError|IOError|ImportError|SerialException|OSError|qtawesome.iconic_font.FontError|redis.exceptions.ConnectionError|builtins.OverflowError|ArgumentError|httptools.parser.errors.HttpParserInvalidURLError|HypothesisException|SSLError|KeyError|Exception|rnc2rng.parser.ParseError|pkg_resources.UnknownExtra|tarfile.ReadError|numpydoc.docscrape.ParseError|distutils.errors.DistutilsOptionError|datalad.support.exceptions.IncompleteResultsError|AssertionError|Cython.Compiler.Errors.CompileError|UnicodeDecodeError|UnicodeEncodeError): .*"), // Rust secondary_matcher!( r"error\[E[0-9]+\]: .*"), secondary_matcher!( "^E DeprecationWarning: .*"), secondary_matcher!( "^E fixture '(.*)' not found"), // Rake secondary_matcher!( r"[0-9]+ runs, [0-9]+ assertions, [0-9]+ failures, [0-9]+ errors, [0-9]+ skips"), // Node secondary_matcher!( r"# failed [0-9]+ of [0-9]+ tests"), // Pytest secondary_matcher!( r"(.*).py:[0-9]+: AssertionError"), secondary_matcher!( r"============================ no tests ran in ([0-9.]+)s ============================="), // Perl secondary_matcher!( r" Failed tests: [0-9-]+"), secondary_matcher!( r"Failed (.*\.t): output changed"), // Go secondary_matcher!( r"no packages to test"), secondary_matcher!( "FAIL\t(.*)\t[0-9.]+s"), secondary_matcher!( r".*.go:[0-9]+:[0-9]+: (?!note:).*"), secondary_matcher!( r"can\'t load package: package \.: no Go files in /<>/(.*)"), // Ld secondary_matcher!( r"\/usr\/bin\/ld: cannot open output file (.*): No such file or directory"), secondary_matcher!( r"configure: error: (.+)"), secondary_matcher!( r"config.status: error: (.*)"), secondary_matcher!( r"E: Build killed with signal TERM after ([0-9]+) minutes of inactivity"), secondary_matcher!( r" \[javac\] [^: ]+:[0-9]+: error: (.*)"), secondary_matcher!( r"1\) TestChannelFeature: ([^:]+):([0-9]+): assert failed"), secondary_matcher!( r"cp: target \'(.*)\' is not a directory"), secondary_matcher!( r"cp: cannot create regular file \'(.*)\': No such file or directory"), secondary_matcher!( r"couldn\'t determine home directory at (.*)"), secondary_matcher!( r"ln: failed to create symbolic link \'(.*)\': File exists"), secondary_matcher!( r"ln: failed to create symbolic link \'(.*)\': No such file or directory"), secondary_matcher!( r"ln: failed to create symbolic link \'(.*)\': Permission denied"), secondary_matcher!( r"ln: invalid option -- .*"), secondary_matcher!( r"mkdir: cannot create directory [‘'](.*)['’]: No such file or directory"), secondary_matcher!( r"mkdir: cannot create directory [‘'](.*)['’]: File exists"), secondary_matcher!( r"mkdir: missing operand"), secondary_matcher!( r"rmdir: failed to remove '.*': No such file or directory"), secondary_matcher!( r"Fatal error: .*"), secondary_matcher!( "Fatal Error: (.*)"), secondary_matcher!( r"Alert: (.*)"), secondary_matcher!( r#"ERROR: Test "(.*)" failed. Exiting."#), // scons secondary_matcher!( r"ERROR: test\(s\) failed in (.*)"), secondary_matcher!( r"./configure: line [0-9]+: syntax error near unexpected token `.*\'"), secondary_matcher!( r"scons: \*\*\* \[.*\] ValueError : unsupported pickle protocol: .*"), // yarn secondary_matcher!( r"ERROR: There are no scenarios; must have at least one."), // perl secondary_matcher!( r"Execution of (.*) aborted due to compilation errors."), // Mocha secondary_matcher!( r" AssertionError \[ERR_ASSERTION\]: Missing expected exception."), // lt (C++) secondary_matcher!( r".*: .*:[0-9]+: .*: Assertion `.*\' failed."), secondary_matcher!( r"(.*).xml: FAILED:"), secondary_matcher!( r" BROKEN .*"), secondary_matcher!( r"failed: [0-9]+-.*"), // ninja secondary_matcher!( r"ninja: build stopped: subcommand failed."), secondary_matcher!( r".*\.s:[0-9]+: Error: .*"), // rollup secondary_matcher!(r"\[\!\] Error: Unexpected token"), // glib secondary_matcher!(r"\(.*:[0-9]+\): [a-zA-Z0-9]+-CRITICAL \*\*: [0-9:.]+: .*"), secondary_matcher!( r"tar: option requires an argument -- \'.\'"), secondary_matcher!( r"tar: .*: Cannot stat: No such file or directory"), secondary_matcher!( r"tar: .*: Cannot open: No such file or directory"), // rsvg-convert secondary_matcher!( r"Could not render file (.*.svg)"), // pybuild tests secondary_matcher!( r"ERROR: file not found: (.*)"), // msgfmt secondary_matcher!( r"/usr/bin/msgfmt: found [0-9]+ fatal errors"), // Docker secondary_matcher!( r"Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running\?"), secondary_matcher!( r"dh_makeshlibs: failing due to earlier errors"), // Ruby secondary_matcher!( r"([^:]+)\.rb:[0-9]+:in `([^\'])+\': (.*) \((.*)\)"), secondary_matcher!( r".*: \*\*\* ERROR: There where errors/warnings in server logs after running test cases."), secondary_matcher!( r"Errno::EEXIST: File exists @ dir_s_mkdir - .*"), secondary_matcher!( r"Test environment was found to be incomplete at configuration time,"), secondary_matcher!( r"libtool: error: cannot find the library \'(.*)\' or unhandled argument \'(.*)\'"), secondary_matcher!( r"npm ERR\! (.*)"), secondary_matcher!( r"install: failed to access \'(.*)\': (.*)"), secondary_matcher!( r"MSBUILD: error MSBUILD[0-9]+: Project file \'(.*)\' not found."), secondary_matcher!( r"E: (.*)"), secondary_matcher!( r"(.*)\(([0-9]+),([0-9]+)\): Error: .*"), // C # secondary_matcher!( r"(.*)\.cs\([0-9]+,[0-9]+\): error CS[0-9]+: .*"), secondary_matcher!( r".*Segmentation fault.*"), secondary_matcher!( r"a2x: ERROR: (.*) returned non-zero exit status ([0-9]+)"), secondary_matcher!( r"-- Configuring incomplete, errors occurred\!"), secondary_matcher!( r#"Error opening link script "(.*)""#), secondary_matcher!( r"cc: error: (.*)"), secondary_matcher!( r"\[ERROR\] .*"), secondary_matcher!( r"dh_auto_(test|build): error: (.*)"), secondary_matcher!( r"tar: This does not look like a tar archive"), secondary_matcher!( r"\[DZ\] no (name|version) was ever set"), secondary_matcher!( r"\[Runtime\] No -phase or -relationship specified at .* line [0-9]+\."), secondary_matcher!( r"diff: (.*): No such file or directory"), secondary_matcher!( r"gpg: signing failed: .*"), // mh_install secondary_matcher!( r"Cannot find the jar to install: (.*)"), secondary_matcher!( r"ERROR: .*"), secondary_matcher!( r"> error: (.*)"), secondary_matcher!( r"error: (.*)"), secondary_matcher!( r"(.*\.hs):[0-9]+:[0-9]+: error:"), secondary_matcher!( r"go1: internal compiler error: .*"), ]; } /// Finds secondary indicators of build failures in log lines. /// /// This function looks for secondary patterns that suggest a build failure /// but aren't the primary error message. /// /// # Arguments /// * `lines` - The log lines to analyze /// * `start_offset` - The number of lines to consider from the end of the log /// /// # Returns /// An optional match for a potential build failure pub fn find_secondary_build_failure( lines: &[&str], start_offset: usize, ) -> Option { for (offset, line) in lines.enumerate_tail_forward(start_offset) { let match_line = line.trim_end_matches('\n'); for regexp in SECONDARY_MATCHERS.iter() { match regexp.is_match(match_line) { Ok(true) => { let origin = Origin(format!("secondary regex {:?}", regexp)); log::debug!( "Found match against {:?} on {:?} (line {})", regexp, line, offset + 1 ); return Some(SingleLineMatch { origin, offset, line: line.to_string(), }); } Ok(false) => { // No match, continue to next regex } Err(fancy_regex::Error::RuntimeError( fancy_regex::RuntimeError::BacktrackLimitExceeded, )) => { // Handle backtracking limit exceeded gracefully log::debug!( "Regex backtracking limit exceeded for pattern '{}' on line (length: {})", regexp.as_str(), match_line.len() ); // Continue to next regex } Err(other_error) => { // Log other regex errors but continue processing log::warn!( "Regex error for pattern '{}': {}", regexp.as_str(), other_error ); // Continue to next regex } } } } None } /// Find the key failure line in build output. /// /// # Returns /// A tuple with (match object, error object) pub fn find_build_failure_description(lines: Vec<&str>) -> BuildFailureResult { pub const OFFSET: usize = 250; // Is this cmake-specific, or rather just kf5 / qmake ? let mut cmake = false; // We search backwards for clear errors. for (lineno, line) in lines.enumerate_backward(Some(250)) { if line.contains("cmake") { cmake = true; } if let Some((mm, merr)) = match_lines(lines.as_slice(), lineno).unwrap() { return (Some(mm), merr); } } // TODO(jelmer): Remove this in favour of CMakeErrorMatcher above. if cmake { // Urgh, multi-line regexes--- for (mut lineno, line) in lines.enumerate_forward(None) { let line = line.trim_end_matches('\n'); if let Some((_, target)) = lazy_regex::regex_captures!(r" Could NOT find (.*) \(missing: .*\)", line) { return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), Some(Box::new(MissingCommand(target.to_lowercase())) as Box), ); } if let Some((_, _target)) = lazy_regex::regex_captures!( r#"\s*The imported target "(.*)" references the file"#, line ) { lineno += 1; while lineno < lines.len() && !line.is_empty() { lineno += 1; } if lines[lineno + 2].starts_with(" but this file does not exist.") { let filename = if let Some((_, entry)) = lazy_regex::regex_captures!(r#"\s*"(.*)""#, line) { entry } else { line }; return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), Some(Box::new(MissingFile { path: filename.into(), }) as Box), ); } continue; } if lineno + 1 < lines.len() { if let Some((_, _pkg)) = lazy_regex::regex_captures!("^ Could not find a package configuration file provided by \"(.*)\" with any of the following names:", &(line.to_string() + " " + lines[lineno + 1].trim_start_matches(' ').trim_end_matches('\n'))) { if lines[lineno + 2] == "\n" { let mut i = 3; let mut filenames = vec![]; while !lines[lineno + i].trim().is_empty() { filenames.push(lines[lineno + i].trim().to_string()); i += 1; } return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex (cmake)") )) as Box), Some(Box::new(CMakeFilesMissing{filenames, version: None}) as Box), ) } } } } } // And forwards for vague ("secondary") errors. let m = find_secondary_build_failure(lines.as_slice(), OFFSET); if let Some(m) = m { return (Some(Box::new(m)), None); } (None, None) } #[cfg(test)] mod tests { use super::*; fn assert_just_match(lines: Vec<&str>, lineno: usize) { let (r#match, actual_err) = super::find_build_failure_description(lines.clone()); assert!(actual_err.is_none()); if let Some(r#match) = r#match.as_ref() { assert_eq!(&r#match.line(), &lines[lineno - 1]); assert_eq!(lineno, r#match.lineno()); } else { assert!(r#match.is_none()); } } fn assert_match(lines: Vec<&str>, lineno: usize, mut expected: Option) { let (r#match, actual_err) = super::find_build_failure_description(lines.clone()); if let Some(r#match) = r#match.as_ref() { assert_eq!(&r#match.line(), &lines[lineno - 1]); assert_eq!(lineno, r#match.lineno()); } else { assert!(r#match.is_none()); } if let Some(expected) = expected.take() { assert!( r#match.is_some(), "err ({:?}) provided but match missing", &expected ); assert_eq!( actual_err.as_ref().map(|x| x.as_ref()), Some(&expected as &dyn Problem) ); } else { assert!(actual_err.is_none()); } } #[test] fn test_make_missing_rule() { assert_match( vec![ "make[1]: *** No rule to make target 'nno.autopgen.bin', needed by 'dan-nno.autopgen.bin'. Stop." ], 1, Some(MissingMakeTarget::new( "nno.autopgen.bin", Some("dan-nno.autopgen.bin"))), ); assert_match(vec![ "make[1]: *** No rule to make target '/usr/share/blah/blah', needed by 'dan-nno.autopgen.bin'. Stop." ], 1, Some(MissingMakeTarget::new("/usr/share/blah/blah", Some("dan-nno.autopgen.bin"))), ); assert_match( vec![ "debian/rules:4: /usr/share/openstack-pkg-tools/pkgos.make: No such file or directory" ], 1, Some(MissingFile::new("/usr/share/openstack-pkg-tools/pkgos.make".into())), ); } #[test] fn test_git_identity() { assert_match( vec![ "fatal: unable to auto-detect email address (got 'jenkins@osuosl167-amd64.(none)')", ], 1, Some(MissingGitIdentity), ); } #[test] fn test_ioerror() { assert_match( vec![ "E IOError: [Errno 2] No such file or directory: '/usr/lib/python2.7/poly1305/rfc7539.txt'" ], 1, Some(MissingFile::new("/usr/lib/python2.7/poly1305/rfc7539.txt".into())), ); } #[test] fn test_vignette() { assert_match( vec![ "Error: processing vignette 'uroot-intro.Rnw' failed with diagnostics:", "pdflatex is not available", ], 2, Some(MissingVagueDependency::simple("pdflatex")), ); } #[test] fn test_upstart_file_present() { assert_match( vec![ "dh_installinit: upstart jobs are no longer supported! Please remove debian/sddm.upstart and check if you need to add a conffile removal" ], 1, Some(UpstartFilePresent("debian/sddm.upstart".into())), ); } #[test] fn test_missing_go_mod_file() { assert_match( vec![ "go: go.mod file not found in current directory or any parent directory; see 'go help modules'" ], 1, Some(MissingGoModFile), ); } #[test] fn test_missing_javascript_runtime() { assert_match( vec![ "ExecJS::RuntimeUnavailable: Could not find a JavaScript runtime. See https://github.com/rails/execjs for a list of available runtimes."], 1, Some(MissingJavaScriptRuntime) ); } #[test] fn test_directory_missing() { assert_match( vec!["debian/components/build: 19: cd: can't cd to rollup-plugin"], 1, Some(DirectoryNonExistant("rollup-plugin".to_owned())), ); } #[test] fn test_vcs_control_directory() { assert_match( vec![" > Cannot find '.git' directory"], 1, Some(VcsControlDirectoryNeeded::new(vec!["git"])), ); } #[test] fn test_missing_sprockets_file() { assert_match( vec![ "Sprockets::FileNotFound: couldn't find file 'activestorage' with type 'application/javascript'" ], 1, Some(MissingSprocketsFile { name: "activestorage".to_owned(), content_type: "application/javascript".to_owned()}), ); } #[test] fn test_gxx_missing_file() { assert_match( vec!["g++: error: /usr/lib/x86_64-linux-gnu/libGL.so: No such file or directory"], 1, Some(MissingFile::new( "/usr/lib/x86_64-linux-gnu/libGL.so".into(), )), ); } #[test] fn test_build_xml_missing_file() { assert_match( vec!["/<>/build.xml:59: /<>/lib does not exist."], 1, Some(MissingBuildFile { filename: "lib".to_owned(), }), ); } #[test] fn test_vignette_builder() { assert_match( vec![" vignette builder 'R.rsp' not found"], 1, Some(MissingRPackage::simple("R.rsp")), ); } #[test] fn test_dh_missing_addon() { assert_match( vec![ " dh_auto_clean -O--buildsystem=pybuild", "E: Please add appropriate interpreter package to Build-Depends, see pybuild(1) for details.this: $VAR1 = bless( {", " 'py3vers' => '3.8',", " 'py3def' => '3.8',", " 'pyvers' => '',", " 'parallel' => '2',", " 'cwd' => '/<>',", " 'sourcedir' => '.',", " 'builddir' => undef,", " 'pypydef' => '',", " 'pydef' => ''", " }, 'Debian::Debhelper::Buildsystem::pybuild' );", "deps: $VAR1 = [];", ], 2, Some(DhAddonLoadFailure{ name: "pybuild".to_owned(), path: "Debian/Debhelper/Buildsystem/pybuild.pm".to_owned()}), ); } #[test] fn test_libtoolize_missing_file() { assert_match( vec!["libtoolize: error: '/usr/share/aclocal/ltdl.m4' does not exist."], 1, Some(MissingFile::new("/usr/share/aclocal/ltdl.m4".into())), ); } #[test] fn test_ruby_missing_file() { assert_match( vec![ "Error: Error: ENOENT: no such file or directory, open '/usr/lib/nodejs/requirejs/text.js'" ], 1, Some(MissingFile::new("/usr/lib/nodejs/requirejs/text.js".into())), ); } #[test] fn test_vcversioner() { assert_match( vec![ "vcversioner: ['git', '--git-dir', '/build/tmp0tlam4pe/pyee/.git', 'describe', '--tags', '--long'] failed and '/build/tmp0tlam4pe/pyee/version.txt' isn't present." ], 1, Some(MissingVcVersionerVersion), ); } #[test] fn test_python_missing_file() { assert_match( vec![ "python3.7: can't open file '/usr/bin/blah.py': [Errno 2] No such file or directory" ], 1, Some(MissingFile::new("/usr/bin/blah.py".into())), ); assert_match( vec!["python3.7: can't open file 'setup.py': [Errno 2] No such file or directory"], 1, Some(MissingBuildFile::new("setup.py".into())), ); assert_match( vec![ "E FileNotFoundError: [Errno 2] No such file or directory: '/usr/share/firmware-microbit-micropython/firmware.hex'" ], 1, Some(MissingFile::new( "/usr/share/firmware-microbit-micropython/firmware.hex".into() )), ); } #[test] fn test_vague() { assert_match( vec![ "configure: error: Please install gnu flex from http://www.gnu.org/software/flex/", ], 1, Some(MissingVagueDependency { name: "gnu flex".to_string(), url: Some("http://www.gnu.org/software/flex/".to_owned()), minimum_version: None, current_version: None, }), ); assert_match( vec!["RuntimeError: cython is missing"], 1, Some(MissingVagueDependency::simple("cython")), ); assert_match( vec![ "configure: error:", "", " Unable to find the Multi Emulator Super System (MESS).", ], 3, Some(MissingVagueDependency::simple( "the Multi Emulator Super System (MESS)", )), ); assert_match( vec![ "configure: error: libwandio 4.0.0 or better is required to compile this version of libtrace. If you have installed libwandio in a non-standard location please use LDFLAGS to specify the location of the library. WANDIO can be obtained from http://research.wand.net.nz/software/libwandio.php" ], 1, Some(MissingVagueDependency{ name: "libwandio".to_owned(), minimum_version: Some("4.0.0".to_owned()), current_version: None, url: None, }), ); assert_match( vec![ "configure: error: libpcap0.8 or greater is required to compile libtrace. If you have installed it in a non-standard location please use LDFLAGS to specify the location of the library" ], 1, Some(MissingVagueDependency::simple("libpcap0.8")), ); assert_match( vec!["Error: Please install xml2 package"], 1, Some(MissingVagueDependency::simple("xml2")), ); } #[test] fn test_gettext_mismatch() { assert_match( vec![ "*** error: gettext infrastructure mismatch: using a Makefile.in.in from gettext version 0.19 but the autoconf macros are from gettext version 0.20" ], 1, Some(MismatchGettextVersions{makefile_version: "0.19".to_string(), autoconf_version: "0.20".to_string()}), ); } #[test] fn test_x11_missing() { assert_match( vec![ "configure: error: *** No X11! Install X-Windows development headers/libraries! ***" ], 1, Some(MissingX11), ); } #[test] fn test_multi_line_configure_error() { assert_just_match( vec!["configure: error:", "", " Some other error."], 3, ); assert_match( vec![ "configure: error:", "", " Unable to find the Multi Emulator Super System (MESS).", "", " Please install MESS, or specify the MESS command with", " a MESS environment variable.", "", "e.g. MESS=/path/to/program/mess ./configure", ], 3, Some(MissingVagueDependency::simple( "the Multi Emulator Super System (MESS)", )), ); } #[test] fn test_interpreter_missing() { assert_match( vec![ "/bin/bash: /usr/bin/rst2man: /usr/bin/python: bad interpreter: No such file or directory" ], 1, Some(MissingFile::new("/usr/bin/python".into())) ); assert_just_match( vec!["env: ‘/<>/socket-activate’: No such file or directory"], 1, ); } #[test] fn test_webpack_missing() { assert_just_match( vec![ "ERROR in Entry module not found: Error: Can't resolve 'index.js' in '/<>'" ], 1, ); } #[test] fn test_installdocs_missing() { assert_match( vec![ r#"dh_installdocs: Cannot find (any matches for) "README.txt" (tried in ., debian/tmp)"#, ], 1, Some(DebhelperPatternNotFound { pattern: "README.txt".to_owned(), tool: "installdocs".to_owned(), directories: vec![".".to_string(), "debian/tmp".to_owned()], }), ); } #[test] fn test_dh_compat_dupe() { assert_match( vec![ "dh_autoreconf: debhelper compat level specified both in debian/compat and via build-dependency on debhelper-compat" ], 1, Some(DuplicateDHCompatLevel{command: "dh_autoreconf".to_owned()}), ); } #[test] fn test_dh_compat_missing() { assert_match( vec!["dh_clean: Please specify the compatibility level in debian/compat"], 1, Some(MissingDHCompatLevel { command: "dh_clean".to_owned(), }), ); } #[test] fn test_dh_compat_too_old() { assert_match( vec! [ "dh_clean: error: Compatibility levels before 7 are no longer supported (level 5 requested)" ], 1, Some(UnsupportedDebhelperCompatLevel{ oldest_supported: 7, requested: 5}) ); } #[test] fn test_dh_udeb_shared_library() { assert_just_match(vec![ "dh_makeshlibs: The udeb libepoxy0-udeb (>= 1.3) does not contain any shared libraries but --add-udeb=libepoxy0-udeb (>= 1.3) was passed!?" ], 1, ); } #[test] fn test_dh_systemd() { assert_just_match( vec![ "dh: unable to load addon systemd: dh: The systemd-sequence is no longer provided in compat >= 11, please rely on dh_installsystemd instead" ], 1, ); } #[test] fn test_dh_before() { assert_just_match(vec![ "dh: The --before option is not supported any longer (#932537). Use override targets instead." ], 1, ); } #[test] fn test_meson_missing_git() { assert_match( vec!["meson.build:13:0: ERROR: Git program not found."], 1, Some(MissingCommand("git".to_owned())), ); } #[test] fn test_meson_missing_lib() { assert_match( vec!["meson.build:85:0: ERROR: C++ shared or static library 'vulkan-1' not found"], 1, Some(MissingLibrary("vulkan-1".to_owned())), ); } #[test] fn test_ocaml_library_missing() { assert_match( vec![r#"Error: Library "camlp-streams" not found."#], 1, Some(MissingOCamlPackage("camlp-streams".to_owned())), ); } #[test] fn test_meson_version() { assert_match( vec!["meson.build:1:0: ERROR: Meson version is 0.49.2 but project requires >=0.50"], 1, Some(MissingVagueDependency { name: "meson".to_owned(), minimum_version: Some("0.50".to_owned()), current_version: Some("0.49.2".to_owned()), url: None, }), ); assert_match( vec!["../meson.build:1:0: ERROR: Meson version is 0.49.2 but project requires >=0.50"], 1, Some(MissingVagueDependency { name: "meson".to_string(), minimum_version: Some("0.50".to_owned()), current_version: Some("0.49.2".to_owned()), url: None, }), ); } #[test] fn test_need_pgbuildext() { assert_match( vec![ "Error: debian/control needs updating from debian/control.in. Run 'pg_buildext updatecontrol'." ], 1, Some(NeedPgBuildExtUpdateControl{generated_path: "debian/control".to_owned(), template_path: "debian/control.in".to_owned()}) ); } #[test] fn test_cmake_missing_command() { assert_match( vec![ " Could NOT find Git (missing: GIT_EXECUTABLE)", "dh_auto_configure: cd obj-x86_64-linux-gnu && cmake with args", ], 1, Some(MissingCommand("git".to_owned())), ); } #[test] fn test_autoconf_version() { assert_match( vec!["configure.ac:13: error: Autoconf version 2.71 or higher is required"], 1, Some(MissingVagueDependency { name: "autoconf".to_string(), minimum_version: Some("2.71".to_string()), current_version: None, url: None, }), ); } #[test] fn test_claws_version() { assert_match( vec!["configure: error: libetpan 0.57 not found"], 1, Some(MissingVagueDependency { name: "libetpan".to_string(), minimum_version: Some("0.57".to_string()), current_version: None, url: None, }), ); } #[test] fn test_config_status_input() { assert_match( vec!["config.status: error: cannot find input file: `po/Makefile.in.in'"], 1, Some(MissingConfigStatusInput { path: "po/Makefile.in.in".to_owned(), }), ); } #[test] fn test_jvm() { assert_match( vec!["ERROR: JAVA_HOME is set to an invalid directory: /usr/lib/jvm/default-java/"], 1, Some(MissingJVM), ); } #[test] fn test_cp() { assert_match( vec![ "cp: cannot stat '/<>/debian/patches/lshw-gtk.desktop': No such file or directory" ], 1, Some(MissingBuildFile::new("debian/patches/lshw-gtk.desktop".to_owned())) ); } #[test] fn test_bash_redir_missing() { assert_match( vec!["/bin/bash: idna-tables-properties.csv: No such file or directory"], 1, Some(MissingBuildFile::new( "idna-tables-properties.csv".to_owned(), )), ); } #[test] fn test_automake_input() { assert_match( vec!["automake: error: cannot open < gtk-doc.make: No such file or directory"], 1, Some(MissingAutomakeInput { path: "gtk-doc.make".to_owned(), }), ); } #[test] fn test_shellcheck() { assert_just_match( vec![ &(" ".repeat(40) + "^----^ SC2086: Double quote to prevent globbing and word splitting."), ], 1, ); } #[test] fn test_autoconf_macro() { assert_match( vec!["configure.in:1802: error: possibly undefined macro: AC_CHECK_CCA"], 1, Some(MissingAutoconfMacro { r#macro: "AC_CHECK_CCA".to_owned(), need_rebuild: false, }), ); assert_match( vec!["./configure: line 12569: PKG_PROG_PKG_CONFIG: command not found"], 1, Some(MissingAutoconfMacro { r#macro: "PKG_PROG_PKG_CONFIG".to_owned(), need_rebuild: false, }), ); assert_match( vec![ "checking for gawk... (cached) mawk", "./configure: line 2368: syntax error near unexpected token `APERTIUM,'", "./configure: line 2368: `PKG_CHECK_MODULES(APERTIUM, apertium >= 3.7.1)'", ], 3, Some(MissingAutoconfMacro { r#macro: "PKG_CHECK_MODULES".to_owned(), need_rebuild: true, }), ); assert_match( vec![ "checking for libexif to use... ./configure: line 15968: syntax error near unexpected token `LIBEXIF,libexif'", "./configure: line 15968: `\t\t\t\t\t\tPKG_CHECK_MODULES(LIBEXIF,libexif >= 0.6.18,have_LIBEXIF=yes,:)'", ], 2, Some(MissingAutoconfMacro{ r#macro: "PKG_CHECK_MODULES".to_owned(), need_rebuild:true}) ); } #[test] fn test_r_missing() { assert_match( vec![ "ERROR: dependencies ‘ellipsis’, ‘pkgload’ are not available for package ‘testthat’" ], 1, Some(MissingRPackage::simple("ellipsis")), ); assert_match( vec![" namespace ‘DBI’ 1.0.0 is being loaded, but >= 1.0.0.9003 is required"], 1, Some(MissingRPackage { package: "DBI".to_owned(), minimum_version: Some("1.0.0.9003".to_owned()), }), ); assert_match( vec![ " namespace ‘spatstat.utils’ 1.13-0 is already loaded, but >= 1.15.0 is required", ], 1, Some(MissingRPackage { package: "spatstat.utils".to_owned(), minimum_version: Some("1.15.0".to_owned()), }), ); assert_match( vec!["Error in library(zeligverse) : there is no package called 'zeligverse'"], 1, Some(MissingRPackage::simple("zeligverse")), ); assert_match( vec!["there is no package called 'mockr'"], 1, Some(MissingRPackage::simple("mockr")), ); assert_match( vec![ "ERROR: dependencies 'igraph', 'matlab', 'expm', 'RcppParallel' are not available for package 'markovchain'" ], 1, Some(MissingRPackage::simple("igraph")) ); assert_match( vec![ "Error: package 'BH' 1.66.0-1 was found, but >= 1.75.0.0 is required by 'RSQLite'", ], 1, Some(MissingRPackage { package: "BH".to_owned(), minimum_version: Some("1.75.0.0".to_owned()), }), ); assert_match( vec![ "Error: package ‘AnnotationDbi’ 1.52.0 was found, but >= 1.53.1 is required by ‘GO.db’" ], 1, Some(MissingRPackage{ package: "AnnotationDbi".to_owned(), minimum_version: Some("1.53.1".to_owned())}) ); assert_match( vec![" namespace 'alakazam' 1.1.0 is being loaded, but >= 1.1.0.999 is required"], 1, Some(MissingRPackage { package: "alakazam".to_string(), minimum_version: Some("1.1.0.999".to_string()), }), ); } #[test] fn test_mv_stat() { assert_match( vec!["mv: cannot stat '/usr/res/boss.png': No such file or directory"], 1, Some(MissingFile::new("/usr/res/boss.png".into())), ); assert_just_match( vec!["mv: cannot stat 'res/boss.png': No such file or directory"], 1, ); } #[test] fn test_dh_link_error() { assert_match( vec![ "dh_link: link destination debian/r-cran-crosstalk/usr/lib/R/site-library/crosstalk/lib/ionrangeslider is a directory" ], 1, Some(DhLinkDestinationIsDirectory( "debian/r-cran-crosstalk/usr/lib/R/site-library/crosstalk/lib/ionrangeslider".to_owned() )), ); } #[test] fn test_go_test() { assert_just_match(vec!["FAIL\tgithub.com/edsrzf/mmap-go\t0.083s"], 1); } #[test] fn test_debhelper_pattern() { assert_match( vec![ r#"dh_install: Cannot find (any matches for) "server/etc/gnumed/gnumed-restore.conf" (tried in ., debian/tmp)"#, ], 1, Some(DebhelperPatternNotFound { pattern: "server/etc/gnumed/gnumed-restore.conf".to_owned(), tool: "install".to_owned(), directories: vec![".".to_string(), "debian/tmp".to_string()], }), ); } #[test] fn test_symbols() { assert_match( vec![ "dpkg-gensymbols: error: some symbols or patterns disappeared in the symbols file: see diff output below" ], 1, Some(DisappearedSymbols) ); } #[test] fn test_missing_php_class() { assert_match( vec![ "PHP Fatal error: Uncaught Error: Class 'PHPUnit_Framework_TestCase' not found in /tmp/autopkgtest.gO7h1t/build.b1p/src/Horde_Text_Diff-2.2.0/test/Horde/Text/Diff/EngineTest.php:9" ], 1, Some(MissingPhpClass{php_class: "PHPUnit_Framework_TestCase".to_owned()}) ); } #[test] fn test_missing_java_class() { assert_match( r#"Caused by: java.lang.ClassNotFoundException: org.codehaus.Xpp3r$Builder \tat org.codehaus.strategy.SelfFirstStrategy.loadClass(lfFirstStrategy.java:50) \tat org.codehaus.realm.ClassRealm.unsynchronizedLoadClass(ClassRealm.java:271) \tat org.codehaus.realm.ClassRealm.loadClass(ClassRealm.java:247) \tat org.codehaus.realm.ClassRealm.loadClass(ClassRealm.java:239) \t... 46 more "# .split("\n") .collect::>(), 1, Some(MissingJavaClass { classname: "org.codehaus.Xpp3r$Builder".to_owned(), }), ); } #[test] fn test_install_docs_link() { assert_just_match( r#"dh_installdocs: --link-doc not allowed between sympow and sympow-data (one is \ arch:all and the other not)"# .split("\n") .collect::>(), 1, ); } #[test] fn test_dh_until_unsupported() { assert_match( vec![ "dh: The --until option is not supported any longer (#932537). Use override targets instead." ], 1, Some(DhUntilUnsupported) ); } #[test] fn test_missing_xml_entity() { assert_match( vec![ "I/O error : Attempt to load network entity http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" ], 1, Some(MissingXmlEntity{url: "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd".to_owned()}) ); } #[test] fn test_ccache_error() { assert_match( vec![ "ccache: error: Failed to create directory /sbuild-nonexistent/.ccache/tmp: Permission denied" ], 1, Some(CcacheError( "Failed to create directory /sbuild-nonexistent/.ccache/tmp: Permission denied".to_owned() )) ); } #[test] fn test_dh_addon_load_failure() { assert_match( vec![ "dh: unable to load addon nodejs: Debian/Debhelper/Sequence/nodejs.pm did not return a true value at (eval 11) line 1." ], 1, Some(DhAddonLoadFailure{name: "nodejs".to_owned(), path: "Debian/Debhelper/Sequence/nodejs.pm".to_owned()}) ); } #[test] fn test_missing_library() { assert_match( vec!["/usr/bin/ld: cannot find -lpthreads"], 1, Some(MissingLibrary("pthreads".to_owned())), ); assert_just_match( vec!["./testFortranCompiler.f:4: undefined reference to `sgemm_'"], 1, ); assert_just_match( vec!["writer.d:59: error: undefined reference to 'sam_hdr_parse_'"], 1, ); } #[test] fn test_assembler() { assert_match(vec!["Found no assembler"], 1, Some(MissingAssembler)) } #[test] fn test_command_missing() { assert_match( vec!["./ylwrap: line 176: yacc: command not found"], 1, Some(MissingCommand("yacc".to_owned())), ); assert_match( vec!["/bin/sh: 1: cmake: not found"], 1, Some(MissingCommand("cmake".to_owned())), ); assert_match( vec!["sh: 1: git: not found"], 1, Some(MissingCommand("git".to_owned())), ); assert_match( vec!["/usr/bin/env: ‘python3’: No such file or directory"], 1, Some(MissingCommand("python3".to_owned())), ); assert_match( vec!["%Error: 'flex' must be installed to build"], 1, Some(MissingCommand("flex".to_owned())), ); assert_match( vec![r#"pkg-config: exec: "pkg-config": executable file not found in $PATH"#], 1, Some(MissingCommand("pkg-config".to_owned())), ); assert_match( vec![r#"Can't exec "git": No such file or directory at Makefile.PL line 25."#], 1, Some(MissingCommand("git".to_owned())), ); assert_match( vec![ "vcver.scm.git.GitCommandError: 'git describe --tags --match 'v*' --abbrev=0' returned an error code 127" ], 1, Some(MissingCommand("git".to_owned())), ); assert_match( vec!["make[1]: docker: Command not found"], 1, Some(MissingCommand("docker".to_owned())), ); assert_match( vec!["make[1]: git: Command not found"], 1, Some(MissingCommand("git".to_owned())), ); assert_just_match(vec!["make[1]: ./docker: Command not found"], 1); assert_match( vec!["make: dh_elpa: Command not found"], 1, Some(MissingCommand("dh_elpa".to_owned())), ); assert_match( vec!["/bin/bash: valac: command not found"], 1, Some(MissingCommand("valac".to_owned())), ); assert_match( vec!["E: Failed to execute “python3”: No such file or directory"], 1, Some(MissingCommand("python3".to_owned())), ); assert_match( vec![ r#"Can't exec "cmake": No such file or directory at /usr/share/perl5/Debian/Debhelper/Dh_Lib.pm line 484."#, ], 1, Some(MissingCommand("cmake".to_owned())), ); assert_match( vec!["Invalid gemspec in [unicorn.gemspec]: No such file or directory - git"], 1, Some(MissingCommand("git".to_owned())), ); assert_match( vec!["dbus-run-session: failed to exec 'xvfb-run': No such file or directory"], 1, Some(MissingCommand("xvfb-run".to_owned())), ); assert_match( vec!["/bin/sh: 1: ./configure: not found"], 1, Some(MissingConfigure), ); assert_match( vec!["xvfb-run: error: xauth command not found"], 1, Some(MissingCommand("xauth".to_owned())), ); assert_match( vec!["meson.build:39:2: ERROR: Program(s) ['wrc'] not found or not executable"], 1, Some(MissingCommand("wrc".to_owned())), ); assert_match( vec![ "/tmp/autopkgtest.FnbV06/build.18W/src/debian/tests/blas-testsuite: 7: dpkg-architecture: not found" ], 1, Some(MissingCommand("dpkg-architecture".to_owned())), ); assert_match( vec![ "Traceback (most recent call last):", r#" File "/usr/lib/python3/dist-packages/mesonbuild/mesonmain.py", line 140, in run"#, " return options.run_func(options)", r#" File "/usr/lib/python3/dist-packages/mesonbuild/mdist.py", line 267, in run"#, " names = create_dist_git(dist_name, archives, src_root, bld_root, dist_sub, b.dist_scripts, subprojects)", r#" File "/usr/lib/python3/dist-packages/mesonbuild/mdist.py", line 119, in create_dist_git"#, " git_clone(src_root, distdir)", r#" File "/usr/lib/python3/dist-packages/mesonbuild/mdist.py", line 108, in git_clone"#, " if git_have_dirty_index(src_root):", r#" File "/usr/lib/python3/dist-packages/mesonbuild/mdist.py", line 104, in git_have_dirty_index"#, " ret = subprocess.call(['git', '-C', src_root, 'diff-index', '--quiet', 'HEAD'])", r#" File "/usr/lib/python3.9/subprocess.py", line 349, in call"#, " with Popen(*popenargs, **kwargs) as p:", r#" File "/usr/lib/python3.9/subprocess.py", line 951, in __init__"#, " self._execute_child(args, executable, preexec_fn, close_fds,", r#" File "/usr/lib/python3.9/subprocess.py", line 1823, in _execute_child"#, " raise child_exception_type(errno_num, err_msg, err_filename)", "FileNotFoundError: [Errno 2] No such file or directory: 'git'", ], 18, Some(MissingCommand("git".to_owned())), ); assert_match( vec![r#"> Cannot run program "git": error=2, No such file or directory"#], 1, Some(MissingCommand("git".to_owned())), ); assert_match( vec!["E ImportError: Bad git executable"], 1, Some(MissingCommand("git".to_owned())), ); assert_match( vec!["E ImportError: Bad git executable."], 1, Some(MissingCommand("git".to_owned())), ); assert_match( vec![r#"Could not find external command "java""#], 1, Some(MissingCommand("java".to_owned())), ); } #[test] fn test_ts_error() { assert_just_match( vec!["blah/tokenizer.ts(175,21): error TS2532: Object is possibly 'undefined'."], 1, ); } #[test] fn test_pkg_config_missing() { assert_match( vec!["configure: error: Package requirements (apertium-3.2 >= 3.2.0) were not met:"], 1, Some(MissingPkgConfig::new( "apertium-3.2".to_owned(), Some("3.2.0".to_owned()), )), ); assert_match( vec![ "checking for GLEW... configure: error: Package requirements (glew) were not met:", ], 1, Some(MissingPkgConfig::simple("glew".to_owned())), ); assert_match( vec!["meson.build:10:0: ERROR: Dependency \"gssdp-1.2\" not found, tried pkgconfig"], 1, Some(MissingPkgConfig::simple("gssdp-1.2".to_owned())), ); assert_match( vec![ "src/plugins/sysprof/meson.build:3:0: ERROR: Dependency \"sysprof-3\" not found, tried pkgconfig" ], 1, Some(MissingPkgConfig::simple("sysprof-3".to_owned())), ); assert_match( vec![ "meson.build:84:0: ERROR: Invalid version of dependency, need 'libpeas-1.0' ['>= 1.24.0'] found '1.22.0'." ], 1, Some(MissingPkgConfig::new("libpeas-1.0".to_owned(), Some("1.24.0".to_owned()))), ); assert_match( vec![ "meson.build:233:0: ERROR: Invalid version of dependency, need 'vte-2.91' ['>=0.63.0'] found '0.62.3'." ], 1, Some(MissingPkgConfig::new("vte-2.91".to_owned(), Some("0.63.0".to_owned()))), ); assert_match( vec!["No package 'tepl-3' found"], 1, Some(MissingPkgConfig::simple("tepl-3".to_owned())), ); assert_match( vec!["Requested 'vte-2.91 >= 0.59.0' but version of vte is 0.58.2"], 1, Some(MissingPkgConfig::new( "vte-2.91".to_owned(), Some("0.59.0".to_owned()), )), ); assert_match( vec!["configure: error: x86_64-linux-gnu-pkg-config sdl2 couldn't be found"], 1, Some(MissingPkgConfig::simple("sdl2".to_owned())), ); assert_match( vec!["configure: error: No package 'libcrypto' found"], 1, Some(MissingPkgConfig::simple("libcrypto".to_owned())), ); assert_match( vec![ "-- Checking for module 'gtk+-3.0'", "-- Package 'gtk+-3.0', required by 'virtual:world', not found", ], 2, Some(MissingPkgConfig::simple("gtk+-3.0".to_owned())), ); assert_match( vec![ "configure: error: libfilezilla not found: Package dependency requirement 'libfilezilla >= 0.17.1' could not be satisfied." ], 1, Some(MissingPkgConfig::new("libfilezilla".to_owned(), Some("0.17.1".to_owned()))), ); } #[test] fn test_pkgconf() { assert_match( vec!["checking for LAPACK... configure: error: \"Cannot check for existence of module lapack without pkgconf\""], 1, Some(MissingCommand("pkgconf".to_owned())), ); } #[test] fn test_dh_with_order() { assert_match( vec!["dh: Unknown sequence --with (options should not come before the sequence)"], 1, Some(DhWithOrderIncorrect), ); } #[test] fn test_fpic() { assert_just_match( vec![ "/usr/bin/ld: pcap-linux.o: relocation R_X86_64_PC32 against symbol `stderr@@GLIBC_2.2.5' can not be used when making a shared object; recompile with -fPIC" ], 1, ); } #[test] fn test_rspec() { assert_just_match( vec![ "rspec ./spec/acceptance/cookbook_resource_spec.rb:20 # Client API operations downloading a cookbook when the cookbook of the name/version is found downloads the cookbook to the destination" ], 1, ); } #[test] fn test_multiple_definition() { assert_just_match( vec![ "./dconf-paths.c:249: multiple definition of `dconf_is_rel_dir'; client/libdconf-client.a(dconf-paths.c.o):./obj-x86_64-linux-gnu/../common/dconf-paths.c:249: first defined here" ], 1, ); assert_just_match( vec![ "/usr/bin/ld: ../lib/libaxe.a(stream.c.o):(.bss+0x10): multiple definition of `gsl_message_mask'; ../lib/libaxe.a(error.c.o):(.bss+0x8): first defined here" ], 1, ); } #[test] fn test_missing_ruby_gem() { assert_match( vec![ "Could not find gem 'childprocess (~> 0.5)', which is required by gem 'selenium-webdriver', in any of the sources." ], 1, Some(MissingRubyGem::new("childprocess".to_owned(), Some("0.5".to_owned()))), ); assert_match( vec![ "Could not find gem 'rexml', which is required by gem 'rubocop', in any of the sources." ], 1, Some(MissingRubyGem::simple("rexml".to_owned())), ); assert_match( vec![ "/usr/lib/ruby/2.5.0/rubygems/dependency.rb:310:in `to_specs': Could not find 'http-parser' (~> 1.2.0) among 59 total gem(s) (Gem::MissingSpecError)" ], 1, Some(MissingRubyGem::new("http-parser".to_owned(), Some("1.2.0".to_string()))), ); assert_match( vec![ "/usr/lib/ruby/2.5.0/rubygems/dependency.rb:312:in `to_specs': Could not find 'celluloid' (~> 0.17.3) - did find: [celluloid-0.16.0] (Gem::MissingSpecVersionError)" ], 1, Some(MissingRubyGem{gem:"celluloid".to_owned(), version:Some("0.17.3".to_owned())}), ); assert_match( vec![ "/usr/lib/ruby/2.5.0/rubygems/dependency.rb:312:in `to_specs': Could not find 'i18n' (~> 0.7) - did find: [i18n-1.5.3] (Gem::MissingSpecVersionError)" ], 1, Some(MissingRubyGem{gem:"i18n".to_owned(), version: Some("0.7".to_owned())}), ); assert_match( vec![ "/usr/lib/ruby/2.5.0/rubygems/dependency.rb:310:in `to_specs': Could not find 'sassc' (>= 2.0.0) among 34 total gem(s) (Gem::MissingSpecError)" ], 1, Some(MissingRubyGem{gem:"sassc".to_string(), version: Some("2.0.0".to_string())}), ); assert_match( vec![ "/usr/lib/ruby/2.7.0/bundler/resolver.rb:290:in `block in verify_gemfile_dependencies_are_found!': Could not find gem 'rake-compiler' in any of the gem sources listed in your Gemfile. (Bundler::GemNotFound)" ], 1, Some(MissingRubyGem::simple("rake-compiler".to_owned())), ); assert_match( vec![ "/usr/lib/ruby/2.7.0/rubygems.rb:275:in `find_spec_for_exe': can't find gem rdoc (>= 0.a) with executable rdoc (Gem::GemNotFoundException)" ], 1, Some(MissingRubyGem::new("rdoc".to_owned(), Some("0.a".to_owned()))), ); } #[test] fn test_missing_maven_artifacts() { assert_match( vec![ "[ERROR] Failed to execute goal on project byteman-bmunit5: Could not resolve dependencies for project org.jboss.byteman:byteman-bmunit5:jar:4.0.7: The following artifacts could not be resolved: org.junit.jupiter:junit-jupiter-api:jar:5.4.0, org.junit.jupiter:junit-jupiter-params:jar:5.4.0, org.junit.jupiter:junit-jupiter-engine:jar:5.4.0: Cannot access central (https://repo.maven.apache.org/maven2) in offline mode and the artifact org.junit.jupiter:junit-jupiter-api:jar:5.4.0 has not been downloaded from it before. -> [Help 1]" ], 1, Some(MissingMavenArtifacts( vec![ "org.junit.jupiter:junit-jupiter-api:jar:5.4.0".to_string(), "org.junit.jupiter:junit-jupiter-params:jar:5.4.0".to_string(), "org.junit.jupiter:junit-jupiter-engine:jar:5.4.0".to_string(), ] )), ); assert_match( vec![ "[ERROR] Failed to execute goal on project opennlp-uima: Could not resolve dependencies for project org.apache.opennlp:opennlp-uima:jar:1.9.2-SNAPSHOT: Cannot access ApacheIncubatorRepository (http://people.apache.org/repo/m2-incubating-repository/) in offline mode and the artifact org.apache.opennlp:opennlp-tools:jar:debian has not been downloaded from it before. -> [Help 1]" ], 1, Some(MissingMavenArtifacts(vec!["org.apache.opennlp:opennlp-tools:jar:debian".to_string()])), ); assert_match( vec![ "[ERROR] Failed to execute goal on project bookkeeper-server: Could not resolve dependencies for project org.apache.bookkeeper:bookkeeper-server:jar:4.4.0: Cannot access central (https://repo.maven.apache.org/maven2) in offline mode and the artifact io.netty:netty:jar:debian has not been downloaded from it before. -> [Help 1]" ], 1, Some(MissingMavenArtifacts(vec!["io.netty:netty:jar:debian".to_string()])), ); assert_match( vec![ "[ERROR] Unresolveable build extension: Plugin org.apache.felix:maven-bundle-plugin:2.3.7 or one of its dependencies could not be resolved: Cannot access central (https://repo.maven.apache.org/maven2) in offline mode and the artifact org.apache.felix:maven-bundle-plugin:jar:2.3.7 has not been downloaded from it before. @" ], 1, Some(MissingMavenArtifacts(vec!["org.apache.felix:maven-bundle-plugin:2.3.7".to_string()])), ); assert_match( vec![ "[ERROR] Plugin org.apache.maven.plugins:maven-jar-plugin:2.6 or one of its dependencies could not be resolved: Cannot access central (https://repo.maven.apache.org/maven2) in offline mode and the artifact org.apache.maven.plugins:maven-jar-plugin:jar:2.6 has not been downloaded from it before. -> [Help 1]" ], 1, Some(MissingMavenArtifacts(vec!["org.apache.maven.plugins:maven-jar-plugin:2.6".to_string()])), ); assert_match( vec![ "[FATAL] Non-resolvable parent POM for org.joda:joda-convert:2.2.1: Cannot access central (https://repo.maven.apache.org/maven2) in offline mode and the artifact org.joda:joda-parent:pom:1.4.0 has not been downloaded from it before. and 'parent.relativePath' points at wrong local POM @ line 8, column 10"], 1, Some(MissingMavenArtifacts(vec!["org.joda:joda-parent:pom:1.4.0".to_string()])), ); assert_match( vec![ "[ivy:retrieve] \t\t:: com.carrotsearch.randomizedtesting#junit4-ant;${/com.carrotsearch.randomizedtesting/junit4-ant}: not found" ], 1, Some(MissingMavenArtifacts( vec!["com.carrotsearch.randomizedtesting:junit4-ant:jar:debian".to_string()] )), ); assert_match( vec![ "[ERROR] Plugin org.apache.maven.plugins:maven-compiler-plugin:3.10.1 or one of its dependencies could not be resolved: Failed to read artifact descriptor for org.apache.maven.plugins:maven-compiler-plugin:jar:3.10.1: 1 problem was encountered while building the effective model for org.apache.maven.plugins:maven-compiler-plugin:3.10.1" ], 1, Some(MissingMavenArtifacts( vec!["org.apache.maven.plugins:maven-compiler-plugin:3.10.1".to_string()] )), ); } #[test] fn test_maven_errors() { assert_just_match( vec![ "[ERROR] Failed to execute goal org.apache.maven.plugins:maven-jar-plugin:3.1.2:jar (default-jar) on project xslthl: Execution default-jar of goal org.apache.maven.plugins:maven-jar-plugin:3.1.2:jar failed: An API incompatibility was encountered while executing org.apache.maven.plugins:maven-jar-plugin:3.1.2:jar: java.lang.NoSuchMethodError: 'void org.codehaus.plexus.util.DirectoryScanner.setFilenameComparator(java.util.Comparator)'"], 1, ); } #[test] fn test_dh_missing_uninstalled() { assert_match( vec![ "dh_missing --fail-missing", "dh_missing: usr/share/man/man1/florence_applet.1 exists in debian/tmp but is not installed to anywhere", "dh_missing: usr/lib/x86_64-linux-gnu/libflorence-1.0.la exists in debian/tmp but is not installed to anywhere", "dh_missing: missing files, aborting", ], 3, Some(DhMissingUninstalled("usr/lib/x86_64-linux-gnu/libflorence-1.0.la".to_owned())), ); } #[test] fn test_missing_perl_module() { assert_match( vec![ "Converting tags.ledger... Can't locate String/Interpolate.pm in @INC (you may need to install the String::Interpolate module) (@INC contains: /etc/perl /usr/local/lib/x86_64-linux-gnu/perl/5.28.1 /usr/local/share/perl/5.28.1 /usr/lib/x86_64-linux-gnu/perl5/5.28 /usr/share/perl5 /usr/lib/x86_64-linux-gnu/perl/5.28 /usr/share/perl/5.28 /usr/local/lib/site_perl /usr/lib/x86_64-linux-gnu/perl-base) at ../bin/ledger2beancount line 23." ], 1, Some(MissingPerlModule { filename: Some("String/Interpolate.pm".to_owned()), module: "String::Interpolate".to_owned(), inc: Some(vec![ "/etc/perl".to_owned(), "/usr/local/lib/x86_64-linux-gnu/perl/5.28.1".to_owned(), "/usr/local/share/perl/5.28.1".to_owned(), "/usr/lib/x86_64-linux-gnu/perl5/5.28".to_owned(), "/usr/share/perl5".to_owned(), "/usr/lib/x86_64-linux-gnu/perl/5.28".to_owned(), "/usr/share/perl/5.28".to_owned(), "/usr/local/lib/site_perl".to_owned(), "/usr/lib/x86_64-linux-gnu/perl-base".to_owned(), ]), minimum_version: None }), ); assert_match( vec![ "Can't locate Test/Needs.pm in @INC (you may need to install the Test::Needs module) (@INC contains: t/lib /<>/blib/lib /<>/blib/arch /etc/perl /usr/local/lib/x86_64-linux-gnu/perl/5.30.0 /usr/local/share/perl/5.30.0 /usr/lib/x86_64-linux-gnu/perl5/5.30 /usr/share/perl5 /usr/lib/x86_64-linux-gnu/perl/5.30 /usr/share/perl/5.30 /usr/local/lib/site_perl /usr/lib/x86_64-linux-gnu/perl-base .) at t/anon-basic.t line 7." ], 1, Some(MissingPerlModule{ filename: Some("Test/Needs.pm".to_owned()), module: "Test::Needs".to_owned(), inc: Some(vec![ "t/lib".to_owned(), "/<>/blib/lib".to_owned(), "/<>/blib/arch".to_owned(), "/etc/perl".to_owned(), "/usr/local/lib/x86_64-linux-gnu/perl/5.30.0".to_owned(), "/usr/local/share/perl/5.30.0".to_owned(), "/usr/lib/x86_64-linux-gnu/perl5/5.30".to_owned(), "/usr/share/perl5".to_owned(), "/usr/lib/x86_64-linux-gnu/perl/5.30".to_owned(), "/usr/share/perl/5.30".to_owned(), "/usr/local/lib/site_perl".to_owned(), "/usr/lib/x86_64-linux-gnu/perl-base".to_owned(), ".".to_owned(), ]), minimum_version: None }), ); assert_match( vec!["- ExtUtils::Depends ...missing. (would need 0.302)"], 1, Some(MissingPerlModule { filename: None, module: "ExtUtils::Depends".to_owned(), inc: None, minimum_version: Some("0.302".to_owned()), }), ); assert_match( vec![ r#"Can't locate object method "new" via package "Dist::Inkt::Profile::TOBYINK" (perhaps you forgot to load "Dist::Inkt::Profile::TOBYINK"?) at /usr/share/perl5/Dist/Inkt.pm line 208."#, ], 1, Some(MissingPerlModule::simple("Dist::Inkt::Profile::TOBYINK")), ); assert_match( vec![ "Can't locate ExtUtils/Depends.pm in @INC (you may need to install the ExtUtils::Depends module) (@INC contains: /etc/perl /usr/local/lib/x86_64-linux-gnu/perl/5.32.1 /usr/local/share/perl/5.32.1 /usr/lib/x86_64-linux-gnu/perl5/5.32 /usr/share/perl5 /usr/lib/x86_64-linux-gnu/perl-base /usr/lib/x86_64-linux-gnu/perl/5.32 /usr/share/perl/5.32 /usr/local/lib/site_perl) at (eval 11) line 1." ], 1, Some(MissingPerlModule{ filename: Some("ExtUtils/Depends.pm".to_owned()), module: "ExtUtils::Depends".to_owned(), inc: Some(vec![ "/etc/perl".to_owned(), "/usr/local/lib/x86_64-linux-gnu/perl/5.32.1".to_owned(), "/usr/local/share/perl/5.32.1".to_owned(), "/usr/lib/x86_64-linux-gnu/perl5/5.32".to_owned(), "/usr/share/perl5".to_owned(), "/usr/lib/x86_64-linux-gnu/perl-base".to_owned(), "/usr/lib/x86_64-linux-gnu/perl/5.32".to_owned(), "/usr/share/perl/5.32".to_owned(), "/usr/local/lib/site_perl".to_owned(), ]), minimum_version: None }), ); assert_match( vec![ "Pod::Weaver::Plugin::WikiDoc (for section -WikiDoc) does not appear to be installed" ], 1, Some(MissingPerlModule::simple("Pod::Weaver::Plugin::WikiDoc")), ); assert_match( vec![ "List::Util version 1.56 required--this is only version 1.55 at /build/tmpttq5hhpt/package/blib/lib/List/AllUtils.pm line 8." ], 1, Some(MissingPerlModule { filename: None, inc: None, module: "List::Util".to_owned(), minimum_version: Some("1.56".to_owned())}), ); } #[test] fn test_missing_perl_file() { assert_match( vec![ "Can't locate debian/perldl.conf in @INC (@INC contains: /<>/inc /etc/perl /usr/local/lib/x86_64-linux-gnu/perl/5.28.1 /usr/local/share/perl/5.28.1 /usr/lib/x86_64-linux-gnu/perl5/5.28 /usr/share/perl5 /usr/lib/x86_64-linux-gnu/perl/5.28 /usr/share/perl/5.28 /usr/local/lib/site_perl /usr/lib/x86_64-linux-gnu/perl-base) at Makefile.PL line 131." ], 1, Some(MissingPerlFile { filename: "debian/perldl.conf".to_owned(), inc: Some(vec![ "/<>/inc".to_owned(), "/etc/perl".to_owned(), "/usr/local/lib/x86_64-linux-gnu/perl/5.28.1".to_owned(), "/usr/local/share/perl/5.28.1".to_owned(), "/usr/lib/x86_64-linux-gnu/perl5/5.28".to_owned(), "/usr/share/perl5".to_owned(), "/usr/lib/x86_64-linux-gnu/perl/5.28".to_owned(), "/usr/share/perl/5.28".to_owned(), "/usr/local/lib/site_perl".to_owned(), "/usr/lib/x86_64-linux-gnu/perl-base".to_owned(), ]), }), ); assert_match( vec![r#"Can't open perl script "Makefile.PL": No such file or directory"#], 1, Some(MissingPerlFile { filename: "Makefile.PL".to_owned(), inc: None, }), ); } #[test] fn test_perl_expand() { assert_match( vec![">(error): Could not expand [ 'Dist::Inkt::Profile::TOBYINK'"], 1, Some(MissingPerlModule::simple("Dist::Inkt::Profile::TOBYINK")), ); } #[test] fn test_perl_missing_predeclared() { assert_match( vec![ "String found where operator expected at Makefile.PL line 13, near \"author_tests 'xt'\"", "\t(Do you need to predeclare author_tests?)", "syntax error at Makefile.PL line 13, near \"author_tests 'xt'\"", r#""strict subs" in use at Makefile.PL line 13."#, ], 2, Some(MissingPerlPredeclared("author_tests".to_owned())), ); assert_match( vec![ "String found where operator expected at Makefile.PL line 8, near \"readme_from 'lib/URL/Encode.pod'\"" ], 1, Some(MissingPerlPredeclared("readme_from".to_owned())), ); assert_match( vec![ r#"Bareword "use_test_base" not allowed while "strict subs" in use at Makefile.PL line 12."#, ], 1, Some(MissingPerlPredeclared("use_test_base".to_owned())), ); } #[test] fn test_unknown_cert_authority() { assert_match( vec![ r#"go: github.com/golangci/golangci-lint@v1.24.0: Get "https://proxy.golang.org/github.com/golangci/golangci-lint/@v/v1.24.0.mod": x509: certificate signed by unknown authority"#, ], 1, Some(UnknownCertificateAuthority( "https://proxy.golang.org/github.com/golangci/golangci-lint/@v/v1.24.0.mod" .to_owned(), )), ); } #[test] fn test_no_disk_space() { assert_match( vec![ "/usr/bin/install: error writing '/<>/debian/tmp/usr/lib/gcc/x86_64-linux-gnu/8/cc1objplus': No space left on device" ], 1, Some(NoSpaceOnDevice) ); assert_match( ["OSError: [Errno 28] No space left on device"].to_vec(), 1, Some(NoSpaceOnDevice), ); } #[test] fn test_segmentation_fault() { assert_just_match( vec![ r#"/bin/bash: line 3: 7392 Segmentation fault itstool -m "${mo}" ${d}/C/index.docbook ${d}/C/legal.xml"#, ], 1, ); } #[test] fn test_missing_perl_plugin() { assert_match( vec!["Required plugin bundle Dist::Zilla::PluginBundle::Git isn't installed."], 1, Some(MissingPerlModule::simple("Dist::Zilla::PluginBundle::Git")), ); assert_match( vec!["Required plugin Dist::Zilla::Plugin::PPPort isn't installed."], 1, Some(MissingPerlModule::simple("Dist::Zilla::Plugin::PPPort")), ); } #[test] fn test_nim_error() { assert_just_match( vec![ "/<>/msgpack4nim.nim(470, 6) Error: usage of 'isNil' is a user-defined error", ], 1, ); } #[test] fn test_scala_error() { assert_just_match( vec![ "core/src/main/scala/org/json4s/JsonFormat.scala:131: error: No JSON deserializer found for type List[T]. Try to implement an implicit Reader or JsonFormat for this type." ], 1, ); } #[test] fn test_vala_error() { assert_just_match( vec![ "../src/Backend/FeedServer.vala:60.98-60.148: error: The name `COLLECTION_CREATE_NONE' does not exist in the context of `Secret.CollectionCreateFlags'" ], 1, ); assert_match( vec![ "error: Package `glib-2.0' not found in specified Vala API directories or GObject-Introspection GIR directories" ], 1, Some(MissingValaPackage("glib-2.0".to_owned())), ); } #[test] fn test_gir() { assert_match( vec!["ValueError: Namespace GnomeDesktop not available"], 1, Some(MissingIntrospectionTypelib("GnomeDesktop".to_owned())), ); } #[test] fn test_missing_boost_components() { assert_match( r#"""CMake Error at /usr/share/cmake-3.18/Modules/FindPackageHandleStandardArgs.cmake:165 (message): Could NOT find Boost (missing: program_options filesystem system graph serialization iostreams) (found suitable version "1.74.0", minimum required is "1.55.0") Call Stack (most recent call first): /usr/share/cmake-3.18/Modules/FindPackageHandleStandardArgs.cmake:458 (_FPHSA_FAILURE_MESSAGE) /usr/share/cmake-3.18/Modules/FindBoost.cmake:2177 (find_package_handle_standard_args) src/CMakeLists.txt:4 (find_package) """#.split_inclusive('\n').collect::>(), 4, Some(MissingCMakeComponents{ name: "Boost".to_owned(), components: vec![ "program_options".to_owned(), "filesystem".to_owned(), "system".to_owned(), "graph".to_owned(), "serialization".to_owned(), "iostreams".to_owned(), ], }), ); } #[test] fn test_pkg_config_too_old() { assert_match( vec![ "checking for pkg-config... no", "", "*** Your version of pkg-config is too old. You need atleast", "*** pkg-config 0.9.0 or newer. You can download pkg-config", "*** from the freedesktop.org software repository at", "***", "*** https://www.freedesktop.org/wiki/Software/pkg-config/", "***", ], 4, Some(MissingVagueDependency { name: "pkg-config".to_owned(), minimum_version: Some("0.9.0".to_owned()), url: None, current_version: None, }), ); } #[test] fn test_missing_jdk() { assert_match( vec![ "> Kotlin could not find the required JDK tools in the Java installation '/usr/lib/jvm/java-8-openjdk-amd64/jre' used by Gradle. Make sure Gradle is running on a JDK, not JRE.", ], 1, Some(MissingJDK::new("/usr/lib/jvm/java-8-openjdk-amd64/jre".to_owned())), ); } #[test] fn test_missing_jre() { assert_match( vec!["ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH."], 1, Some(MissingJRE), ); } #[test] fn test_node_module_missing() { assert_match( vec!["Error: Cannot find module 'tape'"], 1, Some(MissingNodeModule("tape".to_owned())), ); assert_just_match( vec!["✖ ERROR: Cannot find module '/<>/test'"], 1, ); assert_match( vec!["npm ERR! [!] Error: Cannot find module '@rollup/plugin-buble'"], 1, Some(MissingNodeModule("@rollup/plugin-buble".to_owned())), ); assert_match( vec!["npm ERR! Error: Cannot find module 'fs-extra'"], 1, Some(MissingNodeModule("fs-extra".to_owned())), ); assert_match( vec!["\x1b[1m\x1b[31m[!] \x1b[1mError: Cannot find module '@rollup/plugin-buble'"], 1, Some(MissingNodeModule("@rollup/plugin-buble".to_owned())), ); } #[test] fn test_setup_py_command() { assert_match( r#"""/usr/lib/python3.9/distutils/dist.py:274: UserWarning: Unknown distribution option: 'long_description_content_type' warnings.warn(msg) /usr/lib/python3.9/distutils/dist.py:274: UserWarning: Unknown distribution option: 'test_suite' warnings.warn(msg) /usr/lib/python3.9/distutils/dist.py:274: UserWarning: Unknown distribution option: 'python_requires' warnings.warn(msg) usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...] or: setup.py --help [cmd1 cmd2 ...] or: setup.py --help-commands or: setup.py cmd --help error: invalid command 'test' """#.split_inclusive('\n').collect::>(), 12, Some(MissingSetupPyCommand("test".to_owned())), ); } #[test] fn test_c_header_missing() { assert_match( vec!["cdhit-common.h:39:9: fatal error: zlib.h: No such file or directory"], 1, Some(MissingCHeader { header: "zlib.h".to_owned(), }), ); assert_match( vec![ "/<>/Kernel/Operation_Vector.cpp:15:10: fatal error: petscvec.h: No such file or directory" ], 1, Some(MissingCHeader{header: "petscvec.h".to_owned()}), ); assert_match( vec!["src/bubble.h:27:10: fatal error: DBlurEffectWidget: No such file or directory"], 1, Some(MissingCHeader { header: "DBlurEffectWidget".to_owned(), }), ); } #[test] fn test_missing_jdk_file() { assert_match( vec![ "> Could not find tools.jar. Please check that /usr/lib/jvm/java-8-openjdk-amd64 contains a valid JDK installation.", ], 1, Some(MissingJDKFile{jdk_path: "/usr/lib/jvm/java-8-openjdk-amd64".to_owned(), filename: "tools.jar".to_owned()}), ); } #[test] fn test_python2_import() { assert_match( vec!["ImportError: No module named pytz"], 1, Some(MissingPythonModule::simple("pytz".to_owned())), ); assert_just_match(vec!["ImportError: cannot import name SubfieldBase"], 1); } #[test] fn test_python3_import() { assert_match( ["ModuleNotFoundError: No module named 'django_crispy_forms'"].to_vec(), 1, Some(MissingPythonModule { module: "django_crispy_forms".to_owned(), python_version: Some(3), minimum_version: None, }), ); assert_match( [" ModuleNotFoundError: No module named 'Cython'"].to_vec(), 1, Some(MissingPythonModule { module: "Cython".to_owned(), python_version: Some(3), minimum_version: None, }), ); assert_match( ["ModuleNotFoundError: No module named 'distro'"].to_vec(), 1, Some(MissingPythonModule { module: "distro".to_owned(), python_version: Some(3), minimum_version: None, }), ); assert_match( ["E ModuleNotFoundError: No module named 'twisted'"].to_vec(), 1, Some(MissingPythonModule { module: "twisted".to_owned(), python_version: Some(3), minimum_version: None, }), ); assert_match( vec![ "E ImportError: cannot import name 'async_poller' from 'msrest.polling' (/usr/lib/python3/dist-packages/msrest/polling/__init__.py)" ], 1, Some(MissingPythonModule::simple("msrest.polling.async_poller".to_owned())), ); assert_match( vec!["/usr/bin/python3: No module named sphinx"], 1, Some(MissingPythonModule { module: "sphinx".to_owned(), python_version: Some(3), minimum_version: None, }), ); assert_match( vec![ "Could not import extension sphinx.ext.pngmath (exception: No module named pngmath)" ], 1, Some(MissingPythonModule::simple("pngmath".to_owned())), ); assert_match( vec![ "/usr/bin/python3: Error while finding module specification for 'pep517.build' (ModuleNotFoundError: No module named 'pep517')" ], 1, Some(MissingPythonModule{module: "pep517".to_owned(), python_version:Some(3), minimum_version: None}), ); } #[test] fn test_sphinx() { assert_just_match( vec!["There is a syntax error in your configuration file: Unknown syntax: Constant"], 1, ); } #[test] fn test_go_missing() { assert_match( vec![ r#"src/github.com/vuls/config/config.go:30:2: cannot find package "golang.org/x/xerrors" in any of:"#, ], 1, Some(MissingGoPackage { package: "golang.org/x/xerrors".to_owned(), }), ); } #[test] fn test_lazy_font() { assert_match( vec![ "[ERROR] LazyFont - Failed to read font file /usr/share/texlive/texmf-dist/fonts/opentype/public/stix2-otf/STIX2Math.otf java.io.FileNotFoundException: /usr/share/texlive/texmf-dist/fonts/opentype/public/stix2-otf/STIX2Math.otf (No such file or directory)"], 1, Some(MissingFile::new( "/usr/share/texlive/texmf-dist/fonts/opentype/public/stix2-otf/STIX2Math.otf".into() )), ); } #[test] fn test_missing_latex_files() { assert_match( vec!["! LaTeX Error: File `fancyvrb.sty' not found."], 1, Some(MissingLatexFile("fancyvrb.sty".to_owned())), ); } #[test] fn test_pytest_import() { assert_match( vec!["E ImportError: cannot import name cmod"], 1, Some(MissingPythonModule::simple("cmod".to_owned())), ); assert_match( vec!["E ImportError: No module named mock"], 1, Some(MissingPythonModule::simple("mock".to_owned())), ); assert_match( vec![ "pluggy.manager.PluginValidationError: Plugin 'xdist.looponfail' could not be loaded: (pytest 3.10.1 (/usr/lib/python2.7/dist-packages), Requirement.parse('pytest>=4.4.0'))!" ], 1, Some(MissingPythonModule{ module: "pytest".to_owned(), python_version: Some(2), minimum_version: Some("4.4.0".to_owned()) }), ); assert_match( vec![ r#"ImportError: Error importing plugin "tests.plugins.mock_libudev": No module named mock"#, ], 1, Some(MissingPythonModule::simple("mock".to_owned())), ); } #[test] fn test_sed() { assert_match( vec!["sed: can't read /etc/locale.gen: No such file or directory"], 1, Some(MissingFile::new("/etc/locale.gen".into())), ); } #[test] fn test_pytest_args() { assert_match( vec![ "pytest: error: unrecognized arguments: --cov=janitor --cov-report=html --cov-report=term-missing:skip-covered" ], 1, Some(UnsupportedPytestArguments( vec![ "--cov=janitor".to_owned(), "--cov-report=html".to_owned(), "--cov-report=term-missing:skip-covered".to_owned(), ] )), ); } #[test] fn test_pytest_config() { assert_match( vec!["INTERNALERROR> pytest.PytestConfigWarning: Unknown config option: asyncio_mode"], 1, Some(UnsupportedPytestConfigOption("asyncio_mode".to_owned())), ); } #[test] fn test_distutils_missing() { assert_match( vec![ "distutils.errors.DistutilsError: Could not find suitable distribution for Requirement.parse('pytest-runner')" ], 1, Some(MissingPythonDistribution::simple("pytest-runner")), ); assert_match( vec![ "distutils.errors.DistutilsError: Could not find suitable distribution for Requirement.parse('certifi>=2019.3.9')" ], 1, Some(MissingPythonDistribution{distribution: "certifi".to_owned(), minimum_version: Some("2019.3.9".to_owned()), python_version: None }), ); assert_match( vec![ r#"distutils.errors.DistutilsError: Could not find suitable distribution for Requirement.parse('cffi; platform_python_implementation == "CPython"\')"#, ], 1, Some(MissingPythonDistribution::simple("cffi")), ); assert_match( vec!["error: Could not find suitable distribution for Requirement.parse('gitlab')"], 1, Some(MissingPythonDistribution::simple("gitlab")), ); assert_match( vec![ "pkg_resources.DistributionNotFound: The 'configparser>=3.5' distribution was not found and is required by importlib-metadata" ], 1, Some(MissingPythonDistribution{distribution:"configparser".to_owned(), minimum_version: Some("3.5".to_owned()), python_version: None}), ); assert_match( vec![ "error: Command '['/usr/bin/python3.9', '-m', 'pip', '--disable-pip-version-check', 'wheel', '--no-deps', '-w', '/tmp/tmp973_8lhm', '--quiet', 'asynctest']' returned non-zero exit status 1." ], 1, Some(MissingPythonDistribution{distribution: "asynctest".to_owned(), python_version:Some(3), minimum_version: None}), ); assert_match( vec![ "subprocess.CalledProcessError: Command '['/usr/bin/python', '-m', 'pip', '--disable-pip-version-check', 'wheel', '--no-deps', '-w', '/tmp/tmpm2l3kcgv', '--quiet', 'setuptools_scm']' returned non-zero exit status 1." ], 1, Some(MissingPythonDistribution::simple("setuptools-scm")), ); } #[test] fn test_cmake_missing_file() { assert_match( r#"""CMake Error at /usr/lib/x86_64-/cmake/Qt5Gui/Qt5GuiConfig.cmake:27 (message): The imported target "Qt5::Gui" references the file "/usr/lib/x86_64-linux-gnu/libEGL.so" but this file does not exist. Possible reasons include: * The file was deleted, renamed, or moved to another location. * An install or uninstall procedure did not complete successfully. * The installation package was faulty and contained "/usr/lib/x86_64-linux-gnu/cmake/Qt5Gui/Qt5GuiConfigExtras.cmake" but not all the files it references. Call Stack (most recent call first): /usr/lib/x86_64-linux-gnu/QtGui/Qt5Gui.cmake:63 (_qt5_Gui_check_file_exists) /usr/lib/x86_64-linux-gnu/QtGui/Qt5Gui.cmake:85 (_qt5gui_find_extra_libs) /usr/lib/x86_64-linux-gnu/QtGui/Qt5Gui.cmake:186 (include) /usr/lib/x86_64-linux-gnu/QtWidgets/Qt5Widgets.cmake:101 (find_package) /usr/lib/x86_64-linux-gnu/Qt/Qt5Config.cmake:28 (find_package) CMakeLists.txt:34 (find_package) dh_auto_configure: cd obj-x86_64-linux-gnu && cmake with args """# .split_inclusive('\n') .collect::>(), 16, Some(MissingFile::new( "/usr/lib/x86_64-linux-gnu/libEGL.so".into(), )), ); } #[test] fn test_cmake_missing_include() { assert_match( r#"""-- Performing Test _OFFT_IS_64BIT -- Performing Test _OFFT_IS_64BIT - Success -- Performing Test HAVE_DATE_TIME -- Performing Test HAVE_DATE_TIME - Success CMake Error at CMakeLists.txt:43 (include): include could not find load file: KDEGitCommitHooks -- Found KF5Activities: /usr/lib/x86_64-linux-gnu/cmake/KF5Activities/KF5ActivitiesConfig.cmake (found version "5.78.0") -- Found KF5Config: /usr/lib/x86_64-linux-gnu/cmake/KF5Config/KF5ConfigConfig.cmake (found version "5.78.0") """#.split_inclusive('\n').collect::>(), 8, Some(CMakeFilesMissing{filenames:vec!["KDEGitCommitHooks.cmake".to_string()], version :None}), ); } #[test] fn test_cmake_missing_cmake_files() { assert_match( r#"""CMake Error at /usr/share/cmake-3.22/Modules/FindPackageHandleStandardArgs.cmake:230 (message): Could not find a package configuration file provided by "sensor_msgs" with any of the following names: sensor_msgsConfig.cmake sensor_msgs-config.cmake Add the installation prefix of "sensor_msgs" to CMAKE_PREFIX_PATH or set "sensor_msgs_DIR" to a directory containing one of the above files. If "sensor_msgs" provides a separate development package or SDK, be sure it has been installed. dh_auto_configure: cd obj-x86_64-linux-gnu && cmake with args """# .split_inclusive('\n') .collect::>(), 11, Some(CMakeFilesMissing { filenames: vec![ "sensor_msgsConfig.cmake".to_string(), "sensor_msgs-config.cmake".to_string(), ], version: None, }), ); assert_match( r#"""CMake Error at /usr/share/cmake-3.22/Modules/FindPackageHandleStandardArgs.cmake:230 (message): Could NOT find KF5 (missing: Plasma PlasmaQuick Wayland ModemManagerQt NetworkManagerQt) (found suitable version "5.92.0", minimum required is "5.86") """#.split_inclusive('\n').collect::>(), 4, Some(MissingCMakeComponents{ name: "KF5".into(), components: vec![ "Plasma".into(), "PlasmaQuick".into(), "Wayland".into(), "ModemManagerQt".into(), "NetworkManagerQt".into(), ], }), ); } #[test] fn test_cmake_missing_exact_version() { assert_match( r#"""CMake Error at /usr/share/cmake-3.18/Modules/FindPackageHandleStandardArgs.cmake:165 (message): Could NOT find SignalProtocol: Found unsuitable version "2.3.3", but required is exact version "2.3.2" (found /usr/lib/x86_64-linux-gnu/libsignal-protocol-c.so) """#.split_inclusive('\n').collect::>(), 4, Some(CMakeNeedExactVersion{ package: "SignalProtocol".to_owned(), version_found: "2.3.3".to_owned(), exact_version_needed: "2.3.2".to_owned(), path: "/usr/lib/x86_64-linux-gnu/libsignal-protocol-c.so".into(), }), ); } #[test] fn test_cmake_missing_vague() { assert_match( vec![ "CMake Error at CMakeLists.txt:84 (MESSAGE):", " alut not found", ], 2, Some(MissingVagueDependency::simple("alut")), ); assert_match( vec![ "CMake Error at CMakeLists.txt:213 (message):", " could not find zlib", ], 2, Some(MissingVagueDependency::simple("zlib")), ); assert_match( r#"""-- Found LibSolv_ext: /usr/lib/x86_64-linux-gnu/libsolvext.so -- Found LibSolv: /usr/include /usr/lib/x86_64-linux-gnu/libsolv.so;/usr/lib/x86_64-linux-gnu/libsolvext.so -- No usable gpgme flavours found. CMake Error at cmake/modules/FindGpgme.cmake:398 (message): Did not find GPGME Call Stack (most recent call first): CMakeLists.txt:223 (FIND_PACKAGE) """#.split_inclusive('\n').collect::>(), 5, Some(MissingVagueDependency::simple("GPGME")), ); } #[test] fn test_secondary() { assert!(super::find_secondary_build_failure(&["Unknown option --foo"], 10).is_some()); assert!( super::find_secondary_build_failure(&["Unknown option --foo, ignoring."], 10).is_none() ); } #[test] fn test_missing_haskell_dependencies_trait() { let problem = MissingHaskellDependencies(vec!["base".to_string(), "text".to_string()]); assert_eq!(problem.kind(), "missing-haskell-dependencies"); let json = problem.json(); assert_eq!(json["deps"], serde_json::json!(["base", "text"])); } #[test] fn test_missing_ruby_file_trait() { let problem = MissingRubyFile { filename: "foo.rb".to_string(), }; let json = problem.json(); assert_eq!(json["filename"], "foo.rb"); } #[test] fn test_missing_configure_trait() { let problem = MissingConfigure; assert_eq!(problem.kind(), "missing-configure"); } #[test] fn test_missing_latex_file_trait() { let problem = MissingLatexFile("article.cls".to_string()); assert_eq!(problem.kind(), "missing-latex-file"); } #[test] fn test_debhelper_pattern_not_found_trait() { let problem = DebhelperPatternNotFound { pattern: "debian/tmp/usr/lib/*".to_string(), tool: "dh_install".to_string(), directories: vec![], }; assert_eq!(problem.kind(), "debhelper-pattern-not-found"); } #[test] fn test_vcs_control_directory_needed_trait() { let problem = VcsControlDirectoryNeeded { vcs: vec![".git".to_string()], }; assert_eq!(problem.kind(), "vcs-control-directory-needed"); } #[test] fn test_missing_ocaml_package_trait() { let problem = MissingOCamlPackage("lwt".to_string()); let json = problem.json(); assert_eq!(json["package"], "lwt"); } #[test] fn test_missing_python_module_display() { let problem = MissingPythonModule { module: "numpy".to_string(), python_version: Some(3), minimum_version: None, }; let display = format!("{}", problem); assert!(display.contains("numpy")); } #[test] fn test_missing_go_runtime_trait() { let problem = MissingGoRuntime; assert_eq!(problem.kind(), "missing-go-runtime"); } #[test] fn test_unknown_certificate_authority_trait() { let problem = UnknownCertificateAuthority("https://example.com".to_string()); assert_eq!(problem.kind(), "unknown-certificate-authority"); } #[test] fn test_missing_command_or_build_file_trait() { let problem = MissingCommandOrBuildFile { filename: "make".to_string(), }; assert_eq!(problem.kind(), "missing-command-or-build-file"); } #[test] fn test_missing_php_class_trait() { let problem = MissingPhpClass::new("PHPUnit\\Framework\\TestCase".to_string()); assert_eq!(problem.kind(), "missing-php-class"); } #[test] fn test_missing_go_mod_file_display() { let problem = MissingGoModFile; let display = format!("{}", problem); assert_eq!(display, "go.mod file is missing"); } #[test] fn test_backtrack_limit_handling() { // Test that find_secondary_build_failure handles long lines gracefully // without panicking on BacktrackLimitExceeded errors // Create a line that could cause backtracking issues with some regex patterns let long_line = format!("error: {}", "a".repeat(5000)); let lines = vec![long_line.as_str()]; // This should not panic and should return gracefully let _result = find_secondary_build_failure(&lines, 1); // The result could be Some or None, but it shouldn't panic // Test that normal error lines still work let normal_line = "Unknown option --foo"; let lines = vec![normal_line]; let result = find_secondary_build_failure(&lines, 1); assert!(result.is_some()); } } buildlog-consultant-0.1.4/src/cudf.rs000064400000000000000000000212021046102023000156720ustar 00000000000000//! Module for parsing CUDF (Common Upgradeability Description Format) files. //! //! CUDF is a format used for representing package dependency information, //! particularly in the context of dependency resolution problems in package managers. //! This module provides structures and deserialization logic for CUDF files. use debversion::Version; use serde::Deserialize; /// Deserializes a string representation of a version number into a (major, minor) tuple. /// /// This function is used with serde's custom deserializer to convert version strings /// like "1.2" into (1, 2). /// /// # Arguments /// * `deserializer` - The deserializer to use /// /// # Returns /// A result containing either the parsed (major, minor) tuple or an error fn deserialize_output_version<'de, D>(deserializer: D) -> Result<(u8, u8), D::Error> where D: serde::Deserializer<'de>, { let s: String = Deserialize::deserialize(deserializer)?; let (major, minor) = s .split_once('.') .ok_or(serde::de::Error::custom("invalid version string"))?; let major = major.parse().map_err(serde::de::Error::custom)?; let minor = minor.parse().map_err(serde::de::Error::custom)?; Ok((major, minor)) } /// Root structure representing a CUDF document. /// /// This structure represents the top-level of a CUDF document, containing /// version information, architecture, and a list of reports. #[derive(Deserialize, Clone, Debug, PartialEq, Eq)] pub(crate) struct Cudf { /// The version of the CUDF output format. #[serde( rename = "output-version", deserialize_with = "deserialize_output_version" )] pub output_version: (u8, u8), /// The native architecture for the reported packages. #[serde(rename = "native-architecture")] pub native_architecture: String, /// A list of reports about package issues. pub report: Vec, } /// Status of a package as reported in CUDF. /// /// This enum represents the possible status values for packages in a CUDF report. #[derive(Deserialize, Clone, Debug, Eq, PartialEq)] pub enum Status { /// Indicates that a package is broken due to dependency or conflict issues. #[serde(rename = "broken")] Broken, } /// A report about a package in a CUDF document. /// /// This structure represents a report about a specific package, /// including its status and the reasons for any issues. #[derive(Deserialize, Clone, Debug, PartialEq, Eq)] pub(crate) struct Report { /// The name of the package. pub package: String, /// The version of the package. pub version: Version, /// The architecture of the package. pub architecture: String, /// The status of the package (e.g., broken). pub status: Status, /// The reasons why the package has the given status. pub reasons: Vec, } /// A reason for a package's status in a CUDF report. /// /// This structure represents a reason why a package has a particular status, /// such as missing dependencies or conflicts with other packages. #[derive(Deserialize, Clone, Debug, PartialEq, Eq)] pub(crate) struct Reason { /// Information about missing dependencies, if applicable. pub missing: Option, /// Information about package conflicts, if applicable. pub conflict: Option, } /// Information about a missing dependency. /// /// This structure contains information about a package with a missing dependency. #[derive(Deserialize, Clone, Debug, PartialEq, Eq)] pub(crate) struct Missing { /// The package with the missing dependency. pub pkg: Pkg, } /// Information about a package conflict. /// /// This structure contains information about two packages that conflict with each other. #[derive(Deserialize, Clone, Debug, PartialEq, Eq)] pub(crate) struct Conflict { /// The first package in the conflict. pub pkg1: Pkg, /// The second package in the conflict. pub pkg2: Pkg, } /// Information about a package in a CUDF report. /// /// This structure represents a package, including information about /// its unsatisfied dependencies or conflicts. #[derive(Deserialize, Clone, Debug, PartialEq, Eq)] pub(crate) struct Pkg { /// The name of the package. pub package: String, /// The version of the package. pub version: Version, /// The architecture of the package. pub architecture: String, /// The unsatisfied dependency, if any. #[serde(rename = "unsat-dependency")] pub unsat_dependency: Option, /// The unsatisfied conflict, if any. #[serde(rename = "unsat-conflict)")] pub unsat_conflict: Option, } #[cfg(test)] mod tests { use super::*; use serde_json::json; #[test] fn test_deserialize_output_version() { let result: Result<(u8, u8), _> = deserialize_output_version(&mut serde_json::Deserializer::from_str("\"2.0\"")); assert!(result.is_ok()); assert_eq!(result.unwrap(), (2, 0)); } #[test] fn test_deserialize_output_version_invalid() { let result: Result<(u8, u8), _> = deserialize_output_version(&mut serde_json::Deserializer::from_str("\"invalid\"")); assert!(result.is_err()); } #[test] fn test_cudf_deserialization() { let json = json!({ "output-version": "2.0", "native-architecture": "amd64", "report": [ { "package": "libfoo", "version": "1.0-1", "architecture": "amd64", "status": "broken", "reasons": [ { "missing": { "pkg": { "package": "libbar", "version": "2.0-1", "architecture": "amd64", "unsat-dependency": "libzlib (>= 3.0)" } } } ] } ] }); let cudf: Cudf = serde_json::from_value(json).unwrap(); assert_eq!(cudf.output_version, (2, 0)); assert_eq!(cudf.native_architecture, "amd64"); assert_eq!(cudf.report.len(), 1); let report = &cudf.report[0]; assert_eq!(report.package, "libfoo"); assert_eq!(report.version.to_string(), "1.0-1"); assert_eq!(report.architecture, "amd64"); assert_eq!(report.status, Status::Broken); assert_eq!(report.reasons.len(), 1); let reason = &report.reasons[0]; assert!(reason.missing.is_some()); assert!(reason.conflict.is_none()); let missing = reason.missing.as_ref().unwrap(); assert_eq!(missing.pkg.package, "libbar"); assert_eq!(missing.pkg.version.to_string(), "2.0-1"); assert_eq!(missing.pkg.architecture, "amd64"); assert_eq!( missing.pkg.unsat_dependency, Some("libzlib (>= 3.0)".to_string()) ); assert_eq!(missing.pkg.unsat_conflict, None); } #[test] fn test_cudf_with_conflict() { let json = json!({ "output-version": "2.0", "native-architecture": "amd64", "report": [ { "package": "libfoo", "version": "1.0-1", "architecture": "amd64", "status": "broken", "reasons": [ { "conflict": { "pkg1": { "package": "libbar", "version": "2.0-1", "architecture": "amd64" }, "pkg2": { "package": "libbaz", "version": "3.0-1", "architecture": "amd64" } } } ] } ] }); let cudf: Cudf = serde_json::from_value(json).unwrap(); assert_eq!(cudf.output_version, (2, 0)); let report = &cudf.report[0]; let reason = &report.reasons[0]; assert!(reason.missing.is_none()); assert!(reason.conflict.is_some()); let conflict = reason.conflict.as_ref().unwrap(); assert_eq!(conflict.pkg1.package, "libbar"); assert_eq!(conflict.pkg1.version.to_string(), "2.0-1"); assert_eq!(conflict.pkg2.package, "libbaz"); assert_eq!(conflict.pkg2.version.to_string(), "3.0-1"); } } buildlog-consultant-0.1.4/src/lib.rs000064400000000000000000000362351046102023000155330ustar 00000000000000//! Buildlog-consultant provides tools for analyzing build logs to identify problems. //! //! This crate contains functionality for parsing and analyzing build logs from various //! build systems, primarily focusing on Debian package building tools. #![deny(missing_docs)] use std::borrow::Cow; use std::ops::Index; /// Module for handling apt-related logs and problems. pub mod apt; /// Module for processing autopkgtest logs. pub mod autopkgtest; /// Module for Bazaar (brz) version control system logs. pub mod brz; /// Module for Common Upgradeability Description Format (CUDF) logs. pub mod cudf; /// Module for line-level processing. pub mod lines; /// Module containing problem definitions for various build systems. pub mod problems; #[cfg(feature = "chatgpt")] /// Module for interacting with ChatGPT for log analysis. pub mod chatgpt; /// Common utilities and helpers for build log analysis. pub mod common; /// Match-related functionality for finding patterns in logs. pub mod r#match; /// Module for handling sbuild logs and related problems. pub mod sbuild; #[cfg(test)] mod tests { use super::*; #[test] fn test_singlelinematch_line() { let m = SingleLineMatch { origin: Origin("test".to_string()), offset: 10, line: "test line".to_string(), }; assert_eq!(m.line(), "test line"); } #[test] fn test_singlelinematch_origin() { let m = SingleLineMatch { origin: Origin("test".to_string()), offset: 10, line: "test line".to_string(), }; let origin = m.origin(); assert_eq!(origin.as_str(), "test"); } #[test] fn test_singlelinematch_offset() { let m = SingleLineMatch { origin: Origin("test".to_string()), offset: 10, line: "test line".to_string(), }; assert_eq!(m.offset(), 10); } #[test] fn test_singlelinematch_lineno() { let m = SingleLineMatch { origin: Origin("test".to_string()), offset: 10, line: "test line".to_string(), }; assert_eq!(m.lineno(), 11); } #[test] fn test_singlelinematch_linenos() { let m = SingleLineMatch { origin: Origin("test".to_string()), offset: 10, line: "test line".to_string(), }; assert_eq!(m.linenos(), vec![11]); } #[test] fn test_singlelinematch_offsets() { let m = SingleLineMatch { origin: Origin("test".to_string()), offset: 10, line: "test line".to_string(), }; assert_eq!(m.offsets(), vec![10]); } #[test] fn test_singlelinematch_lines() { let m = SingleLineMatch { origin: Origin("test".to_string()), offset: 10, line: "test line".to_string(), }; assert_eq!(m.lines(), vec!["test line"]); } #[test] fn test_singlelinematch_add_offset() { let m = SingleLineMatch { origin: Origin("test".to_string()), offset: 10, line: "test line".to_string(), }; let new_m = m.add_offset(5); assert_eq!(new_m.offset(), 15); } #[test] fn test_multilinelmatch_line() { let m = MultiLineMatch { origin: Origin("test".to_string()), offsets: vec![10, 11, 12], lines: vec![ "line 1".to_string(), "line 2".to_string(), "line 3".to_string(), ], }; assert_eq!(m.line(), "line 3"); } #[test] fn test_multilinelmatch_origin() { let m = MultiLineMatch { origin: Origin("test".to_string()), offsets: vec![10, 11, 12], lines: vec![ "line 1".to_string(), "line 2".to_string(), "line 3".to_string(), ], }; let origin = m.origin(); assert_eq!(origin.as_str(), "test"); } #[test] fn test_multilinelmatch_offset() { let m = MultiLineMatch { origin: Origin("test".to_string()), offsets: vec![10, 11, 12], lines: vec![ "line 1".to_string(), "line 2".to_string(), "line 3".to_string(), ], }; assert_eq!(m.offset(), 12); } #[test] fn test_multilinelmatch_lineno() { let m = MultiLineMatch { origin: Origin("test".to_string()), offsets: vec![10, 11, 12], lines: vec![ "line 1".to_string(), "line 2".to_string(), "line 3".to_string(), ], }; assert_eq!(m.lineno(), 13); } #[test] fn test_multilinelmatch_offsets() { let m = MultiLineMatch { origin: Origin("test".to_string()), offsets: vec![10, 11, 12], lines: vec![ "line 1".to_string(), "line 2".to_string(), "line 3".to_string(), ], }; assert_eq!(m.offsets(), vec![10, 11, 12]); } #[test] fn test_multilinelmatch_lines() { let m = MultiLineMatch { origin: Origin("test".to_string()), offsets: vec![10, 11, 12], lines: vec![ "line 1".to_string(), "line 2".to_string(), "line 3".to_string(), ], }; assert_eq!(m.lines(), vec!["line 1", "line 2", "line 3"]); } #[test] fn test_multilinelmatch_add_offset() { let m = MultiLineMatch { origin: Origin("test".to_string()), offsets: vec![10, 11, 12], lines: vec![ "line 1".to_string(), "line 2".to_string(), "line 3".to_string(), ], }; let new_m = m.add_offset(5); assert_eq!(new_m.offsets(), vec![15, 16, 17]); } #[test] fn test_highlight_lines() { let lines = vec!["line 1", "line 2", "line 3", "line 4", "line 5"]; let m = SingleLineMatch { origin: Origin("test".to_string()), offset: 2, line: "line 3".to_string(), }; // This test just ensures the function doesn't panic highlight_lines(&lines, &m, 1); } } /// Trait for representing a match of content in a log file. /// /// This trait defines the interface for working with matched content in logs, /// providing methods to access the content and its location information. pub trait Match: Send + Sync + std::fmt::Debug + std::fmt::Display { /// Returns the matched line of text. fn line(&self) -> &str; /// Returns the origin information for this match. fn origin(&self) -> &Origin; /// Returns the 0-based offset of the match in the source. fn offset(&self) -> usize; /// Returns the 1-based line number of the match in the source. fn lineno(&self) -> usize { self.offset() + 1 } /// Returns all 1-based line numbers for this match. fn linenos(&self) -> Vec { self.offsets().iter().map(|&x| x + 1).collect() } /// Returns all 0-based offsets for this match. fn offsets(&self) -> Vec; /// Returns all lines of text in this match. fn lines(&self) -> Vec<&str>; /// Creates a new match with all offsets shifted by the given amount. fn add_offset(&self, offset: usize) -> Box; } /// Source identifier for a match. /// /// This struct represents the source/origin of a match, typically a file name or other identifier. #[derive(Clone, Debug)] pub struct Origin(String); impl Origin { /// Returns the inner string value. pub fn as_str(&self) -> &str { &self.0 } } impl std::fmt::Display for Origin { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.0) } } /// A match for a single line in a log file. /// /// This struct implements the `Match` trait for single-line matches. #[derive(Clone, Debug)] pub struct SingleLineMatch { /// Source identifier for the match. pub origin: Origin, /// Zero-based line offset in the source. pub offset: usize, /// The matched line content. pub line: String, } impl Match for SingleLineMatch { fn line(&self) -> &str { &self.line } fn origin(&self) -> &Origin { &self.origin } fn offset(&self) -> usize { self.offset } fn offsets(&self) -> Vec { vec![self.offset] } fn lines(&self) -> Vec<&str> { vec![&self.line] } fn add_offset(&self, offset: usize) -> Box { Box::new(Self { origin: self.origin.clone(), offset: self.offset + offset, line: self.line.clone(), }) } } impl std::fmt::Display for SingleLineMatch { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}:{}: {}", self.origin.0, self.lineno(), self.line) } } impl SingleLineMatch { /// Creates a new `SingleLineMatch` from a collection of lines, an offset, and an optional origin. /// /// # Arguments /// * `lines` - Collection of lines that can be indexed /// * `offset` - Zero-based offset of the line to match /// * `origin` - Optional source identifier /// /// # Returns /// A new `SingleLineMatch` instance pub fn from_lines<'a>( lines: &impl Index, offset: usize, origin: Option<&str>, ) -> Self { let line = &lines[offset]; let origin = origin .map(|s| Origin(s.to_string())) .unwrap_or_else(|| Origin("".to_string())); Self { origin, offset, line: line.to_string(), } } } /// A match for multiple consecutive lines in a log file. /// /// This struct implements the `Match` trait for multi-line matches. #[derive(Clone, Debug)] pub struct MultiLineMatch { /// Source identifier for the match. pub origin: Origin, /// Zero-based line offsets for each matching line. pub offsets: Vec, /// The matched line contents. pub lines: Vec, } impl MultiLineMatch { /// Creates a new `MultiLineMatch` with the specified origin, offsets, and lines. /// /// # Arguments /// * `origin` - The source identifier /// * `offsets` - Vector of zero-based line offsets /// * `lines` - Vector of matched line contents /// /// # Returns /// A new `MultiLineMatch` instance pub fn new(origin: Origin, offsets: Vec, lines: Vec) -> Self { assert!(!offsets.is_empty()); assert!(offsets.len() == lines.len()); Self { origin, offsets, lines, } } /// Creates a new `MultiLineMatch` from a collection of lines, a vector of offsets, and an optional origin. /// /// # Arguments /// * `lines` - Collection of lines that can be indexed /// * `offsets` - Vector of zero-based line offsets to match /// * `origin` - Optional source identifier /// /// # Returns /// A new `MultiLineMatch` instance pub fn from_lines<'a>( lines: &impl Index, offsets: Vec, origin: Option<&str>, ) -> Self { let lines = offsets .iter() .map(|&offset| lines[offset].to_string()) .collect(); let origin = origin .map(|s| Origin(s.to_string())) .unwrap_or_else(|| Origin("".to_string())); Self::new(origin, offsets, lines) } } impl Match for MultiLineMatch { fn line(&self) -> &str { self.lines .last() .expect("MultiLineMatch should have at least one line") } fn origin(&self) -> &Origin { &self.origin } fn offset(&self) -> usize { *self .offsets .last() .expect("MultiLineMatch should have at least one offset") } fn lineno(&self) -> usize { self.offset() + 1 } fn offsets(&self) -> Vec { self.offsets.clone() } fn lines(&self) -> Vec<&str> { self.lines.iter().map(|s| s.as_str()).collect() } fn add_offset(&self, extra: usize) -> Box { let offsets = self.offsets.iter().map(|&offset| offset + extra).collect(); Box::new(Self { origin: self.origin.clone(), offsets, lines: self.lines.clone(), }) } } impl std::fmt::Display for MultiLineMatch { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}:{}: {}", self.origin.0, self.lineno(), self.line()) } } /// Trait for representing a problem found in build logs. /// /// This trait defines the interface for working with problems identified in build logs, /// providing methods to access problem information and properties. pub trait Problem: std::fmt::Display + Send + Sync + std::fmt::Debug { /// Returns the kind/type of problem. fn kind(&self) -> Cow<'_, str>; /// Returns the problem details as a JSON value. fn json(&self) -> serde_json::Value; /// Returns the problem as a trait object that can be downcast. fn as_any(&self) -> &dyn std::any::Any; /// Is this problem universal, i.e. applicable to all build steps? /// /// Good examples of universal problems are e.g. disk full, out of memory, etc. fn is_universal(&self) -> bool { false } } impl PartialEq for dyn Problem { fn eq(&self, other: &Self) -> bool { self.kind() == other.kind() && self.json() == other.json() } } impl Eq for dyn Problem {} impl serde::Serialize for dyn Problem { fn serialize(&self, serializer: S) -> Result { let mut map = serde_json::Map::new(); map.insert( "kind".to_string(), serde_json::Value::String(self.kind().to_string()), ); map.insert("details".to_string(), self.json()); map.serialize(serializer) } } impl std::hash::Hash for dyn Problem { fn hash(&self, state: &mut H) { self.kind().hash(state); self.json().hash(state); } } /// Prints highlighted lines from a match with surrounding context. /// /// # Arguments /// * `lines` - All lines from the source /// * `m` - The match to highlight /// * `context` - Number of lines of context to display before and after the match pub fn highlight_lines(lines: &[&str], m: &dyn Match, context: usize) { use std::cmp::{max, min}; if m.linenos().len() == 1 { println!("Issue found at line {}:", m.lineno()); } else { println!( "Issue found at lines {}-{}:", m.linenos().first().unwrap(), m.linenos().last().unwrap() ); } let start = max(0, m.offsets()[0].saturating_sub(context)); let end = min(lines.len(), m.offsets().last().unwrap() + context + 1); for (i, line) in lines.iter().enumerate().take(end).skip(start) { println!( " {} {}", if m.offsets().contains(&i) { ">" } else { " " }, line.trim_end_matches('\n') ); } } buildlog-consultant-0.1.4/src/lines.rs000064400000000000000000000237711046102023000161000ustar 00000000000000//! Module providing utilities for working with collections of text lines. //! //! This module defines the `Lines` trait and its implementations to provide //! consistent interfaces for iterating through lines in different directions //! and with different options. /// Trait for collections of text lines that provides bidirectional iteration. /// /// This trait provides methods to iterate forward and backward through /// collections of text lines, with optional limits and offset information. pub trait Lines<'a> { /// Iterates forward through the lines. /// /// # Arguments /// * `limit` - Optional maximum number of lines to return /// /// # Returns /// An iterator yielding lines in forward order fn iter_forward(&'a self, limit: Option) -> impl Iterator; /// Iterates forward through the lines with indices. /// /// # Arguments /// * `limit` - Optional maximum number of lines to return /// /// # Returns /// An iterator yielding (index, line) pairs in forward order fn enumerate_forward(&'a self, limit: Option) -> impl Iterator { self.iter_forward(limit).enumerate() } /// Iterates backward through the lines. /// /// # Arguments /// * `limit` - Optional maximum number of lines to return /// /// # Returns /// An iterator yielding lines in reverse order fn iter_backward(&'a self, limit: Option) -> impl DoubleEndedIterator; /// Iterates forward through the last N lines with indices. /// /// # Arguments /// * `limit` - Maximum number of lines to return from the end /// /// # Returns /// An iterator yielding (index, line) pairs for the last `limit` lines fn enumerate_tail_forward(&'a self, limit: usize) -> impl Iterator { let start_offset = self.len().saturating_sub(limit); self.iter_forward(None) .skip(start_offset) .enumerate() .map(move |(i, s)| (i + start_offset, s)) } /// Iterates backward through the lines with original indices. /// /// # Arguments /// * `limit` - Optional maximum number of lines to return /// /// # Returns /// An iterator yielding (original_index, line) pairs in reverse order fn enumerate_backward( &'a self, limit: Option, ) -> impl Iterator { let len = self.len(); self.iter_backward(limit) .enumerate() .map(move |(i, s)| (len - i - 1, s)) } /// Returns the number of lines in the collection. /// /// # Returns /// The line count fn len(&self) -> usize; /// Checks if the collection is empty. /// /// # Returns /// `true` if the collection contains no lines, `false` otherwise fn is_empty(&self) -> bool; } impl<'a> Lines<'a> for Vec<&'a str> { fn iter_forward(&'a self, limit: Option) -> impl Iterator { let limit = limit.unwrap_or(self.len()); self.iter().take(limit).cloned() } fn enumerate_tail_forward(&'a self, limit: usize) -> impl Iterator { let start_offset = self.len().saturating_sub(limit); self[start_offset..] .iter() .cloned() .enumerate() .map(move |(i, s)| (i + start_offset, s)) } fn iter_backward(&'a self, limit: Option) -> impl DoubleEndedIterator { let limit = limit.unwrap_or(self.len()); self.iter().rev().take(limit).cloned() } fn len(&self) -> usize { self.len() } fn is_empty(&self) -> bool { self.is_empty() } } impl<'a> Lines<'a> for Vec { fn iter_forward(&'a self, limit: Option) -> impl Iterator { let limit = limit.unwrap_or(self.len()); self.iter().take(limit).map(|s| s.as_str()) } fn iter_backward(&'a self, limit: Option) -> impl DoubleEndedIterator { let limit = limit.unwrap_or(self.len()); self.iter().rev().take(limit).map(|s| s.as_str()) } fn enumerate_tail_forward(&'a self, limit: usize) -> impl Iterator { let start_offset = self.len().saturating_sub(limit); self[start_offset..] .iter() .map(|s| s.as_str()) .enumerate() .map(move |(i, s)| (i + start_offset, s)) } fn len(&self) -> usize { self.len() } fn is_empty(&self) -> bool { self.is_empty() } } impl<'a> Lines<'a> for &'a [&'a str] { fn iter_forward(&'a self, limit: Option) -> impl Iterator { let limit = limit.unwrap_or(self.len()); self.iter().take(limit).cloned() } fn iter_backward(&'a self, limit: Option) -> impl DoubleEndedIterator { let limit = limit.unwrap_or(self.len()); self.iter().rev().take(limit).cloned() } fn enumerate_tail_forward(&'a self, limit: usize) -> impl Iterator { let start_offset = self.len().saturating_sub(limit); self[start_offset..] .iter() .cloned() .enumerate() .map(move |(i, s)| (i + start_offset, s)) } fn len(&self) -> usize { <[&str]>::len(self) } fn is_empty(&self) -> bool { <[&str]>::is_empty(self) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_iter_forward() { let lines = vec!["a", "b", "c", "d", "e"]; let iter = lines.iter_forward(None); assert_eq!(iter.collect::>(), vec!["a", "b", "c", "d", "e"]); let iter = lines.iter_forward(Some(3)); assert_eq!(iter.collect::>(), vec!["a", "b", "c"]); } #[test] fn test_iter_backward() { let lines = vec!["a", "b", "c", "d", "e"]; let iter = lines.iter_backward(None); assert_eq!(iter.collect::>(), vec!["e", "d", "c", "b", "a"]); let iter = lines.iter_backward(Some(3)); assert_eq!(iter.collect::>(), vec!["e", "d", "c"]); } #[test] fn test_enumerate_tail_forward() { let lines = vec!["a", "b", "c", "d", "e"]; let iter = lines.enumerate_tail_forward(3); assert_eq!(iter.collect::>(), vec![(2, "c"), (3, "d"), (4, "e")]); } #[test] fn test_enumerate_forward() { let lines = vec!["a", "b", "c", "d", "e"]; let iter = lines.enumerate_forward(None); assert_eq!( iter.collect::>(), vec![(0, "a"), (1, "b"), (2, "c"), (3, "d"), (4, "e")] ); let iter = lines.enumerate_forward(Some(3)); assert_eq!(iter.collect::>(), vec![(0, "a"), (1, "b"), (2, "c")]); } #[test] fn test_enumerate_backward() { let lines = vec!["a", "b", "c", "d", "e"]; let iter = lines.enumerate_backward(None); assert_eq!( iter.collect::>(), vec![(4, "e"), (3, "d"), (2, "c"), (1, "b"), (0, "a")] ); let iter = lines.enumerate_backward(Some(3)); assert_eq!(iter.collect::>(), vec![(4, "e"), (3, "d"), (2, "c")]); } #[test] fn test_string_vec_impl() { let lines: Vec = vec!["a".to_string(), "b".to_string(), "c".to_string()]; assert_eq!(lines.len(), 3); assert!(!lines.is_empty()); // Test iter_forward let forward = lines.iter_forward(None).collect::>(); assert_eq!(forward, vec!["a", "b", "c"]); let limited = lines.iter_forward(Some(2)).collect::>(); assert_eq!(limited, vec!["a", "b"]); // Test iter_backward let backward = lines.iter_backward(None).collect::>(); assert_eq!(backward, vec!["c", "b", "a"]); let limited_backward = lines.iter_backward(Some(2)).collect::>(); assert_eq!(limited_backward, vec!["c", "b"]); // Test enumerate_tail_forward let tail = lines.enumerate_tail_forward(2).collect::>(); assert_eq!(tail, vec![(1, "b"), (2, "c")]); } #[test] fn test_slice_impl() { let slice = &["a", "b", "c"][..]; assert_eq!(slice.len(), 3); assert!(!slice.is_empty()); // Test iter_forward let forward = slice.iter_forward(None).collect::>(); assert_eq!(forward, vec!["a", "b", "c"]); let limited = slice.iter_forward(Some(2)).collect::>(); assert_eq!(limited, vec!["a", "b"]); // Test iter_backward let backward = slice.iter_backward(None).collect::>(); assert_eq!(backward, vec!["c", "b", "a"]); let limited_backward = slice.iter_backward(Some(2)).collect::>(); assert_eq!(limited_backward, vec!["c", "b"]); // Test enumerate_tail_forward let tail = slice.enumerate_tail_forward(2).collect::>(); assert_eq!(tail, vec![(1, "b"), (2, "c")]); } #[test] fn test_empty_collections() { let empty_vec: Vec<&str> = Vec::new(); assert_eq!(empty_vec.len(), 0); assert!(empty_vec.is_empty()); assert_eq!(empty_vec.iter_forward(None).count(), 0); assert_eq!(empty_vec.iter_backward(None).count(), 0); assert_eq!(empty_vec.enumerate_forward(None).count(), 0); assert_eq!(empty_vec.enumerate_backward(None).count(), 0); assert_eq!(empty_vec.enumerate_tail_forward(5).count(), 0); let empty_string_vec: Vec = Vec::new(); assert_eq!(empty_string_vec.len(), 0); assert!(empty_string_vec.is_empty()); assert_eq!(empty_string_vec.iter_forward(None).count(), 0); assert_eq!(empty_string_vec.iter_backward(None).count(), 0); let empty_slice: &[&str] = &[]; assert_eq!(empty_slice.len(), 0); assert!(empty_slice.is_empty()); assert_eq!(empty_slice.iter_forward(None).count(), 0); assert_eq!(empty_slice.iter_backward(None).count(), 0); } } buildlog-consultant-0.1.4/src/match.rs000064400000000000000000000374111046102023000160560ustar 00000000000000//! Module providing pattern matching functionality for log analysis. //! //! This module contains tools for matching patterns in logs and extracting problems. //! It includes regex-based matchers and a matcher group for combining multiple matchers. use crate::SingleLineMatch; use crate::{Match, Origin, Problem}; use regex::{Captures, Regex}; use std::fmt::Display; /// Type alias for the result of extracting a match and optional problem pub type MatchResult = Result, Option>)>, Error>; /// Type alias for a callback function that processes regex captures pub type RegexCallback = Box Result>, Error> + Send + Sync>; /// Error type for matchers. /// /// Used when pattern matching or problem extraction fails. #[derive(Debug)] pub struct Error { /// Error message describing what went wrong. pub message: String, } impl Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { self.message.fmt(f) } } impl std::error::Error for Error {} /// A matcher that uses regular expressions to find patterns in single lines. /// /// This matcher applies a regex to individual lines and can extract problem information /// through a callback function when a match is found. pub struct RegexLineMatcher { /// The regular expression to match against each line. regex: Regex, /// Callback function that extracts problem information from regex captures. callback: RegexCallback, } /// Trait for pattern matchers that can extract matches and problems from logs. /// /// Implementors of this trait can search through log lines to find patterns and /// extract problem information. pub trait Matcher: Sync { /// Extracts a match and optional problem from a specific line in a log. /// /// # Arguments /// * `lines` - The collection of log lines /// * `offset` - The line offset to analyze /// /// # Returns /// * `Ok(Some((match, problem)))` - A match was found along with an optional problem /// * `Ok(None)` - No match was found /// * `Err(error)` - An error occurred during matching fn extract_from_lines(&self, lines: &[&str], offset: usize) -> MatchResult; } impl RegexLineMatcher { /// Creates a new `RegexLineMatcher` with the given regex and callback. /// /// # Arguments /// * `regex` - The regex pattern to match against lines /// * `callback` - Function that processes regex captures and returns an optional problem /// /// # Returns /// A new `RegexLineMatcher` instance pub fn new(regex: Regex, callback: RegexCallback) -> Self { Self { regex, callback } } /// Checks if a line matches the regex pattern. /// /// # Arguments /// * `line` - The line to check /// /// # Returns /// `true` if the line matches the pattern, `false` otherwise pub fn matches_line(&self, line: &str) -> bool { self.regex.is_match(line) } /// Attempts to extract problem information from a line. /// /// # Arguments /// * `line` - The line to analyze /// /// # Returns /// * `Ok(Some(Some(problem)))` - A match was found and a problem was extracted /// * `Ok(Some(None))` - A match was found but no problem was extracted /// * `Ok(None)` - No match was found /// * `Err(error)` - An error occurred during matching or problem extraction pub fn extract_from_line(&self, line: &str) -> Result>>, Error> { let c = self.regex.captures(line); if let Some(c) = c { return Ok(Some((self.callback)(&c)?)); } Ok(None) } /// Creates an origin identifier for matches from this matcher. /// /// # Returns /// An `Origin` identifying the regex pattern used for matching fn origin(&self) -> Origin { Origin(format!("direct regex ({})", self.regex.as_str())) } } impl Matcher for RegexLineMatcher { fn extract_from_lines(&self, lines: &[&str], offset: usize) -> MatchResult { let line = lines[offset]; if let Some(problem) = self.extract_from_line(line)? { let m = SingleLineMatch { offset, line: line.to_string(), origin: self.origin(), }; return Ok(Some((Box::new(m), problem))); } Ok(None) } } /// Macro for creating regex-based line matchers. /// /// This macro simplifies the creation of `RegexLineMatcher` instances by automatically /// handling regex compilation and callback boxing. /// /// # Examples /// /// ``` /// # use buildlog_consultant::regex_line_matcher; /// # use buildlog_consultant::r#match::RegexLineMatcher; /// // With callback /// let matcher = regex_line_matcher!(r"error: (.*)", |captures| { /// let message = captures.get(1).unwrap().as_str(); /// // Process the error message /// Ok(None) /// }); /// /// // Without callback (just matches the pattern) /// let simple_matcher = regex_line_matcher!(r"warning"); /// ``` #[macro_export] macro_rules! regex_line_matcher { ($regex:expr, $callback:expr) => { Box::new(RegexLineMatcher::new( regex::Regex::new($regex).unwrap(), Box::new($callback), )) }; ($regex: expr) => { Box::new(RegexLineMatcher::new( regex::Regex::new($regex).unwrap(), Box::new(|_| Ok(None)), )) }; } /// Macro for creating regex-based paragraph matchers. /// /// This macro is similar to `regex_line_matcher`, but creates matchers that can match /// across multiple lines by automatically enabling the "dot matches newline" regex flag (?s). /// /// # Examples /// /// ``` /// # use buildlog_consultant::regex_para_matcher; /// # use buildlog_consultant::r#match::RegexLineMatcher; /// // With callback /// let matcher = regex_para_matcher!(r"BEGIN(.*?)END", |captures| { /// let content = captures.get(1).unwrap().as_str(); /// // Process the content between BEGIN and END /// Ok(None) /// }); /// /// // Without callback /// let simple_matcher = regex_para_matcher!(r"function\s*\{.*?\}"); /// ``` #[macro_export] macro_rules! regex_para_matcher { ($regex:expr, $callback:expr) => {{ Box::new(RegexLineMatcher::new( regex::Regex::new(concat!("(?s)", $regex)).unwrap(), Box::new($callback), )) }}; ($regex: expr) => {{ Box::new(RegexLineMatcher::new( regex::Regex::new(concat!("(?s)", $regex)).unwrap(), Box::new(|_| Ok(None)), )) }}; } /// A group of matchers that can be used to match multiple patterns. /// /// This struct allows combining multiple matchers and trying them in sequence /// until a match is found. pub struct MatcherGroup(Vec>); impl MatcherGroup { /// Creates a new `MatcherGroup` with the given matchers. /// /// # Arguments /// * `matchers` - Vector of boxed matchers /// /// # Returns /// A new `MatcherGroup` instance pub fn new(matchers: Vec>) -> Self { Self(matchers) } } impl Default for MatcherGroup { fn default() -> Self { Self::new(vec![]) } } impl From>> for MatcherGroup { fn from(matchers: Vec>) -> Self { Self::new(matchers) } } impl MatcherGroup { /// Tries each matcher in the group until one finds a match. /// /// This method attempts to extract a match and problem from a specific line /// by trying each matcher in the group in sequence until one succeeds. /// /// # Arguments /// * `lines` - The collection of log lines /// * `offset` - The line offset to analyze /// /// # Returns /// * `Ok(Some((match, problem)))` - A match was found by one of the matchers /// * `Ok(None)` - No match was found by any matcher /// * `Err(error)` - An error occurred during matching pub fn extract_from_lines(&self, lines: &[&str], offset: usize) -> MatchResult { for matcher in self.0.iter() { if let Some(p) = matcher.extract_from_lines(lines, offset)? { return Ok(Some(p)); } } Ok(None) } } #[cfg(test)] mod tests { use super::*; use std::borrow::Cow; #[derive(Debug)] struct TestProblem { description: String, } impl std::fmt::Display for TestProblem { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.description) } } impl Problem for TestProblem { fn kind(&self) -> Cow<'_, str> { Cow::Borrowed("test") } fn json(&self) -> serde_json::Value { serde_json::json!({ "description": self.description, }) } fn as_any(&self) -> &dyn std::any::Any { self } } #[test] fn test_error_display() { let error = Error { message: "test error".to_string(), }; assert_eq!(error.to_string(), "test error"); } #[test] fn test_regex_line_matcher_new() { let regex = Regex::new(r"test").unwrap(); let callback = Box::new(|_: &Captures| -> Result>, Error> { Ok(Some(Box::new(TestProblem { description: "test problem".to_string(), }))) }); let matcher = RegexLineMatcher::new(regex, callback); assert!(matcher.matches_line("test line")); assert!(!matcher.matches_line("other line")); } #[test] fn test_regex_line_matcher_matches_line() { let regex = Regex::new(r"test").unwrap(); let callback = Box::new(|_: &Captures| -> Result>, Error> { Ok(None) }); let matcher = RegexLineMatcher::new(regex, callback); assert!(matcher.matches_line("test line")); assert!(!matcher.matches_line("other line")); } #[test] fn test_regex_line_matcher_extract_from_line() { let regex = Regex::new(r"test").unwrap(); let callback = Box::new(|_: &Captures| -> Result>, Error> { Ok(Some(Box::new(TestProblem { description: "test problem".to_string(), }))) }); let matcher = RegexLineMatcher::new(regex, callback); let result = matcher.extract_from_line("test line").unwrap(); assert!(result.is_some()); let problem = result.unwrap(); assert!(problem.is_some()); let problem = problem.unwrap(); assert_eq!(problem.kind(), "test"); } #[test] fn test_regex_line_matcher_extract_from_line_no_match() { let regex = Regex::new(r"test").unwrap(); let callback = Box::new(|_: &Captures| -> Result>, Error> { Ok(Some(Box::new(TestProblem { description: "test problem".to_string(), }))) }); let matcher = RegexLineMatcher::new(regex, callback); let result = matcher.extract_from_line("other line").unwrap(); assert!(result.is_none()); } #[test] fn test_regex_line_matcher_extract_from_line_no_problem() { let regex = Regex::new(r"test").unwrap(); let callback = Box::new(|_: &Captures| -> Result>, Error> { Ok(None) }); let matcher = RegexLineMatcher::new(regex, callback); let result = matcher.extract_from_line("test line").unwrap(); assert!(result.is_some()); let problem = result.unwrap(); assert!(problem.is_none()); } #[test] fn test_regex_line_matcher_extract_from_lines() { let regex = Regex::new(r"test").unwrap(); let callback = Box::new(|_: &Captures| -> Result>, Error> { Ok(Some(Box::new(TestProblem { description: "test problem".to_string(), }))) }); let matcher = RegexLineMatcher::new(regex, callback); let lines = vec!["line 1", "test line", "line 3"]; let result = matcher.extract_from_lines(&lines, 1).unwrap(); assert!(result.is_some()); let (m, problem) = result.unwrap(); assert_eq!(m.line(), "test line"); assert_eq!(m.offset(), 1); assert!(problem.is_some()); let problem = problem.unwrap(); assert_eq!(problem.kind(), "test"); } #[test] fn test_regex_line_matcher_extract_from_lines_no_match() { let regex = Regex::new(r"test").unwrap(); let callback = Box::new(|_: &Captures| -> Result>, Error> { Ok(Some(Box::new(TestProblem { description: "test problem".to_string(), }))) }); let matcher = RegexLineMatcher::new(regex, callback); let lines = vec!["line 1", "line 2", "line 3"]; let result = matcher.extract_from_lines(&lines, 1).unwrap(); assert!(result.is_none()); } #[test] fn test_matcher_group() { let regex1 = Regex::new(r"test1").unwrap(); let callback1 = Box::new(|_: &Captures| -> Result>, Error> { Ok(Some(Box::new(TestProblem { description: "test problem 1".to_string(), }))) }); let matcher1 = RegexLineMatcher::new(regex1, callback1); let regex2 = Regex::new(r"test2").unwrap(); let callback2 = Box::new(|_: &Captures| -> Result>, Error> { Ok(Some(Box::new(TestProblem { description: "test problem 2".to_string(), }))) }); let matcher2 = RegexLineMatcher::new(regex2, callback2); let group = MatcherGroup::new(vec![Box::new(matcher1), Box::new(matcher2)]); let lines = vec!["line 1", "test2 line", "line 3"]; let result = group.extract_from_lines(&lines, 1).unwrap(); assert!(result.is_some()); let (m, problem) = result.unwrap(); assert_eq!(m.line(), "test2 line"); assert_eq!(m.offset(), 1); assert!(problem.is_some()); let problem = problem.unwrap(); assert_eq!(problem.kind(), "test"); } #[test] fn test_matcher_group_no_match() { let regex1 = Regex::new(r"test1").unwrap(); let callback1 = Box::new(|_: &Captures| -> Result>, Error> { Ok(Some(Box::new(TestProblem { description: "test problem 1".to_string(), }))) }); let matcher1 = RegexLineMatcher::new(regex1, callback1); let regex2 = Regex::new(r"test2").unwrap(); let callback2 = Box::new(|_: &Captures| -> Result>, Error> { Ok(Some(Box::new(TestProblem { description: "test problem 2".to_string(), }))) }); let matcher2 = RegexLineMatcher::new(regex2, callback2); let group = MatcherGroup::new(vec![Box::new(matcher1), Box::new(matcher2)]); let lines = vec!["line 1", "line 2", "line 3"]; let result = group.extract_from_lines(&lines, 1).unwrap(); assert!(result.is_none()); } #[test] fn test_regex_line_matcher_macro() { let matcher = regex_line_matcher!(r"test", |_| { Ok(Some(Box::new(TestProblem { description: "test problem".to_string(), }))) }); let lines = vec!["line 1", "test line", "line 3"]; let result = matcher.extract_from_lines(&lines, 1).unwrap(); assert!(result.is_some()); } #[test] fn test_regex_line_matcher_macro_simple() { let matcher = regex_line_matcher!(r"test"); let lines = vec!["line 1", "test line", "line 3"]; let result = matcher.extract_from_lines(&lines, 1).unwrap(); assert!(result.is_some()); let (_m, problem) = result.unwrap(); assert!(problem.is_none()); } } buildlog-consultant-0.1.4/src/problems/autopkgtest.rs000064400000000000000000000166161046102023000211630ustar 00000000000000use crate::Problem; /// Problem representing unsatisfiable dependencies in autopkgtest. #[derive(Debug, Clone)] pub struct AutopkgtestDepsUnsatisfiable(pub Vec<(Option, String)>); impl AutopkgtestDepsUnsatisfiable { /// Creates a new instance from a blame line string. /// /// Parses a blame line from autopkgtest output to extract dependency issues. /// /// # Arguments /// * `line` - The blame line string to parse pub fn from_blame_line(line: &str) -> Self { let mut args = vec![]; for entry in line.strip_prefix("blame: ").unwrap().split_whitespace() { let (kind, arg) = match entry.split_once(':') { Some((kind, arg)) => (Some(kind), arg), None => (None, entry), }; args.push((kind.map(|x| x.to_string()), arg.to_string())); match kind { Some("deb") | Some("arg") | Some("dsc") | None => {} Some(entry) => { log::warn!("unknown entry {} on badpkg line", entry); } } } Self(args) } } impl Problem for AutopkgtestDepsUnsatisfiable { fn kind(&self) -> std::borrow::Cow<'_, str> { "badpkg".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "args": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for AutopkgtestDepsUnsatisfiable { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "autopkgtest dependencies unsatisfiable: {:?}", self.0) } } /// Problem representing an autopkgtest test that timed out during execution. #[derive(Debug, Clone)] pub struct AutopkgtestTimedOut; impl Problem for AutopkgtestTimedOut { fn kind(&self) -> std::borrow::Cow<'_, str> { "timed-out".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for AutopkgtestTimedOut { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "autopkgtest timed out") } } /// Problem representing a missing XDG_RUNTIME_DIR environment variable. /// /// This issue typically occurs when running GUI tests in autopkgtest. #[derive(Debug, Clone)] pub struct XDGRunTimeNotSet; impl Problem for XDGRunTimeNotSet { fn kind(&self) -> std::borrow::Cow<'_, str> { "xdg-runtime-dir-not-set".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for XDGRunTimeNotSet { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "XDG_RUNTIME_DIR not set") } } /// Problem representing a failure in the autopkgtest testbed. /// /// Contains a string describing the specific reason for the testbed failure. #[derive(Debug, Clone)] pub struct AutopkgtestTestbedFailure(pub String); impl Problem for AutopkgtestTestbedFailure { fn kind(&self) -> std::borrow::Cow<'_, str> { "testbed-failure".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "reason": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for AutopkgtestTestbedFailure { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "autopkgtest testbed failure: {}", self.0) } } /// Problem representing an autopkgtest dependency chroot that disappeared. /// /// This occurs when the chroot environment used for dependency resolution /// becomes unavailable during testing. #[derive(Debug, Clone)] pub struct AutopkgtestDepChrootDisappeared; impl Problem for AutopkgtestDepChrootDisappeared { fn kind(&self) -> std::borrow::Cow<'_, str> { "testbed-chroot-disappeared".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for AutopkgtestDepChrootDisappeared { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "autopkgtest dependency chroot disappeared") } } /// Problem representing an erroneous package in autopkgtest. /// /// Contains a string describing the specific package error encountered. #[derive(Debug, Clone)] pub struct AutopkgtestErroneousPackage(pub String); impl Problem for AutopkgtestErroneousPackage { fn kind(&self) -> std::borrow::Cow<'_, str> { "erroneous-package".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "reason": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for AutopkgtestErroneousPackage { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "autopkgtest erroneous package: {}", self.0) } } /// Problem representing a failure detected from stderr output in autopkgtest. /// /// Contains the stderr line that indicates the failure. #[derive(Debug, Clone)] pub struct AutopkgtestStderrFailure(pub String); impl Problem for AutopkgtestStderrFailure { fn kind(&self) -> std::borrow::Cow<'_, str> { "stderr-output".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "stderr_line": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for AutopkgtestStderrFailure { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "autopkgtest output on stderr: {}", self.0) } } /// Problem representing a failure during autopkgtest testbed setup. /// /// Contains details about the command that failed, its exit status, /// and the error message. #[derive(Debug, Clone)] pub struct AutopkgtestTestbedSetupFailure { /// The command that failed to execute properly. pub command: String, /// The exit status code of the failed command. pub exit_status: i32, /// The error message provided by the command. pub error: String, } impl Problem for AutopkgtestTestbedSetupFailure { fn kind(&self) -> std::borrow::Cow<'_, str> { "testbed-setup-failure".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "command": self.command, "exit_status": self.exit_status, "error": self.error, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for AutopkgtestTestbedSetupFailure { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "autopkgtest testbed setup failure: {} exited with status {}: {}", self.command, self.exit_status, self.error ) } } #[cfg(test)] mod tests { use super::*; use crate::Problem; #[test] fn test_autopkgtest_testbed_setup_failure_trait() { let problem = AutopkgtestTestbedSetupFailure { command: "apt-get update".to_string(), exit_status: 100, error: "Failed to fetch".to_string(), }; let json = problem.json(); assert_eq!(json["command"], "apt-get update"); assert_eq!(json["exit_status"], 100); assert_eq!(json["error"], "Failed to fetch"); } } buildlog-consultant-0.1.4/src/problems/common.rs000064400000000000000000003417131046102023000201000ustar 00000000000000use crate::Problem; use pep508_rs::pep440_rs; use serde::{Deserialize, Serialize}; use std::borrow::Cow; use std::fmt::{self, Debug, Display}; use std::path::PathBuf; /// Problem representing a file that was expected but not found. /// /// This struct is used to report situations where a required file is missing, /// which may cause build or execution failures. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct MissingFile { /// The path to the missing file. pub path: PathBuf, } impl MissingFile { /// Creates a new MissingFile instance. /// /// # Arguments /// * `path` - Path to the missing file pub fn new(path: PathBuf) -> Self { Self { path } } } impl Problem for MissingFile { fn kind(&self) -> Cow<'_, str> { "missing-file".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "path": self.path.to_string_lossy(), }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingFile { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Missing file: {}", self.path.display()) } } /// Problem representing a missing build system file. /// /// This struct is used to report when a file required by the build system /// (such as a Makefile, CMakeLists.txt, etc.) is missing. #[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct MissingBuildFile { /// The name of the missing build file. pub filename: String, } impl MissingBuildFile { /// Creates a new MissingBuildFile instance. /// /// # Arguments /// * `filename` - Name of the missing build file pub fn new(filename: String) -> Self { Self { filename } } } impl Problem for MissingBuildFile { fn kind(&self) -> Cow<'_, str> { "missing-build-file".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "filename": self.filename, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingBuildFile { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Missing build file: {}", self.filename) } } /// Problem representing something that could be either a missing command or build file. /// /// This struct is used when it's not clear whether a missing entity is a /// command (executable) or a build file. #[derive(Clone, Debug, PartialEq, Eq)] pub struct MissingCommandOrBuildFile { /// The name of the missing command or build file. pub filename: String, } impl Problem for MissingCommandOrBuildFile { fn kind(&self) -> Cow<'_, str> { "missing-command-or-build-file".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "filename": self.filename, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingCommandOrBuildFile { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing command or build file: {}", self.filename) } } impl MissingCommandOrBuildFile { /// Returns the command name, which is the same as the filename. /// /// # Returns /// The filename/command name as a String pub fn command(&self) -> String { self.filename.clone() } } /// Problem representing a need for a version control system directory. /// /// This struct is used when a build process requires a version control /// system directory (like .git, .bzr, .svn) to be present. #[derive(Clone, Debug, PartialEq, Eq)] pub struct VcsControlDirectoryNeeded { /// List of version control systems that could provide the needed directory. pub vcs: Vec, } impl Problem for VcsControlDirectoryNeeded { fn kind(&self) -> Cow<'_, str> { "vcs-control-directory-needed".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "vcs": self.vcs, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl VcsControlDirectoryNeeded { /// Creates a new VcsControlDirectoryNeeded instance. /// /// # Arguments /// * `vcs` - List of version control system names pub fn new(vcs: Vec<&str>) -> Self { Self { vcs: vcs.iter().map(|s| s.to_string()).collect(), } } } /// Problem representing a missing Python module. /// /// This struct is used when a required Python module is not available, /// which may include version constraints. #[derive(Debug, Clone)] pub struct MissingPythonModule { /// The name of the missing Python module. pub module: String, /// The Python major version (e.g., 2 or 3) if specific. pub python_version: Option, /// The minimum required version of the module if specified. pub minimum_version: Option, } impl Display for MissingPythonModule { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { if let Some(python_version) = self.python_version { write!( f, "Missing {} Python module: {}", python_version, self.module )?; } else { write!(f, "Missing Python module: {}", self.module)?; } if let Some(minimum_version) = &self.minimum_version { write!(f, " (>= {})", minimum_version)?; } Ok(()) } } impl MissingPythonModule { /// Creates a simple MissingPythonModule instance without version constraints. /// /// # Arguments /// * `module` - Name of the missing Python module /// /// # Returns /// A new MissingPythonModule with no version requirements pub fn simple(module: String) -> MissingPythonModule { MissingPythonModule { module, python_version: None, minimum_version: None, } } } impl Problem for MissingPythonModule { fn kind(&self) -> Cow<'_, str> { "missing-python-module".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "module": self.module, "python_version": self.python_version, "minimum_version": self.minimum_version, }) } fn as_any(&self) -> &dyn std::any::Any { self } } /// Problem representing a missing command-line executable. /// /// This struct is used when a required command is not available in the PATH. #[derive(Debug, Clone)] pub struct MissingCommand(pub String); impl Display for MissingCommand { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing command: {}", self.0) } } impl Problem for MissingCommand { fn kind(&self) -> Cow<'_, str> { "command-missing".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "command": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } /// Problem representing a missing Python package distribution. /// /// This struct is used when a required Python package is not available, /// which may include version constraints. #[derive(Debug, Clone)] pub struct MissingPythonDistribution { /// The name of the missing Python distribution. pub distribution: String, /// The Python major version (e.g., 2 or 3) if specific. pub python_version: Option, /// The minimum required version of the distribution if specified. pub minimum_version: Option, } impl Display for MissingPythonDistribution { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { if let Some(python_version) = self.python_version { write!( f, "Missing {} Python distribution: {}", python_version, self.distribution )?; } else { write!(f, "Missing Python distribution: {}", self.distribution)?; } if let Some(minimum_version) = &self.minimum_version { write!(f, " (>= {})", minimum_version)?; } Ok(()) } } impl Problem for MissingPythonDistribution { fn kind(&self) -> Cow<'_, str> { "missing-python-distribution".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "distribution": self.distribution, "python_version": self.python_version, "minimum_version": self.minimum_version, }) } fn as_any(&self) -> &dyn std::any::Any { self } } fn find_python_version(marker: Vec>) -> Option { let mut major_version = None; for expr in marker.iter().flat_map(|x| x.iter()) { if let pep508_rs::MarkerExpression::Version { key: pep508_rs::MarkerValueVersion::PythonVersion, specifier, } = expr { let version = specifier.version(); major_version = Some(version.release()[0] as i32); } } major_version } impl MissingPythonDistribution { /// Creates a MissingPythonDistribution from a PEP508 requirement string. /// /// Parses a Python package requirement string (in PEP508 format) to extract /// the package name and version constraints. /// /// # Arguments /// * `text` - The requirement string in PEP508 format /// * `python_version` - Optional Python version to override detected version /// /// # Returns /// A new MissingPythonDistribution instance pub fn from_requirement_str( text: &str, python_version: Option, ) -> MissingPythonDistribution { use pep440_rs::Operator; use pep508_rs::{Requirement, VersionOrUrl}; use std::str::FromStr; let depspec: Requirement = Requirement::from_str(text).unwrap(); let distribution = depspec.name.to_string(); let python_version = python_version.or_else(|| find_python_version(depspec.marker.to_dnf())); let minimum_version = if let Some(VersionOrUrl::VersionSpecifier(vs)) = depspec.version_or_url { if vs.len() == 1 && *vs[0].operator() == Operator::GreaterThanEqual { Some(vs[0].version().to_string()) } else { None } } else { None }; MissingPythonDistribution { distribution, python_version, minimum_version, } } /// Creates a simple MissingPythonDistribution without version constraints. /// /// # Arguments /// * `distribution` - Name of the missing Python distribution /// /// # Returns /// A new MissingPythonDistribution with no version requirements pub fn simple(distribution: &str) -> MissingPythonDistribution { MissingPythonDistribution { distribution: distribution.to_string(), python_version: None, minimum_version: None, } } } impl Display for VcsControlDirectoryNeeded { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "VCS control directory needed: {}", self.vcs.join(", ")) } } /// Problem representing a missing Haskell module. /// /// This struct is used when a required Haskell module is not available. #[derive(Debug, Clone)] pub struct MissingHaskellModule { /// The name of the missing Haskell module. pub module: String, } impl MissingHaskellModule { /// Creates a new MissingHaskellModule instance. /// /// # Arguments /// * `module` - Name of the missing Haskell module /// /// # Returns /// A new MissingHaskellModule instance pub fn new(module: String) -> MissingHaskellModule { MissingHaskellModule { module } } } impl Display for MissingHaskellModule { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing Haskell module: {}", self.module) } } impl Problem for MissingHaskellModule { fn kind(&self) -> Cow<'_, str> { "missing-haskell-module".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "module": self.module, }) } fn as_any(&self) -> &dyn std::any::Any { self } } /// Problem representing a missing system library. /// /// This struct is used when a required shared library (.so/.dll/.dylib) is not available. #[derive(Debug, Clone)] pub struct MissingLibrary(pub String); impl Display for MissingLibrary { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing library: {}", self.0) } } impl Problem for MissingLibrary { fn kind(&self) -> Cow<'_, str> { "missing-library".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "library": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } /// Problem representing a missing GObject Introspection typelib. /// /// This struct is used when a required GObject Introspection typelib file is not available. #[derive(Debug, Clone)] pub struct MissingIntrospectionTypelib(pub String); impl Display for MissingIntrospectionTypelib { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing introspection typelib: {}", self.0) } } impl Problem for MissingIntrospectionTypelib { fn kind(&self) -> Cow<'_, str> { "missing-introspection-typelib".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "library": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } /// Problem representing a missing pytest fixture. /// /// This struct is used when a pytest test requires a fixture that is not available. #[derive(Debug, Clone)] pub struct MissingPytestFixture(pub String); impl Display for MissingPytestFixture { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing pytest fixture: {}", self.0) } } impl Problem for MissingPytestFixture { fn kind(&self) -> Cow<'_, str> { "missing-pytest-fixture".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "fixture": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } /// Problem representing an unsupported pytest configuration option. /// /// This struct is used when a pytest configuration specifies an option /// that is not supported in the current environment. #[derive(Debug, Clone)] pub struct UnsupportedPytestConfigOption(pub String); impl Display for UnsupportedPytestConfigOption { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Unsupported pytest config option: {}", self.0) } } impl Problem for UnsupportedPytestConfigOption { fn kind(&self) -> Cow<'_, str> { "unsupported-pytest-config-option".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "name": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } /// Problem representing unsupported pytest command-line arguments. /// /// This struct is used when pytest is invoked with command-line arguments /// that are not supported in the current environment. #[derive(Debug, Clone)] pub struct UnsupportedPytestArguments(pub Vec); impl Display for UnsupportedPytestArguments { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Unsupported pytest arguments: {:?}", self.0) } } impl Problem for UnsupportedPytestArguments { fn kind(&self) -> Cow<'_, str> { "unsupported-pytest-arguments".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "args": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } /// Problem representing a missing R package. /// /// This struct is used when a required R package is not installed /// or not available in the environment. #[derive(Debug, Clone)] pub struct MissingRPackage { /// The name of the missing R package. pub package: String, /// The minimum required version of the package, if specified. pub minimum_version: Option, } impl MissingRPackage { /// Creates a simple MissingRPackage instance without version constraints. /// /// # Arguments /// * `package` - Name of the missing R package /// /// # Returns /// A new MissingRPackage with no version requirements pub fn simple(package: &str) -> Self { Self { package: package.to_string(), minimum_version: None, } } } impl Display for MissingRPackage { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing R package: {}", self.package)?; if let Some(minimum_version) = &self.minimum_version { write!(f, " (>= {})", minimum_version)?; } Ok(()) } } impl Problem for MissingRPackage { fn kind(&self) -> Cow<'_, str> { "missing-r-package".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "package": self.package, "minimum_version": self.minimum_version, }) } fn as_any(&self) -> &dyn std::any::Any { self } } /// Problem representing a missing Go package. /// /// This struct is used when a required Go package is not installed /// or not available in the environment. #[derive(Debug, Clone)] pub struct MissingGoPackage { /// The import path of the missing Go package. pub package: String, } impl Problem for MissingGoPackage { fn kind(&self) -> Cow<'_, str> { "missing-go-package".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "package": self.package, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingGoPackage { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing Go package: {}", self.package) } } /// Problem representing a missing C header file. /// /// This struct is used when a required C header file (.h) is not available /// during compilation. #[derive(Debug, Clone)] pub struct MissingCHeader { /// The name of the missing C header file. pub header: String, } impl Problem for MissingCHeader { fn kind(&self) -> Cow<'_, str> { "missing-c-header".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "header": self.header, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingCHeader { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing C header: {}", self.header) } } impl MissingCHeader { /// Creates a new MissingCHeader instance. /// /// # Arguments /// * `header` - Name of the missing C header file /// /// # Returns /// A new MissingCHeader instance pub fn new(header: String) -> Self { Self { header } } } /// Problem representing a missing Node.js module. /// /// This struct is used when a required Node.js module is not installed /// or cannot be imported. #[derive(Debug, Clone)] pub struct MissingNodeModule(pub String); impl Problem for MissingNodeModule { fn kind(&self) -> Cow<'_, str> { "missing-node-module".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "module": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingNodeModule { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing Node module: {}", self.0) } } /// Problem representing a missing Node.js package. /// /// This struct is used when a required Node.js package is not installed /// via npm/yarn/pnpm or cannot be found in node_modules. #[derive(Debug, Clone)] pub struct MissingNodePackage(pub String); impl Problem for MissingNodePackage { fn kind(&self) -> Cow<'_, str> { "missing-node-package".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "package": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingNodePackage { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing Node package: {}", self.0) } } /// Problem representing a missing configure script. /// /// This struct is used when a build expects to find a configure script /// (typically from autotools) but it doesn't exist. #[derive(Debug, Clone)] pub struct MissingConfigure; impl Problem for MissingConfigure { fn kind(&self) -> Cow<'_, str> { "missing-configure".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingConfigure { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing ./configure") } } /// Problem representing a vague or unspecified dependency. /// /// This struct is used when a build requires a dependency that /// cannot be clearly categorized as a specific type of dependency. #[derive(Debug, Clone)] pub struct MissingVagueDependency { /// The name of the missing dependency. pub name: String, /// An optional URL where the dependency might be found. pub url: Option, /// The minimum required version of the dependency, if specified. pub minimum_version: Option, /// The current version of the dependency, if known. pub current_version: Option, } impl MissingVagueDependency { /// Creates a simple MissingVagueDependency instance with just a name. /// /// # Arguments /// * `name` - Name of the missing dependency /// /// # Returns /// A new MissingVagueDependency with no additional information pub fn simple(name: &str) -> Self { Self { name: name.to_string(), url: None, minimum_version: None, current_version: None, } } } impl Problem for MissingVagueDependency { fn kind(&self) -> Cow<'_, str> { "missing-vague-dependency".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "name": self.name, "url": self.url, "minimum_version": self.minimum_version, "current_version": self.current_version, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingVagueDependency { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing dependency: {}", self.name) } } /// Problem representing missing Qt framework. /// /// This struct is used when a build requires the Qt framework /// but it is not installed or cannot be found. #[derive(Debug, Clone)] pub struct MissingQt; impl Problem for MissingQt { fn kind(&self) -> Cow<'_, str> { "missing-qt".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingQt { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing Qt") } } /// Problem representing missing X11 libraries or headers. /// /// This struct is used when a build requires X11 (X Window System) /// components but they are not installed or cannot be found. #[derive(Debug, Clone)] pub struct MissingX11; impl Problem for MissingX11 { fn kind(&self) -> Cow<'_, str> { "missing-x11".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingX11 { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing X11") } } /// Problem representing a missing autoconf macro. /// /// This struct is used when a build using autoconf requires a macro /// that is not available in the build environment. #[derive(Debug, Clone)] pub struct MissingAutoconfMacro { /// The name of the missing autoconf macro. pub r#macro: String, /// Whether the build system needs to be rebuilt after adding the macro. pub need_rebuild: bool, } impl MissingAutoconfMacro { /// Creates a new MissingAutoconfMacro instance. /// /// # Arguments /// * `macro` - Name of the missing autoconf macro /// /// # Returns /// A new MissingAutoconfMacro instance with need_rebuild set to false pub fn new(r#macro: String) -> Self { Self { r#macro, need_rebuild: false, } } } impl Problem for MissingAutoconfMacro { fn kind(&self) -> Cow<'_, str> { "missing-autoconf-macro".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "macro": self.r#macro, "need_rebuild": self.need_rebuild, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingAutoconfMacro { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing autoconf macro: {}", self.r#macro) } } /// Problem representing a directory that does not exist. /// /// This struct is used when a build process expects a directory to exist /// but it cannot be found in the filesystem. #[derive(Debug, Clone)] pub struct DirectoryNonExistant(pub String); impl Problem for DirectoryNonExistant { fn kind(&self) -> Cow<'_, str> { "local-directory-not-existing".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "path": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for DirectoryNonExistant { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Directory does not exist: {}", self.0) } } /// Problem representing a missing Vala package. /// /// This struct is used when a build requires a Vala package /// that is not installed or cannot be found. #[derive(Debug, Clone)] pub struct MissingValaPackage(pub String); impl Problem for MissingValaPackage { fn kind(&self) -> Cow<'_, str> { "missing-vala-package".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "package": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingValaPackage { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing Vala package: {}", self.0) } } /// Problem representing the presence of an upstart configuration file. /// /// This struct is used to indicate that a package includes an upstart file, /// which may be problematic in environments that have moved to systemd. #[derive(Debug, Clone)] pub struct UpstartFilePresent(pub String); impl Problem for UpstartFilePresent { fn kind(&self) -> Cow<'_, str> { "upstart-file-present".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "filename": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for UpstartFilePresent { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Upstart file present: {}", self.0) } } /// Problem representing a missing PostgreSQL extension. /// /// This struct is used when a build or runtime requires a PostgreSQL extension /// that is not installed or cannot be found. #[derive(Debug, Clone)] pub struct MissingPostgresExtension(pub String); impl Problem for MissingPostgresExtension { fn kind(&self) -> Cow<'_, str> { "missing-postgresql-extension".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "extension": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingPostgresExtension { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing PostgreSQL extension: {}", self.0) } } /// Problem representing a missing pkg-config module. /// /// This struct is used when a build requires a package found via pkg-config /// that is not installed or cannot be found. #[derive(Debug, Clone)] pub struct MissingPkgConfig { /// The name of the missing pkg-config module. pub module: String, /// The minimum required version of the module, if specified. pub minimum_version: Option, } impl Problem for MissingPkgConfig { fn kind(&self) -> Cow<'_, str> { "missing-pkg-config-package".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "module": self.module, "minimum_version": self.minimum_version, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingPkgConfig { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { if let Some(minimum_version) = &self.minimum_version { write!( f, "Missing pkg-config module: {} >= {}", self.module, minimum_version ) } else { write!(f, "Missing pkg-config module: {}", self.module) } } } impl MissingPkgConfig { /// Creates a new MissingPkgConfig instance with optional version constraint. /// /// # Arguments /// * `module` - Name of the missing pkg-config module /// * `minimum_version` - Optional minimum version requirement /// /// # Returns /// A new MissingPkgConfig instance pub fn new(module: String, minimum_version: Option) -> Self { Self { module, minimum_version, } } /// Creates a simple MissingPkgConfig instance without version constraint. /// /// # Arguments /// * `module` - Name of the missing pkg-config module /// /// # Returns /// A new MissingPkgConfig with no version requirements pub fn simple(module: String) -> Self { Self { module, minimum_version: None, } } } /// Problem representing multiple missing Haskell dependencies. /// /// This struct is used when a build requires multiple Haskell packages /// that are not installed or cannot be found. #[derive(Debug, Clone)] pub struct MissingHaskellDependencies(pub Vec); impl Problem for MissingHaskellDependencies { fn kind(&self) -> Cow<'_, str> { "missing-haskell-dependencies".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "deps": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingHaskellDependencies { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing Haskell dependencies: {:?}", self.0) } } /// Problem representing lack of disk space. /// /// This struct is used when a build fails because there is no space /// left on the device/filesystem where the build is running. #[derive(Debug, Clone)] pub struct NoSpaceOnDevice; impl Problem for NoSpaceOnDevice { fn kind(&self) -> Cow<'_, str> { "no-space-on-device".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } /// Indicates that this problem is universal across all build steps. /// /// No space on device is considered a universal problem because it can /// affect any stage of the build process and is not specific to particular /// build steps. fn is_universal(&self) -> bool { true } } impl Display for NoSpaceOnDevice { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "No space left on device") } } /// Problem representing a missing Java Runtime Environment. /// /// This struct is used when a build or runtime requires a Java Runtime /// Environment (JRE) that is not installed or cannot be found. #[derive(Debug, Clone)] pub struct MissingJRE; impl Problem for MissingJRE { fn kind(&self) -> Cow<'_, str> { "missing-jre".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingJRE { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing JRE") } } /// Problem representing a missing Java Development Kit. /// /// This struct is used when a build requires a Java Development Kit (JDK) /// at a specific path but it cannot be found. #[derive(Debug, Clone)] pub struct MissingJDK { /// The path where the JDK was expected to be found. pub jdk_path: String, } impl MissingJDK { /// Creates a new MissingJDK instance. /// /// # Arguments /// * `jdk_path` - Path where the JDK was expected to be found /// /// # Returns /// A new MissingJDK instance pub fn new(jdk_path: String) -> Self { Self { jdk_path } } } impl Problem for MissingJDK { fn kind(&self) -> Cow<'_, str> { "missing-jdk".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "jdk_path": self.jdk_path }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingJDK { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing JDK at {}", self.jdk_path) } } /// Problem representing a missing file in the Java Development Kit. /// /// This struct is used when a build requires a specific file from the JDK /// but it cannot be found in the JDK directory. #[derive(Debug, Clone)] pub struct MissingJDKFile { /// The path to the JDK directory. pub jdk_path: String, /// The name of the file that is missing from the JDK. pub filename: String, } impl MissingJDKFile { /// Creates a new MissingJDKFile instance. /// /// # Arguments /// * `jdk_path` - Path to the JDK directory /// * `filename` - Name of the file that is missing from the JDK /// /// # Returns /// A new MissingJDKFile instance pub fn new(jdk_path: String, filename: String) -> Self { Self { jdk_path, filename } } } impl Problem for MissingJDKFile { fn kind(&self) -> Cow<'_, str> { "missing-jdk-file".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "jdk_path": self.jdk_path, "filename": self.filename }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingJDKFile { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing JDK file {} at {}", self.filename, self.jdk_path) } } /// Problem representing a missing Perl file. /// /// This struct is used when a Perl script attempts to load a file /// but it cannot be found in any of the include paths. #[derive(Debug, Clone)] pub struct MissingPerlFile { /// The name of the missing Perl file. pub filename: String, /// The include paths that were searched, if available. pub inc: Option>, } impl MissingPerlFile { /// Creates a new MissingPerlFile instance. /// /// # Arguments /// * `filename` - Name of the missing Perl file /// * `inc` - List of include paths that were searched, if known /// /// # Returns /// A new MissingPerlFile instance pub fn new(filename: String, inc: Option>) -> Self { Self { filename, inc } } } impl Problem for MissingPerlFile { fn kind(&self) -> Cow<'_, str> { "missing-perl-file".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "filename": self.filename, "inc": self.inc }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingPerlFile { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { if let Some(inc) = self.inc.as_ref() { write!( f, "Missing Perl file {} (INC: {})", self.filename, inc.join(":") ) } else { write!(f, "Missing Perl file {}", self.filename) } } } /// Problem representing a missing Perl module. /// /// This struct is used when a Perl script requires a module /// that is not installed or cannot be found in the include paths. #[derive(Debug, Clone)] pub struct MissingPerlModule { /// The name of the file where the module is required, if known. pub filename: Option, /// The name of the missing Perl module. pub module: String, /// The include paths that were searched, if available. pub inc: Option>, /// The minimum version of the module required, if specified. pub minimum_version: Option, } impl Problem for MissingPerlModule { fn kind(&self) -> Cow<'_, str> { "missing-perl-module".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "filename": self.filename, "module": self.module, "inc": self.inc, "minimum_version": self.minimum_version, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingPerlModule { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { if let Some(filename) = &self.filename { write!( f, "Missing Perl module: {} (from {})", self.module, filename )?; } else { write!(f, "Missing Perl module: {}", self.module)?; } if let Some(minimum_version) = &self.minimum_version { write!(f, " >= {}", minimum_version)?; } if let Some(inc) = &self.inc { write!(f, " (INC: {})", inc.join(", "))?; } Ok(()) } } impl MissingPerlModule { /// Creates a simple MissingPerlModule instance with just a module name. /// /// # Arguments /// * `module` - Name of the missing Perl module /// /// # Returns /// A new MissingPerlModule with no additional information pub fn simple(module: &str) -> Self { Self { filename: None, module: module.to_string(), inc: None, minimum_version: None, } } } /// Problem representing a missing command in a Python setup.py script. /// /// This struct is used when a Python setup.py script is called with a command /// that it does not support or recognize. #[derive(Debug, Clone)] pub struct MissingSetupPyCommand(pub String); impl Problem for MissingSetupPyCommand { fn kind(&self) -> Cow<'_, str> { "missing-setup.py-command".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "command": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingSetupPyCommand { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing setup.py command: {}", self.0) } } /// Problem representing a missing C# compiler. /// /// This struct is used when a build requires a C# compiler /// but none is available in the build environment. #[derive(Debug, Clone)] pub struct MissingCSharpCompiler; impl Problem for MissingCSharpCompiler { fn kind(&self) -> Cow<'_, str> { "missing-c#-compiler".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingCSharpCompiler { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing C# compiler") } } /// Problem representing a missing Rust compiler. /// /// This struct is used when a build requires a Rust compiler (rustc) /// but none is available in the build environment. #[derive(Debug, Clone)] pub struct MissingRustCompiler; impl Problem for MissingRustCompiler { fn kind(&self) -> Cow<'_, str> { "missing-rust-compiler".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingRustCompiler { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing Rust compiler") } } /// Problem representing a missing assembler. /// /// This struct is used when a build requires an assembler (like as or nasm) /// but none is available in the build environment. #[derive(Debug, Clone)] pub struct MissingAssembler; impl Problem for MissingAssembler { fn kind(&self) -> Cow<'_, str> { "missing-assembler".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingAssembler { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing assembler") } } /// Problem representing a missing Rust crate for Cargo. /// /// This struct is used when a Cargo build requires a Rust crate /// that is not available or cannot be found. #[derive(Debug, Clone)] pub struct MissingCargoCrate { /// The name of the missing Rust crate. pub crate_name: String, /// The requirement or dependency that needs this crate, if known. pub requirement: Option, } impl Problem for MissingCargoCrate { fn kind(&self) -> Cow<'_, str> { "missing-cargo-crate".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "crate": self.crate_name, "requirement": self.requirement }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl MissingCargoCrate { /// Creates a simple MissingCargoCrate instance with just a crate name. /// /// # Arguments /// * `crate_name` - Name of the missing Rust crate /// /// # Returns /// A new MissingCargoCrate with no requirement information pub fn simple(crate_name: String) -> Self { Self { crate_name, requirement: None, } } } impl Display for MissingCargoCrate { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { if let Some(requirement) = self.requirement.as_ref() { write!( f, "Missing Cargo crate {} (required by {})", self.crate_name, requirement ) } else { write!(f, "Missing Cargo crate {}", self.crate_name) } } } /// Problem representing incorrect debhelper (dh) command argument order. /// /// This struct is used when the debhelper command is used with arguments /// in an incorrect order, which can cause build issues in Debian packaging. #[derive(Debug, Clone)] pub struct DhWithOrderIncorrect; impl Problem for DhWithOrderIncorrect { fn kind(&self) -> Cow<'_, str> { "debhelper-argument-order".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for DhWithOrderIncorrect { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "dh argument order is incorrect") } } /// Problem representing an unsupported debhelper compatibility level. /// /// This struct is used when a Debian package build specifies a debhelper /// compatibility level that is lower than the minimum supported level /// in the current environment. #[derive(Debug, Clone)] pub struct UnsupportedDebhelperCompatLevel { /// The oldest (minimum) compatibility level supported by the current debhelper. pub oldest_supported: u32, /// The compatibility level requested by the package. pub requested: u32, } impl UnsupportedDebhelperCompatLevel { /// Creates a new UnsupportedDebhelperCompatLevel instance. /// /// # Arguments /// * `oldest_supported` - The oldest (minimum) compatibility level supported /// * `requested` - The compatibility level requested by the package /// /// # Returns /// A new UnsupportedDebhelperCompatLevel instance pub fn new(oldest_supported: u32, requested: u32) -> Self { Self { oldest_supported, requested, } } } impl Problem for UnsupportedDebhelperCompatLevel { fn kind(&self) -> Cow<'_, str> { "unsupported-debhelper-compat-level".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "oldest_supported": self.oldest_supported, "requested": self.requested }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for UnsupportedDebhelperCompatLevel { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "Request debhelper compat level {} lower than supported {}", self.requested, self.oldest_supported ) } } /// Problem representing an issue with setuptools_scm version detection. /// /// This struct is used when the setuptools_scm Python package is unable /// to automatically determine the package version from version control /// metadata, which typically happens when building from a source archive /// rather than a git repository. #[derive(Debug, Clone)] pub struct SetuptoolScmVersionIssue; impl Problem for SetuptoolScmVersionIssue { fn kind(&self) -> Cow<'_, str> { "setuptools-scm-version-issue".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for SetuptoolScmVersionIssue { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "setuptools_scm was unable to find version") } } /// Problem representing missing Maven artifacts. /// /// This struct is used when a Java build process that uses Maven /// is missing required artifacts from Maven repositories. #[derive(Debug, Clone)] pub struct MissingMavenArtifacts(pub Vec); impl Problem for MissingMavenArtifacts { fn kind(&self) -> Cow<'_, str> { "missing-maven-artifacts".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "artifacts": self.0 }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingMavenArtifacts { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing Maven artifacts: {}", self.0.join(", ")) } } /// Problem representing a file that is not executable. /// /// This struct is used when a command or script file that needs to be /// executed does not have the executable permission bit set. #[derive(Debug, Clone)] pub struct NotExecutableFile(pub String); impl NotExecutableFile { /// Creates a new NotExecutableFile instance. /// /// # Arguments /// * `path` - Path to the non-executable file /// /// # Returns /// A new NotExecutableFile instance pub fn new(path: String) -> Self { Self(path) } } impl Problem for NotExecutableFile { fn kind(&self) -> Cow<'_, str> { "not-executable-file".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "path": self.0 }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for NotExecutableFile { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Command not executable: {}", self.0) } } /// Problem representing a debhelper script attempting to access an uninstalled file. /// /// This struct is used when debhelper tries to access a file that has been /// removed or was never installed in the build environment. #[derive(Debug, Clone)] pub struct DhMissingUninstalled(pub String); impl DhMissingUninstalled { /// Creates a new DhMissingUninstalled instance. /// /// # Arguments /// * `missing_file` - Path to the missing file /// /// # Returns /// A new DhMissingUninstalled instance pub fn new(missing_file: String) -> Self { Self(missing_file) } } impl Problem for DhMissingUninstalled { fn kind(&self) -> Cow<'_, str> { "dh-missing-uninstalled".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "missing_file": self.0 }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for DhMissingUninstalled { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "dh_missing file not installed: {}", self.0) } } /// Problem representing a debhelper link whose destination is a directory. /// /// This struct is used when debhelper's dh_link attempts to create a symlink /// to a path that is a directory, which can cause issues in package builds. #[derive(Debug, Clone)] pub struct DhLinkDestinationIsDirectory(pub String); impl DhLinkDestinationIsDirectory { /// Creates a new DhLinkDestinationIsDirectory instance. /// /// # Arguments /// * `path` - Path to the directory that was incorrectly specified as a link destination /// /// # Returns /// A new DhLinkDestinationIsDirectory instance pub fn new(path: String) -> Self { Self(path) } } impl Problem for DhLinkDestinationIsDirectory { fn kind(&self) -> Cow<'_, str> { "dh-link-destination-is-directory".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "path": self.0 }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for DhLinkDestinationIsDirectory { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Link destination {} is directory", self.0) } } /// Problem representing a missing XML entity. /// /// This struct is used when an XML parser attempts to resolve an external /// entity reference but the referenced entity cannot be found at the given URL. #[derive(Debug, Clone)] pub struct MissingXmlEntity { /// The URL where the XML entity was expected to be found. pub url: String, } impl MissingXmlEntity { /// Creates a new MissingXmlEntity instance. /// /// # Arguments /// * `url` - URL where the XML entity was expected to be found /// /// # Returns /// A new MissingXmlEntity instance pub fn new(url: String) -> Self { Self { url } } } impl Problem for MissingXmlEntity { fn kind(&self) -> Cow<'_, str> { "missing-xml-entity".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "url": self.url }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingXmlEntity { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing XML entity: {}", self.url) } } /// Problem representing an error from the ccache compiler cache. /// /// This struct is used when the ccache tool, which accelerates repeated compilations, /// encounters an error during its operation. #[derive(Debug, Clone)] pub struct CcacheError(pub String); impl CcacheError { /// Creates a new CcacheError instance. /// /// # Arguments /// * `error` - The error message from ccache /// /// # Returns /// A new CcacheError instance pub fn new(error: String) -> Self { Self(error) } } impl Problem for CcacheError { fn kind(&self) -> Cow<'_, str> { "ccache-error".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "error": self.0 }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for CcacheError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "ccache error: {}", self.0) } } /// Problem representing a rejected Debian package version string. /// /// This struct is used when a version string for a Debian package is rejected /// by Debian tools as invalid or incompatible with policy requirements. #[derive(Debug, Clone)] pub struct DebianVersionRejected { /// The version string that was rejected. pub version: String, } impl DebianVersionRejected { /// Creates a new DebianVersionRejected instance. /// /// # Arguments /// * `version` - The version string that was rejected /// /// # Returns /// A new DebianVersionRejected instance pub fn new(version: String) -> Self { Self { version } } } impl Problem for DebianVersionRejected { fn kind(&self) -> Cow<'_, str> { "debian-version-rejected".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "version": self.version }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for DebianVersionRejected { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Debian Version Rejected; {}", self.version) } } /// Problem representing a failure to apply a patch. /// /// This struct is used when a build process fails because a patch /// cannot be successfully applied to the source code. #[derive(Debug, Clone)] pub struct PatchApplicationFailed { /// The name of the patch file that could not be applied. pub patchname: String, } impl PatchApplicationFailed { /// Creates a new PatchApplicationFailed instance. /// /// # Arguments /// * `patchname` - Name of the patch file that failed to apply /// /// # Returns /// A new PatchApplicationFailed instance pub fn new(patchname: String) -> Self { Self { patchname } } } impl Problem for PatchApplicationFailed { fn kind(&self) -> Cow<'_, str> { "patch-application-failed".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "patchname": self.patchname }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for PatchApplicationFailed { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Patch application failed: {}", self.patchname) } } /// Problem representing a need to update PostgreSQL build extension control files. /// /// This struct is used when PostgreSQL extension build files need to be updated /// using the pg_buildext updatecontrol command to generate control files from templates. #[derive(Debug, Clone)] pub struct NeedPgBuildExtUpdateControl { /// The path to the generated control file. pub generated_path: String, /// The path to the template file to use for generation. pub template_path: String, } impl NeedPgBuildExtUpdateControl { /// Creates a new NeedPgBuildExtUpdateControl instance. /// /// # Arguments /// * `generated_path` - Path to the generated control file /// * `template_path` - Path to the template file /// /// # Returns /// A new NeedPgBuildExtUpdateControl instance pub fn new(generated_path: String, template_path: String) -> Self { Self { generated_path, template_path, } } } impl Problem for NeedPgBuildExtUpdateControl { fn kind(&self) -> Cow<'_, str> { "need-pg-buildext-updatecontrol".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "generated_path": self.generated_path, "template_path": self.template_path }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for NeedPgBuildExtUpdateControl { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "Need to run 'pg_buildext updatecontrol' to update {}", self.generated_path ) } } /// Problem representing a failure to load a debhelper addon. /// /// This struct is used when debhelper fails to load an addon module, /// which typically provides additional functionality to the debhelper tools. #[derive(Debug, Clone)] pub struct DhAddonLoadFailure { /// The name of the addon that failed to load. pub name: String, /// The path where the addon was expected to be found. pub path: String, } impl DhAddonLoadFailure { /// Creates a new DhAddonLoadFailure instance. /// /// # Arguments /// * `name` - Name of the addon that failed to load /// * `path` - Path where the addon was expected to be found /// /// # Returns /// A new DhAddonLoadFailure instance pub fn new(name: String, path: String) -> Self { Self { name, path } } } impl Problem for DhAddonLoadFailure { fn kind(&self) -> Cow<'_, str> { "dh-addon-load-failure".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "name": self.name, "path": self.path }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for DhAddonLoadFailure { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "dh addon loading failed: {}", self.name) } } /// Problem representing an unsupported usage of the --until flag in debhelper. /// /// This struct is used when the --until flag is used with debhelper (dh) /// but the version of debhelper in use does not support this option. #[derive(Debug, Clone)] pub struct DhUntilUnsupported; impl Default for DhUntilUnsupported { /// Provides a default instance of DhUntilUnsupported. /// /// # Returns /// A new DhUntilUnsupported instance fn default() -> Self { Self::new() } } impl DhUntilUnsupported { /// Creates a new DhUntilUnsupported instance. /// /// # Returns /// A new DhUntilUnsupported instance pub fn new() -> Self { Self } } impl Problem for DhUntilUnsupported { fn kind(&self) -> Cow<'_, str> { "dh-until-unsupported".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for DhUntilUnsupported { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "dh --until is no longer supported") } } /// Problem representing a debhelper file pattern that was not found. /// /// This struct is used when a debhelper tool is looking for files matching /// a specific pattern but cannot find any matches in the searched directories. #[derive(Debug, Clone)] pub struct DebhelperPatternNotFound { /// The file pattern that was being searched for. pub pattern: String, /// The name of the debhelper tool that was performing the search. pub tool: String, /// The list of directories that were searched. pub directories: Vec, } impl DebhelperPatternNotFound { /// Creates a new DebhelperPatternNotFound instance. /// /// # Arguments /// * `pattern` - The file pattern that was being searched for /// * `tool` - The name of the debhelper tool /// * `directories` - The list of directories that were searched /// /// # Returns /// A new DebhelperPatternNotFound instance pub fn new(pattern: String, tool: String, directories: Vec) -> Self { Self { pattern, tool, directories, } } } impl Problem for DebhelperPatternNotFound { fn kind(&self) -> Cow<'_, str> { "debhelper-pattern-not-found".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "pattern": self.pattern, "tool": self.tool, "directories": self.directories }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for DebhelperPatternNotFound { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "debhelper ({}) expansion failed for {:?} (directories: {:?})", self.tool, self.pattern, self.directories ) } } /// Problem representing a missing Perl MANIFEST file. /// /// This struct is used when a Perl module build expects to find a MANIFEST /// file listing all files in the distribution, but it doesn't exist. #[derive(Debug, Clone)] pub struct MissingPerlManifest; impl Default for MissingPerlManifest { /// Provides a default instance of MissingPerlManifest. /// /// # Returns /// A new MissingPerlManifest instance fn default() -> Self { Self::new() } } impl MissingPerlManifest { /// Creates a new MissingPerlManifest instance. /// /// # Returns /// A new MissingPerlManifest instance pub fn new() -> Self { Self } } impl Problem for MissingPerlManifest { fn kind(&self) -> Cow<'_, str> { "missing-perl-manifest".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingPerlManifest { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "missing Perl MANIFEST") } } /// Problem representing a missing ImageMagick delegate. /// /// This struct is used when ImageMagick requires a delegate library /// to handle a specific file format or operation, but the delegate is not available. #[derive(Debug, Clone)] pub struct ImageMagickDelegateMissing { /// The name of the missing ImageMagick delegate. pub delegate: String, } impl ImageMagickDelegateMissing { /// Creates a new ImageMagickDelegateMissing instance. /// /// # Arguments /// * `delegate` - Name of the missing ImageMagick delegate /// /// # Returns /// A new ImageMagickDelegateMissing instance pub fn new(delegate: String) -> Self { Self { delegate } } } impl Problem for ImageMagickDelegateMissing { fn kind(&self) -> Cow<'_, str> { "imagemagick-delegate-missing".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "delegate": self.delegate }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for ImageMagickDelegateMissing { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Imagemagick missing delegate: {}", self.delegate) } } /// Problem representing a cancelled build or operation. /// /// This struct is used when a build process or operation was cancelled /// before completion, typically by user intervention or a timeout. #[derive(Debug, Clone)] pub struct Cancelled; impl Default for Cancelled { /// Provides a default instance of Cancelled. /// /// # Returns /// A new Cancelled instance fn default() -> Self { Self::new() } } impl Cancelled { /// Creates a new Cancelled instance. /// /// # Returns /// A new Cancelled instance pub fn new() -> Self { Self } } impl Problem for Cancelled { fn kind(&self) -> Cow<'_, str> { "cancelled".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for Cancelled { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Cancelled by runner or job manager") } } /// Problem representing symbols that have disappeared from a library. /// /// This struct is used when symbols (functions or variables) that were previously /// exported by a library are no longer present, which can break API compatibility. #[derive(Debug, Clone)] pub struct DisappearedSymbols; impl Default for DisappearedSymbols { /// Provides a default instance of DisappearedSymbols. /// /// # Returns /// A new DisappearedSymbols instance fn default() -> Self { Self::new() } } impl DisappearedSymbols { /// Creates a new DisappearedSymbols instance. /// /// # Returns /// A new DisappearedSymbols instance pub fn new() -> Self { Self } } impl Problem for DisappearedSymbols { fn kind(&self) -> Cow<'_, str> { "disappeared-symbols".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for DisappearedSymbols { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Disappeared symbols") } } /// Problem representing duplicate debhelper compatibility level specifications. /// /// This struct is used when the debhelper compatibility level is specified /// multiple times in different places, which can lead to conflicts. #[derive(Debug, Clone)] pub struct DuplicateDHCompatLevel { /// The command or file where the duplicate compatibility level was found. pub command: String, } impl DuplicateDHCompatLevel { /// Creates a new DuplicateDHCompatLevel instance. /// /// # Arguments /// * `command` - The command or file with the duplicate compatibility level /// /// # Returns /// A new DuplicateDHCompatLevel instance pub fn new(command: String) -> Self { Self { command } } } impl Problem for DuplicateDHCompatLevel { fn kind(&self) -> Cow<'_, str> { "duplicate-dh-compat-level".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "command": self.command }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for DuplicateDHCompatLevel { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "DH Compat Level specified twice (command: {})", self.command ) } } /// Problem representing a missing debhelper compatibility level specification. /// /// This struct is used when debhelper requires a compatibility level to be /// specified, but none was found in the expected locations. #[derive(Debug, Clone)] pub struct MissingDHCompatLevel { /// The command that reported the missing compatibility level. pub command: String, } impl MissingDHCompatLevel { /// Creates a new MissingDHCompatLevel instance. /// /// # Arguments /// * `command` - The command that reported the missing compatibility level /// /// # Returns /// A new MissingDHCompatLevel instance pub fn new(command: String) -> Self { Self { command } } } impl Problem for MissingDHCompatLevel { fn kind(&self) -> Cow<'_, str> { "missing-dh-compat-level".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "command": self.command }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingDHCompatLevel { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing DH Compat Level (command: {})", self.command) } } /// Problem representing a missing Java Virtual Machine (JVM). /// /// This struct is used when a build process requires a Java Virtual Machine /// but cannot find one installed or properly configured in the system. #[derive(Debug, Clone)] pub struct MissingJVM; impl Default for MissingJVM { /// Provides a default instance of MissingJVM. /// /// # Returns /// A new MissingJVM instance fn default() -> Self { Self::new() } } impl MissingJVM { /// Creates a new MissingJVM instance. /// /// # Returns /// A new MissingJVM instance pub fn new() -> Self { Self } } impl Problem for MissingJVM { fn kind(&self) -> Cow<'_, str> { "missing-jvm".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingJVM { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "missing JVM") } } /// Problem representing a missing Ruby gem. /// /// This struct is used when a build process requires a Ruby gem /// that is not installed or available in the current environment. #[derive(Debug, Clone)] pub struct MissingRubyGem { /// The name of the missing Ruby gem. pub gem: String, /// The required version of the gem, if specified. pub version: Option, } impl MissingRubyGem { /// Creates a new MissingRubyGem instance. /// /// # Arguments /// * `gem` - Name of the missing Ruby gem /// * `version` - Optional version requirement for the gem /// /// # Returns /// A new MissingRubyGem instance pub fn new(gem: String, version: Option) -> Self { Self { gem, version } } /// Creates a simple MissingRubyGem instance without version requirements. /// /// # Arguments /// * `gem` - Name of the missing Ruby gem /// /// # Returns /// A new MissingRubyGem instance with no version requirements pub fn simple(gem: String) -> Self { Self::new(gem, None) } } impl Problem for MissingRubyGem { fn kind(&self) -> Cow<'_, str> { "missing-ruby-gem".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "gem": self.gem, "version": self.version }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingRubyGem { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { if let Some(version) = &self.version { write!(f, "missing ruby gem: {} (>= {})", self.gem, version) } else { write!(f, "missing ruby gem: {}", self.gem) } } } /// Problem representing a missing JavaScript runtime environment. /// /// This struct is used when a build process requires a JavaScript runtime /// (like Node.js, Deno, or a browser JavaScript engine) but none is available. #[derive(Debug, Clone)] pub struct MissingJavaScriptRuntime; impl Default for MissingJavaScriptRuntime { /// Provides a default instance of MissingJavaScriptRuntime. /// /// # Returns /// A new MissingJavaScriptRuntime instance fn default() -> Self { Self::new() } } impl MissingJavaScriptRuntime { /// Creates a new MissingJavaScriptRuntime instance. /// /// # Returns /// A new MissingJavaScriptRuntime instance pub fn new() -> Self { Self } } impl Problem for MissingJavaScriptRuntime { fn kind(&self) -> Cow<'_, str> { "javascript-runtime-missing".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingJavaScriptRuntime { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing JavaScript Runtime") } } /// Problem representing a missing Ruby source file. /// /// This struct is used when a Ruby application or library tries to /// load or require a Ruby file that does not exist or cannot be found. #[derive(Debug, Clone)] pub struct MissingRubyFile { /// The name or path of the missing Ruby file. pub filename: String, } impl MissingRubyFile { /// Creates a new MissingRubyFile instance. /// /// # Arguments /// * `filename` - Name or path of the missing Ruby file /// /// # Returns /// A new MissingRubyFile instance pub fn new(filename: String) -> Self { Self { filename } } } impl Problem for MissingRubyFile { fn kind(&self) -> Cow<'_, str> { "missing-ruby-file".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "filename": self.filename }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingRubyFile { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "missing ruby file: {}", self.filename) } } /// Problem representing a missing PHP class. /// /// This struct is used when a PHP application tries to use a class /// that has not been defined or cannot be autoloaded. #[derive(Debug, Clone)] pub struct MissingPhpClass { /// The name of the missing PHP class. pub php_class: String, } impl MissingPhpClass { /// Creates a new MissingPhpClass instance. /// /// # Arguments /// * `php_class` - Name of the missing PHP class /// /// # Returns /// A new MissingPhpClass instance pub fn new(php_class: String) -> Self { Self { php_class } } /// Creates a simple MissingPhpClass instance. /// /// This is an alias for new() for API consistency with other similar types. /// /// # Arguments /// * `php_class` - Name of the missing PHP class /// /// # Returns /// A new MissingPhpClass instance pub fn simple(php_class: String) -> Self { Self::new(php_class) } } impl Problem for MissingPhpClass { fn kind(&self) -> Cow<'_, str> { "missing-php-class".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "php_class": self.php_class }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingPhpClass { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "missing PHP class: {}", self.php_class) } } #[derive(Debug, Clone)] /// Problem representing a missing Java class. /// /// This struct is used when a Java application or build process /// requires a Java class that cannot be found in the classpath. pub struct MissingJavaClass { /// The name of the missing Java class. pub classname: String, } impl MissingJavaClass { /// Creates a new MissingJavaClass instance. /// /// # Arguments /// * `classname` - Name of the missing Java class /// /// # Returns /// A new MissingJavaClass instance pub fn new(classname: String) -> Self { Self { classname } } /// Creates a simple MissingJavaClass instance. /// /// This is an alias for new() for API consistency with other similar types. /// /// # Arguments /// * `classname` - Name of the missing Java class /// /// # Returns /// A new MissingJavaClass instance pub fn simple(classname: String) -> Self { Self::new(classname) } } impl Problem for MissingJavaClass { fn kind(&self) -> Cow<'_, str> { "missing-java-class".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "classname": self.classname }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingJavaClass { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "missing Java class: {}", self.classname) } } #[derive(Debug, Clone)] /// Problem representing a missing Sprockets asset file. /// /// This struct is used when a Ruby on Rails application using the Sprockets /// asset pipeline is missing a required asset file. pub struct MissingSprocketsFile { /// The name of the missing Sprockets asset file. pub name: String, /// The content type of the missing asset file. pub content_type: String, } impl MissingSprocketsFile { /// Creates a new MissingSprocketsFile instance. /// /// # Arguments /// * `name` - Name of the missing Sprockets asset file /// * `content_type` - Content type of the missing asset file /// /// # Returns /// A new MissingSprocketsFile instance pub fn new(name: String, content_type: String) -> Self { Self { name, content_type } } } impl Problem for MissingSprocketsFile { fn kind(&self) -> Cow<'_, str> { "missing-sprockets-file".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "name": self.name, "content_type": self.content_type }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingSprocketsFile { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "missing sprockets file: {} (type: {})", self.name, self.content_type ) } } #[derive(Debug, Clone)] /// Problem representing a missing Xfce desktop environment dependency. /// /// This struct is used when a package build requires an Xfce-specific /// dependency package that is not available. pub struct MissingXfceDependency { /// The name of the missing Xfce dependency package. pub package: String, } impl MissingXfceDependency { /// Creates a new MissingXfceDependency instance. /// /// # Arguments /// * `package` - Name of the missing Xfce dependency package /// /// # Returns /// A new MissingXfceDependency instance pub fn new(package: String) -> Self { Self { package } } } impl Problem for MissingXfceDependency { fn kind(&self) -> Cow<'_, str> { "missing-xfce-dependency".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "package": self.package }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingXfceDependency { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "missing XFCE build dependency: {}", self.package) } } #[derive(Debug, Clone)] /// Problem representing missing GNOME common build tools and macros. /// /// This struct is used when a GNOME-related package build requires the /// gnome-common package, which provides common build tools and macros for GNOME projects. pub struct GnomeCommonMissing; impl Problem for GnomeCommonMissing { fn kind(&self) -> Cow<'_, str> { "missing-gnome-common".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for GnomeCommonMissing { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "gnome-common is not installed") } } #[derive(Debug, Clone)] /// Problem representing a missing input file for GNU config.status. /// /// This struct is used when the GNU autotools config.status script /// is missing one of its required input files. pub struct MissingConfigStatusInput { /// The path to the missing input file. pub path: String, } impl MissingConfigStatusInput { /// Creates a new MissingConfigStatusInput instance. /// /// # Arguments /// * `path` - Path to the missing config.status input file /// /// # Returns /// A new MissingConfigStatusInput instance pub fn new(path: String) -> Self { Self { path } } } impl Problem for MissingConfigStatusInput { fn kind(&self) -> Cow<'_, str> { "missing-config.status-input".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "path": self.path }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingConfigStatusInput { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "missing config.status input {}", self.path) } } #[derive(Debug, Clone)] /// Problem representing a missing GNOME common dependency. /// /// This struct is used when a GNOME-related package build requires a dependency /// that is typically provided by or related to the gnome-common infrastructure. pub struct MissingGnomeCommonDependency { /// The name of the missing GNOME common dependency package. pub package: String, /// The minimum required version of the dependency, if specified. pub minimum_version: Option, } impl MissingGnomeCommonDependency { /// Creates a new MissingGnomeCommonDependency instance. /// /// # Arguments /// * `package` - Name of the missing GNOME common dependency /// * `minimum_version` - Optional minimum version requirement /// /// # Returns /// A new MissingGnomeCommonDependency instance pub fn new(package: String, minimum_version: Option) -> Self { Self { package, minimum_version, } } /// Creates a simple MissingGnomeCommonDependency instance without version constraints. /// /// # Arguments /// * `package` - Name of the missing GNOME common dependency /// /// # Returns /// A new MissingGnomeCommonDependency instance with no version requirements pub fn simple(package: String) -> Self { Self::new(package, None) } } impl Problem for MissingGnomeCommonDependency { fn kind(&self) -> Cow<'_, str> { "missing-gnome-common-dependency".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "package": self.package, "minimum_version": self.minimum_version }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingGnomeCommonDependency { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "missing gnome-common dependency: {}: (>= {})", self.package, self.minimum_version.as_deref().unwrap_or("any") ) } } #[derive(Debug, Clone)] /// Problem representing a missing input file for GNU Automake. /// /// This struct is used when GNU Automake cannot find a required input /// file that it needs to generate build files. pub struct MissingAutomakeInput { /// The path to the missing input file. pub path: String, } impl MissingAutomakeInput { /// Creates a new MissingAutomakeInput instance. /// /// # Arguments /// * `path` - Path to the missing Automake input file /// /// # Returns /// A new MissingAutomakeInput instance pub fn new(path: String) -> Self { Self { path } } } impl Problem for MissingAutomakeInput { fn kind(&self) -> Cow<'_, str> { "missing-automake-input".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "path": self.path }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingAutomakeInput { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "automake input file {} missing", self.path) } } #[derive(Debug, Clone)] /// Problem representing a chroot environment that could not be found. /// /// This struct is used when a build process tries to use a chroot environment /// (a root directory that appears as the system root to enclosed processes), /// but the specified chroot does not exist. pub struct ChrootNotFound { /// The path or name of the chroot that could not be found. pub chroot: String, } impl ChrootNotFound { /// Creates a new ChrootNotFound instance. /// /// # Arguments /// * `chroot` - Path or name of the chroot that could not be found /// /// # Returns /// A new ChrootNotFound instance pub fn new(chroot: String) -> Self { Self { chroot } } } impl Problem for ChrootNotFound { fn kind(&self) -> Cow<'_, str> { "chroot-not-found".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "chroot": self.chroot }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for ChrootNotFound { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "chroot not found: {}", self.chroot) } } #[derive(Debug, Clone)] /// Problem representing missing GNU Libtool. /// /// This struct is used when a build process requires the GNU Libtool /// utility for creating portable shared libraries, but it is not installed. pub struct MissingLibtool; impl Default for MissingLibtool { /// Provides a default instance of MissingLibtool. /// /// # Returns /// A new MissingLibtool instance fn default() -> Self { Self::new() } } impl MissingLibtool { /// Creates a new MissingLibtool instance. /// /// # Returns /// A new MissingLibtool instance pub fn new() -> Self { Self } } impl Problem for MissingLibtool { fn kind(&self) -> Cow<'_, str> { "missing-libtool".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for MissingLibtool { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Libtool is missing") } } #[derive(Debug, Clone)] /// Problem representing missing CMake files. /// /// This struct is used when a CMake-based build process cannot find /// required CMake module or configuration files. pub struct CMakeFilesMissing { /// The names of the missing CMake files. pub filenames: Vec, /// The version of CMake that was requested, if specified. pub version: Option, } impl Problem for CMakeFilesMissing { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-cmake-files".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "filenames": self.filenames, "version": self.version, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for CMakeFilesMissing { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "CMake files missing: {:?}", self.filenames) } } #[derive(Debug, Clone)] /// Problem representing missing CMake package components. /// /// This struct is used when a CMake-based build process requires specific /// components of a package, but they cannot be found. pub struct MissingCMakeComponents { /// The name of the CMake package. pub name: String, /// The names of the missing components. pub components: Vec, } impl Problem for MissingCMakeComponents { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-cmake-components".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "name": self.name, "components": self.components, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingCMakeComponents { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing CMake components: {:?}", self.components) } } #[derive(Debug, Clone)] /// Problem representing a missing CMake package configuration. /// /// This struct is used when a CMake-based build process cannot find /// a required package configuration file. pub struct MissingCMakeConfig { /// The name of the CMake package. pub name: String, /// The version of the package that was requested, if specified. pub version: Option, } impl Problem for MissingCMakeConfig { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-cmake-config".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "name": self.name, "version": self.version, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingCMakeConfig { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { if let Some(version) = &self.version { write!( f, "Missing CMake package configuration for {} (version {})", self.name, version ) } else { write!(f, "Missing CMake package configuration for {}", self.name) } } } #[derive(Debug, Clone)] /// Problem representing a CMake package with mismatched version requirements. /// /// This struct is used when a CMake-based build process found a package, /// but it requires an exact version that doesn't match the found version. pub struct CMakeNeedExactVersion { /// The name of the CMake package. pub package: String, /// The version of the package that was found. pub version_found: String, /// The exact version required by the build. pub exact_version_needed: String, /// The path to the CMake package configuration file. pub path: PathBuf, } impl Problem for CMakeNeedExactVersion { fn kind(&self) -> std::borrow::Cow<'_, str> { "cmake-exact-version-missing".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "package": self.package, "version_found": self.version_found, "exact_version_needed": self.exact_version_needed, "path": self.path, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for CMakeNeedExactVersion { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "CMake needs exact package {}, version {}", self.package, self.exact_version_needed ) } } #[derive(Debug, Clone)] /// Problem representing a missing static library file. /// /// This struct is used when a build process requires a static library /// (typically a .a or .lib file) but it cannot be found. pub struct MissingStaticLibrary { /// The name of the library (without file extension). pub library: String, /// The expected filename of the static library. pub filename: String, } impl Problem for MissingStaticLibrary { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-static-library".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "library": self.library, "filename": self.filename, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingStaticLibrary { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "missing static library: {}", self.library) } } #[derive(Debug, Clone)] /// Problem representing a missing Go runtime. /// /// This struct is used when a build process requires the Go language runtime /// but it is not installed or cannot be found in the system. pub struct MissingGoRuntime; impl Problem for MissingGoRuntime { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-go-runtime".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingGoRuntime { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "go runtime is missing") } } #[derive(Debug, Clone)] /// Problem representing an unknown SSL/TLS certificate authority. /// /// This struct is used when a build process fails to establish a secure connection /// because it cannot verify the certificate authority of a remote server. pub struct UnknownCertificateAuthority(pub String); impl Problem for UnknownCertificateAuthority { fn kind(&self) -> std::borrow::Cow<'_, str> { "unknown-certificate-authority".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "url": self.0 }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for UnknownCertificateAuthority { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Unknown Certificate Authority for {}", self.0) } } #[derive(Debug, Clone)] /// Problem representing a missing predeclared function in Perl. /// /// This struct is used when a Perl script tries to use a predeclared function /// that is not available, often because a required module is not loaded. pub struct MissingPerlPredeclared(pub String); impl Problem for MissingPerlPredeclared { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-perl-predeclared".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "name": self.0 }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingPerlPredeclared { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "missing predeclared function: {}", self.0) } } #[derive(Debug, Clone)] /// Problem representing missing Git user identity configuration. /// /// This struct is used when Git operations that require user identity /// (like commits) fail because the user.name and user.email are not configured. pub struct MissingGitIdentity; impl Problem for MissingGitIdentity { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-git-identity".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingGitIdentity { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing Git Identity") } } #[derive(Debug, Clone)] /// Problem representing a missing GPG secret key. /// /// This struct is used when an operation requires a GPG secret key /// (such as signing packages or commits) but no secret key is available. pub struct MissingSecretGpgKey; impl Problem for MissingSecretGpgKey { fn kind(&self) -> std::borrow::Cow<'_, str> { "no-secret-gpg-key".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingSecretGpgKey { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "No secret GPG key is present") } } #[derive(Debug, Clone)] /// Problem representing missing version information for vcversioner. /// /// This struct is used when the vcversioner Python package cannot determine /// the version from either a Git directory or a version.txt file. pub struct MissingVcVersionerVersion; impl Problem for MissingVcVersionerVersion { fn kind(&self) -> std::borrow::Cow<'_, str> { "no-vcversioner-version".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingVcVersionerVersion { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "vcversion could not find a git directory or version.txt file" ) } } #[derive(Debug, Clone)] /// Problem representing a missing LaTeX file. /// /// This struct is used when a LaTeX build process requires a file /// (such as a class file, style file, or content file) but it cannot be found. pub struct MissingLatexFile(pub String); impl MissingLatexFile { /// Creates a new MissingLatexFile instance. /// /// # Arguments /// * `filename` - Name of the missing LaTeX file /// /// # Returns /// A new MissingLatexFile instance pub fn new(filename: String) -> Self { Self(filename) } } impl Problem for MissingLatexFile { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-latex-file".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "filename": self.0 }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingLatexFile { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing LaTeX file: {}", self.0) } } #[derive(Debug, Clone)] /// Problem representing a missing X Window System display. /// /// This struct is used when a program requires an X11 display connection /// but no display server is available (such as in headless environments). pub struct MissingXDisplay; impl Problem for MissingXDisplay { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-x-display".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingXDisplay { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "No X Display") } } #[derive(Debug, Clone)] /// Problem representing a missing font specification in LaTeX. /// /// This struct is used when a LaTeX document requires a specific font /// but the fontspec package cannot find it. pub struct MissingFontspec(pub String); impl Problem for MissingFontspec { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-fontspec".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "fontspec": self.0 }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingFontspec { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing font spec: {}", self.0) } } #[derive(Debug, Clone)] /// Problem representing a process killed due to inactivity. /// /// This struct is used when a build process was killed by the system /// because it was inactive for too long. pub struct InactiveKilled(pub i64); impl Problem for InactiveKilled { fn kind(&self) -> std::borrow::Cow<'_, str> { "inactive-killed".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "minutes": self.0 }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for InactiveKilled { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Killed due to inactivity after {} minutes", self.0) } } #[derive(Debug, Clone)] /// Problem representing missing PAUSE credentials for Perl module upload. /// /// This struct is used when attempting to upload a Perl module to PAUSE /// (Perl Authors Upload Server) without proper authentication credentials. pub struct MissingPauseCredentials; impl Problem for MissingPauseCredentials { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-pause-credentials".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingPauseCredentials { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing credentials for PAUSE") } } #[derive(Debug, Clone)] /// Problem representing mismatched gettext versions. /// /// This struct is used when there's a version mismatch between gettext versions /// referenced in Makefile.in.in and the autoconf macros. pub struct MismatchGettextVersions { /// The gettext version specified in the Makefile. pub makefile_version: String, /// The gettext version specified in autoconf macros. pub autoconf_version: String, } impl Problem for MismatchGettextVersions { fn kind(&self) -> std::borrow::Cow<'_, str> { "mismatch-gettext-versions".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "makefile_version": self.makefile_version, "autoconf_version": self.autoconf_version }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MismatchGettextVersions { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "Mismatch versions ({}, {})", self.makefile_version, self.autoconf_version ) } } #[derive(Debug, Clone)] /// Problem representing an invalid current user for a build operation. /// /// This struct is used when a build process encounters issues because /// it's running under an unexpected or inappropriate user account. pub struct InvalidCurrentUser(pub String); impl Problem for InvalidCurrentUser { fn kind(&self) -> std::borrow::Cow<'_, str> { "invalid-current-user".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "user": self.0 }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for InvalidCurrentUser { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Can not run as {}", self.0) } } #[derive(Debug, Clone)] /// Problem representing a missing GNU lib directory. /// /// This struct is used when a build process requires a gnulib directory /// (a collection of portable GNU utility functions) but it cannot be found. pub struct MissingGnulibDirectory(pub PathBuf); impl Problem for MissingGnulibDirectory { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-gnulib-directory".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "directory": self.0 }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingGnulibDirectory { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing gnulib directory: {}", self.0.display()) } } #[derive(Debug, Clone)] /// Problem representing a missing Lua module. /// /// This struct is used when a Lua script or application attempts to /// load a Lua module that is not installed or cannot be found. pub struct MissingLuaModule(pub String); impl Problem for MissingLuaModule { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-lua-module".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "module": self.0 }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingLuaModule { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing Lua Module: {}", self.0) } } #[derive(Debug, Clone)] /// Problem representing a missing Go module file. /// /// This struct is used when a Go project requires a go.mod file for /// module and dependency management, but the file is missing. pub struct MissingGoModFile; impl Problem for MissingGoModFile { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-go.mod-file".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingGoModFile { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "go.mod file is missing") } } #[derive(Debug, Clone)] /// Problem representing an outdated Go module file. /// /// This struct is used when a Go project's go.mod file needs to be /// updated due to changes in dependencies or Go version requirements. pub struct OutdatedGoModFile; impl Problem for OutdatedGoModFile { fn kind(&self) -> std::borrow::Cow<'_, str> { "outdated-go.mod-file".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for OutdatedGoModFile { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "go.mod file is outdated") } } #[derive(Debug, Clone)] /// Problem representing insufficient code test coverage. /// /// This struct is used when a build process requires a minimum level of /// code test coverage, but the actual coverage is below the required threshold. pub struct CodeCoverageTooLow { /// The actual code coverage percentage achieved. pub actual: f64, /// The minimum code coverage percentage required. pub required: f64, } impl Problem for CodeCoverageTooLow { fn kind(&self) -> std::borrow::Cow<'_, str> { "code-coverage-too-low".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "actual": self.actual, "required": self.required }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for CodeCoverageTooLow { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "Code coverage too low: {:.2} < {:.2}", self.actual, self.required ) } } #[derive(Debug, Clone)] /// Problem representing improper usage of ES modules. /// /// This struct is used when a JavaScript module is using CommonJS require() /// syntax to load an ES module, which must be loaded with import() instead. pub struct ESModuleMustUseImport(pub String); impl Problem for ESModuleMustUseImport { fn kind(&self) -> std::borrow::Cow<'_, str> { "esmodule-must-use-import".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "path": self.0 }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for ESModuleMustUseImport { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "ESM-only module {} must use import()", self.0) } } #[derive(Debug, Clone)] /// Problem representing a missing PHP extension. /// /// This struct is used when a PHP application requires an extension /// (like mysqli, gd, intl, etc.) that is not installed or enabled. pub struct MissingPHPExtension(pub String); impl Problem for MissingPHPExtension { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-php-extension".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "extension": self.0 }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingPHPExtension { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing PHP Extension: {}", self.0) } } #[derive(Debug, Clone)] /// Problem representing an outdated minimum autoconf version requirement. /// /// This struct is used when a project's configure script specifies a minimum autoconf /// version that is considered too old for modern builds. pub struct MinimumAutoconfTooOld(pub String); impl Problem for MinimumAutoconfTooOld { fn kind(&self) -> std::borrow::Cow<'_, str> { "minimum-autoconf-too-old".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "minimum_version": self.0 }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MinimumAutoconfTooOld { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "configure.{{ac,in}} should require newer autoconf {}", self.0 ) } } #[derive(Debug, Clone)] /// Problem representing a missing file in a Perl distribution. /// /// This struct is used when a Perl module build or installation process /// cannot find a required file that should be part of the distribution. pub struct MissingPerlDistributionFile(pub String); impl Problem for MissingPerlDistributionFile { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-perl-distribution-file".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "filename": self.0 }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingPerlDistributionFile { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing perl distribution file: {}", self.0) } } #[derive(Debug, Clone)] /// Problem representing a missing entry in the Go checksum file. /// /// This struct is used when a Go project requires an entry in the go.sum file /// for a specific package version, but the entry is missing. pub struct MissingGoSumEntry { /// The package import path. pub package: String, /// The version of the package. pub version: String, } impl Problem for MissingGoSumEntry { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-go.sum-entry".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "package": self.package, "version": self.version }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingGoSumEntry { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing go.sum entry: {}@{}", self.package, self.version) } } #[derive(Debug, Clone)] /// Problem representing an issue with the Vala compiler. /// /// This struct is used when the Vala compiler (valac) encounters /// an error that prevents it from compiling Vala source code. pub struct ValaCompilerCannotCompile; impl Problem for ValaCompilerCannotCompile { fn kind(&self) -> std::borrow::Cow<'_, str> { "valac-cannot-compile".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for ValaCompilerCannotCompile { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "valac can not compile") } } #[derive(Debug, Clone)] /// Problem representing a missing Debian build dependency. /// /// This struct is used when a Debian package build requires a dependency /// that is listed in Build-Depends but is not installed in the build environment. pub struct MissingDebianBuildDep(pub String); impl Problem for MissingDebianBuildDep { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-debian-build-dep".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "dep": self.0 }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingDebianBuildDep { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing Debian Build-Depends: {}", self.0) } } #[derive(Debug, Clone)] /// Problem representing missing Qt modules. /// /// This struct is used when a build process requires specific Qt modules /// (like QtCore, QtGui, QtWidgets, etc.) that are not available. pub struct MissingQtModules(pub Vec); impl Problem for MissingQtModules { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-qt-modules".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "modules": self.0 }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingQtModules { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing QT modules: {:?}", self.0) } } #[derive(Debug, Clone)] /// Problem representing a missing OCaml package. /// /// This struct is used when an OCaml project requires a package /// that is not installed or cannot be found in the OCaml environment. pub struct MissingOCamlPackage(pub String); impl Problem for MissingOCamlPackage { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-ocaml-package".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "package": self.0 }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingOCamlPackage { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing OCaml package: {}", self.0) } } #[derive(Debug, Clone)] /// Problem representing a "too many open files" error. /// /// This struct is used when a process hits the system limit for the number /// of files that can be opened simultaneously, often due to a resource leak. pub struct TooManyOpenFiles; impl Problem for TooManyOpenFiles { fn kind(&self) -> std::borrow::Cow<'_, str> { "too-many-open-files".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for TooManyOpenFiles { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Too many open files") } } #[derive(Debug, Clone)] /// Problem representing a missing Makefile target. /// /// This struct is used when a build process tries to run a make target /// that doesn't exist in the Makefile. pub struct MissingMakeTarget(pub String, pub Option); impl MissingMakeTarget { /// Creates a new MissingMakeTarget instance. /// /// # Arguments /// * `target` - The name of the missing make target /// * `required_by` - Optional name of the entity that requires this target /// /// # Returns /// A new MissingMakeTarget instance pub fn new(target: &str, required_by: Option<&str>) -> Self { Self(target.to_string(), required_by.map(String::from)) } /// Creates a simple MissingMakeTarget instance without specifying what requires it. /// /// # Arguments /// * `target` - The name of the missing make target /// /// # Returns /// A new MissingMakeTarget instance with no requirer information pub fn simple(target: &str) -> Self { Self::new(target, None) } } impl std::fmt::Display for MissingMakeTarget { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Unknown make target: {}", self.0) } } impl Problem for MissingMakeTarget { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-make-target".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "target": self.0, "required_by": self.1 }) } fn as_any(&self) -> &dyn std::any::Any { self } } buildlog-consultant-0.1.4/src/problems/debian.rs000064400000000000000000001021021046102023000200150ustar 00000000000000use crate::Problem; use debversion::Version; /// Problem representing a generic dpkg error. /// /// This struct is used for errors reported by the dpkg package manager /// that don't fit into more specific categories. #[derive(Debug)] pub struct DpkgError(pub String); impl Problem for DpkgError { fn kind(&self) -> std::borrow::Cow<'_, str> { "dpkg-error".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "msg": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for DpkgError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "dpkg error: {}", self.0) } } /// Problem representing an error during apt-get update. /// /// This struct is used when the apt package database update process /// fails for any reason. #[derive(Debug, Clone)] pub struct AptUpdateError; impl Problem for AptUpdateError { fn kind(&self) -> std::borrow::Cow<'_, str> { "apt-update-error".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for AptUpdateError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "apt update error") } } /// Problem representing a failure to fetch a package or repository data. /// /// This struct is used when apt cannot download a package or repository /// data from the specified URL. #[derive(Debug, Clone)] pub struct AptFetchFailure { /// The URL that apt was trying to fetch from, if available. pub url: Option, /// The error message from the fetch failure. pub error: String, } impl Problem for AptFetchFailure { fn kind(&self) -> std::borrow::Cow<'_, str> { "apt-file-fetch-failure".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "url": self.url, "error": self.error, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for AptFetchFailure { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { if let Some(url) = &self.url { write!(f, "apt fetch failure: {} ({})", url, self.error) } else { write!(f, "apt fetch failure: {}", self.error) } } } /// Problem representing a missing Release file for a repository. /// /// This struct is used when apt cannot find the Release file for a repository, /// which typically indicates a misconfigured or unavailable repository. #[derive(Debug, Clone)] pub struct AptMissingReleaseFile(pub String); impl Problem for AptMissingReleaseFile { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-release-file".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "url": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for AptMissingReleaseFile { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "apt missing release file: {}", self.0) } } /// Problem representing a package that apt cannot find. /// /// This struct is used when apt cannot find a requested package in any /// of the configured repositories. #[derive(Debug, Clone)] pub struct AptPackageUnknown(pub String); impl Problem for AptPackageUnknown { fn kind(&self) -> std::borrow::Cow<'_, str> { "apt-package-unknown".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "package": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for AptPackageUnknown { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "apt package unknown: {}", self.0) } } /// Problem representing broken package dependencies. /// /// This struct is used when apt reports broken packages in the dependency /// resolution process, which can occur when packages have incompatible dependencies. #[derive(Debug, Clone)] pub struct AptBrokenPackages { /// A description of the broken package situation. pub description: String, /// List of packages that are broken, if available. pub broken: Option>, } impl Problem for AptBrokenPackages { fn kind(&self) -> std::borrow::Cow<'_, str> { "apt-broken-packages".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "description": self.description, "broken": self.broken, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for AptBrokenPackages { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "apt broken packages: {}", self.description) } } /// Problem representing a missing upstream source tarball. /// /// This struct is used when the build process cannot find the upstream /// source tarball for a package, which is required for the build. #[derive(Debug, Clone)] pub struct UnableToFindUpstreamTarball { /// The name of the package. pub package: String, /// The version of the package for which the tarball is missing. pub version: Version, } impl Problem for UnableToFindUpstreamTarball { fn kind(&self) -> std::borrow::Cow<'_, str> { "unable-to-find-upstream-tarball".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "package": self.package, "version": self.version.to_string(), }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for UnableToFindUpstreamTarball { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "Unable to find upstream tarball for {} {}", self.package, self.version ) } } /// Problem representing a source format that cannot be built. /// /// This struct is used when the source package format specified in /// debian/source/format cannot be built for some reason. #[derive(Debug, Clone)] pub struct SourceFormatUnbuildable { /// The source format that can't be built (e.g., "3.0 (quilt)"). pub source_format: String, /// The reason why the source format cannot be built. pub reason: String, } impl Problem for SourceFormatUnbuildable { fn kind(&self) -> std::borrow::Cow<'_, str> { "source-format-unbuildable".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "source_format": self.source_format, "reason": self.reason, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for SourceFormatUnbuildable { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "Source format {} is unbuildable: {}", self.source_format, self.reason ) } } /// Problem representing a source format that is not supported. /// /// This struct is used when the source package format specified in /// debian/source/format is not supported by the build environment. #[derive(Debug, Clone)] pub struct SourceFormatUnsupported(pub String); impl Problem for SourceFormatUnsupported { fn kind(&self) -> std::borrow::Cow<'_, str> { "source-format-unsupported".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "source_format": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for SourceFormatUnsupported { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Source format {} is unsupported", self.0) } } /// Problem representing a missing patch file. /// /// This struct is used when a build requires a patch file that is /// referenced in the debian/patches directory but is not found. #[derive(Debug, Clone)] pub struct PatchFileMissing(pub std::path::PathBuf); impl Problem for PatchFileMissing { fn kind(&self) -> std::borrow::Cow<'_, str> { "patch-file-missing".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "path": self.0.display().to_string(), }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for PatchFileMissing { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Patch file missing: {}", self.0.display()) } } /// Problem representing unexpected local changes in the source package. /// /// This struct is used when dpkg-source detects unexpected local changes /// to upstream source code, which should be represented as patches instead. #[derive(Debug, Clone)] pub struct DpkgSourceLocalChanges { /// Path to the diff file showing the changes, if available. pub diff_file: Option, /// List of files that have been changed locally, if available. pub files: Option>, } impl Problem for DpkgSourceLocalChanges { fn kind(&self) -> std::borrow::Cow<'_, str> { "unexpected-local-upstream-changes".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "diff_file": self.diff_file, "files": self.files, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for DpkgSourceLocalChanges { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(files) = self.files.as_ref() { if files.len() < 5 { write!(f, "Tree has local changes: {:?}", files)?; return Ok(()); } write!(f, "Tree has local changes: {} files", files.len())?; } else { write!(f, "Tree has local changes")?; } Ok(()) } } /// Problem representing changes that cannot be represented in the source package. /// /// This struct is used when dpkg-source detects changes that cannot be /// represented in the chosen source format, such as mode changes in some formats. #[derive(Debug, Clone)] pub struct DpkgSourceUnrepresentableChanges; impl Problem for DpkgSourceUnrepresentableChanges { fn kind(&self) -> std::borrow::Cow<'_, str> { "unrepresentable-local-changes".into() } fn json(&self) -> serde_json::Value { serde_json::Value::Null } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for DpkgSourceUnrepresentableChanges { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Tree has unrepresentable changes") } } /// Problem representing unwanted binary files in the source package. /// /// This struct is used when dpkg-source detects binary files in the source /// package that are not allowed, which can happen when the source is dirty. #[derive(Debug, Clone)] pub struct DpkgUnwantedBinaryFiles; impl Problem for DpkgUnwantedBinaryFiles { fn kind(&self) -> std::borrow::Cow<'_, str> { "unwanted-binary-files".into() } fn json(&self) -> serde_json::Value { serde_json::Value::Null } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for DpkgUnwantedBinaryFiles { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Tree has unwanted binary files") } } /// Problem representing changes to binary files in the source package. /// /// This struct is used when dpkg-source detects that binary files have been /// changed, which cannot be properly represented in source package formats. #[derive(Debug, Clone)] pub struct DpkgBinaryFileChanged(pub Vec); impl Problem for DpkgBinaryFileChanged { fn kind(&self) -> std::borrow::Cow<'_, str> { "binary-file-changed".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "files": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for DpkgBinaryFileChanged { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Binary file changed") } } /// Problem representing a missing debian control file. /// /// This struct is used when the debian/control file, which is required for /// any Debian package, is missing from the source package. #[derive(Debug, Clone)] pub struct MissingControlFile(pub std::path::PathBuf); impl Problem for MissingControlFile { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-control-file".into() } fn json(&self) -> serde_json::Value { serde_json::Value::Null } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingControlFile { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Missing control file: {}", self.0.display()) } } /// Problem representing unknown Mercurial extra fields. /// /// This struct is used when the build process encounters unknown extra fields /// in Mercurial version control metadata. #[derive(Debug, Clone)] pub struct UnknownMercurialExtraFields(pub String); impl Problem for UnknownMercurialExtraFields { fn kind(&self) -> std::borrow::Cow<'_, str> { "unknown-mercurial-extra-fields".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "field": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for UnknownMercurialExtraFields { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Unknown Mercurial extra field: {}", self.0) } } /// Problem representing a failure to verify an upstream PGP signature. /// /// This struct is used when the build process cannot verify the PGP signature /// of an upstream source tarball, which may indicate a security issue. #[derive(Debug, Clone)] pub struct UpstreamPGPSignatureVerificationFailed; impl Problem for UpstreamPGPSignatureVerificationFailed { fn kind(&self) -> std::borrow::Cow<'_, str> { "upstream-pgp-signature-verification-failed".into() } fn json(&self) -> serde_json::Value { serde_json::Value::Null } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for UpstreamPGPSignatureVerificationFailed { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Upstream PGP signature verification failed") } } /// Problem representing a missing requested version in uscan. /// /// This struct is used when the uscan tool (which checks for upstream versions) /// cannot find a specifically requested version. #[derive(Debug, Clone)] pub struct UScanRequestVersionMissing(pub String); impl Problem for UScanRequestVersionMissing { fn kind(&self) -> std::borrow::Cow<'_, str> { "uscan-request-version-missing".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "version": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for UScanRequestVersionMissing { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "UScan request version missing: {}", self.0) } } /// Problem representing a failure in the debcargo tool. /// /// This struct is used when the debcargo tool, which is used to package /// Rust crates as Debian packages, encounters an error. #[derive(Debug, Clone)] pub struct DebcargoFailure(pub String); impl Problem for DebcargoFailure { fn kind(&self) -> std::borrow::Cow<'_, str> { "debcargo-failure".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "reason": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for DebcargoFailure { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Debcargo failure: {}", self.0) } } /// Problem representing an error parsing a debian/changelog file. /// /// This struct is used when the build process encounters a syntax error /// or other issue when parsing the debian/changelog file. #[derive(Debug, Clone)] pub struct ChangelogParseError(pub String); impl Problem for ChangelogParseError { fn kind(&self) -> std::borrow::Cow<'_, str> { "changelog-parse-error".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "reason": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for ChangelogParseError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Changelog parse error: {}", self.0) } } /// Problem representing a generic error in the uscan tool. /// /// This struct is used when the uscan tool, which checks for upstream versions, /// encounters an error that doesn't fit into more specific categories. #[derive(Debug, Clone)] pub struct UScanError(pub String); impl Problem for UScanError { fn kind(&self) -> std::borrow::Cow<'_, str> { "uscan-error".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "reason": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for UScanError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "UScan error: {}", self.0) } } /// Problem representing a failure in the uscan tool. /// /// This struct is used when the uscan tool fails to find or process /// upstream versions from a specific URL. #[derive(Debug, Clone)] pub struct UScanFailed { /// The URL that uscan was trying to process. pub url: String, /// The reason for the failure. pub reason: String, } impl Problem for UScanFailed { fn kind(&self) -> std::borrow::Cow<'_, str> { "uscan-failed".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "url": self.url, "reason": self.reason, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for UScanFailed { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "UScan failed: {}", self.reason) } } /// Problem representing inconsistency between source format and version. /// /// This struct is used when there's an inconsistency between the source format /// specified in debian/source/format and the version numbering scheme. #[derive(Debug, Clone)] pub struct InconsistentSourceFormat { /// Whether the version is inconsistent with the source format. pub version: bool, /// Whether the source format is inconsistent with the version. pub source_format: bool, } impl Problem for InconsistentSourceFormat { fn kind(&self) -> std::borrow::Cow<'_, str> { "inconsistent-source-format".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "version": self.version, "source_format": self.source_format, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for InconsistentSourceFormat { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "Inconsistent source format between version and source format" ) } } /// Problem representing an error parsing the debian/upstream/metadata file. /// /// This struct is used when the build process cannot parse the /// debian/upstream/metadata file, which contains information about the upstream project. #[derive(Debug, Clone)] pub struct UpstreamMetadataFileParseError { /// The path to the metadata file. pub path: std::path::PathBuf, /// The reason for the parsing failure. pub reason: String, } impl Problem for UpstreamMetadataFileParseError { fn kind(&self) -> std::borrow::Cow<'_, str> { "debian-upstream-metadata-invalid".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "path": self.path.display().to_string(), "reason": self.reason, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for UpstreamMetadataFileParseError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Upstream metadata file parse error: {}", self.reason) } } /// Problem representing a failure in dpkg-source when packaging source files. /// /// This struct is used when dpkg-source cannot package the source files /// into a source package for various reasons. #[derive(Debug, Clone)] pub struct DpkgSourcePackFailed(pub String); impl Problem for DpkgSourcePackFailed { fn kind(&self) -> std::borrow::Cow<'_, str> { "dpkg-source-pack-failed".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "reason": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for DpkgSourcePackFailed { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Dpkg source pack failed: {}", self.0) } } /// Problem representing an invalid version string in a package. /// /// This struct is used when dpkg encounters a version string that /// doesn't follow the Debian version format rules. #[derive(Debug, Clone)] pub struct DpkgBadVersion { /// The invalid version string. pub version: String, /// The reason why the version is invalid, if available. pub reason: Option, } impl Problem for DpkgBadVersion { fn kind(&self) -> std::borrow::Cow<'_, str> { "dpkg-bad-version".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "version": self.version, "reason": self.reason, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for DpkgBadVersion { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(reason) = &self.reason { write!(f, "Version {} is invalid: {}", self.version, reason) } else { write!(f, "Version {} is invalid", self.version) } } } /// Problem representing a missing Rust crate in debcargo. /// /// This struct is used when debcargo cannot find a Rust crate /// that is required for the build. #[derive(Debug, Clone)] pub struct MissingDebcargoCrate { /// The name of the missing Rust crate. pub cratename: String, /// The version of the crate that is required, if specified. pub version: Option, } impl Problem for MissingDebcargoCrate { fn kind(&self) -> std::borrow::Cow<'_, str> { "debcargo-missing-crate".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "crate": self.cratename, "version": self.version, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingDebcargoCrate { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(version) = &self.version { write!( f, "debcargo can't find crate {} (version: {})", self.cratename, version ) } else { write!(f, "debcargo can't find crate {}", self.cratename) } } } impl MissingDebcargoCrate { /// Creates a MissingDebcargoCrate instance from a string. /// /// Parses a string in the format "cratename=version" or just "cratename" /// to create a new instance. /// /// # Arguments /// * `text` - The string to parse /// /// # Returns /// A new MissingDebcargoCrate instance pub fn from_string(text: &str) -> Self { let text = text.trim(); if let Some((cratename, version)) = text.split_once('=') { Self { cratename: cratename.trim().to_string(), version: Some(version.trim().to_string()), } } else { Self { cratename: text.to_string(), version: None, } } } } /// Problem representing a missing pristine-tar tree reference. /// /// This struct is used when a pristine-tar operation cannot find /// a referenced tree in the git repository. #[derive(Debug, Clone)] pub struct PristineTarTreeMissing(pub String); impl Problem for PristineTarTreeMissing { fn kind(&self) -> std::borrow::Cow<'_, str> { "pristine-tar-missing-tree".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "treeish": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for PristineTarTreeMissing { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Pristine-tar tree missing: {}", self.0) } } /// Problem representing a missing revision in version control. /// /// This struct is used when a build process references a revision /// in version control that does not exist. #[derive(Debug, Clone)] pub struct MissingRevision(pub Vec); impl Problem for MissingRevision { fn kind(&self) -> std::borrow::Cow<'_, str> { "missing-revision".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "revision": String::from_utf8_lossy(&self.0), }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for MissingRevision { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Missing revision: {}", String::from_utf8_lossy(&self.0)) } } /// Problem representing a Rust crate dependency predicate that debcargo cannot handle. /// /// This struct is used when debcargo cannot represent a predicate in a Rust /// crate dependency, such as certain prerelease version constraints. #[derive(Debug)] pub struct DebcargoUnacceptablePredicate { /// The name of the crate with the unacceptable predicate. pub cratename: String, /// The predicate that cannot be represented. pub predicate: String, } impl Problem for DebcargoUnacceptablePredicate { fn kind(&self) -> std::borrow::Cow<'_, str> { "debcargo-unacceptable-predicate".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "crate": self.cratename, "predicate": self.predicate, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for DebcargoUnacceptablePredicate { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "Cannot represent prerelease part of dependency: {}", self.predicate ) } } /// Problem representing a Rust crate dependency comparator that debcargo cannot handle. /// /// This struct is used when debcargo cannot represent a version comparison operator /// in a Rust crate dependency, such as certain complex version constraints. #[derive(Debug)] pub struct DebcargoUnacceptableComparator { /// The name of the crate with the unacceptable comparator. pub cratename: String, /// The comparator that cannot be represented. pub comparator: String, } impl Problem for DebcargoUnacceptableComparator { fn kind(&self) -> std::borrow::Cow<'_, str> { "debcargo-unacceptable-comparator".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "crate": self.cratename, "comparator": self.comparator, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for DebcargoUnacceptableComparator { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "Cannot represent prerelease part of dependency: {}", self.comparator ) } } /// Problem representing a "too many requests" error from uscan. /// /// This struct is used when uscan receives a rate limiting response /// from a server it is checking for upstream versions. #[derive(Debug)] pub struct UScanTooManyRequests(pub String); impl Problem for UScanTooManyRequests { fn kind(&self) -> std::borrow::Cow<'_, str> { "uscan-too-many-requests".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "reason": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for UScanTooManyRequests { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "UScan too many requests: {}", self.0) } } /// Problem representing unsatisfied conflicts in apt dependencies. /// /// This struct is used when apt cannot resolve package conflicts /// during the dependency resolution process. #[derive(Debug)] pub struct UnsatisfiedAptConflicts(pub String); impl Problem for UnsatisfiedAptConflicts { fn kind(&self) -> std::borrow::Cow<'_, str> { "unsatisfied-apt-conflicts".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "relations": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for UnsatisfiedAptConflicts { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "unsatisfied apt conflicts: {}", self.0) } } impl std::error::Error for UnsatisfiedAptConflicts {} /// Problem representing an architecture not in the supported architecture list. /// /// This struct is used when a build is attempted for an architecture that /// is not in the list of architectures supported by the package. #[derive(Debug, Clone)] pub struct ArchitectureNotInList { /// The architecture being built for. pub arch: String, /// The list of supported architectures. pub arch_list: Vec, } impl Problem for ArchitectureNotInList { fn kind(&self) -> std::borrow::Cow<'_, str> { "arch-not-in-list".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "arch": self.arch, "arch_list": self.arch_list, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for ArchitectureNotInList { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Architecture {} not a build arch", self.arch) } } /// Problem representing unsatisfied dependencies in apt. /// /// This struct is used when apt cannot satisfy the dependencies /// required for a package installation. #[derive(Debug)] pub struct UnsatisfiedAptDependencies(pub String); impl Problem for UnsatisfiedAptDependencies { fn kind(&self) -> std::borrow::Cow<'_, str> { "unsatisfied-apt-dependencies".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "relations": self.0 }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for UnsatisfiedAptDependencies { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "unsatisfied apt dependencies: {}", self.0) } } /// Problem representing insufficient disk space for a build. /// /// This struct is used when a build process determines that there is /// not enough disk space available to complete the build. #[derive(Debug)] pub struct InsufficientDiskSpace { /// The amount of disk space needed for the build in KiB. pub needed: i64, /// The amount of free disk space available in KiB. pub free: i64, } impl Problem for InsufficientDiskSpace { fn kind(&self) -> std::borrow::Cow<'_, str> { "insufficient-disk-space".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "needed": self.needed, "free": self.free, }) } fn as_any(&self) -> &dyn std::any::Any { self } } impl std::fmt::Display for InsufficientDiskSpace { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "Insufficient disk space for build. Need: {} KiB, free: {} KiB", self.needed, self.free ) } } #[cfg(test)] mod tests { use super::*; use crate::Problem; #[test] fn test_dpkg_source_local_changes_trait() { let problem = DpkgSourceLocalChanges { diff_file: Some("/tmp/diff.patch".to_string()), files: Some(vec!["file1.txt".to_string(), "file2.txt".to_string()]), }; let json = problem.json(); assert_eq!(json["diff_file"], "/tmp/diff.patch"); assert_eq!(json["files"], serde_json::json!(["file1.txt", "file2.txt"])); } #[test] fn test_uscan_too_many_requests_trait() { let problem = UScanTooManyRequests("rate limit exceeded".to_string()); let json = problem.json(); assert_eq!(json["reason"], "rate limit exceeded"); } } buildlog-consultant-0.1.4/src/problems/mod.rs000064400000000000000000000006531046102023000173620ustar 00000000000000//! Module containing problem definitions for various build systems. //! //! This module provides problems that can be identified in build logs, //! organized by the build system or tool they relate to. /// Problems specific to autopkgtest logs. pub mod autopkgtest; /// Common problems that can occur in various build environments. pub mod common; /// Problems specific to Debian packaging and build tools. pub mod debian; buildlog-consultant-0.1.4/src/sbuild.rs000064400000000000000000001742221046102023000162460ustar 00000000000000//! Module for parsing and analyzing Debian sbuild logs. //! //! This module provides functionality for parsing Debian sbuild logs, extracting //! structured information from them, and identifying common build failures. //! It can analyze logs for issues like dependency problems, space issues, //! compilation errors, and other common build failures. use crate::common::find_build_failure_description; use crate::lines::Lines; use crate::problems::common::{ChrootNotFound, NoSpaceOnDevice, PatchApplicationFailed}; use crate::problems::debian::*; use crate::{Match, Problem, SingleLineMatch}; use debversion::Version; use serde::ser::{Serialize, SerializeStruct, Serializer}; use std::collections::HashMap; use std::fs::File; use std::io::{BufRead, BufReader}; use std::iter::Iterator; use std::str::FromStr; use std::time::Duration; /// Type alias for sbuild failure result pub type SbuildFailureResult = (Option>, Option>); /// Type alias for sbuild failure result with extra info pub type SbuildDetailedResult<'a> = ( Option<&'a str>, Option>, Option>, ); /// Finds the failed stage in sbuild log lines. /// /// This function searches for a line starting with "Fail-Stage: " and returns /// the stage value if found. /// /// # Arguments /// * `lines` - The log lines to search /// /// # Returns /// An optional reference to the failed stage string pub fn find_failed_stage<'a>(lines: &'a [&'a str]) -> Option<&'a str> { for line in lines { if let Some(value) = line.strip_prefix("Fail-Stage: ") { return Some(value.trim()); } } None } /// Summary information extracted from an sbuild log. /// /// This structure contains metadata about a build extracted from an sbuild log, /// including build times, architectures, package information, and failure details. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Summary { /// The architecture the package was built for. build_architecture: Option, /// The type of build (e.g., binary, source). build_type: Option, /// How long the build process took. build_time: Option, /// Disk space used by the build. build_space: Option, /// The architecture of the host system. host_architecture: Option, /// How long package installation took. install_time: Option, /// Output from lintian, if available. lintian: Option, /// The name of the package being built. package: Option, /// How long the packaging step took. package_time: Option, /// The target distribution (e.g., unstable, bullseye). distribution: Option, /// The stage at which the build failed, if applicable. fail_stage: Option, /// Job identifier. job: Option, /// Autopkgtest information, if available. autopkgtest: Option, /// The version of the source package. source_version: Option, /// The machine architecture. machine_architecture: Option, /// The final status of the build (e.g., successful, failed). status: Option, /// Disk space information. space: Option, /// The version of the built package. version: Option, } /// Representation of disk space information. /// /// This enum represents disk space information, either as a byte count /// or indicating that space information is not available. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Space { /// Indicates that space information is not available. NotAvailable, /// Space in bytes. Bytes(u64), } impl std::str::FromStr for Space { type Err = std::num::ParseIntError; fn from_str(s: &str) -> Result { if s == "n/a" { Ok(Space::NotAvailable) } else { Ok(Space::Bytes(s.parse()?)) } } } /// Parses sbuild summary information from log lines. /// /// This function extracts metadata about a build from sbuild log lines, /// such as build times, architectures, package information, and status. /// /// # Arguments /// * `lines` - The log lines to parse /// /// # Returns /// A `Summary` structure containing the extracted information pub fn parse_summary(lines: &[&str]) -> Summary { let mut build_architecture = None; let mut build_type = None; let mut build_time = None; let mut build_space = None; let mut host_architecture = None; let mut install_time = None; let mut lintian = None; let mut package = None; let mut distribution = None; let mut job = None; let mut autopkgtest = None; let mut status = None; let mut package_time = None; let mut source_version = None; let mut machine_architecture = None; let mut fail_stage = None; let mut space = None; let mut version = None; for line in lines { if line.trim() == "" { continue; } if let Some((key, value)) = line.trim_end().split_once(": ") { let value = value.trim(); match key { "Fail-Stage" => fail_stage = Some(value.to_string()), "Build Architecture" => build_architecture = Some(value.to_string()), "Build Type" => build_type = Some(value.to_string()), "Build-Time" => build_time = Some(Duration::from_secs(value.parse().unwrap())), "Build-Space" => build_space = Some(value.parse().unwrap()), "Host Architecture" => host_architecture = Some(value.to_string()), "Install-Time" => install_time = Some(Duration::from_secs(value.parse().unwrap())), "Lintian" => lintian = Some(value.to_string()), "Package" => package = Some(value.to_string()), "Package-Time" => package_time = Some(Duration::from_secs(value.parse().unwrap())), "Source-Version" => source_version = Some(value.parse().unwrap()), "Job" => job = Some(value.parse().unwrap()), "Machine Architecture" => machine_architecture = Some(value.to_string()), "Distribution" => distribution = Some(value.to_string()), "Autopkgtest" => autopkgtest = Some(value.to_string()), "Status" => status = Some(value.to_string()), "Space" => space = Some(value.parse().unwrap()), "Version" => version = Some(value.parse().unwrap()), n => { log::warn!("Unknown key in summary: {}", n); } } } else { log::warn!("Unknown line in summary: {}", line); } } Summary { build_architecture, build_type, build_time, build_space, host_architecture, install_time, lintian, package, package_time, distribution, fail_stage, job, autopkgtest, source_version, machine_architecture, status, space, version, } } /// Structure representing a parsed sbuild log file. /// /// This structure contains the parsed sections of an sbuild log file, /// allowing for easy access to different parts of the build log. #[derive(Debug, Clone)] pub struct SbuildLog(pub Vec); impl SbuildLog { /// Get the first section with the given title, if it exists. pub fn get_section(&self, title: Option<&str>) -> Option<&SbuildLogSection> { self.0.iter().find(|s| s.title.as_deref() == title) } /// Get the lines of a section, if it exists. pub fn get_section_lines(&self, title: Option<&str>) -> Option> { self.get_section(title).map(|s| s.lines()) } /// Get the titles of sections pub fn section_titles(&self) -> Vec<&str> { self.0.iter().filter_map(|s| s.title.as_deref()).collect() } /// Get the failed stage, if it is provided pub fn get_failed_stage(&self) -> Option { if let Some(summary) = self.summary() { summary.fail_stage } else { None } } /// Iterate ove the sections pub fn sections(&self) -> impl Iterator { self.0.iter() } /// Get the summary information from the log. /// /// This method extracts and parses the Summary section of the log /// if it exists. /// /// # Returns /// An optional `Summary` structure containing build metadata pub fn summary(&self) -> Option { let lines = self.get_section_lines(Some("Summary")); lines.map(|lines| parse_summary(lines.as_slice())) } } impl TryFrom> for SbuildLog { type Error = std::io::Error; fn try_from(reader: BufReader) -> Result { let sections = parse_sbuild_log(reader); Ok(SbuildLog(sections.collect())) } } impl TryFrom for SbuildLog { type Error = std::io::Error; fn try_from(f: File) -> Result { let reader = BufReader::new(f); reader.try_into() } } impl FromStr for SbuildLog { type Err = std::io::Error; fn from_str(s: &str) -> Result { let reader = BufReader::new(s.as_bytes()); let sections = parse_sbuild_log(reader); Ok(SbuildLog(sections.collect())) } } /// A section of an sbuild log file. /// /// This structure represents a section of an sbuild log file, identified by /// its title, line offsets, and content. #[derive(Debug, Clone)] pub struct SbuildLogSection { /// The title of the section, if any. pub title: Option, /// The starting and ending line offsets of the section in the original log. pub offsets: (usize, usize), /// The lines of text in the section. pub lines: Vec, } impl SbuildLogSection { /// Returns the lines in this section as string slices. /// /// # Returns /// A vector of string slices, one for each line in the section pub fn lines(&self) -> Vec<&str> { self.lines.iter().map(|x| x.as_str()).collect() } } /// Parses an sbuild log file into sections. /// /// This function reads an sbuild log file and divides it into sections based on /// the standard sbuild section formatting with separator lines. /// /// # Arguments /// * `reader` - A buffered reader providing access to the log file /// /// # Returns /// An iterator over the sections found in the log file pub fn parse_sbuild_log(mut reader: R) -> impl Iterator { let mut begin_offset = 1; let mut lines = Vec::new(); let mut title: Option = None; // Separator line (78 '-' characters, bookended by '+'). let sep = "+".to_string() + &"-".repeat(78) + "+"; let mut lineno = 0; // We'll store our sections in this Vec and return it as an iterator at the end. let mut sections = Vec::new(); loop { let mut line = String::new(); // Read a line from the file. Break if EOF. if reader.read_line(&mut line).unwrap() == 0 { break; } lineno += 1; // Trim trailing whitespace and newline characters. let line_trimmed = line.trim().to_string(); if line_trimmed == sep { // Read next two lines let mut l1 = String::new(); let mut l2 = String::new(); reader.read_line(&mut l1).unwrap(); reader.read_line(&mut l2).unwrap(); lineno += 2; // Trim trailing whitespace and newline characters. let l1_trimmed = l1.trim(); let l2_trimmed = l2.trim(); if l1_trimmed.starts_with('|') && l1_trimmed.ends_with('|') && l2_trimmed == sep { let mut end_offset = lineno - 3; // Drop trailing empty lines while lines.last() == Some(&"\n".to_string()) { lines.pop(); end_offset -= 1; } if !lines.is_empty() { // The unwrap_or_else is to provide a default value in case 'title' is None. sections.push(SbuildLogSection { title: std::mem::take(&mut title), offsets: (begin_offset, end_offset), lines: std::mem::take(&mut lines), }); } title = Some(l1_trimmed.trim_matches('|').trim().to_string()); begin_offset = lineno; } else { lines.push(line); lines.push(l1); lines.push(l2); } } else { lines.push(line); } } // Generate the final section. sections.push(SbuildLogSection { title, offsets: (begin_offset, lineno), lines, }); // Return the sections as an iterator. sections.into_iter() } /// Represents a failure in an sbuild log. /// /// This structure contains information about a build failure, including /// the stage where it failed, a description of the failure, and the /// specific problem that caused the failure. pub struct SbuildFailure { /// The build stage where the failure occurred (e.g., "unpack", "build"). pub stage: Option, /// A human-readable description of the failure. pub description: Option, /// The specific problem that caused the failure. pub error: Option>, /// The phase of the build process where the failure occurred. pub phase: Option, /// The log section containing the failure. pub section: Option, /// The matched text identifying the failure. pub r#match: Option>, } impl Serialize for SbuildFailure { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut state = serializer.serialize_struct("SbuildFailure", 6)?; state.serialize_field("stage", &self.stage)?; state.serialize_field("phase", &self.phase)?; state.serialize_field( "section", &self.section.as_ref().map(|s| s.title.as_deref()), )?; state.serialize_field( "origin", &self.r#match.as_ref().map(|m| m.origin().as_str()), )?; state.serialize_field( "lineno", &self .section .as_ref() .map(|s| s.offsets.0 + self.r#match.as_ref().unwrap().lineno()), )?; if let Some(error) = &self.error { state.serialize_field("kind", &error.kind())?; state.serialize_field("details", &error.json())?; } state.end() } } impl std::fmt::Display for SbuildFailure { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(stage) = &self.stage { write!(f, "Failed at stage: {}", stage)?; } if let Some(description) = &self.description { write!(f, " ({})", description)?; } Ok(()) } } /// Searches for build failure descriptions in the preamble of a log file. /// /// This function looks for various error patterns in the preamble section of a build log, /// such as dpkg-source errors, local changes, unrepresentable changes, and patch failures. /// /// # Arguments /// * `lines` - The log lines to search /// /// # Returns /// A tuple containing an optional Match and an optional Problem representing the failure pub fn find_preamble_failure_description(lines: Vec<&str>) -> SbuildFailureResult { let mut ret: SbuildFailureResult = (None, None); for (lineno, line) in lines.enumerate_backward(Some(100)) { let line = line.trim_end_matches('\n'); if let Some((_, diff_file)) = lazy_regex::regex_captures!( "dpkg-source: error: aborting due to unexpected upstream changes, see (.*)", line ) { let mut j = lineno - 1; let mut files = vec![]; while j > 0 { if lines[j] == ("dpkg-source: info: local changes detected, the modified files are:\n") { let err = Some(Box::new(DpkgSourceLocalChanges { diff_file: Some(diff_file.to_string()), files: Some(files), }) as Box); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), ))), err, ); } files.push(lines[j].trim().to_string()); j -= 1; } let err = Some(Box::new(DpkgSourceLocalChanges { diff_file: Some(diff_file.to_string()), files: Some(files), }) as Box); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), err, ); } if line == "dpkg-source: error: unrepresentable changes to source" { let err = Some(Box::new(DpkgSourceUnrepresentableChanges) as Box); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct match"), ))), err, ); } if lazy_regex::regex_is_match!( r"dpkg-source: error: detected ([0-9]+) unwanted binary file.*", line ) { let err = Some(Box::new(DpkgUnwantedBinaryFiles) as Box); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), ))), err, ); } if let Some((_, path)) = lazy_regex::regex_captures!( "dpkg-source: error: cannot read (.*/debian/control): No such file or directory", line, ) { let err = Some(Box::new(MissingControlFile(path.into())) as Box); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), ))), err, ); } if lazy_regex::regex_is_match!("dpkg-source: error: .*: No space left on device", line) { let err = Some(Box::new(NoSpaceOnDevice) as Box); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), err, ); } if lazy_regex::regex_is_match!("tar: .*: Cannot write: No space left on device", line) { let err = Some(Box::new(NoSpaceOnDevice) as Box); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), err, ); } if let Some((_, path)) = lazy_regex::regex_captures!( "dpkg-source: error: cannot represent change to (.*): binary file contents changed", line ) { let err = Some(Box::new(DpkgBinaryFileChanged(vec![path.to_string()])) as Box); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), err, ); } if let Some((_, format, _, _, _)) = lazy_regex::regex_captures!( r"dpkg-source: error: source package format \'(.*)\' is not supported: Can\'t locate (.*) in \@INC \(you may need to install the (.*) module\) \(\@INC contains: (.*)\) at \(eval [0-9]+\) line [0-9]+\.", line ) { let err = Some(Box::new(SourceFormatUnsupported(format.to_string())) as Box); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), err, ); } if let Some((_, reason)) = lazy_regex::regex_captures!("E: Failed to package source directory (.*)", line) { let err = Some(Box::new(DpkgSourcePackFailed(reason.to_string())) as Box); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), err, ); } if let Some((_, _path)) = lazy_regex::regex_captures!("E: Bad version unknown in (.*)", line) { if lines[lineno - 1].starts_with("LINE: ") { if let Some((_, version, reason)) = lazy_regex::regex_captures!( r"dpkg-parsechangelog: warning: .*\(l[0-9]+\): version \'(.*)\' is invalid: (.*)", lines[lineno - 2] ) { let err = Some(Box::new(DpkgBadVersion { version: version.to_string(), reason: Some(reason.to_string()), }) as Box); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), err, ); } } } if let Some((_, patchname)) = lazy_regex::regex_captures!("Patch (.*) does not apply \\(enforce with -f\\)\n", line) { let patchname = patchname.rsplit_once('/').unwrap().1; let err = Some(Box::new(PatchApplicationFailed { patchname: patchname.to_string(), }) as Box); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), err, ); } if let Some((_, patchname)) = lazy_regex::regex_captures!( r"dpkg-source: error: LC_ALL=C patch .* --reject-file=- < .*\/debian\/patches\/([^ ]+) subprocess returned exit status 1", line ) { let err = Some(Box::new(PatchApplicationFailed { patchname: patchname.to_string(), }) as Box); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), err, ); } if let Some((_, source_format, reason)) = lazy_regex::regex_captures!( "dpkg-source: error: can't build with source format '(.*)': (.*)", line ) { let err = Some(Box::new(SourceFormatUnbuildable { source_format: source_format.to_string(), reason: reason.to_string(), }) as Box); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), err, ); } if let Some((_, path)) = lazy_regex::regex_captures!( "dpkg-source: error: cannot read (.*): No such file or directory", line ) { let _patchname = path.rsplit_once('/').unwrap().1; let err = Some(Box::new(PatchFileMissing(path.into())) as Box); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), err, ); } if let Some((_, format, msg)) = lazy_regex::regex_captures!( "dpkg-source: error: source package format '(.*)' is not supported: (.*)", line ) { let (_, p) = find_build_failure_description(vec![msg]); let p = p.unwrap_or_else(|| { Box::new(SourceFormatUnsupported(format.to_string())) as Box }); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), Some(p), ); } if let Some((_, _, revid)) = lazy_regex::regex_captures!( "breezy.errors.NoSuchRevision: (.*) has no revision b'(.*)'", line ) { let err = Some(Box::new(MissingRevision(revid.as_bytes().to_vec())) as Box); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), ))), err, ); } if let Some((_, arg)) = lazy_regex::regex_captures!( r"fatal: ambiguous argument \'(.*)\': unknown revision or path not in the working tree.", line ) { let err = Some(Box::new(PristineTarTreeMissing(arg.to_string())) as Box); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), err, ); } if let Some((_, msg)) = lazy_regex::regex_captures!("dpkg-source: error: (.*)", line) { let err = Some(Box::new(DpkgSourcePackFailed(msg.to_string())) as Box); ret = ( Some(Box::new(SingleLineMatch::from_lines( &lines, lineno, Some("direct regex"), )) as Box), err, ); } } ret } /// The phase of the build process where a failure occurred. /// /// This enum represents the different phases of the build process /// that can be identified when analyzing a build failure. #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum Phase { /// Autopkgtest phase with a test name. #[serde(rename = "autopkgtest")] AutoPkgTest(String), /// Main build phase. #[serde(rename = "build")] Build, /// Build environment setup phase. #[serde(rename = "build-env")] BuildEnv, /// Session creation phase. #[serde(rename = "create-session")] CreateSession, } impl std::fmt::Display for Phase { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Phase::AutoPkgTest(s) => write!(f, "autopkgtest: {}", s), Phase::Build => write!(f, "build"), Phase::BuildEnv => write!(f, "build-env"), Phase::CreateSession => write!(f, "create-session"), } } } /// Default number of lines to look back when searching for build information. pub const DEFAULT_LOOK_BACK: usize = 50; /// Strips the unnecessary tail from build logs and extracts file contents. /// /// This function removes boilerplate content from the end of build logs and /// extracts content labeled with "==> FILE <==". /// /// # Arguments /// * `lines` - The log lines to process /// * `look_back` - Optional number of lines to look back when searching for build end markers /// /// # Returns /// A tuple containing: /// * The trimmed log body /// * A map of file names to their contents pub fn strip_build_tail<'a>( lines: &'a [&'a str], look_back: Option, ) -> (&'a [&'a str], HashMap<&'a str, &'a [&'a str]>) { let look_back = look_back.unwrap_or(DEFAULT_LOOK_BACK); let mut interesting_lines: &'_ [&'a str] = lines; // Strip off useless tail for (i, line) in lines.enumerate_tail_forward(look_back) { if line.starts_with("Build finished at ") { interesting_lines = &lines[..i]; if let Some(last_line) = interesting_lines.last() { if last_line == &("-".repeat(80)) { interesting_lines = &interesting_lines[..interesting_lines.len() - 1]; } } break; } } let mut files: HashMap<&'a str, &'a [&'a str]> = std::collections::HashMap::new(); let mut body: &'a [&'a str] = interesting_lines; let mut current_file = None; let mut current_contents: &[&str] = &[]; let mut start = 0; for (i, line) in interesting_lines.iter().enumerate() { if let Some((_, header)) = lazy_regex::regex_captures!(r"==> (.*) <==", line) { if let Some(current_file) = current_file { files.insert(current_file, current_contents); } else { body = current_contents; } current_file = Some(header); current_contents = &[]; start = i + 1; continue; } else { current_contents = &interesting_lines[start..i + 1]; } } if let Some(current_file) = current_file { files.insert(current_file, current_contents); } else { body = current_contents; } (body, files) } /// Analyzes log sections to find failures in the fetch source stage. /// /// This function examines the log for errors that occurred during the "fetch source files" /// stage of an sbuild build process. /// /// # Arguments /// * `sbuildlog` - The parsed sbuild log to analyze /// * `failed_stage` - The stage name that failed /// /// # Returns /// An optional SbuildFailure structure with information about the failure pub fn find_failure_fetch_src(sbuildlog: &SbuildLog, failed_stage: &str) -> Option { let section = if let Some(section) = sbuildlog.get_section(Some("fetch source files")) { section } else { log::warn!("expected section: fetch source files"); return None; }; let section_lines = section.lines(); let section_lines = if section_lines[0].trim().is_empty() { section_lines[1..].to_vec() } else { section_lines.to_vec() }; if section_lines.len() == 1 && section_lines[0].starts_with("E: Could not find ") { let (r#match, error) = find_preamble_failure_description(sbuildlog.get_section_lines(None)?); return Some(SbuildFailure { stage: Some("unpack".to_string()), description: error.as_ref().map(|x| x.to_string()), error, section: Some(section.clone()), r#match, phase: None, }); } let (r#match, error) = crate::apt::find_apt_get_failure(section.lines()); let description = format!("build failed stage {}", failed_stage); Some(SbuildFailure { stage: Some(failed_stage.to_string()), description: Some(description), error, phase: None, section: Some(section.clone()), r#match, }) } /// Analyzes log sections to find failures in the session creation stage. /// /// This function examines the log for errors that occurred during the "create-session" /// stage of an sbuild build process, such as chroot not found errors. /// /// # Arguments /// * `sbuildlog` - The parsed sbuild log to analyze /// * `failed_stage` - The stage name that failed /// /// # Returns /// An optional SbuildFailure structure with information about the failure pub fn find_failure_create_session( sbuildlog: &SbuildLog, failed_stage: &str, ) -> Option { let section = sbuildlog.get_section(None)?; let (r#match, error) = find_creation_session_error(section.lines()); let phase = Phase::CreateSession; let description = format!("build failed stage {}", failed_stage); Some(SbuildFailure { stage: Some(failed_stage.to_owned()), description: Some(description), error, phase: Some(phase), section: Some(section.clone()), r#match, }) } /// Searches for errors that occur during session creation. /// /// This function examines log lines to find errors related to session creation, /// such as chroot not found or disk space issues. /// /// # Arguments /// * `lines` - The log lines to search /// /// # Returns /// A tuple containing an optional Match and an optional Problem representing the failure pub fn find_creation_session_error(lines: Vec<&str>) -> SbuildFailureResult { let mut ret: SbuildFailureResult = (None, None); for (i, line) in lines.enumerate_backward(None) { if line.starts_with("E: ") { ret = ( Some(Box::new(SingleLineMatch::from_lines( &lines, i, Some("direct regex"), ))), None, ); } if let Some((_, distribution, architecture)) = lazy_regex::regex_captures!( "E: Chroot for distribution (.*), architecture (.*) not found\n", line ) { let err = Some(Box::new(ChrootNotFound { chroot: format!("{}-{}-sbuild", distribution, architecture), }) as Box); ret = ( Some(Box::new(SingleLineMatch::from_lines( &lines, i, Some("direct regex"), ))), err, ); } if line.ends_with(": No space left on device\n") { return ( Some(Box::new(SingleLineMatch::from_lines( &lines, i, Some("direct regex"), ))), Some(Box::new(NoSpaceOnDevice)), ); } } ret } /// Analyzes log sections to find failures in the unpack stage. /// /// This function examines the log for errors that occurred during the "unpack" /// stage of an sbuild build process. /// /// # Arguments /// * `sbuildlog` - The parsed sbuild log to analyze /// * `failed_stage` - The stage name that failed /// /// # Returns /// An optional SbuildFailure structure with information about the failure pub fn find_failure_unpack(sbuildlog: &SbuildLog, failed_stage: &str) -> Option { let section = sbuildlog.get_section(Some("build")); if let Some(section) = section { let (r#match, error) = find_preamble_failure_description(section.lines()); if let Some(error) = error { return Some(SbuildFailure { stage: Some(failed_stage.to_string()), description: Some(error.to_string()), error: Some(error), section: Some(section.clone()), r#match, phase: None, }); } } let description = format!("build failed stage {}", failed_stage); Some(SbuildFailure { stage: Some(failed_stage.to_string()), description: Some(description), error: None, phase: None, section: section.cloned(), r#match: None, }) } /// Analyzes log sections to find failures in the build stage. /// /// This function examines the log for errors that occurred during the main "build" /// stage of an sbuild build process, looking for compilation errors and other build issues. /// /// # Arguments /// * `sbuildlog` - The parsed sbuild log to analyze /// * `failed_stage` - The stage name that failed /// /// # Returns /// An optional SbuildFailure structure with information about the failure pub fn find_failure_build(sbuildlog: &SbuildLog, failed_stage: &str) -> Option { let phase = Phase::Build; let (section, r#match, error) = if let Some(section) = sbuildlog.get_section(Some("build")) { let lines_ref = section.lines(); let (section_lines, _files) = strip_build_tail(&lines_ref, None); let (r#match, error) = find_build_failure_description(section_lines.to_vec()); (Some(section), r#match, error) } else { (None, None, None) }; let description = if let Some(error) = error.as_ref() { error.to_string() } else if let Some(r#match) = r#match.as_ref() { r#match.line().trim_end_matches('\n').to_string() } else { format!("build failed stage {}", failed_stage) }; Some(SbuildFailure { stage: Some(failed_stage.to_string()), description: Some(description), error, phase: Some(phase), section: section.cloned(), r#match, }) } /// Analyzes log sections to find failures in the apt-get update stage. /// /// This function examines the log for errors that occurred during the "apt-get update" /// stage of an sbuild build process, such as repository access issues. /// /// # Arguments /// * `sbuildlog` - The parsed sbuild log to analyze /// * `failed_stage` - The stage name that failed /// /// # Returns /// An optional SbuildFailure structure with information about the failure pub fn find_failure_apt_get_update( sbuildlog: &SbuildLog, failed_stage: &str, ) -> Option { let (focus_section, r#match, error) = crate::apt::find_apt_get_update_failure(sbuildlog); let description = if let Some(error) = error.as_ref() { error.to_string() } else if let Some(r#match) = r#match.as_ref() { r#match.line().trim_end_matches('\n').to_string() } else { format!("build failed stage {}", failed_stage) }; Some(SbuildFailure { stage: Some(failed_stage.to_string()), description: Some(description), error, phase: None, section: sbuildlog.get_section(focus_section.as_deref()).cloned(), r#match, }) } /// Searches for architecture check failures in log lines. /// /// This function examines log lines to identify architecture compatibility issues, /// such as when a package doesn't support the current architecture. /// /// # Arguments /// * `lines` - The log lines to search /// /// # Returns /// A tuple containing an optional Match and an optional Problem representing the failure fn find_arch_check_failure_description(lines: Vec<&str>) -> SbuildFailureResult { for (offset, line) in lines.enumerate_forward(None) { if let Some((_, arch, arch_list)) = lazy_regex::regex_captures!( "E: dsc: (.*) not in arch list or does not match any arch wildcards: (.*) -- skipping", line ) { let error = ArchitectureNotInList { arch: arch.to_string(), arch_list: arch_list .split_whitespace() .map(|x| x.to_string()) .collect(), }; return ( Some(Box::new(SingleLineMatch::from_lines( &lines, offset, Some("direct regex"), ))), Some(Box::new(error)), ); } } ( Some(Box::new(SingleLineMatch::from_lines( &lines, lines.len() - 1, Some("direct regex"), ))), None, ) } /// Analyzes log sections to find failures in the architecture check stage. /// /// This function examines the log for errors that occurred during the "arch-check" /// stage of an sbuild build process, such as unsupported architectures. /// /// # Arguments /// * `sbuildlog` - The parsed sbuild log to analyze /// * `failed_stage` - The stage name that failed /// /// # Returns /// An optional SbuildFailure structure with information about the failure pub fn find_failure_arch_check(sbuildlog: &SbuildLog, failed_stage: &str) -> Option { let section = sbuildlog.get_section(Some("check architectures")); let (r#match, error) = section.map_or((None, None), |s| { find_arch_check_failure_description(s.lines()) }); let description = if let Some(error) = error.as_ref() { error.to_string() } else { format!("build failed stage {}", failed_stage) }; Some(SbuildFailure { stage: Some(failed_stage.to_string()), description: Some(description), error, phase: None, section: section.cloned(), r#match, }) } /// Analyzes log sections to find failures in the disk space check stage. /// /// This function examines the log for errors that occurred during the "check-space" /// stage of an sbuild build process, such as insufficient disk space. /// /// # Arguments /// * `sbuildlog` - The parsed sbuild log to analyze /// * `failed_stage` - The stage name that failed /// /// # Returns /// An optional SbuildFailure structure with information about the failure pub fn find_failure_check_space( sbuildlog: &SbuildLog, failed_stage: &str, ) -> Option { let section = sbuildlog.get_section(Some("cleanup"))?; let (r#match, error) = find_check_space_failure_description(section.lines()); let description = if let Some(ref error) = error { error.to_string() } else { format!("build failed stage {}", failed_stage) }; Some(SbuildFailure { stage: Some(failed_stage.to_string()), description: Some(description), error, phase: None, section: Some(section.clone()), r#match, }) } /// Section title for dose3 dependency resolution logs. /// /// This is the standard section title used in sbuild logs when using the dose3/aspcud /// dependency resolver to install build dependencies. pub const DOSE3_SECTION: &str = "install dose3 build dependencies (aspcud-based resolver)"; /// Examines sbuild logs for dependency installation failures. /// /// This function looks for failures during the dependency installation phase, /// searching in multiple possible dependency installation sections including /// dose3-based and apt-based dependency resolution. /// /// # Arguments /// * `sbuildlog` - The parsed sbuild log to analyze /// /// # Returns /// A tuple containing: /// * An optional section title where the failure was found /// * An optional Match representing the specific failure /// * An optional Problem describing the dependency issue pub fn find_install_deps_failure_description(sbuildlog: &SbuildLog) -> SbuildDetailedResult<'_> { let dose3_lines = sbuildlog.get_section_lines(Some(DOSE3_SECTION)); if let Some(dose3_lines) = dose3_lines { let dose3 = crate::apt::find_cudf_output(dose3_lines.clone()); if let Some((dose3_offsets, dose3_output)) = dose3 { let error = crate::apt::error_from_dose3_reports(dose3_output.report.as_slice()); let r#match = crate::MultiLineMatch::from_lines(&dose3_lines, dose3_offsets, None); return (Some(DOSE3_SECTION), Some(Box::new(r#match)), error); } } const SECTION: &str = "Install package build dependencies"; let build_dependencies_lines = sbuildlog.get_section_lines(Some(SECTION)); if let Some(build_dependencies_lines) = build_dependencies_lines { let dose3 = crate::apt::find_cudf_output(build_dependencies_lines.clone()); if let Some((dose3_offsets, dose3_output)) = dose3 { let error = crate::apt::error_from_dose3_reports(dose3_output.report.as_slice()); let r#match = crate::MultiLineMatch::from_lines(&build_dependencies_lines, dose3_offsets, None); return (Some(SECTION), Some(Box::new(r#match)), error); } let (r#match, error) = crate::apt::find_apt_get_failure(build_dependencies_lines); return (Some(SECTION), r#match, error); } for section in sbuildlog.sections() { if section.title.is_none() { continue; } if lazy_regex::regex_is_match!( "install (.*) build dependencies.*", §ion.title.as_ref().unwrap().to_lowercase() ) { let (r#match, error) = crate::apt::find_apt_get_failure(section.lines()); if r#match.is_some() { return (section.title.as_deref(), r#match, error); } } } (None, None, None) } /// Analyzes log sections to find failures in the dependency installation stage. /// /// This function examines the log for errors that occurred during the "install-deps" /// or "explain-bd-uninstallable" stages of an sbuild build process, such as /// unresolvable dependencies or package conflicts. /// /// # Arguments /// * `sbuildlog` - The parsed sbuild log to analyze /// * `failed_stage` - The stage name that failed /// /// # Returns /// An optional SbuildFailure structure with information about the failure pub fn find_failure_install_deps( sbuildlog: &SbuildLog, failed_stage: &str, ) -> Option { let (focus_section, r#match, error) = find_install_deps_failure_description(sbuildlog); let description = if let Some(error) = error.as_ref() { error.to_string() } else if let Some(r#match) = r#match.as_ref() { if let Some(rest) = r#match.line().strip_prefix("E: ") { rest.trim_end_matches('\n').to_string() } else { r#match.line().trim_end_matches('\n').to_string() } } else { format!("build failed stage {}", failed_stage) }; let phase = Phase::Build; Some(SbuildFailure { stage: Some(failed_stage.to_string()), description: Some(description.to_string()), error, phase: Some(phase), section: sbuildlog.get_section(focus_section).cloned(), r#match, }) } /// Analyzes log sections to find failures in the autopkgtest stage. /// /// This function examines the log for errors that occurred during the autopkgtest /// stages of an sbuild build process, including post-build commands and /// automated test failures. /// /// # Arguments /// * `sbuildlog` - The parsed sbuild log to analyze /// * `failed_stage` - The stage name that failed /// /// # Returns /// An optional SbuildFailure structure with information about the failure pub fn find_failure_autopkgtest( sbuildlog: &SbuildLog, failed_stage: &str, ) -> Option { let focus_section = match failed_stage { "run-post-build-commands" => "post build commands", "post-build" => "post build", "autopkgtest" => "autopkgtest", _ => { unreachable!(); } }; let section = sbuildlog.get_section(Some(focus_section)); let (description, error, r#match, phase) = if let Some(section) = section { let (r#match, testname, error, description) = crate::autopkgtest::find_autopkgtest_failure_description(section.lines()); let description = description.or_else(|| error.as_ref().map(|x| x.to_string())); let phase = testname.map(Phase::AutoPkgTest); (description, error, r#match, phase) } else { (None, None, None, None) }; let description = description.unwrap_or_else(|| format!("build failed stage {}", failed_stage)); Some(SbuildFailure { stage: Some(failed_stage.to_string()), description: Some(description), error, phase, section: section.cloned(), r#match, }) } /// Creates a SbuildFailure by analyzing a complete sbuild log. /// /// This function analyzes a complete sbuild log and creates a SbuildFailure /// containing information about what went wrong during the build. /// /// # Arguments /// * `sbuildlog` - The parsed sbuild log to analyze /// /// # Returns /// A SbuildFailure structure with information about the failure pub fn worker_failure_from_sbuild_log(sbuildlog: &SbuildLog) -> SbuildFailure { // TODO(jelmer): Doesn't this do the same thing as the tail? if sbuildlog .sections() .map(|x| x.title.as_deref()) .collect::>() == vec![None] { let section = sbuildlog.sections().next().unwrap(); let (r#match, error) = find_preamble_failure_description(section.lines()); if let Some(error) = error { return SbuildFailure { stage: Some("unpack".to_string()), description: Some(error.to_string()), error: Some(error), section: Some(section.clone()), r#match, phase: None, }; } } let failed_stage = sbuildlog.get_failed_stage(); let overall_failure = failed_stage.as_ref().and_then(|failed_stage| { match failed_stage.as_str() { "fetch-src" => find_failure_fetch_src(sbuildlog, failed_stage), "create-session" => find_failure_create_session(sbuildlog, failed_stage), "unpack" => find_failure_unpack(sbuildlog, failed_stage), "build" => find_failure_build(sbuildlog, failed_stage), "apt-get-update" => find_failure_apt_get_update(sbuildlog, failed_stage), "arch-check" => find_failure_arch_check(sbuildlog, failed_stage), "check-space" => find_failure_check_space(sbuildlog, failed_stage), "install-deps" => find_failure_install_deps(sbuildlog, failed_stage), "explain-bd-uninstallable" => find_failure_install_deps(sbuildlog, failed_stage), "autopkgtest" => find_failure_autopkgtest(sbuildlog, failed_stage), // We run autopkgtest as only post-build step at the moment. "run-post-build-commands" => find_failure_autopkgtest(sbuildlog, failed_stage), "post-build" => find_failure_autopkgtest(sbuildlog, failed_stage), _ => { log::warn!("unknown failed stage: {}", &failed_stage); None } } }); if let Some(overall_failure) = overall_failure { return overall_failure; } else if let Some(failed_stage) = failed_stage { log::warn!("unknown failed stage: {}", failed_stage); let description = format!("build failed stage {}", failed_stage); return SbuildFailure { stage: Some(failed_stage), description: Some(description), error: None, phase: None, section: None, r#match: None, }; } let mut description = Some("build failed".to_string()); let mut r#match = None; let mut error = None; let mut section = None; let phase = Phase::BuildEnv; if sbuildlog .sections() .map(|s| s.title.as_deref()) .collect::>() == vec![None] { let s = sbuildlog.sections().next().unwrap(); (r#match, error) = find_preamble_failure_description(s.lines()); if let Some(error) = error.as_ref() { description = Some(error.to_string()); } else { (r#match, error) = find_build_failure_description(s.lines()); if let Some(r#match) = r#match.as_ref() { description = Some(r#match.line().trim_end_matches('\n').to_string()); } else if let Some((e, d)) = crate::brz::find_brz_build_error(s.lines()) { description = Some(d.to_string()); error = e; } } section = Some(s); } SbuildFailure { stage: failed_stage, description, error, phase: Some(phase), section: section.cloned(), r#match, } } /// Searches for disk space check failures in log lines. /// /// This function examines log lines to identify disk space issues, /// such as insufficient space for the build process. /// /// # Arguments /// * `lines` - The log lines to search /// /// # Returns /// A tuple containing an optional Match and an optional Problem representing the failure fn find_check_space_failure_description(lines: Vec<&str>) -> SbuildFailureResult { for (offset, line) in lines.enumerate_forward(None) { if line == "E: Disk space is probably not sufficient for building.\n" { if let Some((_, needed, free)) = lazy_regex::regex_captures!( "I: Source needs ([0-9]+) KiB, while ([0-9]+) KiB is free.\n", lines[offset + 1] ) { return ( Some(Box::new(SingleLineMatch::from_lines( &lines, offset, Some("direct regex"), )) as Box), Some(Box::new(InsufficientDiskSpace { needed: needed.parse().unwrap(), free: free.parse().unwrap(), }) as Box), ); } return ( Some(Box::new(SingleLineMatch::from_lines( &lines, offset, Some("direct match"), ))), None, ); } } (None, None) } #[cfg(test)] mod tests { use super::*; #[test] fn test_parse_sbuild_log() { let log = include_str!("testdata/sbuild.0.log"); let sbuild_log: SbuildLog = log.parse().unwrap(); assert_eq!( sbuild_log.section_titles(), vec![ "Update chroot", "Fetch source files", "Check architectures", "Build environment", "Cleanup", "Summary" ] ); assert_eq!(sbuild_log.get_failed_stage(), None); assert_eq!( sbuild_log.summary().unwrap(), Summary { version: Some("0.1.3-1".parse().unwrap()), fail_stage: None, autopkgtest: Some("pass".to_string()), build_architecture: Some("amd64".to_string()), build_type: Some("binary".to_string()), build_space: Some(Space::Bytes(41428)), build_time: Some(Duration::from_secs(3)), distribution: Some("unstable".to_string()), host_architecture: Some("amd64".to_string()), install_time: Some(Duration::from_secs(4)), job: Some( "/home/jelmer/src/debcargo-conf/build/rust-always-assert_0.1.3-1.dsc" .to_string() ), lintian: Some("warn".to_string()), machine_architecture: Some("amd64".to_string()), package: Some("rust-always-assert".to_string()), package_time: Some(Duration::from_secs(72)), source_version: Some("0.1.3-1".parse().unwrap()), space: Some(Space::Bytes(41428)), status: Some("successful".to_string()), } ); } #[test] fn test_strip_build_tail() { assert_eq!( strip_build_tail( &[ "Build finished at 2023-09-16T16:47:58Z", "--------------------------------------------------------------------------------", "Finished at 2023-09-16T16:47:58Z", "Build needed 00:01:12, 41428k disk space", ], None ), ( &[ ][..], HashMap::new() ) ); let meson_log_lines = r#"Build started at 2022-07-21T04:21:47.088879 Main binary: /usr/bin/python3 Build Options: -Dprefix=/usr -Dlibdir=lib/x86_64-linux-gnu -Dlocalstatedir=/var -Dsysconfdir=/etc -Dbuildtype=plain -Dwrap_mode=nodownload Python system: Linux The Meson build system Version: 0.56.2 Source dir: /<> Build dir: /<>/obj-x86_64-linux-gnu Build type: native build ../meson.build:1:0: ERROR: Meson version is 0.56.2 but project requires >= 0.57.0 dh_auto_configure: error: cd obj-x86_64-linux-gnu && LC_ALL=C.UTF-8 meson .. --wrap-mode=nodownload --buildtype=plain --prefix=/usr --sysconfdir=/etc --localstatedir=/var --libdir=lib/x86_64-linux-gnu returned exit code 1 make: *** [debian/rules:13: binary] Error 25 dpkg-buildpackage: error: debian/rules binary subprocess returned exit status 2 "#.lines().collect::>(); assert_eq!( strip_build_tail( include_str!("testdata/sbuild.meson.log") .lines() .collect::>() .as_slice(), None ), ( r#" --sysconfdir=/etc --localstatedir=/var --libdir=lib/x86_64-linux-gnu The Meson build system Version: 0.56.2 Source dir: /<> Build dir: /<>/obj-x86_64-linux-gnu Build type: native build ../meson.build:1:0: ERROR: Meson version is 0.56.2 but project requires >= 0.57.0 A full log can be found at /<>/obj-x86_64-linux-gnu/meson-logs/meson-log.txt cd obj-x86_64-linux-gnu && tail -v -n \+0 meson-logs/meson-log.txt "# .lines() .collect::>() .as_slice(), maplit::hashmap! { "meson-logs/meson-log.txt" => meson_log_lines.as_slice(), } ) ); } #[test] fn test_find_failed_stage() { let lines = &["Foo: bar", "Fail-Stage: unpack", "Bar: baz"]; assert_eq!(find_failed_stage(lines), Some("unpack")); let lines = &["Foo: bar", "Bar: baz"]; assert_eq!(find_failed_stage(lines), None); } #[test] fn test_parse_summary() { let summary_lines = &[ "Package: rust-always-assert", "Version: 0.1.3-1", "Distribution: unstable", "Status: successful", "Build-Time: 3", ]; let summary = parse_summary(summary_lines); assert_eq!(summary.package, Some("rust-always-assert".to_string())); assert_eq!(summary.version, Some("0.1.3-1".parse().unwrap())); assert_eq!(summary.distribution, Some("unstable".to_string())); assert_eq!(summary.status, Some("successful".to_string())); assert_eq!(summary.build_time, Some(Duration::from_secs(3))); } #[test] fn test_space_from_str() { let space: Space = "1024".parse().unwrap(); assert_eq!(space, Space::Bytes(1024)); let space: Space = "n/a".parse().unwrap(); assert_eq!(space, Space::NotAvailable); } #[test] fn test_sbuild_log_get_section() { let sections = vec![ SbuildLogSection { title: Some("Section1".to_string()), offsets: (1, 5), lines: vec!["Line1".to_string(), "Line2".to_string()], }, SbuildLogSection { title: Some("Section2".to_string()), offsets: (6, 10), lines: vec!["Line3".to_string(), "Line4".to_string()], }, ]; let log = SbuildLog(sections); let section = log.get_section(Some("Section1")); assert!(section.is_some()); assert_eq!(section.unwrap().lines, vec!["Line1", "Line2"]); let section = log.get_section(Some("NonExistent")); assert!(section.is_none()); } #[test] fn test_sbuild_log_get_section_lines() { let sections = vec![SbuildLogSection { title: Some("Section1".to_string()), offsets: (1, 5), lines: vec!["Line1".to_string(), "Line2".to_string()], }]; let log = SbuildLog(sections); let lines = log.get_section_lines(Some("Section1")); assert!(lines.is_some()); assert_eq!(lines.unwrap(), vec!["Line1", "Line2"]); } #[test] fn test_sbuild_log_section_titles() { let sections = vec![ SbuildLogSection { title: Some("Section1".to_string()), offsets: (1, 5), lines: vec![], }, SbuildLogSection { title: Some("Section2".to_string()), offsets: (6, 10), lines: vec![], }, ]; let log = SbuildLog(sections); assert_eq!(log.section_titles(), vec!["Section1", "Section2"]); } #[test] fn test_sbuild_log_sections() { let sections = vec![ SbuildLogSection { title: Some("Section1".to_string()), offsets: (1, 5), lines: vec![], }, SbuildLogSection { title: Some("Section2".to_string()), offsets: (6, 10), lines: vec![], }, ]; let log = SbuildLog(sections.clone()); let sections_iter: Vec<_> = log.sections().collect(); assert_eq!(sections_iter.len(), 2); assert_eq!(sections_iter[0].title, Some("Section1".to_string())); assert_eq!(sections_iter[1].title, Some("Section2".to_string())); } #[test] fn test_sbuild_log_from_str() { let log_content = r#"+------------------------------------------------------------------------------+ | | +------------------------------------------------------------------------------+ +------------------------------------------------------------------------------+ | Section1 | +------------------------------------------------------------------------------+ Line1 Line2 +------------------------------------------------------------------------------+ | Section2 | +------------------------------------------------------------------------------+ Line3 Line4 "#; let log: SbuildLog = log_content.parse().unwrap(); assert_eq!(log.section_titles(), vec!["Section1", "Section2"]); let section1 = log.get_section(Some("Section1")).unwrap(); assert_eq!(section1.lines(), vec!["Line1\n", "Line2\n"]); let section2 = log.get_section(Some("Section2")).unwrap(); assert_eq!(section2.lines(), vec!["Line3\n", "Line4\n"]); } } buildlog-consultant-0.1.4/src/testdata/sbuild-cudf.log000064400000000000000000000175601046102023000211340ustar 00000000000000 Setup apt archive ----------------- Merged Build-Depends: debhelper (>= 12), dh-cargo (>= 25), cargo, rustc, libstd-rust-dev, librust-breezyshim+default-dev (>= 0.1.138-~~), librust-breezyshim+dirty-tracker-dev (>= 0.1.138-~~), librust-chrono+default-dev (>= 0.4-~~), librust-chrono+serde-dev (>= 0.4-~~), librust-configparser-3+default-dev, librust-debian-changelog+default-dev (>= 0.1-~~), librust-debian-control+default-dev (>= 0.1-~~), librust-debian-copyright-0.1+default-dev (>= 0.1.2-~~), librust-debversion+default-dev (>= 0.3.0-~~), librust-debversion+python-debian-dev (>= 0.3.0-~~), librust-debversion+serde-dev (>= 0.3.0-~~), librust-dep3-0.1+default-dev, librust-distro-info+default-dev (>= 0.4.0-~~), librust-lazy-regex+default-dev (>= 2-~~), librust-lazy-static-1+default-dev (>= 1.4.0-~~), librust-log-0.4+default-dev, librust-makefile-lossless-0.1+default-dev, librust-maplit-1+default-dev (>= 1.0.2-~~), librust-patchkit-0.1+default-dev, librust-pyo3+chrono-dev (>= 0.22-~~), librust-pyo3+default-dev (>= 0.22-~~), librust-pyo3+serde-dev (>= 0.22-~~), librust-reqwest+blocking-dev (>= 0.11-~~), librust-reqwest+default-dev (>= 0.11-~~), librust-reqwest+json-dev (>= 0.11-~~), librust-serde-1+default-dev, librust-serde-1+derive-dev, librust-serde-json-1+default-dev, librust-tempfile-3+default-dev, librust-url-2+default-dev, build-essential, fakeroot, dumb-init Filtered Build-Depends: debhelper (>= 12), dh-cargo (>= 25), cargo, rustc, libstd-rust-dev, librust-breezyshim+default-dev (>= 0.1.138-~~), librust-breezyshim+dirty-tracker-dev (>= 0.1.138-~~), librust-chrono+default-dev (>= 0.4-~~), librust-chrono+serde-dev (>= 0.4-~~), librust-configparser-3+default-dev, librust-debian-changelog+default-dev (>= 0.1-~~), librust-debian-control+default-dev (>= 0.1-~~), librust-debian-copyright-0.1+default-dev (>= 0.1.2-~~), librust-debversion+default-dev (>= 0.3.0-~~), librust-debversion+python-debian-dev (>= 0.3.0-~~), librust-debversion+serde-dev (>= 0.3.0-~~), librust-dep3-0.1+default-dev, librust-distro-info+default-dev (>= 0.4.0-~~), librust-lazy-regex+default-dev (>= 2-~~), librust-lazy-static-1+default-dev (>= 1.4.0-~~), librust-log-0.4+default-dev, librust-makefile-lossless-0.1+default-dev, librust-maplit-1+default-dev (>= 1.0.2-~~), librust-patchkit-0.1+default-dev, librust-pyo3+chrono-dev (>= 0.22-~~), librust-pyo3+default-dev (>= 0.22-~~), librust-pyo3+serde-dev (>= 0.22-~~), librust-reqwest+blocking-dev (>= 0.11-~~), librust-reqwest+default-dev (>= 0.11-~~), librust-reqwest+json-dev (>= 0.11-~~), librust-serde-1+default-dev, librust-serde-1+derive-dev, librust-serde-json-1+default-dev, librust-tempfile-3+default-dev, librust-url-2+default-dev, build-essential, fakeroot, dumb-init dpkg-deb: building package 'sbuild-build-depends-main-dummy' in '/<>/apt_archive/sbuild-build-depends-main-dummy.deb'. Ign:1 copy:/<>/apt_archive ./ InRelease Get:2 copy:/<>/apt_archive ./ Release [615 B] Ign:3 copy:/<>/apt_archive ./ Release.gpg Get:4 copy:/<>/apt_archive ./ Sources [2269 B] Get:5 copy:/<>/apt_archive ./ Packages [1922 B] Fetched 4806 B in 0s (0 B/s) Reading package lists... Get:1 file:/<>/resolver-lsZ2jW/apt_archive ./ InRelease Ign:1 file:/<>/resolver-lsZ2jW/apt_archive ./ InRelease Get:2 file:/<>/resolver-lsZ2jW/apt_archive ./ Release [606 B] Get:2 file:/<>/resolver-lsZ2jW/apt_archive ./ Release [606 B] Get:3 file:/<>/resolver-lsZ2jW/apt_archive ./ Release.gpg Ign:3 file:/<>/resolver-lsZ2jW/apt_archive ./ Release.gpg Reading package lists... Reading package lists... Install main build dependencies (apt-based resolver) ---------------------------------------------------- Installing build dependencies Reading package lists... Building dependency tree... Reading state information... Some packages could not be installed. This may mean that you have requested an impossible situation or if you are using the unstable distribution that some required packages have not yet been created or been moved out of Incoming. The following information may help to resolve the situation: The following packages have unmet dependencies: sbuild-build-depends-main-dummy : Depends: librust-breezyshim+dirty-tracker-dev (>= 0.1.138-~~) but it is not installable E: Unable to correct problems, you have held broken packages. apt-get failed. E: Package installation failed Not removing build depends: cloned chroot in use Setup apt archive ----------------- Merged Build-Depends: dose-distcheck Filtered Build-Depends: dose-distcheck dpkg-deb: building package 'sbuild-build-depends-dose3-dummy' in '/<>/apt_archive/sbuild-build-depends-dose3-dummy.deb'. Ign:1 copy:/<>/apt_archive ./ InRelease Get:2 copy:/<>/apt_archive ./ Release [615 B] Ign:3 copy:/<>/apt_archive ./ Release.gpg Get:4 copy:/<>/apt_archive ./ Sources [2848 B] Get:5 copy:/<>/apt_archive ./ Packages [2537 B] Fetched 6000 B in 0s (0 B/s) Reading package lists... Get:1 file:/<>/resolver-lsZ2jW/apt_archive ./ InRelease Ign:1 file:/<>/resolver-lsZ2jW/apt_archive ./ InRelease Get:2 file:/<>/resolver-lsZ2jW/apt_archive ./ Release [606 B] Get:2 file:/<>/resolver-lsZ2jW/apt_archive ./ Release [606 B] Get:3 file:/<>/resolver-lsZ2jW/apt_archive ./ Release.gpg Ign:3 file:/<>/resolver-lsZ2jW/apt_archive ./ Release.gpg Reading package lists... Reading package lists... Install dose3 build dependencies (apt-based resolver) ----------------------------------------------------- Installing build dependencies Reading package lists... Building dependency tree... Reading state information... The following packages were automatically installed and are no longer required: g++-13 g++-13-x86-64-linux-gnu libpython3.11-minimal libpython3.11-stdlib libstd-rust-1.75 libstdc++-13-dev python3.11 python3.11-minimal Use 'apt autoremove' to remove them. The following additional packages will be installed: dose-distcheck The following NEW packages will be installed: dose-distcheck sbuild-build-depends-dose3-dummy 0 upgraded, 2 newly installed, 0 to remove and 0 not upgraded. Need to get 1089 kB of archives. After this operation, 4472 kB of additional disk space will be used. Get:1 copy:/<>/apt_archive ./ sbuild-build-depends-dose3-dummy 0.invalid.0 [852 B] Get:2 http://deb.debian.org/debian unstable/main amd64 dose-distcheck amd64 7.0.0-5+b2 [1088 kB] debconf: delaying package configuration, since apt-utils is not installed Fetched 1089 kB in 0s (8038 kB/s) Selecting previously unselected package dose-distcheck. (Reading database ... 24915 files and directories currently installed.) Preparing to unpack .../dose-distcheck_7.0.0-5+b2_amd64.deb ... Unpacking dose-distcheck (7.0.0-5+b2) ... Selecting previously unselected package sbuild-build-depends-dose3-dummy. Preparing to unpack .../sbuild-build-depends-dose3-dummy_0.invalid.0_amd64.deb ... Unpacking sbuild-build-depends-dose3-dummy (0.invalid.0) ... Setting up dose-distcheck (7.0.0-5+b2) ... Setting up sbuild-build-depends-dose3-dummy (0.invalid.0) ... Processing triggers for man-db (2.12.1-2) ... (I)Doseparse: Parsing and normalizing... (I)Dose_deb: Parsing Packages file -... (I)Dose_common: total packages 71128 (I)Dose_applications: Cudf Universe: 71128 packages (I)Dose_applications: --checkonly specified, consider all packages as background packages (I)Dose_applications: Solving... output-version: 1.2 native-architecture: amd64 report: - package: sbuild-build-depends-main-dummy version: 0.invalid.0 architecture: amd64 status: broken reasons: - missing: pkg: package: sbuild-build-depends-main-dummy version: 0.invalid.0 architecture: amd64 unsat-dependency: librust-breezyshim+dirty-tracker-dev:amd64 (>= 0.1.138-~~) background-packages: 71127 foreground-packages: 1 total-packages: 71128 broken-packages: 1 buildlog-consultant-0.1.4/src/testdata/sbuild.0.log000064400000000000000000000275011046102023000203470ustar 00000000000000sbuild (Debian sbuild) 0.85.2 (11 March 2023) on charis.vpn.jelmer.uk +==============================================================================+ | rust-always-assert 0.1.3-1 (amd64) Sat, 16 Sep 2023 16:46:46 +0000 | +==============================================================================+ Package: rust-always-assert Version: 0.1.3-1 Source Version: 0.1.3-1 Distribution: unstable Machine Architecture: amd64 Host Architecture: amd64 Build Architecture: amd64 Build Type: binary Unpacking /home/jelmer/.cache/sbuild/debcargo-unstable-amd64-sbuild.tar.xz to /dev/shm/tmp.sbuild.3lAQxFDuCZ... I: NOTICE: Log filtering will replace 'sbuild-unshare-dummy-location' with '<>' I: NOTICE: Log filtering will replace 'build/rust-always-assert-SPcsaf/resolver-Vb7vZ2' with '<>' +------------------------------------------------------------------------------+ | Update chroot | +------------------------------------------------------------------------------+ ... +------------------------------------------------------------------------------+ | Fetch source files | +------------------------------------------------------------------------------+ Local sources ------------- ... Install main build dependencies (apt-based resolver) ---------------------------------------------------- ... +------------------------------------------------------------------------------+ | Check architectures | +------------------------------------------------------------------------------+ Arch check ok (amd64 included in any) +------------------------------------------------------------------------------+ | Build environment | +------------------------------------------------------------------------------+ Kernel: Linux 6.4.0-4-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.4.13-1 (2023-08-31) amd64 (x86_64) Toolchain package versions: binutils_2.41-5 dpkg-dev_1.22.0 g++-12_12.3.0-9 g++-13_13.2.0-4 gcc-12_12.3.0-9 gcc-13_13.2.0-4 libc6-dev_2.37-10 libstdc++-12-dev_12.3.0-9 libstdc++-13-dev_13.2.0-4 libstdc++6_13.2.0-4 linux-libc-dev_6.5.3-1 Package versions: adduser_3.137 apt_2.7.5 autoconf_2.71-3 automake_1:1.16.5-1.3 autopoint_0.21-13 autotools-dev_20220109.1 base-files_13 base-passwd_3.6.1 bash_5.2.15-2+b5 binutils_2.41-5 binutils-common_2.41-5 binutils-x86-64-linux-gnu_2.41-5 bsdextrautils_2.39.2-1 bsdutils_1:2.39.2-1 build-essential_12.10 bzip2_1.0.8-5+b1 ca-certificates_20230311 cargo_0.66.0+ds1-1 ccache_4.8.3-1 coreutils_9.1-1 cpp_4:13.2.0-1 cpp-12_12.3.0-9 cpp-13_13.2.0-4 dash_0.5.12-6 debconf_1.5.82 debhelper_13.11.6 debian-archive-keyring_2023.4 debianutils_5.12 dh-autoreconf_20 dh-cargo_30 dh-strip-nondeterminism_1.13.1-1 diffstat_1.65-1 diffutils_1:3.8-4 dirmngr_2.2.40-1.1 dpkg_1.22.0 dpkg-dev_1.22.0 dwz_0.15-1 e2fsprogs_1.47.0-2+b1 eatmydata_131-1 fakeroot_1.32.1-1 file_1:5.45-2 findutils_4.9.0-5 g++_4:13.2.0-1 g++-12_12.3.0-9 g++-13_13.2.0-4 gcc_4:13.2.0-1 gcc-12_12.3.0-9 gcc-12-base_12.3.0-9 gcc-13_13.2.0-4 gcc-13-base_13.2.0-4 gettext_0.21-13+b1 gettext-base_0.21-13+b1 gnupg_2.2.40-1.1 gnupg-l10n_2.2.40-1.1 gnupg-utils_2.2.40-1.1 gpg_2.2.40-1.1 gpg-agent_2.2.40-1.1 gpg-wks-client_2.2.40-1.1 gpg-wks-server_2.2.40-1.1 gpgconf_2.2.40-1.1 gpgsm_2.2.40-1.1 gpgv_2.2.40-1.1 grep_3.11-3 groff-base_1.23.0-2 gzip_1.12-1 hostname_3.23+nmu1 init-system-helpers_1.65.2 intltool-debian_0.35.0+20060710.6 iso-codes_4.15.0-1 libacl1_2.3.1-3 libaliased-perl_0.34-3 libapt-pkg-perl_0.1.40+b2 libapt-pkg6.0_2.7.5 libarchive-zip-perl_1.68-1 libasan8_13.2.0-4 libassuan0_2.5.6-1 libatomic1_13.2.0-4 libattr1_1:2.5.1-4 libaudit-common_1:3.1.1-1 libaudit1_1:3.1.1-1 libb-hooks-endofscope-perl_0.26-1 libb-hooks-op-check-perl_0.22-2+b1 libberkeleydb-perl_0.64-2+b1 libbinutils_2.41-5 libblkid1_2.39.2-1 libbrotli1_1.0.9-2+b6 libbsd0_0.11.7-4 libbz2-1.0_1.0.8-5+b1 libc-bin_2.37-10 libc-dev-bin_2.37-10 libc6_2.37-10 libc6-dev_2.37-10 libcap-ng0_0.8.3-1+b3 libcap2_1:2.66-4 libcapture-tiny-perl_0.48-2 libcc1-0_13.2.0-4 libcgi-pm-perl_4.57-1 libclass-data-inheritable-perl_0.08-3 libclass-method-modifiers-perl_2.15-1 libclass-xsaccessor-perl_1.19-4+b1 libclone-perl_0.46-1 libcom-err2_1.47.0-2+b1 libconfig-tiny-perl_2.29-1 libconst-fast-perl_0.014-2 libcpanel-json-xs-perl_4.37-1 libcrypt-dev_1:4.4.36-2 libcrypt1_1:4.4.36-2 libctf-nobfd0_2.41-5 libctf0_2.41-5 libcurl3-gnutls_8.3.0-1 libdata-dpath-perl_0.58-2 libdata-messagepack-perl_1.02-1+b1 libdata-optlist-perl_0.114-1 libdata-validate-domain-perl_0.10-1.1 libdata-validate-ip-perl_0.31-1 libdata-validate-uri-perl_0.07-2 libdb5.3_5.3.28+dfsg2-2 libdebconfclient0_0.270 libdebhelper-perl_13.11.6 libdevel-callchecker-perl_0.008-2 libdevel-size-perl_0.83-2+b1 libdevel-stacktrace-perl_2.0400-2 libdpkg-perl_1.22.0 libdynaloader-functions-perl_0.003-3 libeatmydata1_131-1 libedit2_3.1-20230828-1 libelf1_0.189-4 libemail-address-xs-perl_1.05-1+b1 libencode-locale-perl_1.05-3 libexception-class-perl_1.45-1 libexpat1_2.5.0-2 libext2fs2_1.47.0-2+b1 libfakeroot_1.32.1-1 libffi8_3.4.4-1 libfile-basedir-perl_0.09-2 libfile-find-rule-perl_0.34-3 libfile-listing-perl_6.15-1 libfile-stripnondeterminism-perl_1.13.1-1 libfont-ttf-perl_1.06-2 libgcc-12-dev_12.3.0-9 libgcc-13-dev_13.2.0-4 libgcc-s1_13.2.0-4 libgcrypt20_1.10.2-2 libgdbm-compat4_1.23-3 libgdbm6_1.23-3 libgit2-1.5_1.5.1+ds-1 libgmp10_2:6.3.0+dfsg-2 libgnutls30_3.8.1-4+b1 libgomp1_13.2.0-4 libgpg-error0_1.47-2 libgprofng0_2.41-5 libgssapi-krb5-2_1.20.1-4 libhiredis0.14_0.14.1-4 libhogweed6_3.9.1-2 libhtml-form-perl_6.11-1 libhtml-html5-entities-perl_0.004-3 libhtml-parser-perl_3.81-1 libhtml-tagset-perl_3.20-6 libhtml-tokeparser-simple-perl_3.16-4 libhtml-tree-perl_5.07-3 libhttp-cookies-perl_6.10-1 libhttp-date-perl_6.05-2 libhttp-message-perl_6.44-2 libhttp-negotiate-perl_6.01-2 libhttp-parser2.9_2.9.4-6 libhwasan0_13.2.0-4 libicu72_72.1-3 libidn2-0_2.3.4-1+b1 libimport-into-perl_1.002005-2 libio-html-perl_1.004-3 libio-interactive-perl_1.023-2 libio-socket-ssl-perl_2.083-1 libio-string-perl_1.08-4 libipc-run3-perl_0.048-3 libipc-system-simple-perl_1.30-2 libisl23_0.26-3 libiterator-perl_0.03+ds1-2 libiterator-util-perl_0.02+ds1-2 libitm1_13.2.0-4 libjansson4_2.14-2 libjson-maybexs-perl_1.004005-1 libk5crypto3_1.20.1-4 libkeyutils1_1.6.3-2 libkrb5-3_1.20.1-4 libkrb5support0_1.20.1-4 libksba8_1.6.4-2 libldap-2.5-0_2.5.13+dfsg-5 liblist-compare-perl_0.55-2 liblist-someutils-perl_0.59-1 liblist-utilsby-perl_0.12-2 libllvm14_1:14.0.6-16 libllvm15_1:15.0.7-10 liblsan0_13.2.0-4 liblwp-mediatypes-perl_6.04-2 liblwp-protocol-https-perl_6.11-1 liblz1_1.13-6 liblz4-1_1.9.4-1 liblzma5_5.4.4-0.1 liblzo2-2_2.10-2 libmagic-mgc_1:5.45-2 libmagic1_1:5.45-2 libmarkdown2_2.2.7-2 libmbedcrypto7_2.28.4-1 libmbedtls14_2.28.4-1 libmbedx509-1_2.28.4-1 libmd0_1.1.0-1 libmldbm-perl_2.05-4 libmodule-implementation-perl_0.09-2 libmodule-runtime-perl_0.016-2 libmoo-perl_2.005005-1 libmoox-aliases-perl_0.001006-2 libmount1_2.39.2-1 libmouse-perl_2.5.10-1+b3 libmpc3_1.3.1-1 libmpfr6_4.2.1-1 libnamespace-clean-perl_0.27-2 libncursesw6_6.4+20230625-2 libnet-domain-tld-perl_1.75-3 libnet-http-perl_6.23-1 libnet-ipv6addr-perl_1.02-1 libnet-netmask-perl_2.0002-2 libnet-ssleay-perl_1.92-2+b1 libnetaddr-ip-perl_4.079+dfsg-2+b1 libnettle8_3.9.1-2 libnghttp2-14_1.56.0-1 libnpth0_1.6-3 libnsl-dev_1.3.0-2 libnsl2_1.3.0-2 libnumber-compare-perl_0.03-3 libp11-kit0_0.25.0-4 libpackage-stash-perl_0.40-1 libpam-modules_1.5.2-7 libpam-modules-bin_1.5.2-7 libpam-runtime_1.5.2-7 libpam0g_1.5.2-7 libparams-classify-perl_0.015-2+b1 libparams-util-perl_1.102-2+b1 libpath-tiny-perl_0.144-1 libpcre2-8-0_10.42-4 libperl5.36_5.36.0-9 libperlio-gzip-perl_0.20-1+b1 libperlio-utf8-strict-perl_0.010-1 libpipeline1_1.5.7-1 libproc-processtable-perl_0.636-1 libpsl5_0.21.2-1+b1 libpython3-stdlib_3.11.4-5+b1 libpython3.11-minimal_3.11.5-3 libpython3.11-stdlib_3.11.5-3 libquadmath0_13.2.0-4 libreadline8_8.2-1.3 libregexp-ipv6-perl_0.03-3 libregexp-wildcards-perl_1.05-3 librole-tiny-perl_2.002004-1 librtmp1_2.4+20151223.gitfa8646d.1-2+b2 libsasl2-2_2.1.28+dfsg1-3 libsasl2-modules-db_2.1.28+dfsg1-3 libseccomp2_2.5.4-1+b3 libselinux1_3.5-1 libsemanage-common_3.5-1 libsemanage2_3.5-1 libsepol2_3.5-1 libsereal-decoder-perl_5.004+ds-1 libsereal-encoder-perl_5.004+ds-1 libsframe1_2.41-5 libsmartcols1_2.39.2-1 libsort-versions-perl_1.62-3 libsqlite3-0_3.43.1-1 libss2_1.47.0-2+b1 libssh2-1_1.11.0-2 libssl3_3.0.10-1 libstd-rust-1.63_1.63.0+dfsg1-2 libstd-rust-1.69_1.69.0+dfsg1-1 libstd-rust-dev_1.69.0+dfsg1-1 libstdc++-12-dev_12.3.0-9 libstdc++-13-dev_13.2.0-4 libstdc++6_13.2.0-4 libstrictures-perl_2.000006-1 libsub-exporter-perl_0.990-1 libsub-exporter-progressive-perl_0.001013-3 libsub-identify-perl_0.14-3 libsub-install-perl_0.929-1 libsub-name-perl_0.27-1 libsub-override-perl_0.09-4 libsub-quote-perl_2.006008-1 libsyntax-keyword-try-perl_0.29-1 libsystemd0_254.3-1 libtasn1-6_4.19.0-3 libterm-readkey-perl_2.38-2+b1 libtext-glob-perl_0.11-3 libtext-levenshteinxs-perl_0.03-5+b1 libtext-markdown-discount-perl_0.16-1 libtext-xslate-perl_3.5.9-1+b2 libtime-duration-perl_1.21-2 libtime-moment-perl_0.44-2+b1 libtimedate-perl_2.3300-2 libtinfo6_6.4+20230625-2 libtirpc-common_1.3.3+ds-1 libtirpc-dev_1.3.3+ds-1 libtirpc3_1.3.3+ds-1 libtool_2.4.7-7 libtry-tiny-perl_0.31-2 libtsan2_13.2.0-4 libubsan1_13.2.0-4 libuchardet0_0.0.7-1 libudev1_254.3-1 libunicode-utf8-perl_0.62-2 libunistring2_1.0-2 libunistring5_1.1-2 liburi-perl_5.21-1 libuuid1_2.39.2-1 libvariable-magic-perl_0.63-1+b1 libwww-mechanize-perl_2.17-1 libwww-perl_6.72-1 libwww-robotrules-perl_6.02-1 libxml-libxml-perl_2.0207+dfsg+really+2.0134-1+b1 libxml-namespacesupport-perl_1.12-2 libxml-sax-base-perl_1.09-3 libxml-sax-perl_1.02+dfsg-3 libxml2_2.9.14+dfsg-1.3 libxs-parse-keyword-perl_0.38-1 libxxhash0_0.8.2-2 libyaml-0-2_0.2.5-1 libyaml-libyaml-perl_0.86+ds-1 libz3-4_4.8.12-3.1 libzstd1_1.5.5+dfsg2-1 lintian_2.116.3 linux-libc-dev_6.5.3-1 login_1:4.13+dfsg1-1+b1 logsave_1.47.0-2+b1 lzop_1.04-2 m4_1.4.19-4 make_4.3-4.1 man-db_2.11.2-3 mawk_1.3.4.20230808-1 media-types_10.1.0 mount_2.39.2-1 ncurses-base_6.4+20230625-2 ncurses-bin_6.4+20230625-2 netbase_6.4 openssl_3.0.10-1 passwd_1:4.13+dfsg1-1+b1 patch_2.7.6-7 patchutils_0.4.2-1 perl_5.36.0-9 perl-base_5.36.0-9 perl-modules-5.36_5.36.0-9 perl-openssl-defaults_7+b1 pinentry-curses_1.2.1-1 plzip_1.10-6 po-debconf_1.0.21+nmu1 python3_3.11.4-5+b1 python3-minimal_3.11.4-5+b1 python3.11_3.11.5-3 python3.11-minimal_3.11.5-3 readline-common_8.2-1.3 rpcsvc-proto_1.4.3-1 rustc_1.69.0+dfsg1-1 sbuild-build-depends-main-dummy_0.invalid.0 sed_4.9-1 sensible-utils_0.0.20 sysvinit-utils_3.07-1 t1utils_1.41-4 tar_1.34+dfsg-1.2 tzdata_2023c-10 ucf_3.0043+nmu1 unzip_6.0-28 usrmerge_37 util-linux_2.39.2-1 util-linux-extra_2.39.2-1 xz-utils_5.4.4-0.1 zlib1g_1:1.2.13.dfsg-3 +------------------------------------------------------------------------------+ | Cleanup | +------------------------------------------------------------------------------+ Purging /<> Not cleaning session: cloned chroot in use +------------------------------------------------------------------------------+ | Summary | +------------------------------------------------------------------------------+ Autopkgtest: pass Build Architecture: amd64 Build Type: binary Build-Space: 41428 Build-Time: 3 Distribution: unstable Host Architecture: amd64 Install-Time: 4 Job: /home/jelmer/src/debcargo-conf/build/rust-always-assert_0.1.3-1.dsc Lintian: warn Machine Architecture: amd64 Package: rust-always-assert Package-Time: 72 Source-Version: 0.1.3-1 Space: 41428 Status: successful Version: 0.1.3-1 -------------------------------------------------------------------------------- Finished at 2023-09-16T16:47:58Z Build needed 00:01:12, 41428k disk space buildlog-consultant-0.1.4/src/testdata/sbuild.meson.log000064400000000000000000000026041046102023000213260ustar 00000000000000 --sysconfdir=/etc --localstatedir=/var --libdir=lib/x86_64-linux-gnu The Meson build system Version: 0.56.2 Source dir: /<> Build dir: /<>/obj-x86_64-linux-gnu Build type: native build ../meson.build:1:0: ERROR: Meson version is 0.56.2 but project requires >= 0.57.0 A full log can be found at /<>/obj-x86_64-linux-gnu/meson-logs/meson-log.txt cd obj-x86_64-linux-gnu && tail -v -n \+0 meson-logs/meson-log.txt ==> meson-logs/meson-log.txt <== Build started at 2022-07-21T04:21:47.088879 Main binary: /usr/bin/python3 Build Options: -Dprefix=/usr -Dlibdir=lib/x86_64-linux-gnu -Dlocalstatedir=/var -Dsysconfdir=/etc -Dbuildtype=plain -Dwrap_mode=nodownload Python system: Linux The Meson build system Version: 0.56.2 Source dir: /<> Build dir: /<>/obj-x86_64-linux-gnu Build type: native build ../meson.build:1:0: ERROR: Meson version is 0.56.2 but project requires >= 0.57.0 dh_auto_configure: error: cd obj-x86_64-linux-gnu && LC_ALL=C.UTF-8 meson .. --wrap-mode=nodownload --buildtype=plain --prefix=/usr --sysconfdir=/etc --localstatedir=/var --libdir=lib/x86_64-linux-gnu returned exit code 1 make: *** [debian/rules:13: binary] Error 25 dpkg-buildpackage: error: debian/rules binary subprocess returned exit status 2 -------------------------------------------------------------------------------- Build finished at 2022-07-21T04:21:47Z