buildlog-consultant-0.1.1/.cargo_vcs_info.json0000644000000001610000000000100150220ustar { "git": { "sha1": "72113c45290302e06fd850769c8a0fc1d7cc6e8d", "dirty": true }, "path_in_vcs": "" }buildlog-consultant-0.1.1/.codespellrc000064400000000000000000000001201046102023000161050ustar 00000000000000[codespell] ignore-words-list = crate,gir,atleast,seperately,runnning,swith,ser buildlog-consultant-0.1.1/AUTHORS000064400000000000000000000000431046102023000146610ustar 00000000000000Jelmer Vernooij buildlog-consultant-0.1.1/CODE_OF_CONDUCT.md000064400000000000000000000064221046102023000164170ustar 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.1/Cargo.lock0000644000001430130000000000100130010ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "addr2line" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[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.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", "windows-sys 0.59.0", ] [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", "windows-targets 0.52.6", ] [[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.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f839cdf7e2d3198ac6ca003fd8ebc61715755f41c1cad15ff13df67531e00ed" [[package]] name = "buildlog-consultant" version = "0.1.1" 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.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytes" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cc" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" dependencies = [ "shlex", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[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.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", "windows-targets 0.52.6", ] [[package]] name = "clap" version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim 0.11.1", ] [[package]] name = "clap_derive" version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", "quote", "syn 2.0.87", ] [[package]] name = "clap_lex" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "colorchoice" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[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.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b6e5cafe61e77421a090e2a33b8a2e4e2ff1b106fd906ebade111307064d981" dependencies = [ "proc-macro2", "quote", "syn 2.0.87", ] [[package]] name = "deb822-lossless" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812bb5c8052a89edc6d45d1bc3b3400e8186dd166e9b0a9520bfa5a2bd8477ee" dependencies = [ "deb822-derive", "regex", "rowan", "serde", ] [[package]] name = "debian-control" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8a22cedff98f1dde7406971869258ec8837728042cdcd9daf5795b6bc5becb5" dependencies = [ "chrono", "deb822-lossless", "debversion", "regex", "rowan", "url", ] [[package]] name = "debversion" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b892997e53d52f9ac5c30bdac09cbea6bb1eeb3f93a204b8548774081a44b496" dependencies = [ "chrono", "lazy-regex", "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.87", ] [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[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.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ "log", "regex", ] [[package]] name = "env_logger" version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" dependencies = [ "anstream", "anstyle", "env_filter", "humantime", "log", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "fancy-regex" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" dependencies = [ "bit-set", "regex-automata", "regex-syntax", ] [[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.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 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.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "h2" version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" 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.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "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.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" version = "0.14.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", "h2", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", "socket2", "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.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "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 = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ "displaydoc", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locid" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_locid_transform" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ "displaydoc", "icu_locid", "icu_locid_transform_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_locid_transform_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" [[package]] name = "icu_normalizer" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "utf16_iter", "utf8_iter", "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" [[package]] name = "icu_properties" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ "displaydoc", "icu_collections", "icu_locid_transform", "icu_properties_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_properties_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" [[package]] name = "icu_provider" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ "displaydoc", "icu_locid", "icu_provider_macros", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_provider_macros" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", "syn 2.0.87", ] [[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.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "indexmap" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown 0.15.1", ] [[package]] name = "inventory" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" [[package]] name = "ipnet" version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[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.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" [[package]] name = "js-sys" version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy-regex" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d8e41c97e6bc7ecb552016274b99fbb5d035e8de288c582d9b933af6677bfda" dependencies = [ "lazy-regex-proc_macros", "once_cell", "regex", ] [[package]] name = "lazy-regex-proc_macros" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76e1d8b05d672c53cb9c7b920bbba8783845ae4f0b076e02a3db1d02c81b4163" dependencies = [ "proc-macro2", "quote", "regex", "syn 2.0.87", ] [[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.164" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" [[package]] name = "litemap" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[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.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", ] [[package]] name = "mio" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi", "libc", "wasi", "windows-sys 0.52.0", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "object" version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "pep440_rs" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0922a442c78611fa8c5ed6065d2d898a820cf12fa90604217fdb2d01675efec7" dependencies = [ "serde", "unicode-width 0.2.0", "unscanny", "version-ranges", ] [[package]] name = "pep508_rs" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c2feee999fa547bacab06a4881bacc74688858b92fa8ef1e206c748b0a76048" dependencies = [ "boxcar", "indexmap", "itertools", "once_cell", "pep440_rs", "regex", "rustc-hash 2.0.0", "serde", "smallvec", "thiserror", "unicode-width 0.2.0", "url", "urlencoding", "version-ranges", ] [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "proc-macro2" version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[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.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", "getrandom", "libc", "spin", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rowan" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "028acc0aeb6c46a4e4390928ef6ec0f4b7e9432f37bd4129a976e77f86f93322" dependencies = [ "countme", "hashbrown 0.14.5", "rustc-hash 1.1.0", "text-size", ] [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[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.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" [[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 = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[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.215" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", "syn 2.0.87", ] [[package]] name = "serde_json" version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[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.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smawk" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[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.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" 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.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", "syn 2.0.87", ] [[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.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" dependencies = [ "smawk", "unicode-linebreak", "unicode-width 0.1.14", ] [[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.87", ] [[package]] name = "tinystr" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tokio" version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", "bytes", "libc", "mio", "pin-project-lite", "socket2", "tokio-macros", "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", "syn 2.0.87", ] [[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.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" 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.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 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.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-linebreak" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-width" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[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.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" 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 = "utf16_iter" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" [[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.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn 2.0.87", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", "syn 2.0.87", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "web-sys" version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" 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.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ "windows-targets 0.52.6", ] [[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.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.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 = "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 = "write16" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] name = "writeable" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "yoke" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" dependencies = [ "proc-macro2", "quote", "syn 2.0.87", "synstructure", ] [[package]] name = "zerofrom" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" dependencies = [ "proc-macro2", "quote", "syn 2.0.87", "synstructure", ] [[package]] name = "zerovec" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", "syn 2.0.87", ] buildlog-consultant-0.1.1/Cargo.toml0000644000000053710000000000100130300ustar # 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.1" 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" [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.1.18" [dependencies.debversion] version = "^0.4" features = ["serde"] [dependencies.env_logger] version = ">=0.10" optional = true [dependencies.fancy-regex] version = "0.14" [dependencies.inventory] version = "0.3" [dependencies.lazy-regex] version = "3.0.2" [dependencies.lazy_static] version = "1" [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" [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" [features] chatgpt = ["dep:chatgpt_rs"] cli = [ "dep:clap", "dep:env_logger", ] default = ["cli"] buildlog-consultant-0.1.1/Cargo.toml.orig000064400000000000000000000032721046102023000165070ustar 00000000000000[package] name = "buildlog-consultant" version = "0.1.1" 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" serde_json = "1" serde = { version = "1", features = ["derive"] } shlex = "1" log = "0.4.20" text-size = "1.1.1" debversion = { version = "^0.4", features = ["serde"] } chrono = "0.4.31" fancy-regex = "0.14" 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.1.18" maplit = "1.0.2" pep508_rs = "0.9.1" [dev-dependencies] maplit = "1.0.2" buildlog-consultant-0.1.1/LICENSE000064400000000000000000000432541046102023000146310ustar 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.1/README.md000064400000000000000000000032471046102023000151010ustar 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.1/SECURITY.md000064400000000000000000000004561046102023000154120ustar 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.1/disperse.toml000064400000000000000000000001361046102023000163270ustar 00000000000000name = "buildlog-consultant" tag-name = "v$VERSION" tarball-location = [] release-timeout = 5 buildlog-consultant-0.1.1/pyproject.toml000064400000000000000000000036101046102023000165300ustar 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.7.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", "ANN101", "ANN102", "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.1/src/apt.rs000064400000000000000000000342351046102023000155440ustar 00000000000000use 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}; pub fn find_apt_get_failure( lines: Vec<&str>, ) -> (Option>, Option>) { let mut ret: (Option>, Option>) = (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 } pub fn find_apt_get_update_failure( sbuildlog: &crate::sbuild::SbuildLog, ) -> ( Option, Option>, Option>, ) { 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) } 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())) } 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_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); } } buildlog-consultant-0.1.1/src/autopkgtest.rs000064400000000000000000001476511046102023000173410ustar 00000000000000use crate::lines::Lines; use crate::problems::autopkgtest::*; use crate::{Match, Problem, SingleLineMatch}; use std::collections::HashMap; #[derive(Debug, PartialEq, Eq, Clone)] pub enum Packet<'a> { Source, Summary, TestBeginOutput(&'a str), TestEndOutput(&'a str), Results(&'a str), Stderr(&'a str), TestbedSetup(&'a str), TestOutput(&'a str, &'a str), Error(&'a str), Other(&'a str), } 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))); } } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum TestResult { Pass, Fail, Skip, 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(()), } } } #[derive(Debug, PartialEq, Eq, Clone)] pub struct Summary { pub offset: usize, pub name: String, pub result: TestResult, pub reason: Option, pub extra: Vec, } impl Summary { pub fn offset(&self) -> usize { self.offset } pub fn lineno(&self) -> usize { self.offset + 1 } } 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 } #[derive(Debug, PartialEq, Eq, Clone, std::hash::Hash)] enum Field { Output(String), FieldOutput(String, String), Stderr(String), Results(String), PrepareTestbed(String), 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::FieldOutput(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::FieldOutput(testname, _) => Some(testname), Field::Summary => None, } } } /// Find the autopkgtest failure in output. pub fn find_autopkgtest_failure_description( mut lines: Vec<&str>, ) -> ( Option>, Option, Option>, Option, ) { 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(error) = error { assert!(r#match.is_some()); let description = r#match.as_ref().unwrap().line(); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, test_output.get(&field).unwrap().1 + r#match.unwrap().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(); 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 error.is_some() && r#match.is_some() { let description = r#match.as_ref().unwrap().line(); return (r#match, last_test, error, 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 error.is_some() && r#match.is_some() { let description = r#match.as_ref().unwrap().line(); return (Some(r#match.unwrap()), last_test, error, 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()); let description = r#match.as_ref().unwrap().line(); if error.is_some() && r#match.is_some() { return ( r#match, last_test.map(|x| x.to_owned()), error, 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()); let description = r#match.as_ref().unwrap().line(); if error.is_some() && r#match.is_some() { return ( r#match, last_test.map(|x| x.to_owned()), error, 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 error.is_some() && r#match.is_some() && test_output.contains_key(current_field) { let description = r#match.as_ref().unwrap().line(); return ( Some(Box::new(SingleLineMatch::from_lines( &lines, test_output.get(current_field).unwrap().1 + r#match.unwrap().offset(), Some("direct regex"), )) as Box), last_test.map(|x| x.to_owned()), error, 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::FieldOutput(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 r#match.is_some() && stderr_offset.is_some() { offset = Some(r#match.as_ref().unwrap().offset() + stderr_offset.unwrap()); description = Some(r#match.as_ref().unwrap().line()); } 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_owned()); 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()), ) } 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 !output_lines.is_empty() && output_offset.is_some() { let (r#match, error) = crate::apt::find_apt_get_failure(output_lines); if error.is_some() && r#match.is_some() { return ( Some(Box::new(SingleLineMatch::from_lines( &lines, r#match.unwrap().offset() + output_offset.unwrap(), Some("direct regex"), )) as Box), Some(packet.name), error, 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 = if r#match.is_none() || output_offset.is_none() { summary_offset + packet.offset() } else { r#match.as_ref().unwrap().offset() + output_offset.unwrap() }; let description = if let Some(r#match) = r#match.as_ref() { r#match.line() } 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) } pub fn find_testbed_setup_failure( lines: Vec<&str>, ) -> (Option>, Option>) { 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.1/src/bin/analyze-apt-log.rs000064400000000000000000000015771046102023000205370ustar 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 }) .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.1/src/bin/analyze-autopkgtest-log.rs000064400000000000000000000031251046102023000223140ustar 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 }) .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.1/src/bin/analyze-build-log.rs000064400000000000000000000065011046102023000210420ustar 00000000000000use buildlog_consultant::common::find_build_failure_description; use buildlog_consultant::{Match, Problem}; use clap::Parser; use std::cmp::{max, 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().clone()), ); 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 }) .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() ); } for i in max(0, m.offsets()[0] - args.context) ..min(lines.len(), m.offsets().last().unwrap() + args.context + 1) { log::info!( " {} {}", if m.offsets().contains(&i) { ">" } else { " " }, lines[i].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.1/src/bin/analyze-sbuild-log.rs000064400000000000000000000065661046102023000212400ustar 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().clone()), ); 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 }) .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.1/src/bin/chatgpt-analyze-log.rs000064400000000000000000000017411046102023000213760ustar 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::>(), )); if let Some(m) = m { log::info!("match: {}", m); } } buildlog-consultant-0.1.1/src/brz.rs000064400000000000000000000215541046102023000155550ustar 00000000000000use crate::lines::Lines; use crate::problems::common::NoSpaceOnDevice; use crate::problems::debian::*; use crate::Problem; 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()]; for n in lines[i + 1..].iter() { if n.starts_with(" ") { rest.push(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 } 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: Vec<(regex::Regex, fn(®ex::Captures, Vec<&str>) -> Option>)> = 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))) ]; } 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(), ); } return (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.1/src/chatgpt.rs000064400000000000000000000025671046102023000164150ustar 00000000000000use crate::SingleLineMatch; use chatgpt::prelude::*; use chatgpt::types::CompletionResponse; pub const MAX_TOKENS: usize = 4096; pub const INITIAL_PROMPT: &str = "Which line in the log file below is the clearest explanation of a problem:\n\n"; pub async fn analyze(chatgpt_key: String, lines: Vec<&str>) -> Option { let client = ChatGPT::new(chatgpt_key).unwrap(); // Select lines from the end, but not more than MAX_TOKENS - INITIAL_PROMPT.len() // Also, only include full lines let mut truncated = lines .iter() .rev() .take_while(|line| line.len() < MAX_TOKENS - INITIAL_PROMPT.len()) .collect::>(); // Reverse the lines back truncated.reverse(); let prompt = format!( "{}{}", INITIAL_PROMPT, truncated .into_iter() .map(|line| *line) .collect::>() .join("\n") ); // Sending a message and getting the completion let response: CompletionResponse = client.send_message(&prompt).await.unwrap(); let text = &response.message().content; for (i, line) in lines.iter().enumerate().rev() { if line.starts_with(text) { return Some(SingleLineMatch::from_lines(&lines, i, Some("chatgpt"))); } } log::debug!("Unable to find chatgpt answer in lines: {:?}", text); None } buildlog-consultant-0.1.1/src/common.rs000064400000000000000000007632321046102023000162560ustar 00000000000000use crate::lines::Lines; use crate::problems::common::*; /// Common code for all environments. // TODO(jelmer): Right now this is just a straight port from Python. It needs a massive amount of // refactoring, including a split of the file. 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; 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); } return 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(|c| c == '\r' || c == '\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(|c| c == '\r' || c == '\n'); if line != "# The following modules are not available." { return Ok(None); } if lines[offset + 1].trim_end_matches(|c| c == '\r' || c == '\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))) } } } pub fn match_lines( lines: &[&str], offset: usize, ) -> Result, Option>)>, Error> { 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: .*"), ]; } 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() { if regexp.is_match(match_line).unwrap() { 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(), }); } } } 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>, ) -> (Option>, Option>) { 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() ); } } buildlog-consultant-0.1.1/src/cudf.rs000064400000000000000000000035351046102023000157000ustar 00000000000000use debversion::Version; use serde::Deserialize; 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)) } #[derive(Deserialize, Clone, Debug, PartialEq, Eq)] pub(crate) struct Cudf { #[serde( rename = "output-version", deserialize_with = "deserialize_output_version" )] pub output_version: (u8, u8), #[serde(rename = "native-architecture")] pub native_architecture: String, pub report: Vec, } #[derive(Deserialize, Clone, Debug, Eq, PartialEq)] pub enum Status { #[serde(rename = "broken")] Broken, } #[derive(Deserialize, Clone, Debug, PartialEq, Eq)] pub(crate) struct Report { pub package: String, pub version: Version, pub architecture: String, pub status: Status, pub reasons: Vec, } #[derive(Deserialize, Clone, Debug, PartialEq, Eq)] pub(crate) struct Reason { pub missing: Option, pub conflict: Option, } #[derive(Deserialize, Clone, Debug, PartialEq, Eq)] pub(crate) struct Missing { pub pkg: Pkg, } #[derive(Deserialize, Clone, Debug, PartialEq, Eq)] pub(crate) struct Conflict { pub pkg1: Pkg, pub pkg2: Pkg, } #[derive(Deserialize, Clone, Debug, PartialEq, Eq)] pub(crate) struct Pkg { pub package: String, pub version: Version, pub architecture: String, #[serde(rename = "unsat-dependency")] pub unsat_dependency: Option, #[serde(rename = "unsat-conflict)")] pub unsat_conflict: Option, } buildlog-consultant-0.1.1/src/lib.rs000064400000000000000000000134361046102023000155260ustar 00000000000000use std::borrow::Cow; use std::ops::Index; pub mod apt; pub mod autopkgtest; pub mod brz; pub mod cudf; pub mod lines; pub mod problems; #[cfg(feature = "chatgpt")] pub mod chatgpt; pub trait Match: Send + Sync + std::fmt::Debug + std::fmt::Display { fn line(&self) -> String; fn origin(&self) -> Origin; fn offset(&self) -> usize; fn lineno(&self) -> usize { self.offset() + 1 } fn linenos(&self) -> Vec { self.offsets().iter().map(|&x| x + 1).collect() } fn offsets(&self) -> Vec; fn lines(&self) -> Vec; fn add_offset(&self, offset: usize) -> Box; } #[derive(Clone, Debug)] pub struct Origin(String); impl std::fmt::Display for Origin { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.0) } } #[derive(Clone, Debug)] pub struct SingleLineMatch { pub origin: Origin, pub offset: usize, pub line: String, } impl Match for SingleLineMatch { fn line(&self) -> String { self.line.clone() } fn origin(&self) -> Origin { self.origin.clone() } fn offset(&self) -> usize { self.offset } fn offsets(&self) -> Vec { vec![self.offset] } fn lines(&self) -> Vec { vec![self.line.clone()] } 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 { 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(), } } } #[derive(Clone, Debug)] pub struct MultiLineMatch { pub origin: Origin, pub offsets: Vec, pub lines: Vec, } impl MultiLineMatch { pub fn new(origin: Origin, offsets: Vec, lines: Vec) -> Self { assert!(!offsets.is_empty()); assert!(offsets.len() == lines.len()); Self { origin, offsets, lines, } } 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) -> String { self.lines.last().unwrap().clone() } fn origin(&self) -> Origin { self.origin.clone() } fn offset(&self) -> usize { *self.offsets.last().unwrap() } fn lineno(&self) -> usize { self.offset() + 1 } fn offsets(&self) -> Vec { self.offsets.clone() } fn lines(&self) -> Vec { self.lines.clone() } 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()) } } pub trait Problem: std::fmt::Display + Send + Sync + std::fmt::Debug { fn kind(&self) -> Cow; fn json(&self) -> serde_json::Value; 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); } } pub mod common; pub mod r#match; pub mod sbuild; 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() ); } for i in max(0, m.offsets()[0] - context) ..min(lines.len(), m.offsets().last().unwrap() + context + 1) { println!( " {} {}", if m.offsets().contains(&i) { ">" } else { " " }, lines[i].trim_end_matches('\n') ); } } buildlog-consultant-0.1.1/src/lines.rs000064400000000000000000000125201046102023000160630ustar 00000000000000pub trait Lines<'a> { fn iter_forward(&'a self, limit: Option) -> impl Iterator; fn enumerate_forward(&'a self, limit: Option) -> impl Iterator { self.iter_forward(limit).enumerate() } fn iter_backward(&'a self, limit: Option) -> impl DoubleEndedIterator; 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)) } 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)) } fn len(&self) -> usize; 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")]); } } buildlog-consultant-0.1.1/src/match.rs000064400000000000000000000067111046102023000160520ustar 00000000000000use crate::SingleLineMatch; use crate::{Match, Origin, Problem}; use regex::{Captures, Regex}; use std::fmt::Display; #[derive(Debug)] pub struct Error { 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 {} pub struct RegexLineMatcher { regex: Regex, callback: Box Result>, Error> + Send + Sync>, } pub trait Matcher: Sync { fn extract_from_lines( &self, lines: &[&str], offset: usize, ) -> Result, Option>)>, Error>; } impl RegexLineMatcher { pub fn new( regex: Regex, callback: Box Result>, Error> + Send + Sync>, ) -> Self { Self { regex, callback } } pub fn matches_line(&self, line: &str) -> bool { self.regex.is_match(line) } 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) } 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, ) -> Result, Option>)>, Error> { 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_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_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)), )) }}; } pub struct MatcherGroup(Vec>); impl MatcherGroup { 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 { pub fn extract_from_lines( &self, lines: &[&str], offset: usize, ) -> Result, Option>)>, Error> { for matcher in self.0.iter() { if let Some(p) = matcher.extract_from_lines(lines, offset)? { return Ok(Some(p)); } } Ok(None) } } buildlog-consultant-0.1.1/src/problems/autopkgtest.rs000064400000000000000000000126431046102023000211540ustar 00000000000000use crate::Problem; impl AutopkgtestDepsUnsatisfiable { 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) } } #[derive(Debug, Clone)] pub struct AutopkgtestDepsUnsatisfiable(pub Vec<(Option, String)>); impl Problem for AutopkgtestDepsUnsatisfiable { fn kind(&self) -> std::borrow::Cow { "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) } } #[derive(Debug, Clone)] pub struct AutopkgtestTimedOut; impl Problem for AutopkgtestTimedOut { fn kind(&self) -> std::borrow::Cow { "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") } } #[derive(Debug, Clone)] pub struct XDGRunTimeNotSet; impl Problem for XDGRunTimeNotSet { fn kind(&self) -> std::borrow::Cow { "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") } } #[derive(Debug, Clone)] pub struct AutopkgtestTestbedFailure(pub String); impl Problem for AutopkgtestTestbedFailure { fn kind(&self) -> std::borrow::Cow { "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) } } #[derive(Debug, Clone)] pub struct AutopkgtestDepChrootDisappeared; impl Problem for AutopkgtestDepChrootDisappeared { fn kind(&self) -> std::borrow::Cow { "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") } } #[derive(Debug, Clone)] pub struct AutopkgtestErroneousPackage(pub String); impl Problem for AutopkgtestErroneousPackage { fn kind(&self) -> std::borrow::Cow { "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) } } #[derive(Debug, Clone)] pub struct AutopkgtestStderrFailure(pub String); impl Problem for AutopkgtestStderrFailure { fn kind(&self) -> std::borrow::Cow { "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) } } #[derive(Debug, Clone)] pub struct AutopkgtestTestbedSetupFailure { pub command: String, pub exit_status: i32, pub error: String, } impl Problem for AutopkgtestTestbedSetupFailure { fn kind(&self) -> std::borrow::Cow { "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 ) } } buildlog-consultant-0.1.1/src/problems/common.rs000064400000000000000000002233711046102023000200740ustar 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; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct MissingFile { pub path: PathBuf, } impl MissingFile { pub fn new(path: PathBuf) -> Self { Self { path } } } impl Problem for MissingFile { fn kind(&self) -> Cow { "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()) } } #[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct MissingBuildFile { pub filename: String, } impl MissingBuildFile { pub fn new(filename: String) -> Self { Self { filename } } } impl Problem for MissingBuildFile { fn kind(&self) -> Cow { "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) } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct MissingCommandOrBuildFile { pub filename: String, } impl Problem for MissingCommandOrBuildFile { fn kind(&self) -> Cow { "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 { pub fn command(&self) -> String { self.filename.clone() } } #[derive(Clone, Debug, PartialEq, Eq)] pub struct VcsControlDirectoryNeeded { pub vcs: Vec, } impl Problem for VcsControlDirectoryNeeded { fn kind(&self) -> Cow { "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 { pub fn new(vcs: Vec<&str>) -> Self { Self { vcs: vcs.iter().map(|s| s.to_string()).collect(), } } } #[derive(Debug, Clone)] pub struct MissingPythonModule { pub module: String, pub python_version: Option, 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 { pub fn simple(module: String) -> MissingPythonModule { MissingPythonModule { module, python_version: None, minimum_version: None, } } } impl Problem for MissingPythonModule { fn kind(&self) -> Cow { "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 } } #[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 { "command-missing".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "command": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } #[derive(Debug, Clone)] pub struct MissingPythonDistribution { pub distribution: String, pub python_version: Option, 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 { "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()) { match expr { pep508_rs::MarkerExpression::Version { key: pep508_rs::MarkerValueVersion::PythonVersion, specifier, } => { let version = specifier.version(); major_version = Some(version.release()[0] as i32); } _ => {} } } major_version } impl MissingPythonDistribution { 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(v_u) = depspec.version_or_url { if let VersionOrUrl::VersionSpecifier(vs) = v_u { if vs.len() == 1 { if *vs[0].operator() == Operator::GreaterThanEqual { Some(vs[0].version().to_string()) } else { None } } else { None } } else { None } } else { None }; MissingPythonDistribution { distribution, python_version, minimum_version, } } 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(", ")) } } #[derive(Debug, Clone)] pub struct MissingHaskellModule { pub module: String, } impl MissingHaskellModule { 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 { "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 } } #[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 { "missing-library".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "library": self.0, }) } fn as_any(&self) -> &dyn std::any::Any { self } } #[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 { "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 } } #[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 { "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 } } #[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 { "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 } } #[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 { "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 } } #[derive(Debug, Clone)] pub struct MissingRPackage { pub package: String, pub minimum_version: Option, } impl MissingRPackage { 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 { "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 } } #[derive(Debug, Clone)] pub struct MissingGoPackage { pub package: String, } impl Problem for MissingGoPackage { fn kind(&self) -> Cow { "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) } } #[derive(Debug, Clone)] pub struct MissingCHeader { pub header: String, } impl Problem for MissingCHeader { fn kind(&self) -> Cow { "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 { pub fn new(header: String) -> Self { Self { header } } } #[derive(Debug, Clone)] pub struct MissingNodeModule(pub String); impl Problem for MissingNodeModule { fn kind(&self) -> Cow { "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) } } #[derive(Debug, Clone)] pub struct MissingNodePackage(pub String); impl Problem for MissingNodePackage { fn kind(&self) -> Cow { "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) } } #[derive(Debug, Clone)] pub struct MissingConfigure; impl Problem for MissingConfigure { fn kind(&self) -> Cow { "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") } } #[derive(Debug, Clone)] pub struct MissingVagueDependency { pub name: String, pub url: Option, pub minimum_version: Option, pub current_version: Option, } impl MissingVagueDependency { 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 { "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) } } #[derive(Debug, Clone)] pub struct MissingQt; impl Problem for MissingQt { fn kind(&self) -> Cow { "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") } } #[derive(Debug, Clone)] pub struct MissingX11; impl Problem for MissingX11 { fn kind(&self) -> Cow { "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") } } #[derive(Debug, Clone)] pub struct MissingAutoconfMacro { pub r#macro: String, pub need_rebuild: bool, } impl MissingAutoconfMacro { pub fn new(r#macro: String) -> Self { Self { r#macro, need_rebuild: false, } } } impl Problem for MissingAutoconfMacro { fn kind(&self) -> Cow { "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) } } #[derive(Debug, Clone)] pub struct DirectoryNonExistant(pub String); impl Problem for DirectoryNonExistant { fn kind(&self) -> Cow { "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) } } #[derive(Debug, Clone)] pub struct MissingValaPackage(pub String); impl Problem for MissingValaPackage { fn kind(&self) -> Cow { "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) } } #[derive(Debug, Clone)] pub struct UpstartFilePresent(pub String); impl Problem for UpstartFilePresent { fn kind(&self) -> Cow { "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) } } #[derive(Debug, Clone)] pub struct MissingPostgresExtension(pub String); impl Problem for MissingPostgresExtension { fn kind(&self) -> Cow { "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) } } #[derive(Debug, Clone)] pub struct MissingPkgConfig { pub module: String, pub minimum_version: Option, } impl Problem for MissingPkgConfig { fn kind(&self) -> Cow { "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 { pub fn new(module: String, minimum_version: Option) -> Self { Self { module, minimum_version, } } pub fn simple(module: String) -> Self { Self { module, minimum_version: None, } } } #[derive(Debug, Clone)] pub struct MissingHaskellDependencies(pub Vec); impl Problem for MissingHaskellDependencies { fn kind(&self) -> Cow { "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) } } #[derive(Debug, Clone)] pub struct NoSpaceOnDevice; impl Problem for NoSpaceOnDevice { fn kind(&self) -> Cow { "no-space-on-device".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } fn as_any(&self) -> &dyn std::any::Any { self } } impl Display for NoSpaceOnDevice { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "No space left on device") } } #[derive(Debug, Clone)] pub struct MissingJRE; impl Problem for MissingJRE { fn kind(&self) -> Cow { "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") } } #[derive(Debug, Clone)] pub struct MissingJDK { pub jdk_path: String, } impl MissingJDK { pub fn new(jdk_path: String) -> Self { Self { jdk_path } } } impl Problem for MissingJDK { fn kind(&self) -> Cow { "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) } } #[derive(Debug, Clone)] pub struct MissingJDKFile { pub jdk_path: String, pub filename: String, } impl MissingJDKFile { pub fn new(jdk_path: String, filename: String) -> Self { Self { jdk_path, filename } } } impl Problem for MissingJDKFile { fn kind(&self) -> Cow { "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) } } #[derive(Debug, Clone)] pub struct MissingPerlFile { pub filename: String, pub inc: Option>, } impl MissingPerlFile { pub fn new(filename: String, inc: Option>) -> Self { Self { filename, inc } } } impl Problem for MissingPerlFile { fn kind(&self) -> Cow { "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) } } } #[derive(Debug, Clone)] pub struct MissingPerlModule { pub filename: Option, pub module: String, pub inc: Option>, pub minimum_version: Option, } impl Problem for MissingPerlModule { fn kind(&self) -> Cow { "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 { pub fn simple(module: &str) -> Self { Self { filename: None, module: module.to_string(), inc: None, minimum_version: None, } } } #[derive(Debug, Clone)] pub struct MissingSetupPyCommand(pub String); impl Problem for MissingSetupPyCommand { fn kind(&self) -> Cow { "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) } } #[derive(Debug, Clone)] pub struct MissingCSharpCompiler; impl Problem for MissingCSharpCompiler { fn kind(&self) -> Cow { "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") } } #[derive(Debug, Clone)] pub struct MissingRustCompiler; impl Problem for MissingRustCompiler { fn kind(&self) -> Cow { "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") } } #[derive(Debug, Clone)] pub struct MissingAssembler; impl Problem for MissingAssembler { fn kind(&self) -> Cow { "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") } } #[derive(Debug, Clone)] pub struct MissingCargoCrate { pub crate_name: String, pub requirement: Option, } impl Problem for MissingCargoCrate { fn kind(&self) -> Cow { "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 { 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) } } } #[derive(Debug, Clone)] pub struct DhWithOrderIncorrect; impl Problem for DhWithOrderIncorrect { fn kind(&self) -> Cow { "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") } } #[derive(Debug, Clone)] pub struct UnsupportedDebhelperCompatLevel { pub oldest_supported: u32, pub requested: u32, } impl UnsupportedDebhelperCompatLevel { pub fn new(oldest_supported: u32, requested: u32) -> Self { Self { oldest_supported, requested, } } } impl Problem for UnsupportedDebhelperCompatLevel { fn kind(&self) -> Cow { "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 ) } } #[derive(Debug, Clone)] pub struct SetuptoolScmVersionIssue; impl Problem for SetuptoolScmVersionIssue { fn kind(&self) -> Cow { "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") } } #[derive(Debug, Clone)] pub struct MissingMavenArtifacts(pub Vec); impl Problem for MissingMavenArtifacts { fn kind(&self) -> Cow { "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(", ")) } } #[derive(Debug, Clone)] pub struct NotExecutableFile(pub String); impl NotExecutableFile { pub fn new(path: String) -> Self { Self(path) } } impl Problem for NotExecutableFile { fn kind(&self) -> Cow { "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) } } #[derive(Debug, Clone)] pub struct DhMissingUninstalled(pub String); impl DhMissingUninstalled { pub fn new(missing_file: String) -> Self { Self(missing_file) } } impl Problem for DhMissingUninstalled { fn kind(&self) -> Cow { "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) } } #[derive(Debug, Clone)] pub struct DhLinkDestinationIsDirectory(pub String); impl DhLinkDestinationIsDirectory { pub fn new(path: String) -> Self { Self(path) } } impl Problem for DhLinkDestinationIsDirectory { fn kind(&self) -> Cow { "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) } } #[derive(Debug, Clone)] pub struct MissingXmlEntity { pub url: String, } impl MissingXmlEntity { pub fn new(url: String) -> Self { Self { url } } } impl Problem for MissingXmlEntity { fn kind(&self) -> Cow { "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) } } #[derive(Debug, Clone)] pub struct CcacheError(pub String); impl CcacheError { pub fn new(error: String) -> Self { Self(error) } } impl Problem for CcacheError { fn kind(&self) -> Cow { "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) } } #[derive(Debug, Clone)] pub struct DebianVersionRejected { pub version: String, } impl DebianVersionRejected { pub fn new(version: String) -> Self { Self { version } } } impl Problem for DebianVersionRejected { fn kind(&self) -> Cow { "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) } } #[derive(Debug, Clone)] pub struct PatchApplicationFailed { pub patchname: String, } impl PatchApplicationFailed { pub fn new(patchname: String) -> Self { Self { patchname } } } impl Problem for PatchApplicationFailed { fn kind(&self) -> Cow { "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) } } #[derive(Debug, Clone)] pub struct NeedPgBuildExtUpdateControl { pub generated_path: String, pub template_path: String, } impl NeedPgBuildExtUpdateControl { pub fn new(generated_path: String, template_path: String) -> Self { Self { generated_path, template_path, } } } impl Problem for NeedPgBuildExtUpdateControl { fn kind(&self) -> Cow { "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 ) } } #[derive(Debug, Clone)] pub struct DhAddonLoadFailure { pub name: String, pub path: String, } impl DhAddonLoadFailure { pub fn new(name: String, path: String) -> Self { Self { name, path } } } impl Problem for DhAddonLoadFailure { fn kind(&self) -> Cow { "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) } } #[derive(Debug, Clone)] pub struct DhUntilUnsupported; impl Default for DhUntilUnsupported { fn default() -> Self { Self::new() } } impl DhUntilUnsupported { pub fn new() -> Self { Self } } impl Problem for DhUntilUnsupported { fn kind(&self) -> Cow { "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") } } #[derive(Debug, Clone)] pub struct DebhelperPatternNotFound { pub pattern: String, pub tool: String, pub directories: Vec, } impl DebhelperPatternNotFound { pub fn new(pattern: String, tool: String, directories: Vec) -> Self { Self { pattern, tool, directories, } } } impl Problem for DebhelperPatternNotFound { fn kind(&self) -> Cow { "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 ) } } #[derive(Debug, Clone)] pub struct MissingPerlManifest; impl Default for MissingPerlManifest { fn default() -> Self { Self::new() } } impl MissingPerlManifest { pub fn new() -> Self { Self } } impl Problem for MissingPerlManifest { fn kind(&self) -> Cow { "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") } } #[derive(Debug, Clone)] pub struct ImageMagickDelegateMissing { pub delegate: String, } impl ImageMagickDelegateMissing { pub fn new(delegate: String) -> Self { Self { delegate } } } impl Problem for ImageMagickDelegateMissing { fn kind(&self) -> Cow { "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) } } #[derive(Debug, Clone)] pub struct Cancelled; impl Default for Cancelled { fn default() -> Self { Self::new() } } impl Cancelled { pub fn new() -> Self { Self } } impl Problem for Cancelled { fn kind(&self) -> Cow { "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") } } #[derive(Debug, Clone)] pub struct DisappearedSymbols; impl Default for DisappearedSymbols { fn default() -> Self { Self::new() } } impl DisappearedSymbols { pub fn new() -> Self { Self } } impl Problem for DisappearedSymbols { fn kind(&self) -> Cow { "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") } } #[derive(Debug, Clone)] pub struct DuplicateDHCompatLevel { pub command: String, } impl DuplicateDHCompatLevel { pub fn new(command: String) -> Self { Self { command } } } impl Problem for DuplicateDHCompatLevel { fn kind(&self) -> Cow { "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 ) } } #[derive(Debug, Clone)] pub struct MissingDHCompatLevel { pub command: String, } impl MissingDHCompatLevel { pub fn new(command: String) -> Self { Self { command } } } impl Problem for MissingDHCompatLevel { fn kind(&self) -> Cow { "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) } } #[derive(Debug, Clone)] pub struct MissingJVM; impl Default for MissingJVM { fn default() -> Self { Self::new() } } impl MissingJVM { pub fn new() -> Self { Self } } impl Problem for MissingJVM { fn kind(&self) -> Cow { "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") } } #[derive(Debug, Clone)] pub struct MissingRubyGem { pub gem: String, pub version: Option, } impl MissingRubyGem { pub fn new(gem: String, version: Option) -> Self { Self { gem, version } } pub fn simple(gem: String) -> Self { Self::new(gem, None) } } impl Problem for MissingRubyGem { fn kind(&self) -> Cow { "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) } } } #[derive(Debug, Clone)] pub struct MissingJavaScriptRuntime; impl Default for MissingJavaScriptRuntime { fn default() -> Self { Self::new() } } impl MissingJavaScriptRuntime { pub fn new() -> Self { Self } } impl Problem for MissingJavaScriptRuntime { fn kind(&self) -> Cow { "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") } } #[derive(Debug, Clone)] pub struct MissingRubyFile { pub filename: String, } impl MissingRubyFile { pub fn new(filename: String) -> Self { Self { filename } } } impl Problem for MissingRubyFile { fn kind(&self) -> Cow { "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) } } #[derive(Debug, Clone)] pub struct MissingPhpClass { pub php_class: String, } impl MissingPhpClass { pub fn new(php_class: String) -> Self { Self { php_class } } pub fn simple(php_class: String) -> Self { Self::new(php_class) } } impl Problem for MissingPhpClass { fn kind(&self) -> Cow { "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)] pub struct MissingJavaClass { pub classname: String, } impl MissingJavaClass { pub fn new(classname: String) -> Self { Self { classname } } pub fn simple(classname: String) -> Self { Self::new(classname) } } impl Problem for MissingJavaClass { fn kind(&self) -> Cow { "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)] pub struct MissingSprocketsFile { pub name: String, pub content_type: String, } impl MissingSprocketsFile { pub fn new(name: String, content_type: String) -> Self { Self { name, content_type } } } impl Problem for MissingSprocketsFile { fn kind(&self) -> Cow { "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)] pub struct MissingXfceDependency { pub package: String, } impl MissingXfceDependency { pub fn new(package: String) -> Self { Self { package } } } impl Problem for MissingXfceDependency { fn kind(&self) -> Cow { "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)] pub struct GnomeCommonMissing; impl Problem for GnomeCommonMissing { fn kind(&self) -> Cow { "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)] pub struct MissingConfigStatusInput { pub path: String, } impl MissingConfigStatusInput { pub fn new(path: String) -> Self { Self { path } } } impl Problem for MissingConfigStatusInput { fn kind(&self) -> Cow { "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)] pub struct MissingGnomeCommonDependency { pub package: String, pub minimum_version: Option, } impl MissingGnomeCommonDependency { pub fn new(package: String, minimum_version: Option) -> Self { Self { package, minimum_version, } } pub fn simple(package: String) -> Self { Self::new(package, None) } } impl Problem for MissingGnomeCommonDependency { fn kind(&self) -> Cow { "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)] pub struct MissingAutomakeInput { pub path: String, } impl MissingAutomakeInput { pub fn new(path: String) -> Self { Self { path } } } impl Problem for MissingAutomakeInput { fn kind(&self) -> Cow { "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)] pub struct ChrootNotFound { pub chroot: String, } impl ChrootNotFound { pub fn new(chroot: String) -> Self { Self { chroot } } } impl Problem for ChrootNotFound { fn kind(&self) -> Cow { "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)] pub struct MissingLibtool; impl Default for MissingLibtool { fn default() -> Self { Self::new() } } impl MissingLibtool { pub fn new() -> Self { Self } } impl Problem for MissingLibtool { fn kind(&self) -> Cow { "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)] pub struct CMakeFilesMissing { pub filenames: Vec, pub version: Option, } impl Problem for CMakeFilesMissing { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MissingCMakeComponents { pub name: String, pub components: Vec, } impl Problem for MissingCMakeComponents { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MissingCMakeConfig { pub name: String, pub version: Option, } impl Problem for MissingCMakeConfig { fn kind(&self) -> std::borrow::Cow { "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)] pub struct CMakeNeedExactVersion { pub package: String, pub version_found: String, pub exact_version_needed: String, pub path: PathBuf, } impl Problem for CMakeNeedExactVersion { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MissingStaticLibrary { pub library: String, pub filename: String, } impl Problem for MissingStaticLibrary { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MissingGoRuntime; impl Problem for MissingGoRuntime { fn kind(&self) -> std::borrow::Cow { "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)] pub struct UnknownCertificateAuthority(pub String); impl Problem for UnknownCertificateAuthority { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MissingPerlPredeclared(pub String); impl Problem for MissingPerlPredeclared { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MissingGitIdentity; impl Problem for MissingGitIdentity { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MissingSecretGpgKey; impl Problem for MissingSecretGpgKey { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MissingVcVersionerVersion; impl Problem for MissingVcVersionerVersion { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MissingLatexFile(pub String); impl MissingLatexFile { pub fn new(filename: String) -> Self { Self(filename) } } impl Problem for MissingLatexFile { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MissingXDisplay; impl Problem for MissingXDisplay { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MissingFontspec(pub String); impl Problem for MissingFontspec { fn kind(&self) -> std::borrow::Cow { "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)] pub struct InactiveKilled(pub i64); impl Problem for InactiveKilled { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MissingPauseCredentials; impl Problem for MissingPauseCredentials { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MismatchGettextVersions { pub makefile_version: String, pub autoconf_version: String, } impl Problem for MismatchGettextVersions { fn kind(&self) -> std::borrow::Cow { "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)] pub struct InvalidCurrentUser(pub String); impl Problem for InvalidCurrentUser { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MissingGnulibDirectory(pub PathBuf); impl Problem for MissingGnulibDirectory { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MissingLuaModule(pub String); impl Problem for MissingLuaModule { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MissingGoModFile; impl Problem for MissingGoModFile { fn kind(&self) -> std::borrow::Cow { "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)] pub struct OutdatedGoModFile; impl Problem for OutdatedGoModFile { fn kind(&self) -> std::borrow::Cow { "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)] pub struct CodeCoverageTooLow { pub actual: f64, pub required: f64, } impl Problem for CodeCoverageTooLow { fn kind(&self) -> std::borrow::Cow { "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)] pub struct ESModuleMustUseImport(pub String); impl Problem for ESModuleMustUseImport { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MissingPHPExtension(pub String); impl Problem for MissingPHPExtension { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MinimumAutoconfTooOld(pub String); impl Problem for MinimumAutoconfTooOld { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MissingPerlDistributionFile(pub String); impl Problem for MissingPerlDistributionFile { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MissingGoSumEntry { pub package: String, pub version: String, } impl Problem for MissingGoSumEntry { fn kind(&self) -> std::borrow::Cow { "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)] pub struct ValaCompilerCannotCompile; impl Problem for ValaCompilerCannotCompile { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MissingDebianBuildDep(pub String); impl Problem for MissingDebianBuildDep { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MissingQtModules(pub Vec); impl Problem for MissingQtModules { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MissingOCamlPackage(pub String); impl Problem for MissingOCamlPackage { fn kind(&self) -> std::borrow::Cow { "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)] pub struct TooManyOpenFiles; impl Problem for TooManyOpenFiles { fn kind(&self) -> std::borrow::Cow { "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)] pub struct MissingMakeTarget(pub String, pub Option); impl MissingMakeTarget { pub fn new(target: &str, required_by: Option<&str>) -> Self { Self(target.to_string(), required_by.map(String::from)) } 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 { "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.1/src/problems/debian.rs000064400000000000000000000564611046102023000200320ustar 00000000000000use crate::Problem; use debversion::Version; #[derive(Debug)] pub struct DpkgError(pub String); impl Problem for DpkgError { fn kind(&self) -> std::borrow::Cow { "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) } } #[derive(Debug, Clone)] pub struct AptUpdateError; impl Problem for AptUpdateError { fn kind(&self) -> std::borrow::Cow { "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") } } #[derive(Debug, Clone)] pub struct AptFetchFailure { pub url: Option, pub error: String, } impl Problem for AptFetchFailure { fn kind(&self) -> std::borrow::Cow { "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) } } } #[derive(Debug, Clone)] pub struct AptMissingReleaseFile(pub String); impl Problem for AptMissingReleaseFile { fn kind(&self) -> std::borrow::Cow { "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) } } #[derive(Debug, Clone)] pub struct AptPackageUnknown(pub String); impl Problem for AptPackageUnknown { fn kind(&self) -> std::borrow::Cow { "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) } } #[derive(Debug, Clone)] pub struct AptBrokenPackages { pub description: String, pub broken: Option>, } impl Problem for AptBrokenPackages { fn kind(&self) -> std::borrow::Cow { "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) } } #[derive(Debug, Clone)] pub struct UnableToFindUpstreamTarball { pub package: String, pub version: Version, } impl Problem for UnableToFindUpstreamTarball { fn kind(&self) -> std::borrow::Cow { "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 ) } } #[derive(Debug, Clone)] pub struct SourceFormatUnbuildable { pub source_format: String, pub reason: String, } impl Problem for SourceFormatUnbuildable { fn kind(&self) -> std::borrow::Cow { "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 ) } } #[derive(Debug, Clone)] pub struct SourceFormatUnsupported(pub String); impl Problem for SourceFormatUnsupported { fn kind(&self) -> std::borrow::Cow { "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) } } #[derive(Debug, Clone)] pub struct PatchFileMissing(pub std::path::PathBuf); impl Problem for PatchFileMissing { fn kind(&self) -> std::borrow::Cow { "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()) } } #[derive(Debug, Clone)] pub struct DpkgSourceLocalChanges { pub diff_file: Option, pub files: Option>, } impl Problem for DpkgSourceLocalChanges { fn kind(&self) -> std::borrow::Cow { "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(()) } } #[derive(Debug, Clone)] pub struct DpkgSourceUnrepresentableChanges; impl Problem for DpkgSourceUnrepresentableChanges { fn kind(&self) -> std::borrow::Cow { "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") } } #[derive(Debug, Clone)] pub struct DpkgUnwantedBinaryFiles; impl Problem for DpkgUnwantedBinaryFiles { fn kind(&self) -> std::borrow::Cow { "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") } } #[derive(Debug, Clone)] pub struct DpkgBinaryFileChanged(pub Vec); impl Problem for DpkgBinaryFileChanged { fn kind(&self) -> std::borrow::Cow { "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") } } #[derive(Debug, Clone)] pub struct MissingControlFile(pub std::path::PathBuf); impl Problem for MissingControlFile { fn kind(&self) -> std::borrow::Cow { "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()) } } #[derive(Debug, Clone)] pub struct UnknownMercurialExtraFields(pub String); impl Problem for UnknownMercurialExtraFields { fn kind(&self) -> std::borrow::Cow { "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) } } #[derive(Debug, Clone)] pub struct UpstreamPGPSignatureVerificationFailed; impl Problem for UpstreamPGPSignatureVerificationFailed { fn kind(&self) -> std::borrow::Cow { "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") } } #[derive(Debug, Clone)] pub struct UScanRequestVersionMissing(pub String); impl Problem for UScanRequestVersionMissing { fn kind(&self) -> std::borrow::Cow { "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) } } #[derive(Debug, Clone)] pub struct DebcargoFailure(pub String); impl Problem for DebcargoFailure { fn kind(&self) -> std::borrow::Cow { "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) } } #[derive(Debug, Clone)] pub struct ChangelogParseError(pub String); impl Problem for ChangelogParseError { fn kind(&self) -> std::borrow::Cow { "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) } } #[derive(Debug, Clone)] pub struct UScanError(pub String); impl Problem for UScanError { fn kind(&self) -> std::borrow::Cow { "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) } } #[derive(Debug, Clone)] pub struct UScanFailed { pub url: String, pub reason: String, } impl Problem for UScanFailed { fn kind(&self) -> std::borrow::Cow { "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) } } #[derive(Debug, Clone)] pub struct InconsistentSourceFormat { pub version: bool, pub source_format: bool, } impl Problem for InconsistentSourceFormat { fn kind(&self) -> std::borrow::Cow { "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" ) } } #[derive(Debug, Clone)] pub struct UpstreamMetadataFileParseError { pub path: std::path::PathBuf, pub reason: String, } impl Problem for UpstreamMetadataFileParseError { fn kind(&self) -> std::borrow::Cow { "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) } } #[derive(Debug, Clone)] pub struct DpkgSourcePackFailed(pub String); impl Problem for DpkgSourcePackFailed { fn kind(&self) -> std::borrow::Cow { "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) } } #[derive(Debug, Clone)] pub struct DpkgBadVersion { pub version: String, pub reason: Option, } impl Problem for DpkgBadVersion { fn kind(&self) -> std::borrow::Cow { "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) } } } #[derive(Debug, Clone)] pub struct MissingDebcargoCrate { pub cratename: String, pub version: Option, } impl Problem for MissingDebcargoCrate { fn kind(&self) -> std::borrow::Cow { "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 { 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, } } } } #[derive(Debug, Clone)] pub struct PristineTarTreeMissing(pub String); impl Problem for PristineTarTreeMissing { fn kind(&self) -> std::borrow::Cow { "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) } } #[derive(Debug, Clone)] pub struct MissingRevision(pub Vec); impl Problem for MissingRevision { fn kind(&self) -> std::borrow::Cow { "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)) } } #[derive(Debug)] pub struct DebcargoUnacceptablePredicate { pub cratename: String, pub predicate: String, } impl Problem for DebcargoUnacceptablePredicate { fn kind(&self) -> std::borrow::Cow { "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 ) } } #[derive(Debug)] pub struct DebcargoUnacceptableComparator { pub cratename: String, pub comparator: String, } impl Problem for DebcargoUnacceptableComparator { fn kind(&self) -> std::borrow::Cow { "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 ) } } #[derive(Debug)] pub struct UScanTooManyRequests(pub String); impl Problem for UScanTooManyRequests { fn kind(&self) -> std::borrow::Cow { "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) } } #[derive(Debug)] pub struct UnsatisfiedAptConflicts(pub String); impl Problem for UnsatisfiedAptConflicts { fn kind(&self) -> std::borrow::Cow { "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 {} #[derive(Debug, Clone)] pub struct ArchitectureNotInList { pub arch: String, pub arch_list: Vec, } impl Problem for ArchitectureNotInList { fn kind(&self) -> std::borrow::Cow { "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) } } #[derive(Debug)] pub struct UnsatisfiedAptDependencies(pub String); impl Problem for UnsatisfiedAptDependencies { fn kind(&self) -> std::borrow::Cow { "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) } } #[derive(Debug)] pub struct InsufficientDiskSpace { pub needed: i64, pub free: i64, } impl Problem for InsufficientDiskSpace { fn kind(&self) -> std::borrow::Cow { "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 ) } } buildlog-consultant-0.1.1/src/problems/mod.rs000064400000000000000000000000651046102023000173540ustar 00000000000000pub mod autopkgtest; pub mod common; pub mod debian; buildlog-consultant-0.1.1/src/sbuild.rs000064400000000000000000001335571046102023000162510ustar 00000000000000//! Parsing of Debian sbuild logs //! //! This module provides a parser for Debian sbuild logs. It extracts the different sections of the //! log file, and makes them accessible. 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; 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 } #[derive(Debug, Clone, PartialEq, Eq)] pub struct Summary { build_architecture: Option, build_type: Option, build_time: Option, build_space: Option, host_architecture: Option, install_time: Option, lintian: Option, package: Option, package_time: Option, distribution: Option, fail_stage: Option, job: Option, autopkgtest: Option, source_version: Option, machine_architecture: Option, status: Option, space: Option, version: Option, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum Space { NotAvailable, 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()?)) } } } 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, } } #[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() } 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())) } } #[derive(Debug, Clone)] pub struct SbuildLogSection { pub title: Option, pub offsets: (usize, usize), pub lines: Vec, } impl SbuildLogSection { pub fn lines(&self) -> Vec<&str> { self.lines.iter().map(|x| x.as_str()).collect() } } 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: title.clone(), offsets: (begin_offset, end_offset), lines: lines.clone(), }); } title = Some(l1_trimmed.trim_matches('|').trim().to_string()); lines.clear(); 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() } pub struct SbuildFailure { pub stage: Option, pub description: Option, pub error: Option>, pub phase: Option, pub section: Option, 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().0))?; 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(()) } } pub fn find_preamble_failure_description( lines: Vec<&str>, ) -> (Option>, Option>) { let mut ret: (Option>, Option>) = (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 } #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum Phase { #[serde(rename = "autopkgtest")] AutoPkgTest(String), #[serde(rename = "build")] Build, #[serde(rename = "build-env")] BuildEnv, #[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"), } } } pub const DEFAULT_LOOK_BACK: usize = 50; 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) } 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, }) } 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, }) } pub fn find_creation_session_error( lines: Vec<&str>, ) -> (Option>, Option>) { let mut ret: (Option>, Option>) = (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 } 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, }) } 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, }) } 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, }) } fn find_arch_check_failure_description( lines: Vec<&str>, ) -> (Option>, Option>) { 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, ) } 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, }) } 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, }) } pub const DOSE3_SECTION: &str = "install dose3 build dependencies (aspcud-based resolver)"; pub fn find_install_deps_failure_description( sbuildlog: &SbuildLog, ) -> ( Option<&str>, Option>, Option>, ) { 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) } 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, }) } 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, }) } 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, } } fn find_check_space_failure_description( lines: Vec<&str>, ) -> (Option>, Option>) { 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(), } ) ); } } buildlog-consultant-0.1.1/src/testdata/sbuild-cudf.log000064400000000000000000000175601046102023000211310ustar 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.1/src/testdata/sbuild.0.log000064400000000000000000000275011046102023000203440ustar 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.1/src/testdata/sbuild.meson.log000064400000000000000000000026041046102023000213230ustar 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