././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1694192519.7426732 buildlog-consultant-0.0.34/0000755000175000017500000000000014476651610015157 5ustar00jelmerjelmer././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/.flake80000644000175000017500000000021214476651536016334 0ustar00jelmerjelmer[flake8] extend-ignore = E203, E266, E501, W293, W291, W503, B950, B905 max-line-length = 88 max-complexity = 18 select = B,C,E,F,W,T4,B9 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1694192519.738673 buildlog-consultant-0.0.34/.github/0000755000175000017500000000000014476651610016517 5ustar00jelmerjelmer././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1694192519.738673 buildlog-consultant-0.0.34/.github/workflows/0000755000175000017500000000000014476651610020554 5ustar00jelmerjelmer././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/.github/workflows/disperse.yml0000644000175000017500000000027414476651536023127 0ustar00jelmerjelmer--- name: Disperse configuration "on": - push jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: jelmer/action-disperse-validate@v1 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/.github/workflows/pythonpackage.yml0000644000175000017500000000233314476651536024144 0ustar00jelmerjelmername: Python package on: push: pull_request: schedule: - cron: '0 6 * * *' # Daily 6AM UTC build jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] python-version: [3.9, "3.10", "3.11"] fail-fast: false steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip git+https://salsa.debian.org/python-debian-team/python-debian@c51c41dc2e32a8e9c255c0fdae6602011c414018 PyYAML setuptools-rust python -m pip install ".[chatgpt]" - name: Build run: | python3 setup.py build_ext -i - name: Style checks run: | python -m pip install --upgrade flake8 python -m flake8 - name: Typing checks run: | pip install -U mypy types-PyYAML python -m mypy buildlog_consultant if: "matrix.python-version != 'pypy3'" - name: Test suite run run: | python -m unittest tests.test_suite env: PYTHONHASHSEED: random ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/.github/workflows/wheels.yaml0000644000175000017500000000515014476651536022737 0ustar00jelmerjelmername: Build Python Wheels on: push: pull_request: schedule: - cron: "0 6 * * *" # Daily 6AM UTC build jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] fail-fast: true steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: python-version: '3.x' - name: Install native dependencies (Ubuntu) run: sudo apt-get update && sudo apt-get install -y libgpgme-dev if: "matrix.os == 'ubuntu-latest'" - name: set up rust if: matrix.os != 'ubuntu' uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: nightly override: true - name: Install native dependencies (MacOS) run: brew install swig gpgme if: "matrix.os == 'macos-latest'" - name: Install dependencies run: | python -m pip install --upgrade pip pip install setuptools wheel cibuildwheel - name: Install gpg on supported platforms run: pip install -U gpg if: "matrix.os != 'windows-latest'" - name: Set up QEMU uses: docker/setup-qemu-action@v1 if: "matrix.os == 'ubuntu-latest'" - name: Build wheels run: python -m cibuildwheel --output-dir wheelhouse env: CIBW_ARCHS_LINUX: x86_64 # ARM builds fail at the moment: # CIBW_ARCHS_LINUX: x86_64 aarch64 CIBW_ARCHS_MACOS: x86_64 arm64 universal2 CIBW_SKIP: '*-win32' CIBW_ENVIRONMENT: 'PATH="$HOME/.cargo/bin:$PATH"' CIBW_BEFORE_BUILD: > pip install -U setuptools-rust && rustup default nightly && rustup target add all && rustup show CIBW_BEFORE_BUILD_LINUX: > pip install -U setuptools-rust && curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain=nightly --profile=minimal -y && rustup target add all && rustup show - name: Upload wheels uses: actions/upload-artifact@v3 with: path: ./wheelhouse/*.whl publish: runs-on: ubuntu-latest needs: build if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') permissions: id-token: write environment: name: pypi url: https://pypi.org/p/buildlog-consultant steps: - uses: actions/setup-python@v3 - name: Download wheels uses: actions/download-artifact@v2 - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/.gitignore0000644000175000017500000000017114476651536017155 0ustar00jelmerjelmerupdate.sh buildlog_consultant.egg-info __pycache__ *~ build .tox target buildlog_consultant/_buildlog_consultant_rs.*.so ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/AUTHORS0000644000175000017500000000004314476651536016233 0ustar00jelmerjelmerJelmer Vernooij ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/CODE_OF_CONDUCT.md0000644000175000017500000000642314476651536017772 0ustar00jelmerjelmer# 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, socio-economic 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 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192481.0 buildlog-consultant-0.0.34/Cargo.lock0000644000175000017500000002475614476651541017105 0ustar00jelmerjelmer# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" dependencies = [ "memchr", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "buildlog-consultant" version = "0.0.34" dependencies = [ "inventory", "lazy_static", "pyo3", "regex", "serde", "serde_json", "shlex", ] [[package]] name = "buildlog-consultant-py" version = "0.0.0" dependencies = [ "buildlog-consultant", "pyo3", "serde_json", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "indoc" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" [[package]] name = "inventory" version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1be380c410bf0595e94992a648ea89db4dd3f3354ba54af206fd2a68cf5ac8e" [[package]] name = "itoa" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" [[package]] name = "lock_api" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "memchr" version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] name = "memoffset" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "parking_lot" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets", ] [[package]] name = "proc-macro2" version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e681a6cfdc4adcc93b4d3cf993749a4552018ee0a9b65fc0ccfad74352c72a38" dependencies = [ "cfg-if", "indoc", "libc", "memoffset", "parking_lot", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", "unindent", ] [[package]] name = "pyo3-build-config" version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "076c73d0bc438f7a4ef6fdd0c3bb4732149136abd952b110ac93e4edb13a6ba5" dependencies = [ "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e53cee42e77ebe256066ba8aa77eff722b3bb91f3419177cf4cd0f304d3284d9" dependencies = [ "libc", "pyo3-build-config", ] [[package]] name = "pyo3-macros" version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfeb4c99597e136528c6dd7d5e3de5434d1ceaf487436a3f03b2d56b6fc9efd1" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", "syn 1.0.109", ] [[package]] name = "pyo3-macros-backend" version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "947dc12175c254889edc0c02e399476c2f652b4b9ebd123aa655c224de259536" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "quote" version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ "bitflags", ] [[package]] name = "regex" version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "ryu" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", "syn 2.0.31", ] [[package]] name = "serde_json" version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "shlex" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" [[package]] name = "smallvec" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[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.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "target-lexicon" version = "0.12.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" [[package]] name = "unicode-ident" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" [[package]] name = "unindent" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[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_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[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_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[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_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192479.0 buildlog-consultant-0.0.34/Cargo.toml0000644000175000017500000000120314476651537017113 0ustar00jelmerjelmer[package] name = "buildlog-consultant" version = "0.0.34" 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" [workspace] members = [ "buildlog-consultant-py",] [dependencies] inventory = "0.3" regex = "1" lazy_static = "1" shlex = "1" [workspace.dependencies] pyo3 = "0.19" serde_json = "1" [dependencies.pyo3] workspace = true [dependencies.serde_json] workspace = true [dependencies.serde] version = "1" features = [ "derive",] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/LICENSE0000644000175000017500000004325414476651536016203 0ustar00jelmerjelmer 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. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/MANIFEST.in0000644000175000017500000000031714476651536016725 0ustar00jelmerjelmerinclude CODE_OF_CONDUCT.md include AUTHORS include README.md include SECURITY.md include Cargo.toml buildlog-consultant-py/Cargo.toml recursive-include buildlog-consultant-py *.rs recursive-include src *.rs ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/Makefile0000644000175000017500000000043414476651536016627 0ustar00jelmerjelmerexport PYTHON=python3 all: check build-inplace: $(PYTHON) setup.py build_ext --inplace check:: testsuite testsuite: build-inplace $(PYTHON) -m unittest tests.test_suite check:: style style: flake8 check:: typing typing: build-inplace $(PYTHON) -m mypy buildlog_consultant ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1694192519.7426732 buildlog-consultant-0.0.34/PKG-INFO0000644000175000017500000000121014476651610016246 0ustar00jelmerjelmerMetadata-Version: 2.1 Name: buildlog-consultant Version: 0.0.34 Summary: buildlog parser and analyser Author-email: Jelmer Vernooij Project-URL: Homepage, https://github.com/jelmer/buildlog-consultant Project-URL: Repository, https://github.com/jelmer/buildlog-consultant.git Requires-Python: >=3.9 Description-Content-Type: text/markdown Provides-Extra: chatgpt License-File: LICENSE License-File: AUTHORS The 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). ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/README.md0000644000175000017500000000033514476651536016446 0ustar00jelmerjelmerThe 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). ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/SECURITY.md0000644000175000017500000000045614476651536016764 0ustar00jelmerjelmer# 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 ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1694192519.738673 buildlog-consultant-0.0.34/buildlog-consultant-py/0000755000175000017500000000000014476651610021576 5ustar00jelmerjelmer././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/buildlog-consultant-py/Cargo.lock0000644000175000017500000002474714476651536023530 0ustar00jelmerjelmer# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" dependencies = [ "memchr", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "buildlog-consultant" version = "0.0.33" dependencies = [ "inventory", "lazy_static", "pyo3", "regex", "serde", "serde_json", "shlex", ] [[package]] name = "buildlog-consultant-py" version = "0.0.0" dependencies = [ "buildlog-consultant", "pyo3", "serde_json", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "ghost" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e77ac7b51b8e6313251737fcef4b1c01a2ea102bde68415b62c0ee9268fec357" dependencies = [ "proc-macro2", "quote", "syn 2.0.18", ] [[package]] name = "indoc" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" [[package]] name = "inventory" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0539b5de9241582ce6bd6b0ba7399313560151e58c9aaf8b74b711b1bdce644" dependencies = [ "ghost", ] [[package]] name = "itoa" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" [[package]] name = "lock_api" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memoffset" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "parking_lot" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets", ] [[package]] name = "proc-macro2" version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3b1ac5b3731ba34fdaa9785f8d74d17448cd18f30cf19e0c7e7b1fdb5272109" dependencies = [ "cfg-if", "indoc", "libc", "memoffset", "parking_lot", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", "unindent", ] [[package]] name = "pyo3-build-config" version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cb946f5ac61bb61a5014924910d936ebd2b23b705f7a4a3c40b05c720b079a3" dependencies = [ "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd4d7c5337821916ea2a1d21d1092e8443cf34879e53a0ac653fbb98f44ff65c" dependencies = [ "libc", "pyo3-build-config", ] [[package]] name = "pyo3-macros" version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d39c55dab3fc5a4b25bbd1ac10a2da452c4aca13bb450f22818a002e29648d" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", "syn 1.0.109", ] [[package]] name = "pyo3-macros-backend" version = "0.18.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97daff08a4c48320587b5224cc98d609e3c27b6d437315bd40b605c98eeb5918" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "quote" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ "bitflags", ] [[package]] name = "regex" version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "ryu" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", "syn 2.0.18", ] [[package]] name = "serde_json" version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "shlex" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "smallvec" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[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.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "target-lexicon" version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" [[package]] name = "unicode-ident" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unindent" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" [[package]] name = "windows-targets" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/buildlog-consultant-py/Cargo.toml0000644000175000017500000000070514476651536023537 0ustar00jelmerjelmer[package] name = "buildlog-consultant-py" version = "0.0.0" authors = ["Jelmer Vernooij "] edition = "2018" license = "Apache-2.0" repository = "https://github.com/jelmer/buildlog-consultant.git" homepage = "https://github.com/jelmer/buildlog-consultant" [lib] crate-type = ["cdylib"] [dependencies] buildlog-consultant = { path = ".." } pyo3 = { workspace = true, features = ["extension-module"] } serde_json = { workspace = true } ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1694192519.738673 buildlog-consultant-0.0.34/buildlog-consultant-py/src/0000755000175000017500000000000014476651610022365 5ustar00jelmerjelmer././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/buildlog-consultant-py/src/lib.rs0000644000175000017500000000651714476651536023521 0ustar00jelmerjelmeruse pyo3::exceptions::PyNotImplementedError; use pyo3::prelude::*; use pyo3::pyclass::CompareOp; use std::collections::HashMap; #[pyclass] struct Match(Box); #[pymethods] impl Match { #[getter] fn line(&self) -> String { self.0.line().to_string() } #[getter] fn offset(&self) -> usize { self.0.offset() } #[getter] fn origin(&self) -> String { self.0.origin().to_string() } #[getter] fn lineno(&self) -> usize { self.0.lineno() } fn __repr__(&self) -> String { format!( "Match({:?}, {}, {})", self.0.line(), self.0.lineno(), self.0.lineno() ) } } #[pyclass] struct Problem(Box); fn json_to_py(py: Python, json: serde_json::Value) -> PyResult { match json { serde_json::Value::Null => Ok(py.None()), serde_json::Value::Bool(b) => Ok(b.into_py(py)), serde_json::Value::Number(n) => { if let Some(i) = n.as_i64() { Ok(i.into_py(py)) } else if let Some(u) = n.as_u64() { Ok(u.into_py(py)) } else if let Some(f) = n.as_f64() { Ok(f.into_py(py)) } else { Err(PyErr::new::( "Invalid number", )) } } serde_json::Value::String(s) => Ok(s.into_py(py)), serde_json::Value::Array(a) => { let mut ret = Vec::with_capacity(a.len()); for v in a { ret.push(json_to_py(py, v)?); } Ok(ret.into_py(py)) } serde_json::Value::Object(o) => { let mut ret = pyo3::types::PyDict::new(py); for (k, v) in o { ret.set_item(k, json_to_py(py, v)?)?; } Ok(ret.into()) } } } #[pymethods] impl Problem { #[getter] fn kind(&self) -> String { self.0.kind().to_string() } fn __repr__(&self) -> String { format!("Problem({:?}, {})", self.0.kind(), self.0.json()) } fn json(&self, py: Python) -> PyResult { json_to_py(py, self.0.json()) } fn __richcmp__(&self, other: PyRef, op: CompareOp) -> PyResult { let s = self.0.json(); let o = other.0.json(); match op { CompareOp::Eq => Ok(self.0.kind() == other.0.kind() && s == o), CompareOp::Ne => Ok(self.0.kind() != other.0.kind() || s != o), _ => Err(PyNotImplementedError::new_err( "Only == and != are implemented", )), } } } #[pyfunction] fn match_lines(lines: Vec<&str>, offset: usize) -> PyResult<(Option, Option)> { let ret = buildlog_consultant::common::match_lines(lines.as_slice(), offset) .map_err(|e| pyo3::exceptions::PyException::new_err(format!("Error: {}", e)))?; if let Some((m, p)) = ret { Ok((Some(Match(m)), p.map(|p| Some(Problem(p))).unwrap_or(None))) } else { Ok((None, None)) } } #[pymodule] fn _buildlog_consultant_rs(_py: Python, m: &PyModule) -> PyResult<()> { m.add_class::()?; m.add_class::()?; m.add_function(wrap_pyfunction!(match_lines, m)?)?; Ok(()) } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1694192519.7426732 buildlog-consultant-0.0.34/buildlog_consultant/0000755000175000017500000000000014476651610021232 5ustar00jelmerjelmer././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192479.0 buildlog-consultant-0.0.34/buildlog_consultant/__init__.py0000644000175000017500000001043114476651537023352 0ustar00jelmerjelmer#!/usr/bin/python # Copyright (C) 2019-2021 Jelmer Vernooij # encoding: utf-8 # # 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 from typing import Optional __version__ = (0, 0, 34) version_string = '.'.join(map(str, __version__)) problem_clses: dict[str, type["Problem"]] = {} class Problem: kind: str is_global: bool = False def __init_subclass__(cls, kind: str, is_global: bool = False, **kwargs): super().__init_subclass__(**kwargs) cls.kind = kind cls.is_global = is_global if kind in problem_clses: raise AssertionError('class {!r} already registered for kind {} (not {!r})'.format( problem_clses[kind], kind, cls)) problem_clses[kind] = cls def __init__(self, *args, **kwargs) -> None: for name, arg in list(zip( list(type(self).__annotations__.keys()), list(args))) + list(kwargs.items()): setattr(self, name, arg) def json(self): ret = {} for key in type(self).__annotations__.keys(): if key not in ('kind', 'is_global'): ret[key] = getattr(self, key) return ret @classmethod def from_json(cls, data): return cls(**data) def __eq__(self, other): if self.kind != getattr(other, "kind", None): return False return self.json() == other.json() def __repr__(self): return f"{type(self).__name__}({self.kind}, {self.json()})" class Match: origin: Optional[str] line: str lines: list[str] lineno: int linenos: list[int] offset: int offsets: list[int] class SingleLineMatch(Match): offset: int line: str def __init__(self, offset: int, line: str, *, origin: Optional[str] = None) -> None: self.offset = offset self.line = line self.origin = origin def __repr__(self) -> str: return f"{type(self).__name__}({self.offset!r}, {self.line!r})" def __eq__(self, other): return ( isinstance(self, type(other)) and self.offset == other.offset and self.line == other.line ) @property def lines(self) -> list[str]: # type: ignore return [self.line] @property def linenos(self) -> list[int]: # type: ignore return [self.lineno] @property def offsets(self) -> list[int]: # type: ignore return [self.offset] @property def lineno(self) -> int: # type: ignore return self.offset + 1 @classmethod def from_lines(cls, lines, offset, *, origin: Optional[str] = None): return cls(offset, lines[offset], origin=origin) class MultiLineMatch(Match): offsets: list[int] lines: list[str] def __init__(self, offsets: list[int], lines: list[str], *, origin: Optional[str] = None) -> None: self.offsets = offsets self.lines = lines self.origin = origin def __repr__(self) -> str: return f"{type(self).__name__}({self.offsets!r}, {self.lines!r})" def __eq__(self, other): return ( isinstance(self, type(other)) and self.offsets == other.offsets and self.lines == other.lines ) @property def line(self): return self.lines[-1] @property def offset(self): return self.offsets[-1] @property def lineno(self): return self.offset + 1 @property def linenos(self): return [o + 1 for o in self.offsets] @classmethod def from_lines(cls, lines, offsets, *, origin: Optional[str] = None): return cls(offsets, [lines[o] for o in offsets], origin=origin) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/buildlog_consultant/__main__.py0000644000175000017500000000221514476651536023333 0ustar00jelmerjelmer#!/usr/bin/python # Copyright (C) 2022 Jelmer Vernooij # encoding: utf-8 # # 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 import sys print("""\ The buildlog_consultant module itself is not executable. However, depending on the type of file you are trying to analyse, you may want to execute one of: * buildlog_consultant.autopkgtest (for Debian autopkgtest logs) * buildlog_consultant.common (for regular build logs) * buildlog_consultant.sbuild (for sbuild logs) """) sys.exit(1) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/buildlog_consultant/apt.py0000644000175000017500000003302414476651536022401 0ustar00jelmerjelmer#!/usr/bin/python # Copyright (C) 2019-2021 Jelmer Vernooij # encoding: utf-8 # # 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 import re from typing import Optional, TypedDict import yaml from debian.changelog import Version from debian.deb822 import PkgRelation from . import Match, MultiLineMatch, Problem, SingleLineMatch from .common import NoSpaceOnDevice class DpkgError(Problem, kind="dpkg-error"): error: str def __eq__(self, other): return isinstance(other, type(self)) and self.error == other.error def __str__(self) -> str: return "Dpkg Error: %s" % self.error def __repr__(self) -> str: return f"{type(self).__name__}({self.error!r})" class AptUpdateError(Problem, kind="apt-update-error"): """Apt update error.""" class AptFetchFailure(AptUpdateError, kind="apt-file-fetch-failure"): """Apt file fetch failed.""" url: str error: str def __eq__(self, other): if not isinstance(other, type(self)): return False if self.url != other.url: return False if self.error != other.error: return False return True def __str__(self) -> str: return "Apt file fetch error: %s" % self.error class AptMissingReleaseFile(AptUpdateError, kind="missing-release-file"): url: str def __eq__(self, other): if not isinstance(other, type(self)): return False if self.url != self.url: return False return True def __str__(self) -> str: return "Missing release file: %s" % self.url class AptPackageUnknown(Problem, kind="apt-package-unknown"): package: str def __eq__(self, other): return isinstance(other, type(self)) and self.package == other.package def __str__(self) -> str: return "Unknown package: %s" % self.package def __repr__(self) -> str: return f"{type(self).__name__}({self.package!r})" class AptBrokenPackages(Problem, kind="apt-broken-packages"): description: str broken: Optional[str] = None def __str__(self) -> str: if self.broken: return "Broken apt packages: %r" % self.broken return f"Broken apt packages: {self.description}" def __repr__(self) -> str: return f"{type(self).__name__}({self.description!r}, {self.broken!r})" def __eq__(self, other): return (isinstance(other, type(self)) and self.description == other.description and self.broken == other.broken) def find_apt_get_failure(lines: list[str]) -> tuple[Optional[Match], Optional[Problem]]: # noqa: C901 """Find the key failure line in apt-get-output. Returns: tuple with (match, error object) """ problem: Problem ret = (None, None) OFFSET = 50 for i in range(1, OFFSET): lineno = len(lines) - i if lineno < 0: break line = lines[lineno].strip("\n") if line.startswith("E: Failed to fetch "): m = re.match("^E: Failed to fetch ([^ ]+) (.*)", line) if m: if "No space left on device" in m.group(2): problem = NoSpaceOnDevice() else: problem = AptFetchFailure(m.group(1), m.group(2)) return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), problem return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), None if line == "E: Broken packages": error = AptBrokenPackages(lines[lineno - 1].strip()) return SingleLineMatch.from_lines(lines, lineno - 1, origin="direct match"), error if line == "E: Unable to correct problems, you have held broken packages.": offsets = [] broken = [] for j in range(lineno - 1, 0, -1): m = re.match(r'\s*Depends: (.*) but it is not (going to be installed|installable)', lines[j]) if m: offsets.append(j) broken.append(m.group(1)) continue m = re.match(r'\s*(.*) : Depends: (.*) but it is not (going to be installed|installable)', lines[j]) if m: offsets.append(j) broken.append(m.group(2)) continue break error = AptBrokenPackages(lines[lineno].strip(), broken) return MultiLineMatch.from_lines(lines, offsets + [lineno], origin="direct match"), error m = re.match("E: The repository '([^']+)' does not have a Release file.", line) if m: return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), AptMissingReleaseFile( m.group(1) ) m = re.match( "dpkg-deb: error: unable to write file '(.*)': " "No space left on device", line, ) if m: return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), NoSpaceOnDevice() m = re.match(r"E: You don't have enough free space in (.*)\.", line) if m: return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), NoSpaceOnDevice() if line.startswith("E: ") and ret[0] is None: ret = SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), None m = re.match(r"E: Unable to locate package (.*)", line) if m: return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), AptPackageUnknown( m.group(1) ) if line == "E: Write error - write (28: No space left on device)": return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), NoSpaceOnDevice() m = re.match(r"dpkg: error: (.*)", line) if m: if m.group(1).endswith(": No space left on device"): return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), NoSpaceOnDevice() return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), DpkgError(m.group(1)) m = re.match(r"dpkg: error processing package (.*) \((.*)\):", line) if m: return ( SingleLineMatch.from_lines(lines, lineno + 1, origin="direct regex"), DpkgError(f"processing package {m.group(1)} ({m.group(2)})"), ) for i, line in enumerate(lines): m = re.match( r" cannot copy extracted data for '(.*)' to " r"'(.*)': failed to write \(No space left on device\)", line, ) if m: return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), NoSpaceOnDevice() m = re.match(r" .*: No space left on device", line) if m: return SingleLineMatch.from_lines(lines, i, origin="direct regex"), NoSpaceOnDevice() return ret def find_apt_get_update_failure(sbuildlog): focus_section = "update chroot" lines = sbuildlog.get_section_lines(focus_section) match, error = find_apt_get_failure(lines) return focus_section, match, error def find_cudf_output(lines): for i in range(len(lines) - 1, 0, -1): if lines[i].startswith("output-version: "): break else: return None output = [] while lines[i].strip(): output.append(lines[i]) i += 1 return yaml.safe_load("\n".join(output)) class ParsedRelation(TypedDict): name: str archqual: Optional[str] version: Optional[tuple[str, str]] arch: Optional[list['PkgRelation.ArchRestriction']] restrictions: Optional[list[list['PkgRelation.BuildRestriction']]] class UnsatisfiedAptDependencies(Problem, kind="unsatisfied-apt-dependencies"): relations: list[list[list[ParsedRelation]]] def __str__(self) -> str: return "Unsatisfied APT dependencies: %s" % ( PkgRelation.str(self.relations)) # type: ignore @classmethod def from_str(cls, text): return cls(PkgRelation.parse_relations(text)) def __eq__(self, other): return isinstance(other, type(self)) and other.relations == self.relations def json(self): return PkgRelation.str(self.relations) # type: ignore @classmethod def from_json(cls, data): if isinstance(data, str): return cls.from_str(data) relations = [] for relation in data['relations']: sub = [] for entry in relation: pkg = { 'name': entry['name'], 'archqual': entry.get('archqual'), 'arch': entry.get('arch'), 'restrictions': entry.get('restrictions'), 'version': (entry['version'][0], Version(entry['version'][1])) if entry['version'] else None, } sub.append(pkg) relations.append(sub) return cls(relations=relations) def __repr__(self) -> str: return "{}.from_str({!r})".format( type(self).__name__, PkgRelation.str(self.relations), # type: ignore ) class UnsatisfiedAptConflicts(Problem, kind="unsatisfied-apt-conflicts"): relations: list[list[list[ParsedRelation]]] def __str__(self) -> str: return "Unsatisfied APT conflicts: %s" % PkgRelation.str( self.relations) # type: ignore def error_from_dose3_report(report): def fixup_relation(rel): for o in rel: for d in o: if d['version']: try: newoperator = {'<': '<<', '>': '>>'}[d['version'][0]] except KeyError: pass else: d['version'] = (newoperator, d['version'][1]) packages = [entry["package"] for entry in report] assert packages == ["sbuild-build-depends-main-dummy"] if report[0]["status"] != "broken": return None missing = [] conflict = [] for reason in report[0]["reasons"]: if "missing" in reason: relation = PkgRelation.parse_relations( reason["missing"]["pkg"]["unsat-dependency"] ) fixup_relation(relation) missing.extend(relation) if "conflict" in reason: relation = PkgRelation.parse_relations( reason["conflict"]["pkg1"]["unsat-conflict"] ) fixup_relation(relation) conflict.extend(relation) if missing: return UnsatisfiedAptDependencies(missing) if conflict: return UnsatisfiedAptConflicts(conflict) def find_install_deps_failure_description(sbuildlog) -> tuple[Optional[str], Optional[Match], Optional[Problem]]: error = None DOSE3_SECTION = "install dose3 build dependencies (aspcud-based resolver)" dose3_lines = sbuildlog.get_section_lines(DOSE3_SECTION) if dose3_lines: dose3_output = find_cudf_output(dose3_lines) if dose3_output: error = error_from_dose3_report(dose3_output["report"]) return DOSE3_SECTION, None, error SECTION = "install package build dependencies" build_dependencies_lines = sbuildlog.get_section_lines(SECTION) if build_dependencies_lines: dose3_output = find_cudf_output(build_dependencies_lines) if dose3_output: error = error_from_dose3_report(dose3_output["report"]) return SECTION, None, error match, error = find_apt_get_failure(build_dependencies_lines) return SECTION, match, error for section in sbuildlog.sections: if section.title is None: continue if re.match("install (.*) build dependencies.*", section.title.lower()): match, error = find_apt_get_failure(section.lines) if match is not None: return section.title, match, error return section.title, None, error if __name__ == '__main__': import argparse import logging parser = argparse.ArgumentParser() parser.add_argument("--debug", action="store_true", help="Display debug output.") parser.add_argument( "--context", "-c", type=int, default=5, help="Number of context lines to print." ) parser.add_argument("path", type=str) args = parser.parse_args() if args.debug: loglevel = logging.DEBUG else: loglevel = logging.INFO logging.basicConfig(level=loglevel, format="%(message)s") with open(args.path) as f: lines = list(f.readlines()) match, error = find_apt_get_failure(lines) if error: logging.info("Error: %s", error) if match: logging.info("Failed line: %d:", match.lineno) for i in range( max(0, match.offset - args.context), min(len(lines), match.offset + args.context + 1), ): logging.info( " %s %s", ">" if match.offset == i else " ", lines[i].rstrip("\n"), ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/buildlog_consultant/autopkgtest.py0000644000175000017500000005533214476651536024175 0ustar00jelmerjelmer#!/usr/bin/python # Copyright (C) 2019-2021 Jelmer Vernooij # encoding: utf-8 # # 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 import logging import re from typing import Any, Optional, Union from . import Match, Problem, SingleLineMatch, version_string from .apt import AptFetchFailure, find_apt_get_failure from .common import ChrootNotFound, find_build_failure_description logger = logging.getLogger(__name__) class AutopkgtestDepsUnsatisfiable(Problem, kind="badpkg"): args: list[str] @classmethod def from_blame_line(cls, line): args = [] entries = line[len("blame: ") :].rstrip("\n").split(" ") for entry in entries: try: (kind, arg) = entry.split(":", 1) except ValueError: kind = None arg = entry args.append((kind, arg)) if kind not in ("deb", "arg", "dsc", None): logger.warn("unknown entry %s on badpkg line", entry) return cls(args) class AutopkgtestTimedOut(Problem, kind="timed-out"): def __str__(self) -> str: return "Timed out" class XDGRunTimeNotSet(Problem, kind="xdg-runtime-dir-not-set"): def __str__(self) -> str: return "XDG_RUNTIME_DIR is not set" class AutopkgtestTestbedFailure(Problem, kind="testbed-failure"): reason: str def __eq__(self, other): return type(self) is type(other) and self.reason == other.reason def __repr__(self) -> str: return f"{type(self).__name__}({self.reason!r})" def __str__(self) -> str: return self.reason class AutopkgtestDepChrootDisappeared(Problem, kind="testbed-chroot-disappeared"): def __str__(self) -> str: return "chroot disappeared" def __repr__(self) -> str: return "%s()" % (type(self).__name__) def __eq__(self, other): return isinstance(self, type(other)) class AutopkgtestErroneousPackage(Problem, kind="erroneous-package"): reason: str def __eq__(self, other): return type(self) is type(other) and self.reason == other.reason def __repr__(self) -> str: return f"{type(self).__name__}({self.reason!r})" def __str__(self) -> str: return self.reason class AutopkgtestStderrFailure(Problem, kind="stderr-output"): stderr_line: str def __eq__(self, other): return isinstance(self, type(other)) and self.stderr_line == other.stderr_line def __repr__(self) -> str: return f"{type(self).__name__}({self.stderr_line!r})" def __str__(self) -> str: return "output on stderr: %s" % self.stderr_line def parse_autopgktest_line(line: str) -> Union[str, tuple[str, Union[tuple[str, ...]]]]: m = re.match(r"autopkgtest \[([0-9:]+)\]: (.*)", line) if not m: return line timestamp = m.group(1) message = m.group(2) if message.startswith("@@@@@@@@@@@@@@@@@@@@ source "): return (timestamp, ("source",)) elif message.startswith("@@@@@@@@@@@@@@@@@@@@ summary"): return (timestamp, ("summary",)) elif message.startswith("test "): (testname, test_status) = message[len("test ") :].rstrip("\n").split(": ", 1) if test_status == "[-----------------------": return ( timestamp, ( "test", testname, "begin output", ), ) elif test_status == "-----------------------]": return ( timestamp, ( "test", testname, "end output", ), ) elif test_status == (" - - - - - - - - - - results - - - - - - - - - -"): return ( timestamp, ( "test", testname, "results", ), ) elif test_status == (" - - - - - - - - - - stderr - - - - - - - - - -"): return ( timestamp, ( "test", testname, "stderr", ), ) elif test_status == "preparing testbed": return (timestamp, ("test", testname, "prepare testbed")) else: return (timestamp, ("test", testname, test_status)) elif message.startswith("ERROR:"): return (timestamp, ("error", message[len("ERROR: ") :])) else: return (timestamp, (message,)) def parse_autopkgtest_summary(lines): i = 0 while i < len(lines): line = lines[i] m = re.match("([^ ]+)(?:[ ]+)PASS", line) if m: yield i, m.group(1), "PASS", None, [] i += 1 continue m = re.match("([^ ]+)(?:[ ]+)(FAIL|PASS|SKIP|FLAKY) (.+)", line) if not m: i += 1 continue testname = m.group(1) result = m.group(2) reason = m.group(3) offset = i extra = [] if reason == "badpkg": while i + 1 < len(lines) and ( lines[i + 1].startswith("badpkg:") or lines[i + 1].startswith("blame:") ): extra.append(lines[i + 1]) i += 1 yield offset, testname, result, reason, extra i += 1 class AutopkgtestTestbedSetupFailure(Problem, kind="testbed-setup-failure"): command: str exit_status: int error: str def __str__(self) -> str: return "Error setting up testbed %r failed (%d): %s" % ( self.command, self.exit_status, self.error, ) def find_autopkgtest_failure_description( # noqa: C901 lines: list[str], ) -> tuple[ Optional[Match], Optional[str], Optional["Problem"], Optional[str] ]: """Find the autopkgtest failure in output. Returns: tuple with (line offset, testname, error, description) """ error: Optional["Problem"] test_output: dict[tuple[str, ...], list[str]] = {} test_output_offset: dict[tuple[str, ...], int] = {} current_field: Optional[tuple[str, ...]] = None i = -1 while i < len(lines) - 1: i += 1 line = lines[i] parsed = parse_autopgktest_line(line) if isinstance(parsed, tuple): (timestamp, content) = parsed if content[0] == "test": if content[2] == "end output": current_field = None continue elif content[2] == "begin output": current_field = (content[1], "output") else: current_field = (content[1], content[2]) if current_field in test_output: logger.warn("duplicate output fields for %r", current_field) test_output[current_field] = [] test_output_offset[current_field] = i + 1 elif content == ("summary",): current_field = ("summary",) test_output[current_field] = [] test_output_offset[current_field] = i + 1 elif content[0] == "error": if content[1].startswith('"') and content[1].count('"') == 1: sublines = [content[1]] while i < len(lines): i += 1 sublines += lines[i] if lines[i].count('"') == 1: break content = (content[0], "".join(sublines)) last_test: Optional[str] if current_field is not None: last_test = current_field[0] else: last_test = None msg = content[1] m = re.fullmatch('"(.*)" failed with stderr "(.*)("?)', msg) if m: stderr = m.group(2) m = re.fullmatch( "W: (.*): " "Failed to stat file: No such file or directory", stderr, ) if m: error = AutopkgtestDepChrootDisappeared() return ( SingleLineMatch.from_lines(lines, i, origin="direct regex"), last_test, error, stderr, ) m = re.fullmatch(r"testbed failure: (.*)", msg) if m: testbed_failure_reason = m.group(1) if ( current_field is not None and testbed_failure_reason == "testbed auxverb failed with exit code 255" ): field = (current_field[0], "output") (match, error) = find_build_failure_description( test_output[field] ) if error is not None: assert match is not None return ( SingleLineMatch.from_lines( lines, test_output_offset[field] + match.offset, origin="direct regex" ), last_test, error, match.line, ) if ( testbed_failure_reason == "sent `auxverb_debug_fail', got `copy-failed', " "expected `ok...'" ): (match, error) = find_build_failure_description(lines) if error is not None: return (match, last_test, error, match.line if match else None) if ( testbed_failure_reason == "cannot send to testbed: [Errno 32] Broken pipe" ): match, error = find_testbed_setup_failure(lines) if error and match: return (match, last_test, error, match.line) if ( testbed_failure_reason == "apt repeatedly failed to download packages" ): match, error = find_apt_get_failure(lines) if error and match: return (match, last_test, error, match.line) return ( SingleLineMatch.from_lines(lines, i, origin="direct regex"), last_test, AptFetchFailure(None, testbed_failure_reason), None, ) return ( SingleLineMatch.from_lines(lines, i, origin="direct regex"), last_test, AutopkgtestTestbedFailure(testbed_failure_reason), None, ) m = re.fullmatch(r"erroneous package: (.*)", msg) if m: (match, error) = find_build_failure_description(lines[:i]) if error and match: return (match, last_test, error, match.line) return ( SingleLineMatch.from_lines(lines, i, origin="direct regex"), last_test, AutopkgtestErroneousPackage(m.group(1)), None, ) if msg == 'unexpected error:': (match, error) = find_build_failure_description(lines[i + 1:]) if error and match: return (match, last_test, error, match.line) if current_field is not None: match, error = find_apt_get_failure(test_output[current_field]) if ( error is not None and match is not None and current_field in test_output_offset ): return ( SingleLineMatch.from_lines( lines, test_output_offset[current_field] + match.offset, origin="direct regex" ), last_test, error, match.line, ) if msg == "autopkgtest": if lines[i + 1].rstrip() == ": error cleaning up:": return ( SingleLineMatch.from_lines( lines, test_output_offset[current_field], origin="direct regex" # type: ignore ), last_test, AutopkgtestTimedOut(), lines[i - 1].rstrip(), ) return (SingleLineMatch.from_lines(lines, i, origin="direct regex"), last_test, None, msg) else: if current_field: test_output[current_field].append(line) try: summary_lines = test_output[("summary",)] summary_offset = test_output_offset[("summary",)] except KeyError: while lines and not lines[-1].strip(): lines.pop(-1) if not lines: return (None, None, None, None) else: return ( SingleLineMatch.from_lines(lines, len(lines) - 1, origin="direct regex"), lines[-1], None, None, ) else: for (lineno, testname, result, reason, extra) in parse_autopkgtest_summary( summary_lines ): if result in ("PASS", "SKIP"): continue assert result in ("FAIL", "FLAKY") if reason == "timed out": error = AutopkgtestTimedOut() return ( SingleLineMatch.from_lines(lines, summary_offset + lineno, origin="direct regex"), testname, error, reason, ) elif reason.startswith("stderr: "): output = reason[len("stderr: ") :] stderr_lines = test_output.get((testname, "stderr"), []) stderr_offset = test_output_offset.get((testname, "stderr")) description: Optional[str] if stderr_lines: (match, error) = find_build_failure_description(stderr_lines) if match is not None and stderr_offset is not None: offset = match.offset + stderr_offset description = match.line elif len(stderr_lines) == 1 and re.match( r"QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to \'(.*)\'", stderr_lines[0], ): error = XDGRunTimeNotSet() description = stderr_lines[0] offset = stderr_offset # type: ignore else: if stderr_offset is not None: offset = stderr_offset description = None else: (match, error) = find_build_failure_description([output]) if match is not None: offset = summary_offset + lineno + match.offset description = match.line else: offset = None description = None if offset is None: offset = summary_offset + lineno if error is None: error = AutopkgtestStderrFailure(output) if description is None: description = ( "Test {} failed due to " "unauthorized stderr output: {}".format(testname, error.stderr_line) ) return ( SingleLineMatch.from_lines(lines, offset, origin="direct regex"), testname, error, description, ) elif reason == "badpkg": output_lines = test_output.get((testname, "prepare testbed"), []) output_offset = test_output_offset.get((testname, "prepare testbed")) if output_lines and output_offset: match, error = find_apt_get_failure(output_lines) if error and match: return ( SingleLineMatch.from_lines( lines, match.offset + output_offset, origin="direct regex" ), testname, error, None, ) badpkg = None blame = None for extra_offset, line in enumerate(extra, 1): if line.startswith("badpkg: "): badpkg = line[len("badpkg: ") :] if line.startswith("blame: "): blame = line blame_offset = extra_offset if badpkg is not None: description = "Test {} failed: {}".format(testname, badpkg.rstrip("\n")) else: description = "Test %s failed" % testname error = AutopkgtestDepsUnsatisfiable.from_blame_line(blame) return ( SingleLineMatch.from_lines( lines, summary_offset + lineno + blame_offset, origin="direct regex" ), testname, error, description, ) else: output_lines = test_output.get((testname, "output"), []) output_offset = test_output_offset.get((testname, "output")) (match, error) = find_build_failure_description(output_lines) if match is None or output_offset is None: offset = summary_offset + lineno else: offset = match.offset + output_offset if match is None: description = f"Test {testname} failed: {reason}" else: description = match.line return SingleLineMatch.from_lines(lines, offset, origin="direct regex"), testname, error, description # type: ignore return None, None, None, None def find_testbed_setup_failure(lines): for i in range(len(lines) - 1, 0, -1): line = lines[i] m = re.fullmatch( r"\[(.*)\] failed \(exit status ([0-9]+), stderr \'(.*)\'\)\n", line ) if m: command = m.group(1) status_code = int(m.group(2)) stderr = m.group(3) m = re.fullmatch(r"E: (.*): Chroot not found\\n", stderr) if m: return ( SingleLineMatch.from_lines(lines, i, origin="direct regex"), ChrootNotFound(m.group(1)), ) return ( SingleLineMatch.from_lines(lines, i, origin="direct regex"), AutopkgtestTestbedSetupFailure(command, status_code, stderr), ) m = re.fullmatch( r"\: failure: \[\'(.*)\'\] " r"unexpectedly produced stderr output `(.*)", line, ) if m: command = m.group(1) stderr_output = m.group(2) n = re.match( r"W: /var/lib/schroot/session/(.*): " "Failed to stat file: No such file or directory", stderr_output, ) if n: return ( SingleLineMatch.from_lines(lines, i, origin="direct regex"), AutopkgtestDepChrootDisappeared(), ) return ( SingleLineMatch.from_lines(lines, i, origin="direct regex"), AutopkgtestTestbedSetupFailure(command, 1, stderr_output), ) return None, None def main(argv=None): import argparse import json parser = argparse.ArgumentParser("analyse-autopkgtest-log") parser.add_argument("--debug", action="store_true", help="Display debug output.") parser.add_argument("--json", action="store_true", help="Output JSON.") parser.add_argument( "--context", "-c", type=int, default=5, help="Number of context lines to print." ) parser.add_argument( "--version", action="version", version="%(prog)s " + version_string ) parser.add_argument("path", type=str) args = parser.parse_args() if args.debug: loglevel = logging.DEBUG elif args.json: loglevel = logging.WARNING else: loglevel = logging.INFO logging.basicConfig(level=loglevel, format="%(message)s") with open(args.path) as f: lines = list(f) (match, testname, error, description) = find_autopkgtest_failure_description(lines) if args.json: ret: dict[str, Any] ret = {'testname': testname, 'error': error, 'description': description} if match: ret['offset'] = match.offset json.dump(ret, sys.stdout, indent=4) if testname: logging.info("Test name: %s", testname) if error: logging.info("Error: %s", error) if match: logging.info("Failed line: %d:", match.lineno) for i in range( max(0, match.offset - args.context), min(len(lines), match.offset + args.context + 1), ): logging.info( " %s %s", ">" if match.offset == i else " ", lines[i].rstrip("\n"), ) if __name__ == "__main__": import sys sys.exit(main(sys.argv)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/buildlog_consultant/chatgpt.py0000644000175000017500000000406414476651536023251 0ustar00jelmerjelmer#!/usr/bin/python # Copyright (C) 2019-2021 Jelmer Vernooij # encoding: utf-8 # # 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 import logging import openai from . import SingleLineMatch def chatgpt_analyze(lines): truncated = ''.join(lines)[-4000:] openai_logger = logging.getLogger("openai") openai_logger.setLevel(logging.WARNING) prompt = ( "Which line in the log file below is the clearest explanation of a problem:\n\n" + truncated) response = openai.Completion.create( model="text-davinci-003", temperature=0, max_tokens=256, prompt=prompt) text = response["choices"][0]["text"].lstrip('\n').split('\n')[0] for i, line in enumerate(lines): if line.startswith(text): return SingleLineMatch.from_lines(lines, i, origin="chatgpt") logging.debug('Unable to find chatgpt answer in lines: %r', text) return None if __name__ == '__main__': import argparse parser = argparse.ArgumentParser() parser.add_argument('--debug', action='store_true') parser.add_argument('path', type=str) args = parser.parse_args() logging.basicConfig( format='%(message)s', level=(logging.INFO if not args.debug else logging.DEBUG)) with open(args.path, encoding='utf-8') as f: match = chatgpt_analyze(f.readlines()) if match: logging.info('match: %s', match) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/buildlog_consultant/common.py0000644000175000017500000034022214476651536023106 0ustar00jelmerjelmer#!/usr/bin/python # Copyright (C) 2019-2021 Jelmer Vernooij # encoding: utf-8 # # 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 import logging import os import posixpath import re import textwrap from typing import Optional from . import ( Match, MultiLineMatch, Problem, SingleLineMatch, version_string, ) from . import _buildlog_consultant_rs # type: ignore logger = logging.getLogger(__name__) class MissingPythonModule(Problem, kind="missing-python-module"): module: str python_version: Optional[str] = None minimum_version: Optional[str] = None def __str__(self) -> str: if self.python_version: ret = "Missing python %s module: " % self.python_version else: ret = "Missing python module: " ret += self.module if self.minimum_version: return ret + " (>= %s)" % self.minimum_version else: return ret def __repr__(self) -> str: return "{}({!r}, python_version={!r}, minimum_version={!r})".format( type(self).__name__, self.module, self.python_version, self.minimum_version, ) class SetuptoolScmVersionIssue(Problem, kind="setuptools-scm-version-issue"): def __str__(self) -> str: return "setuptools-scm was unable to find version" class MissingOCamlPackage(Problem, kind='missing-ocaml-package'): package: str def __str__(self) -> str: return "Missing OCaml package: %s" % self.package class MissingPythonDistribution(Problem, kind="missing-python-distribution"): distribution: str python_version: Optional[int] = None minimum_version: Optional[str] = None def __str__(self) -> str: if self.python_version: ret = "Missing python %d distribution: " % self.python_version else: ret = "Missing python distribution: " ret += self.distribution if self.minimum_version: return ret + " (>= %s)" % self.minimum_version else: return ret @classmethod def from_requirement_str(cls, text, python_version=None): from requirements.requirement import Requirement req = Requirement.parse(text) if len(req.specs) == 1 and req.specs[0][0] == ">=": return cls(req.name, python_version, req.specs[0][1]) return cls(req.name, python_version) def __repr__(self) -> str: return "{}({!r}, python_version={!r}, minimum_version={!r})".format( type(self).__name__, self.distribution, self.python_version, self.minimum_version, ) class VcsControlDirectoryNeeded(Problem, kind='vcs-control-directory-needed'): vcs: list[str] def __str__(self) -> str: return "Version control directory needed" class PatchApplicationFailed(Problem, kind="patch-application-failed"): patchname: str def __str__(self) -> str: return "Patch application failed: %s" % self.patchname class MissingVagueDependency(Problem, kind="missing-vague-dependency"): name: str url: Optional[str] = None minimum_version: Optional[str] = None current_version: Optional[str] = None def __repr__(self) -> str: return "{}({!r}, url={!r}, minimum_version={!r}, current_version={!r})".format( type(self).__name__, self.name, self.url, self.minimum_version, self.current_version) def __str__(self) -> str: return "Missing dependency: %s" % self.name class MissingQt(Problem, kind="missing-qt"): minimum_version: Optional[str] = None def __str__(self) -> str: if self.minimum_version: return "Missing QT installation (at least %s)" % ( self.minimum_version) return "Missing QT installation" class MissingQtModules(Problem, kind="missing-qt-modules"): modules: list[str] def __str__(self) -> str: return "Missing QT modules: %r" % self.modules class MissingX11(Problem, kind="missing-x11"): def __str__(self) -> str: return "Missing X11 headers" class MissingGitIdentity(Problem, kind="missing-git-identity"): def __str__(self) -> str: return "Missing Git Identity" class MissingFile(Problem, kind="missing-file"): path: str def __str__(self) -> str: return "Missing file: %s" % self.path class MissingCommandOrBuildFile(Problem, kind="missing-command-or-build-file"): filename: str @property def command(self): return self.filename def __str__(self) -> str: return "Missing command or build file: %s" % self.filename class MissingBuildFile(Problem, kind="missing-build-file"): filename: str def __str__(self) -> str: return "Missing build file: %s" % self.filename def file_not_found(m): if m.group(1).startswith("/") and not m.group(1).startswith("/<>"): return MissingFile(m.group(1)) elif m.group(1).startswith("/<>/"): return MissingBuildFile(m.group(1)[len("/<>/"):]) if m.group(1) == '.git/HEAD': return VcsControlDirectoryNeeded(['git']) if m.group(1) == 'CVS/Root': return VcsControlDirectoryNeeded(['cvs']) if '/' not in m.group(1): # Maybe a missing command? return MissingBuildFile(m.group(1)) return None def file_not_found_maybe_executable(m): if m.group(1).startswith("/") and not m.group(1).startswith("/<>"): return MissingFile(m.group(1)) if '/' not in m.group(1): # Maybe a missing command? return MissingCommandOrBuildFile(m.group(1)) return None def webpack_file_missing(m): path = posixpath.join(m.group(2), m.group(1)) if path.startswith("/") and not path.startswith("/<>"): return MissingFile(path) return None class MissingJDKFile(Problem, kind="missing-jdk-file"): jdk_path: str filename: str def __str__(self) -> str: return f"Missing JDK file {self.filename} (JDK Path: {self.jdk_path})" class MissingJDK(Problem, kind="missing-jdk"): jdk_path: str def __str__(self) -> str: return "Missing JDK (JDK Path: %s)" % (self.jdk_path) class MissingJRE(Problem, kind="missing-jre"): def __str__(self) -> str: return "Missing JRE" def interpreter_missing(m): if m.group(1).startswith("/"): if m.group(1).startswith("/<>"): return None return MissingFile(m.group(1)) if "/" in m.group(1): return None return MissingCommand(m.group(1)) class ChrootNotFound(Problem, kind="chroot-not-found"): chroot: str def __str__(self) -> str: return "Chroot not found: %s" % self.chroot class MissingSprocketsFile(Problem, kind="missing-sprockets-file"): name: str content_type: str def __str__(self) -> str: return f"Missing sprockets file: {self.name} (type: {self.content_type})" class MissingGoPackage(Problem, kind="missing-go-package"): package: str def __str__(self) -> str: return "Missing Go package: %s" % self.package class MissingCHeader(Problem, kind="missing-c-header"): header: str def __str__(self) -> str: return "Missing C Header: %s" % self.header class MissingNodeModule(Problem, kind="missing-node-module"): module: str def __str__(self) -> str: return "Missing Node Module: %s" % self.module class MissingNodePackage(Problem, kind="missing-node-package"): package: str def __str__(self) -> str: return "Missing Node Package: %s" % self.package def node_module_missing(m): if m.group(1).startswith("/<>/"): return None if m.group(1).startswith("./"): return None return MissingNodeModule(m.group(1)) class MissingCommand(Problem, kind="command-missing"): command: str def __str__(self) -> str: return "Missing command: %s" % self.command def __repr__(self) -> str: return f"{type(self).__name__}({self.command!r})" class NotExecutableFile(Problem, kind="command-not-executable"): path: str def __str__(self) -> str: return "Command not executable: %s" % self.path class MissingSecretGpgKey(Problem, kind="no-secret-gpg-key"): def __str__(self) -> str: return "No secret GPG key is present" class MissingVcVersionerVersion(Problem, kind="no-vcversioner-version"): def __str__(self) -> str: return "vcversion could not find a git directory or version.txt file" class MissingConfigure(Problem, kind="missing-configure"): def __str__(self) -> str: return "Missing configure script" def command_missing(m): command = m.group(1) if "PKGBUILDDIR" in command: return None if command == "./configure": return MissingConfigure() if command.startswith("./") or command.startswith("../"): return None if command == "debian/rules": return None return MissingCommand(command) class MissingJavaScriptRuntime(Problem, kind="javascript-runtime-missing"): def __str__(self) -> str: return "Missing JavaScript Runtime" class MissingPHPExtension(Problem, kind="missing-php-extension"): extension: str def __str__(self) -> str: return "Missing PHP Extension: %s" % self.extension class MinimumAutoconfTooOld(Problem, kind="minimum-autoconf-too-old"): minimum_version: str def __str__(self) -> str: return "configure.{ac,in} should require newer autoconf %s" % self.minimum_version class MissingPkgConfig(Problem, kind="missing-pkg-config-package"): module: str minimum_version: Optional[str] = None def __str__(self) -> str: if self.minimum_version: return "Missing pkg-config file: {} (>= {})".format( self.module, self.minimum_version, ) else: return "Missing pkg-config file: %s" % self.module def __repr__(self) -> str: return "{}({!r}, minimum_version={!r})".format( type(self).__name__, self.module, self.minimum_version, ) class MissingGoRuntime(Problem, kind="missing-go-runtime"): def __str__(self) -> str: return "go runtime is missing" def pkg_config_missing(m): expr = m.group(1).strip().split("\t")[0] if ">=" in expr: pkg, minimum = expr.split(">=", 1) return MissingPkgConfig(pkg.strip(), minimum.strip()) if " " not in expr: return MissingPkgConfig(expr) # Hmm return None class MissingCMakeComponents(Problem, kind="missing-cmake-components"): name: str components: list[str] def __str__(self) -> str: return f"Missing {self.name} components: {self.components!r}" class CMakeFilesMissing(Problem, kind="missing-cmake-files"): filenames: list[str] version: Optional[str] = None def __str__(self) -> str: if self.version: return f"Missing CMake package configuration files (version {self.version}): {self.filenames!r}" return f"Missing CMake package configuration files: {self.filenames!r}" class MissingCMakeConfig(Problem, kind="missing-cmake-config"): name: str version: str def __str__(self) -> str: if self.version: return f"Missing CMake package configuration for {self.name} (version {self.version})" return f"Missing CMake package configuration for {self.name}" class DhWithOrderIncorrect(Problem, kind="debhelper-argument-order"): def __str__(self) -> str: return "dh argument order is incorrect" class UnsupportedDebhelperCompatLevel(Problem, kind="unsupported-debhelper-compat-level"): oldest_supported: int requested: int def __str__(self) -> str: return "Request debhelper compat level %d lower than supported %d" % ( self.requested, self.oldest_supported) class NoSpaceOnDevice(Problem, kind="no-space-on-device", is_global=True): def __str__(self) -> str: return "No space on device" class MissingPerlPredeclared(Problem, kind="missing-perl-predeclared"): name: str def __str__(self) -> str: return "missing predeclared function: %s" % self.name class MissingPerlDistributionFile(Problem, kind="missing-perl-distribution-file"): filename: str def __str__(self) -> str: return "Missing perl distribution file: %s" % self.filename class InvalidCurrentUser(Problem, kind="invalid-current-user"): user: str def __str__(self) -> str: return "Can not run as %s" % self.user class MissingPerlModule(Problem, kind="missing-perl-module"): filename: Optional[str] module: str inc: Optional[list[str]] = None minimum_version: Optional[str] = None def __str__(self) -> str: if self.filename: return "Missing Perl module: {} (filename: {!r})".format( self.module, self.filename, ) else: return "Missing Perl Module: %s" % self.module class MissingPerlFile(Problem, kind="missing-perl-file"): filename: str inc: Optional[list[str]] = None def __str__(self) -> str: return f"Missing Perl file: {self.filename} (inc: {self.inc!r})" class MissingMavenArtifacts(Problem, kind="missing-maven-artifacts"): artifacts: list[tuple[str, str, str, str]] def __str__(self) -> str: return "Missing maven artifacts: %r" % self.artifacts def __repr__(self) -> str: return f"{type(self).__name__}({self.artifacts!r})" class DhUntilUnsupported(Problem, kind="dh-until-unsupported"): def __str__(self) -> str: return "dh --until is no longer supported" class DhAddonLoadFailure(Problem, kind="dh-addon-load-failure"): name: str path: str def __str__(self) -> str: return "dh addon loading failed: %s" % self.name class DhMissingUninstalled(Problem, kind="dh-missing-uninstalled"): missing_file: str def __str__(self) -> str: return "File built by Debian not installed: %r" % self.missing_file class DhLinkDestinationIsDirectory(Problem, kind="dh-link-destination-is-directory"): path: str def __str__(self) -> str: return "Link destination %s is directory" % self.path def maven_missing_artifact(m): artifacts = m.group(1).split(",") return MissingMavenArtifacts([a.strip() for a in artifacts]) class MissingXmlEntity(Problem, kind="missing-xml-entity"): url: str def __str__(self) -> str: return "Missing XML entity: %s" % self.url class CcacheError(Problem, kind="ccache-error"): error: str def __str__(self) -> str: return "ccache error: %s" % self.error class MissingDebianBuildDep(Problem, kind='missing-debian-build-dep'): dep: str def __str__(self) -> str: return f"Missing Debian Build-Depends: {self.dep}" class MissingGoSumEntry(Problem, kind="missing-go.sum-entry"): package: str version: str def __str__(self) -> str: return "Missing go.sum entry: {}@{}".format( self.package, self.version) class MissingLibrary(Problem, kind="missing-library"): library: str def __str__(self) -> str: return "missing library: %s" % self.library class MissingStaticLibrary(Problem, kind="missing-static-library"): library: str filename: str def __str__(self) -> str: return "missing static library: %s" % self.library class MissingRubyGem(Problem, kind="missing-ruby-gem"): gem: str version: Optional[str] = None def __str__(self) -> str: if self.version: return f"missing ruby gem: {self.gem} (>= {self.version})" else: return "missing ruby gem: %s" % self.gem def ruby_missing_gem(m): minimum_version = None for grp in m.group(2).split(","): (cond, val) = grp.strip().split(" ", 1) if cond == ">=": minimum_version = val break if cond == "~>": minimum_version = val return MissingRubyGem(m.group(1), minimum_version) class MissingRubyFile(Problem, kind="missing-ruby-file"): filename: str def __str__(self) -> str: return f"Missing ruby file: {self.filename}" class MissingPhpClass(Problem, kind="missing-php-class"): php_class: str def __str__(self) -> str: return "missing PHP class: %s" % self.php_class class MissingJavaClass(Problem, kind="missing-java-class"): classname: str def __str__(self) -> str: return "missing java class: %s" % self.classname class MissingRPackage(Problem, kind="missing-r-package"): package: str minimum_version: Optional[str] = None def __str__(self) -> str: if self.minimum_version: return "missing R package: {} (>= {})".format( self.package, self.minimum_version, ) else: return "missing R package: %s" % self.package def r_missing_package(m): fragment = m.group(1) deps = [dep.strip("‘’' ") for dep in fragment.split(",")] return MissingRPackage(deps[0]) class DebhelperPatternNotFound(Problem, kind="debhelper-pattern-not-found"): pattern: str tool: str directories: list[str] def __str__(self) -> str: return "debhelper ({}) expansion failed for {!r} (directories: {!r})".format( self.tool, self.pattern, self.directories, ) class GnomeCommonMissing(Problem, kind="missing-gnome-common"): def __str__(self) -> str: return "gnome-common is not installed" class MissingXfceDependency(Problem, kind="missing-xfce-dependency"): package: str def __str__(self) -> str: return "Missing XFCE build dependency: %s" % (self.package) class MissingAutomakeInput(Problem, kind="missing-automake-input"): path: str def __str__(self) -> str: return "automake input file %s missing" % self.path class MissingAutoconfMacro(Problem, kind="missing-autoconf-macro"): macro: str need_rebuild: bool = False def __str__(self) -> str: return "autoconf macro %s missing" % self.macro class MissingGnomeCommonDependency(Problem, kind="missing-gnome-common-dependency"): package: str minimum_version: Optional[str] = None def __str__(self) -> str: return "Missing gnome-common dependency: {}: (>= {})".format( self.package, self.minimum_version, ) class MissingConfigStatusInput(Problem, kind="missing-config.status-input"): path: str def __str__(self) -> str: return "missing config.status input %s" % self.path class MissingJVM(Problem, kind="missing-jvm"): def __str__(self) -> str: return "Missing JVM" class MissingPerlManifest(Problem, kind="missing-perl-manifest"): def __str__(self) -> str: return "missing Perl MANIFEST" class UpstartFilePresent(Problem, kind="upstart-file-present"): filename: str def __str__(self) -> str: return "Upstart file present: %s" % self.filename class NeedPgBuildExtUpdateControl(Problem, kind="need-pg-buildext-updatecontrol"): generated_path: str template_path: str def __str__(self) -> str: return "Need to run 'pg_buildext updatecontrol' to update %s" % ( self.generated_path ) class MissingValaPackage(Problem, kind="missing-vala-package"): package: str def __str__(self) -> str: return "Missing Vala package: %s" % self.package MAVEN_ERROR_PREFIX = "(?:\\[ERROR\\]|\\[\x1b\\[1;31mERROR\x1b\\[m\\]) " class DirectoryNonExistant(Problem, kind="local-directory-not-existing"): path: str def __str__(self) -> str: return "Directory does not exist: %s" % self.path class ImageMagickDelegateMissing(Problem, kind="imagemagick-delegate-missing"): delegate: str def __str__(self) -> str: return "Imagemagick missing delegate: %s" % self.delegate class DebianVersionRejected(Problem, kind="debian-version-rejected"): version: str def __str__(self) -> str: return "Debian Version Rejected; %s" % self.version class ValaCompilerCannotCompile(Problem, kind="valac-cannot-compile"): def __str__(self) -> str: return "valac can not compile" class MissingHaskellDependencies(Problem, kind="missing-haskell-dependencies"): deps: list[str] def __repr__(self) -> str: return f"{type(self).__name__}({self.deps!r})" def __str__(self) -> str: return "Missing Haskell dependencies: %r" % self.deps class MissingHaskellModule(Problem, kind="missing-haskell-module"): module: str def __repr__(self) -> str: return f"{type(self).__name__}({self.module!r})" def __str__(self) -> str: return "Missing Haskell module: %r" % self.module class Matcher: def match(self, line: list[str], i: int) -> tuple[list[int], Optional[Problem], str]: raise NotImplementedError(self.match) class MatcherError(Exception): """Error during matching.""" class SingleLineMatcher(Matcher): def __init__(self, regexp, cb=None) -> None: self.regexp = re.compile(regexp) self.cb = cb def __repr__(self) -> str: return f"<{type(self).__name__}({self.regexp.pattern!r})>" def match(self, lines, i): m = self.regexp.match(lines[i].rstrip("\n")) if not m: return [], None, None if self.cb: try: err = self.cb(m) except (ValueError, IndexError) as e: raise MatcherError( "Error while matching {!r} against {!r} ({!r}): {!r}".format( self.regexp, lines[i], m, e)) from e else: err = None return [i], err, f"direct regex ({self.regexp.pattern}" class MissingSetupPyCommand(Problem, kind="missing-setup.py-command"): command: str def __str__(self) -> str: return "missing setup.py subcommand: %s" % self.command class SetupPyCommandMissingMatcher(Matcher): final_line_re = re.compile(r"error: invalid command \'(.*)\'") warning_match = re.compile( r"usage: setup.py \[global_opts\] cmd1 " r"\[cmd1_opts\] \[cmd2 \[cmd2_opts\] \.\.\.\]" ) def match(self, lines, i): m = self.final_line_re.fullmatch(lines[i].rstrip("\n")) if not m: return [], None, None for j in range(i, max(0, i - 20), -1): if self.warning_match.fullmatch(lines[j].rstrip("\n")): return [i], MissingSetupPyCommand(m.group(1)), f"direct regex ({self.final_line_re.pattern})" return [], None, None class PythonFileNotFoundErrorMatcher(Matcher): final_line_re = re.compile( r"^(?:E +)?FileNotFoundError: \[Errno 2\] " r"No such file or directory: \'(.*)\'" ) def match(self, lines, i): m = self.final_line_re.fullmatch(lines[i].rstrip("\n")) if not m: return [], None, None if i - 2 >= 0 and "subprocess" in lines[i - 2]: return [i], MissingCommand(m.group(1)), f"direct regex ({self.final_line_re.pattern})" return [i], file_not_found_maybe_executable(m), None class HaskellMissingDependencyMatcher(Matcher): regexp = re.compile(r"(.*): Encountered missing or private dependencies:") def match(self, lines, i): m = self.regexp.fullmatch(lines[i].rstrip("\n")) if not m: return [], None, None deps = [] linenos = [i] for line in lines[i + 1 :]: if not line.strip("\n"): break deps.extend([x.strip() for x in line.split(",", 1)]) linenos.append(linenos[-1] + 1) return linenos, MissingHaskellDependencies([dep for dep in deps if dep]), f"direct regex ({self.regexp.pattern})" def cmake_compiler_failure(m): compiler_output = textwrap.dedent(m.group(3)) match, error = find_build_failure_description(compiler_output.splitlines(True)) return error def cmake_compiler_missing(m): if m.group(1) == "Fortran": return MissingFortranCompiler() return None class CMakeNeedExactVersion(Problem, kind="cmake-exact-version-missing"): package: str version_found: str exact_version_needed: str path: str def __repr__(self) -> str: return "{}({!r}, {!r}, {!r}, {!r})".format( type(self).__name__, self.package, self.version_found, self.exact_version_needed, self.path, ) def __str__(self) -> str: return "CMake needs exact package {}, version {}".format( self.package, self.exact_version_needed, ) class CMakeErrorMatcher(Matcher): regexp = re.compile(r"CMake (Error|Warning) at (.+):([0-9]+) \((.*)\):") cmake_errors = [ ( r'Could NOT find (.*) \(missing:\s(.*)\)\s\(found\ssuitable\sversion\s.*', lambda m: MissingCMakeComponents(m.group(1), m.group(2).split())), ( r"\s*--\s+Package \'(.*)\', required by \'(.*)\', not found", lambda m: MissingPkgConfig(m.group(1)), ), ( r'Could not find a package configuration file provided by\s' r'"(.*)" \(requested\sversion\s(.*)\)\swith\sany\s+of\s+the\s+following\snames:' r'\n\n( .*\n)+\n.*$', lambda m: CMakeFilesMissing( [e.strip() for e in m.group(3).splitlines()], m.group(2)) ), ( r"Could NOT find (.*) \(missing: (.*)\)", lambda m: MissingCMakeComponents(m.group(1), m.group(2).split()), ), ( r'The (.+) compiler\n\n "(.*)"\n\nis not able to compile a ' r"simple test program\.\n\nIt fails with the following output:\n\n" r"(.*)\n\n" r"CMake will not be able to correctly generate this project.\n$", cmake_compiler_failure, ), ( r"Could NOT find (.*): Found unsuitable version \"(.*)\",\sbut\s" r"required\sis\sexact version \"(.*)\" \(found\s(.*)\)", lambda m: CMakeNeedExactVersion( m.group(1), m.group(2), m.group(3), m.group(4) ), ), ( r"(.*) couldn't be found \(missing: .*_LIBRARIES .*_INCLUDE_DIR\)", lambda m: MissingVagueDependency(m.group(1)), ), ( r"Could NOT find (.*): Found unsuitable version \"(.*)\",\sbut\s" r"required\sis\sat\sleast\s\"(.*)\" \(found\s(.*)\)", lambda m: MissingPkgConfig(m.group(1), m.group(3)), ), ( r'The imported target \"(.*)\" references the file\n\n\s*"(.*)"\n\n' r"but this file does not exist\.(.*)", lambda m: MissingFile(m.group(2)), ), ( r'Could not find a configuration file for package "(.*)"\sthat\sis\s' r'compatible\swith\srequested\sversion\s"(.*)"\.', lambda m: MissingCMakeConfig(m.group(1), m.group(2)), ), ( r'.*Could not find a package configuration file provided by "(.*)"\s+' r"with\s+any\s+of\s+the\s+following\s+names:\n\n( .*\n)+\n.*$", lambda m: CMakeFilesMissing([e.strip() for e in m.group(2).splitlines()]) ), ( r'.*Could not find a package configuration file provided by "(.*)"\s' r"\(requested\sversion\s(.+\))\swith\sany\sof\sthe\sfollowing\snames:\n" r"\n( .*\n)+\n.*$", lambda m: CMakeFilesMissing([e.strip() for e in m.group(3).splitlines()], m.group(2)), ), ( r"No CMAKE_(.*)_COMPILER could be found.\n" r"\n" r"Tell CMake where to find the compiler by setting either" r'\sthe\senvironment\svariable\s"(.*)"\sor\sthe\sCMake\scache' r"\sentry\sCMAKE_(.*)_COMPILER\sto\sthe\sfull\spath\sto" r"\sthe\scompiler,\sor\sto\sthe\scompiler\sname\sif\sit\sis\sin\s" r"the\sPATH.\n", lambda m: MissingCommand(m.group(1).lower()), ), (r'file INSTALL cannot find\s"(.*)".\n', lambda m: MissingFile(m.group(1))), ( r'file INSTALL cannot copy file\n"(.*)"\sto\s"(.*)":\s' r"No space left on device.\n", lambda m: NoSpaceOnDevice(), ), ( r"patch: \*\*\*\* write error : No space left on device", lambda m: NoSpaceOnDevice(), ), ( r".*\(No space left on device\)", lambda m: NoSpaceOnDevice(), ), (r'file INSTALL cannot copy file\n"(.*)"\nto\n"(.*)"\.\n', None), ( r"Missing (.*)\. Either your\n" r"lib(.*) version is too old, or lib(.*) wasn\'t found in the place you\n" r"said.", lambda m: MissingLibrary(m.group(1)), ), ( r"need (.*) of version (.*)", lambda m: MissingVagueDependency( m.group(1), minimum_version=m.group(2).strip() ), ), ( r"\*\*\* (.*) is required to build (.*)\n", lambda m: MissingVagueDependency(m.group(1)), ), (r"\[([^ ]+)\] not found", lambda m: MissingVagueDependency(m.group(1))), (r"([^ ]+) not found", lambda m: MissingVagueDependency(m.group(1))), (r"error: could not find git .*", lambda m: MissingCommand("git")), (r'Could not find \'(.*)\' executable[\!,].*', lambda m: MissingCommand(m.group(1))), (r'Could not find (.*)_STATIC_LIBRARIES using the following names: ([a-zA-z0-9_.]+)', lambda m: MissingStaticLibrary(m.group(1), m.group(2))), ('include could not find (requested|load) file:\n\n (.*)\n', lambda m: CMakeFilesMissing([m.group(2) + '.cmake' if not m.group(2).endswith('.cmake') else m.group(2)])), (r'(.*) and (.*) are required', lambda m: MissingVagueDependency(m.group(1))), (r'Please check your (.*) installation', lambda m: MissingVagueDependency(m.group(1))), (r'Python module (.*) not found\!', lambda m: MissingPythonModule(m.group(1))), (r'\s*could not find ([^\s]+)$', lambda m: MissingVagueDependency(m.group(1))), (r'Please install (.*) before installing (.*)\.', lambda m: MissingVagueDependency(m.group(1))), (r"Please get (.*) from (www\..*)", lambda m: MissingVagueDependency(m.group(1), url=m.group(2))), (r'Found unsuitable Qt version "" from NOTFOUND, ' r'this code requires Qt 4.x', lambda m: MissingQt('4')), (r'(.*) executable not found\! Please install (.*)\.', lambda m: MissingCommand(m.group(2))), (r'(.*) tool not found', lambda m: MissingCommand(m.group(1))), (r'-- Requested \'(.*) >= (.*)\' but version of (.*) is (.*)', lambda m: MissingPkgConfig(m.group(1), m.group(2))), (r'-- No package \'(.*)\' found', lambda m: MissingPkgConfig(m.group(1))), (r'([^ ]+) library not found\.', lambda m: MissingLibrary(m.group(1))), (r'Please install (.*) so that it is on the PATH and try again\.', command_missing), (r'-- Unable to find git\. Setting git revision to \'unknown\'\.', lambda m: MissingCommand('git')), (r'(.*) must be installed before configuration \& building can ' r'proceed', lambda m: MissingVagueDependency(m.group(1))), (r'(.*) development files not found\.', lambda m: MissingVagueDependency(m.group(1))), (r'.* but no (.*) dev libraries found', lambda m: MissingVagueDependency(m.group(1))), (r'Failed to find (.*) \(missing: .*\)', lambda m: MissingVagueDependency(m.group(1))), (r'Couldn\'t find ([^ ]+) development files\..*', lambda m: MissingVagueDependency(m.group(1))), (r'Could not find required (.*) package\!', lambda m: MissingVagueDependency(m.group(1))), (r'Cannot find (.*), giving up\. .*', lambda m: MissingVagueDependency(m.group(1))), (r'Cannot find (.*)\. (.*) is required for (.*)', lambda m: MissingVagueDependency(m.group(1))), (r'The development\sfiles\sfor\s(.*)\sare\s' r'required\sto\sbuild (.*)\.', lambda m: MissingVagueDependency(m.group(1))), (r'Required library (.*) not found\.', lambda m: MissingVagueDependency(m.group(1))), (r'(.*) required to compile (.*)', lambda m: MissingVagueDependency(m.group(1))), (r'(.*) requires (.*) ([0-9].*) or newer. See (https://.*)\s*', lambda m: MissingVagueDependency(m.group(2), minimum_version=m.group(3), url=m.group(4))), (r'(.*) requires (.*) ([0-9].*) or newer.\s*', lambda m: MissingVagueDependency(m.group(2), minimum_version=m.group(3))), (r'(.*) requires (.*) to build', lambda m: MissingVagueDependency(m.group(2))), (r'(.*) library missing', lambda m: MissingVagueDependency(m.group(1))), (r'(.*) requires (.*)', lambda m: MissingVagueDependency(m.group(2))), (r'Could not find ([A-Za-z-]+)', lambda m: MissingVagueDependency(m.group(1))), (r'(.+) is required for (.*)\.', lambda m: MissingVagueDependency(m.group(1))), (r'No (.+) version could be found in your system\.', lambda m: MissingVagueDependency(m.group(1))), (r'([^ ]+) >= (.*) is required', lambda m: MissingVagueDependency(m.group(1), minimum_version=m.group(2))), (r'\s*([^ ]+) is required', lambda m: MissingVagueDependency(m.group(1))), (r'([^ ]+) binary not found\!', lambda m: MissingCommand(m.group(1))), (r'error: could not find git for clone of .*', lambda m: MissingCommand('git')), (r'Did not find ([^\s]+)', lambda m: MissingVagueDependency(m.group(1))), (r'Could not find the ([^ ]+) external dependency\.', lambda m: MissingVagueDependency(m.group(1))), (r'Couldn\'t find (.*)', lambda m: MissingVagueDependency(m.group(1))), ] @classmethod def _extract_error_lines(cls, lines, i): linenos = [i] error_lines = [] for j, line in enumerate(lines[i + 1 :]): if line.rstrip('\n') and not line.startswith(" "): break error_lines.append(line.rstrip('\n') + '\n') linenos.append(i + 1 + j) while error_lines and error_lines[-1].rstrip('\n') == "": error_lines.pop(-1) linenos.pop(-1) return linenos, textwrap.dedent("".join(error_lines)).splitlines(True) def match(self, lines, i): m = self.regexp.fullmatch(lines[i].rstrip("\n")) if not m: return [], None, None path = m.group(2) # noqa: F841 start_lineno = int(m.group(3)) # noqa: F841 linenos, error_lines = self._extract_error_lines(lines, i) for r, fn in self.cmake_errors: m = re.match(r, "".join(error_lines), flags=re.DOTALL) if m: if fn is None: error = None else: error = fn(m) return linenos, error, f"direct regex ({self.regexp.pattern})" return linenos, None, f"direct regex ({self.regexp.pattern})" class MissingFortranCompiler(Problem, kind="missing-fortran-compiler"): def __str__(self) -> str: return "No Fortran compiler found" class MissingCSharpCompiler(Problem, kind="missing-c#-compiler"): def __str__(self) -> str: return "No C# compiler found" class MissingRustCompiler(Problem, kind="missing-rust-compiler"): def __str__(self) -> str: return "No Rust compiler found" class MissingAssembler(Problem, kind="missing-assembler"): def __str__(self) -> str: return "No assembler found" class MissingLibtool(Problem, kind="missing-libtool"): def __str__(self) -> str: return "Libtool is missing" class UnsupportedPytestArguments(Problem, kind="unsupported-pytest-arguments"): args: list[str] def __str__(self) -> str: return "Unsupported pytest arguments: %r" % self.args def __repr__(self) -> str: return f"{type(self).__name__}({self.args!r})" class UnsupportedPytestConfigOption(Problem, kind="unsupported-pytest-config-option"): name: str def __str__(self) -> str: return f"Unsupported pytest configuration option: {self.name}" class MissingPytestFixture(Problem, kind="missing-pytest-fixture"): fixture: str def __str__(self) -> str: return "Missing pytest fixture: %s" % self.fixture def __repr__(self) -> str: return f"{type(self).__name__}({self.fixture!r})" class MissingCargoCrate(Problem, kind="missing-cargo-crate"): crate: str requirement: Optional[str] = None def __str__(self) -> str: if self.requirement: return f"Missing crate: {self.crate} ({self.requirement})" else: return "Missing crate: %s" % self.crate def cargo_missing_requirement(m): try: crate, requirement = m.group(1).split(" ", 1) except ValueError: crate = m.group(1) requirement = None return MissingCargoCrate(crate, requirement) class MissingLatexFile(Problem, kind="missing-latex-file"): filename: str def __str__(self) -> str: return "Missing LaTeX file: %s" % self.filename class MissingFontspec(Problem, kind="missing-fontspec"): fontspec: str def __str__(self) -> str: return "Missing font spec: %s" % self.fontspec class MissingDHCompatLevel(Problem, kind="missing-dh-compat-level"): command: str def __str__(self) -> str: return "Missing DH Compat Level (command: %s)" % self.command class DuplicateDHCompatLevel(Problem, kind="duplicate-dh-compat-level"): command: str def __str__(self) -> str: return "DH Compat Level specified twice (command: %s)" % self.command class MissingIntrospectionTypelib(Problem, kind="missing-introspection-typelib"): library: str def __str__(self) -> str: return "Missing introspection typelib: %s" % self.library class UnknownCertificateAuthority(Problem, kind="unknown-certificate-authority"): url: str def __str__(self) -> str: return "Unknown Certificate Authority for %s" % self.url class MissingXDisplay(Problem, kind="missing-x-display"): def __str__(self) -> str: return "No X Display" class MissingPostgresExtension(Problem, kind="missing-postgresql-extension"): extension: str def __str__(self) -> str: return "Missing postgres extension: %s" % self.extension class MissingLuaModule(Problem, kind="missing-lua-module"): module: str def __str__(self) -> str: return "Missing Lua Module: %s" % self.module class Cancelled(Problem, kind="cancelled"): def __str__(self) -> str: return "Cancelled by runner or job manager" class InactiveKilled(Problem, kind="inactive-killed"): minutes: int def __str__(self) -> str: return "Killed due to inactivity" class MissingPauseCredentials(Problem, kind="missing-pause-credentials"): def __str__(self) -> str: return "Missing credentials for PAUSE" class MismatchGettextVersions(Problem, kind="mismatch-gettext-versions"): makefile_version: str autoconf_version: str def __str__(self) -> str: return "Mismatch versions ({}, {})".format( self.makefile_version, self.autoconf_version) class DisappearedSymbols(Problem, kind="disappeared-symbols"): def __str__(self) -> str: return "Disappeared symbols" class MissingGnulibDirectory(Problem, kind="missing-gnulib-directory"): directory: str def __str__(self) -> str: return "Missing gnulib directory %s" % self.directory class MissingGoModFile(Problem, kind="missing-go.mod-file"): def __str__(self) -> str: return "go.mod file is missing" class OutdatedGoModFile(Problem, kind="outdated-go.mod-file"): def __str__(self) -> str: return "go.mod file is outdated" class CodeCoverageTooLow(Problem, kind="code-coverage-too-low"): actual: float required: float def __str__(self) -> str: return f"Code coverage too low: {self.actual:f} < {self.required:f}" class ESModuleMustUseImport(Problem, kind="esmodule-must-use-import"): path: str def __str__(self) -> str: return "ESM-only module %s must use import()" % self.path build_failure_regexps = [ # Maven ( 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", maven_missing_artifact, ), ( MAVEN_ERROR_PREFIX + r"Failed to execute goal on project .*: " "\x1b\\[1;31mCould not resolve dependencies for project .*: " "Could not find artifact (.*)\x1b\\[m .*", maven_missing_artifact, ), ( MAVEN_ERROR_PREFIX + r"Failed to execute goal on project .*: " r"Could not resolve dependencies for project .*: " r"The following artifacts could not be resolved: (.*): " r"Cannot access central \(https://repo\.maven\.apache\.org/maven2\) " r"in offline mode and the artifact .* has not been downloaded from " r"it before..*", maven_missing_artifact, ), ( MAVEN_ERROR_PREFIX + r"Unresolveable build extension: " r"Plugin (.*) or one of its dependencies could not be resolved: " r"Cannot access central \(https://repo.maven.apache.org/maven2\) " r"in offline mode and the artifact .* has not been downloaded " "from it before. @", lambda m: MissingMavenArtifacts([m.group(1)]) ), ( MAVEN_ERROR_PREFIX + r"Non-resolvable import POM: Cannot access central " r"\(https://repo.maven.apache.org/maven2\) in offline mode and the " r"artifact (.*) has not been downloaded from it before. " r"@ line [0-9]+, column [0-9]+", maven_missing_artifact, ), ( r"\[FATAL\] Non-resolvable parent POM for .*: Cannot access central " r"\(https://repo.maven.apache.org/maven2\) in offline mode and the " "artifact (.*) has not been downloaded from it before. .*", maven_missing_artifact, ), ( MAVEN_ERROR_PREFIX + r"Plugin (.*) or one of its dependencies could " r"not be resolved: Cannot access central " r"\(https://repo.maven.apache.org/maven2\) in offline mode and the " r"artifact .* has not been downloaded from it before. -> \[Help 1\]", lambda m: MissingMavenArtifacts([m.group(1)]) ), ( MAVEN_ERROR_PREFIX + r"Plugin (.+) or one of its dependencies could " r"not be resolved: Failed to read artifact descriptor for " r"(.*): (.*)", lambda m: MissingMavenArtifacts([m.group(1)]), ), ( MAVEN_ERROR_PREFIX + r"Failed to execute goal on project .*: " r"Could not resolve dependencies for project .*: Cannot access " r".* \([^\)]+\) in offline mode and the artifact " r"(.*) has not been downloaded from it before. -> \[Help 1\]", maven_missing_artifact, ), ( MAVEN_ERROR_PREFIX + r"Failed to execute goal on project .*: " r"Could not resolve dependencies for project .*: Cannot access central " r"\(https://repo.maven.apache.org/maven2\) in offline mode and the " r"artifact (.*) has not been downloaded from it before..*", maven_missing_artifact, ), (MAVEN_ERROR_PREFIX + "Failed to execute goal (.*) on project (.*): (.*)", None), ( MAVEN_ERROR_PREFIX + r"Error resolving version for plugin \'(.*)\' from the repositories " r"\[.*\]: Plugin not found in any plugin repository -> \[Help 1\]", lambda m: MissingMavenArtifacts([m.group(1)]) ), ( r'E: eatmydata: unable to find \'(.*)\' in PATH', lambda m: MissingCommand(m.group(1)), ), ( r'\'(.*)\' not found in PATH at (.*) line ([0-9]+)\.', lambda m: MissingCommand(m.group(1)), ), ( r'/usr/bin/eatmydata: [0-9]+: exec: (.*): not found', command_missing ), ( r'/usr/bin/eatmydata: [0-9]+: exec: (.*): Permission denied', lambda m: NotExecutableFile(m.group(1)), ), ( r"(.*): exec: \"(.*)\": executable file not found in \$PATH", lambda m: MissingCommand(m.group(2)), ), ( r"Can't exec \"(.*)\": No such file or directory at (.*) line ([0-9]+)\.", command_missing, ), ( r"dh_missing: (warning: )?(.*) exists in debian/.* but is not " r"installed to anywhere", lambda m: DhMissingUninstalled(m.group(2)), ), (r"dh_link: link destination (.*) is a directory", lambda m: DhLinkDestinationIsDirectory(m.group(1))), (r"I/O error : Attempt to load network entity (.*)", lambda m: MissingXmlEntity(m.group(1))), (r"ccache: error: (.*)", lambda m: CcacheError(m.group(1))), ( r"dh: The --until option is not supported any longer \(#932537\). " r"Use override targets instead.", lambda m: DhUntilUnsupported(), ), ( r"dh: unable to load addon (.*): (.*) did not return a true " r"value at \(eval 11\) line ([0-9]+).", lambda m: DhAddonLoadFailure(m.group(1), m.group(2)), ), ( "ERROR: dependencies (.*) are not available for package [‘'](.*)['’]", r_missing_package, ), ( "ERROR: dependency [‘'](.*)['’] is not available for package [‘'](.*)[’']", r_missing_package, ), ( r"Error in library\(.*\) : there is no package called \'(.*)\'", r_missing_package, ), (r'Error in .* : there is no package called \'(.*)\'', r_missing_package), (r"there is no package called \'(.*)\'", r_missing_package), ( r" namespace ‘(.*)’ ([^ ]+) is being loaded, but >= ([^ ]+) is required", lambda m: MissingRPackage(m.group(1), m.group(3)) ), ( r" namespace ‘(.*)’ ([^ ]+) is already loaded, but >= ([^ ]+) " r"is required", lambda m: MissingRPackage(m.group(1), m.group(3)) ), (r'b\'convert convert: ' r'Unable to read font \((.*)\) \[No such file or directory\]\.\\n\'', file_not_found), (r"mv: cannot stat \'(.*)\': No such file or directory", file_not_found), (r"mv: cannot move \'.*\' to \'(.*)\': No such file or directory", file_not_found), ( r"(/usr/bin/install|mv): " r"will not overwrite just-created \'(.*)\' with \'(.*)\'", None, ), (r"IOError: \[Errno 2\] No such file or directory: \'(.*)\'", file_not_found_maybe_executable), (r"error: \[Errno 2\] No such file or directory: \'(.*)\'", file_not_found_maybe_executable), (r"E IOError: \[Errno 2\] No such file or directory: \'(.*)\'", file_not_found_maybe_executable), ("FAIL\t(.+\\/.+\\/.+)\t([0-9.]+)s", None), ( r'dh_(.*): Cannot find \(any matches for\) "(.*)" \(tried in (.*)\)', lambda m: DebhelperPatternNotFound( m.group(2), m.group(1), [d.strip() for d in m.group(3).split(",")]) ), ( r'Can\'t exec "(.*)": No such file or directory at ' r"/usr/share/perl5/Debian/Debhelper/Dh_Lib.pm line [0-9]+.", command_missing, ), ( r'Can\'t exec "(.*)": Permission denied at (.*) line [0-9]+\.', lambda m: NotExecutableFile(m.group(1)), ), ( r'/usr/bin/fakeroot: [0-9]+: (.*): Permission denied', lambda m: NotExecutableFile(m.group(1)), ), (r".*: error: (.*) command not found", command_missing), (r'error: command \'(.*)\' failed: No such file or directory', command_missing), ( r"dh_install: Please use dh_missing " "--list-missing/--fail-missing instead", None, ), ( r'dh([^:]*): Please use the third-party "pybuild" build system ' "instead of python-distutils", None, ), # A Python error, but not likely to be actionable. The previous # line will have the actual line that failed. (r"ImportError: cannot import name (.*)", None), # Rust ? (r"\s*= note: /usr/bin/ld: cannot find -l([^ ]+): .*", lambda m: MissingLibrary(m.group(1))), (r"\s*= note: /usr/bin/ld: cannot find -l([^ ]+)", lambda m: MissingLibrary(m.group(1))), (r"/usr/bin/ld: cannot find -l([^ ]+): .*", lambda m: MissingLibrary(m.group(1))), (r"/usr/bin/ld: cannot find -l([^ ]+)", lambda m: MissingLibrary(m.group(1))), ( r"Could not find gem \'([^ ]+) \(([^)]+)\)\', " r"which is required by gem.*", ruby_missing_gem, ), ( r"Could not find gem \'([^ \']+)\', " r"which is required by gem.*", lambda m: MissingRubyGem(m.group(1)), ), ( r"[^:]+:[0-9]+:in \`to_specs\': Could not find \'(.*)\' \(([^)]+)\) " r"among [0-9]+ total gem\(s\) \(Gem::MissingSpecError\)", ruby_missing_gem, ), ( r"[^:]+:[0-9]+:in \`to_specs\': Could not find \'(.*)\' \(([^)]+)\) " r"- .* \(Gem::MissingSpecVersionError\)", ruby_missing_gem, ), ( r"[^:]+:[0-9]+:in \`block in verify_gemfile_dependencies_are_found\!\': " r"Could not find gem \'(.*)\' in any of the gem sources listed in " r"your Gemfile\. \(Bundler::GemNotFound\)", lambda m: MissingRubyGem(m.group(1)), ), ( r"Exception: (.*) not in path[!.]*", lambda m: MissingCommand(m.group(1)) ), ( r"Exception: Building sdist requires that ([^ ]+) be installed\.", lambda m: MissingVagueDependency(m.group(1)) ), ( r"[^:]+:[0-9]+:in \`find_spec_for_exe\': can\'t find gem " r"(.*) \(([^)]+)\) with executable (.*) \(Gem::GemNotFoundException\)", ruby_missing_gem, ), ( r".?PHP Fatal error: Uncaught Error: Class \'(.*)\' not found in " r"(.*):([0-9]+)", lambda m: MissingPhpClass(m.group(1)) ), (r"Caused by: java.lang.ClassNotFoundException: (.*)", lambda m: MissingJavaClass(m.group(1))), ( r"\[(.*)\] \t\t:: (.*)\#(.*);\$\{(.*)\}: not found", lambda m: MissingMavenArtifacts( [f"{m.group(2)}:{m.group(3)}:jar:debian"] ), ), ( r"Caused by: java.lang.IllegalArgumentException: " r"Cannot find JAR \'(.*)\' required by module \'(.*)\' " r"using classpath or distribution directory \'(.*)\'", None, ), ( r".*\.xml:[0-9]+: Unable to find a javac compiler;", lambda m: MissingJavaClass("com.sun.tools.javac.Main"), ), ( r'checking for (.*)\.\.\. configure: error: "Cannot check for existence of module (.*) without pkgconf"', lambda m: MissingCommand("pkgconf"), ), ( r'configure: error: Could not find \'(.*)\' in path\.', lambda m: MissingCommand(m.group(1)), ), ( r"autoreconf was not found; .*", lambda m: MissingCommand("autoreconf"), ), (r"g\+\+: error: (.*): No such file or directory", file_not_found), (r"strip: \'(.*)\': No such file", file_not_found), ( r"Sprockets::FileNotFound: couldn\'t find file \'(.*)\' " r"with type \'(.*)\'", lambda m: MissingSprocketsFile(m.group(1), m.group(2)), ), ( r'xdt-autogen: You must have "(.*)" installed. You can get if from', lambda m: MissingXfceDependency(m.group(1)), ), ( r"autogen.sh: You must have GNU autoconf installed.", lambda m: MissingCommand("autoconf"), ), ( r"\s*You must have (autoconf|automake|aclocal|libtool|libtoolize) installed to compile (.*)\.", lambda m: MissingCommand(m.group(1)), ), ( r"It appears that Autotools is not correctly installed on this system.", lambda m: MissingCommand("autoconf"), ), ( r"\*\*\* No autoreconf found \*\*\*", lambda m: MissingCommand("autoreconf"), ), (r"You need to install gnome-common module and make.*", lambda m: GnomeCommonMissing()), (r"You need to install the gnome-common module and make.*", lambda m: GnomeCommonMissing()), ( r"You need to install gnome-common from the GNOME (git|CVS|SVN)", lambda m: GnomeCommonMissing(), ), ( r"automake: error: cannot open < (.*): No such file or directory", lambda m: MissingAutomakeInput(m.group(1)), ), ( r"configure(|\.in|\.ac):[0-9]+: error: possibly undefined macro: (.*)", lambda m: MissingAutoconfMacro(m.group(2)), ), ( r"configure.(in|ac):[0-9]+: error: macro (.*) is not defined; " r"is a m4 file missing\?", lambda m: MissingAutoconfMacro(m.group(2)), ), ( r"config.status: error: cannot find input file: `(.*)\'", lambda m: MissingConfigStatusInput(m.group(1)), ), ( r"\*\*\*Error\*\*\*: You must have glib-gettext >= (.*) installed.*", lambda m: MissingGnomeCommonDependency("glib-gettext", m.group(1)), ), ( r"ERROR: JAVA_HOME is set to an invalid directory: " r"/usr/lib/jvm/default-java/", lambda m: MissingJVM(), ), ( r'Error: The file "MANIFEST" is missing from this distribution\. ' r'The MANIFEST lists all files included in the distribution\.', lambda m: MissingPerlManifest() ), ( r"dh_installdocs: --link-doc not allowed between (.*) and (.*) " r"\(one is arch:all and the other not\)", None, ), ( r"dh: unable to load addon systemd: dh: The systemd-sequence is " "no longer provided in compat >= 11, please rely on dh_installsystemd " "instead", None, ), ( r"dh: The --before option is not supported any longer \(#932537\). " r"Use override targets instead.", None, ), (r"\(.*\): undefined reference to `(.*)'", None), ("(.*):([0-9]+): undefined reference to `(.*)'", None), ("(.*):([0-9]+): error: undefined reference to '(.*)'", None), ( r"\/usr\/bin\/ld:(.*): multiple definition of `*.\'; " r"(.*): first defined here", None, ), (r".+\.go:[0-9]+: undefined reference to `(.*)'", None), (r"ar: libdeps specified more than once", None), ( r"\/usr\/bin\/ld: .*\(.*\):\(.*\): multiple definition of `*.\'; " r"(.*):\((.*)\) first defined here", None, ), ( r"\/usr\/bin\/ld:(.*): multiple definition of `*.\'; " r"(.*):\((.*)\) first defined here", None, ), (r"\/usr\/bin\/ld: (.*): undefined reference to `(.*)\'", None), (r"\/usr\/bin\/ld: (.*): undefined reference to symbol \'(.*)\'", None), ( r"\/usr\/bin\/ld: (.*): relocation (.*) against symbol `(.*)\' " r"can not be used when making a shared object; recompile with -fPIC", None, ), ( "(.*):([0-9]+): multiple definition of `(.*)'; (.*):([0-9]+): " "first defined here", None, ), ( "(dh.*): debhelper compat level specified both in debian/compat " "and via build-dependency on debhelper-compat", lambda m: DuplicateDHCompatLevel(m.group(1)), ), ( "(dh.*): (error: )?Please specify the compatibility level in " "debian/compat", lambda m: MissingDHCompatLevel(m.group(1)), ), ( "dh_makeshlibs: The udeb (.*) does not contain any shared libraries " "but --add-udeb=(.*) was passed!?", None, ), ( "dpkg-gensymbols: error: some symbols or patterns disappeared in the " "symbols file: see diff output below", lambda m: DisappearedSymbols(), ), ( r"Failed to copy \'(.*)\': No such file or directory at " r"/usr/share/dh-exec/dh-exec-install-rename line [0-9]+.*", file_not_found, ), (r"Invalid gemspec in \[.*\]: No such file or directory - (.*)", command_missing), ( r".*meson.build:[0-9]+:[0-9]+: ERROR: Program\(s\) \[\'(.*)\'\] not " r"found or not executable", command_missing, ), ( r".*meson.build:[0-9]+:[0-9]: ERROR: Git program not found\.", lambda m: MissingCommand("git"), ), ( r"Failed: [pytest] section in setup.cfg files is no longer " r"supported, change to [tool:pytest] instead.", None, ), (r"cp: cannot stat \'(.*)\': No such file or directory", file_not_found), (r"cp: \'(.*)\' and \'(.*)\' are the same file", None), (r".?PHP Fatal error: (.*)", None), (r"sed: no input files", None), (r"sed: can\'t read (.*): No such file or directory", file_not_found), ( r"ERROR in Entry module not found: Error: Can\'t resolve " r"\'(.*)\' in \'(.*)\'", webpack_file_missing, ), ( r".*:([0-9]+): element include: XInclude error : " r"could not load (.*), and no fallback was found", None, ), (r"E: Child terminated by signal ‘Terminated’", lambda m: Cancelled(), ), (r"E: Caught signal ‘Terminated’", lambda m: Cancelled(), ), (r"E: Failed to execute “(.*)”: No such file or directory", command_missing), (r"E ImportError: Bad (.*) executable(\.?)", command_missing), (r"E: The Debian version .* cannot be used as an ELPA version.", None), # ImageMagick ( r"convert convert: Image pixel limit exceeded " r"\(see -limit Pixels\) \(-1\).", None, ), (r"convert convert: Improper image header \(.*\).", None), (r"convert convert: invalid primitive argument \([0-9]+\).", None), (r"convert convert: Unexpected end-of-file \(\)\.", None), (r"convert convert: Unrecognized option \((.*)\)\.", None), (r"convert convert: Unrecognized channel type \((.*)\)\.", None), ( r"convert convert: Unable to read font \((.*)\) " r"\[No such file or directory\].", file_not_found, ), ( r"convert convert: Unable to open file (.*) \[No such file or directory\]\.", file_not_found, ), ( r"convert convert: No encode delegate for this image format \((.*)\) " r"\[No such file or directory\].", lambda m: ImageMagickDelegateMissing(m.group(1)), ), (r"ERROR: Sphinx requires at least Python (.*) to run.", None), (r"Can\'t find (.*) directory in (.*)", None), ( r"/bin/sh: [0-9]: cannot create (.*): Directory nonexistent", lambda m: DirectoryNonExistant(os.path.dirname(m.group(1))), ), (r"dh: Unknown sequence (.*) \(choose from: .*\)", None), (r".*\.vala:[0-9]+\.[0-9]+-[0-9]+.[0-9]+: error: (.*)", None), ( r"error: Package `(.*)\' not found in specified Vala API directories " r"or GObject-Introspection GIR directories", lambda m: MissingValaPackage(m.group(1)), ), (r".*.scala:[0-9]+: error: (.*)", None), # JavaScript (r"error TS6053: File \'(.*)\' not found.", file_not_found), # Mocha (r"Error \[ERR_MODULE_NOT_FOUND\]: Cannot find package '(.*)' " "imported from (.*)", lambda m: MissingNodePackage(m.group(1))), (r'\s*Uncaught Error \[ERR_MODULE_NOT_FOUND\]: ' r'Cannot find package \'(.*)\' imported from (.*)', lambda m: MissingNodePackage(m.group(1))), (r"(.*\.ts)\([0-9]+,[0-9]+\): error TS[0-9]+: (.*)", None), (r"(.*.nim)\([0-9]+, [0-9]+\) Error: .*", None), ( r"dh_installinit: upstart jobs are no longer supported\! " r"Please remove (.*) and check if you need to add a conffile removal", lambda m: UpstartFilePresent(m.group(1)), ), ( r"dh_installinit: --no-restart-on-upgrade has been renamed to " "--no-stop-on-upgrade", None, ), (r"find: paths must precede expression: .*", None), (r"find: ‘(.*)’: No such file or directory", file_not_found), (r"ninja: fatal: posix_spawn: Argument list too long", None), ("ninja: fatal: chdir to '(.*)' - No such file or directory", lambda m: DirectoryNonExistant(m.group(1))), # Java (r"error: Source option [0-9] is no longer supported. Use [0-9] or later.", None), ( r"(dh.*|jh_build): -s/--same-arch has been removed; " r"please use -a/--arch instead", None, ), ( r"dh_systemd_start: dh_systemd_start is no longer used in " r"compat >= 11, please use dh_installsystemd instead", None, ), (r"Trying patch (.*) at level 1 \.\.\. 0 \.\.\. 2 \.\.\. failure.", None), # QMake (r"Project ERROR: (.*) development package not found", pkg_config_missing), (r"Package \'(.*)\', required by \'(.*)\', not found\n", pkg_config_missing), (r"pkg-config cannot find (.*)", pkg_config_missing), ( r"configure: error: .* not found: Package dependency requirement " r"\'([^\']+)\' could not be satisfied.", pkg_config_missing, ), ( r"configure: error: (.*) is required to build documentation", lambda m: MissingVagueDependency(m.group(1)), ), (r".*:[0-9]+: (.*) does not exist.", file_not_found), # uglifyjs (r"ERROR: can\'t read file: (.*)", file_not_found), (r'jh_build: Cannot find \(any matches for\) "(.*)" \(tried in .*\)', None), ( r"-- Package \'(.*)\', required by \'(.*)\', not found", lambda m: MissingPkgConfig(m.group(1)), ), ( r".*.rb:[0-9]+:in `require_relative\': cannot load such file " r"-- (.*) \(LoadError\)", None, ), ( r":[0-9]+:in `require': cannot load such file -- (.*) \(LoadError\)", lambda m: MissingRubyFile(m.group(1)), ), ( r".*.rb:[0-9]+:in `require\': cannot load such file " r"-- (.*) \(LoadError\)", lambda m: MissingRubyFile(m.group(1)), ), (r"LoadError: cannot load such file -- (.*)", lambda m: MissingRubyFile(m.group(1))), (r" cannot load such file -- (.*)", lambda m: MissingRubyFile(m.group(1))), # TODO(jelmer): This is a fairly generic string; perhaps combine with other # checks for ruby? (r"File does not exist: ([a-z/]+)$", lambda m: MissingRubyFile(m.group(1))), ( r".*:[0-9]+:in `do_check_dependencies\': E: " r"dependency resolution check requested but no working " r"gemspec available \(RuntimeError\)", None, ), (r"rm: cannot remove \'(.*)\': Is a directory", None), (r"rm: cannot remove \'(.*)\': No such file or directory", file_not_found), # Invalid option from Python (r"error: option .* not recognized", None), # Invalid option from go (r"flag provided but not defined: .*", None), (r'CMake Error: The source directory "(.*)" does not exist.', lambda m: DirectoryNonExistant(m.group(1))), (r".*: [0-9]+: cd: can\'t cd to (.*)", lambda m: DirectoryNonExistant(m.group(1))), (r"/bin/sh: 0: Can\'t open (.*)", file_not_found_maybe_executable), (r"/bin/sh: [0-9]+: cannot open (.*): No such file", file_not_found_maybe_executable), (r".*: line [0-9]+: (.*): No such file or directory", file_not_found_maybe_executable), (r"/bin/sh: [0-9]+: Syntax error: .*", None), (r"error: No member named \$memberName", None), ( r"(?:/usr/bin/)?install: cannot create regular file \'(.*)\': " r"Permission denied", None, ), (r"(?:/usr/bin/)?install: cannot create directory .(.*).: File exists", None), (r"/usr/bin/install: missing destination file operand after .*", None), # Ruby (r"rspec .*\.rb:[0-9]+ # (.*)", None), # help2man (r"Addendum (.*) does NOT apply to (.*) \(translation discarded\).", None), ( r"dh_installchangelogs: copy\((.*), (.*)\): No such file or directory", file_not_found, ), (r"dh_installman: mv (.*) (.*): No such file or directory", file_not_found), (r"dh_installman: Could not determine section for (.*)", None), ( r"failed to initialize build cache at (.*): mkdir (.*): " r"permission denied", None, ), ( r'Can\'t exec "(.*)": No such file or directory at (.*) line ([0-9]+).', command_missing, ), ( r'E OSError: No command "(.*)" found on host .*', command_missing ), # PHPUnit (r'Cannot open file "(.*)".', file_not_found), ( r".*Could not find a JavaScript runtime\. See " r"https://github.com/rails/execjs for a list of available runtimes\..*", lambda m: MissingJavaScriptRuntime(), ), PythonFileNotFoundErrorMatcher(), # ruby (r"Errno::ENOENT: No such file or directory - (.*)", file_not_found), (r"(.*.rb):[0-9]+:in `.*\': .* \(.*\) ", None), # JavaScript (r".*: ENOENT: no such file or directory, open \'(.*)\'", file_not_found), (r"\[Error: ENOENT: no such file or directory, stat \'(.*)\'\] \{", file_not_found), ( r"(.*):[0-9]+: error: Libtool library used but \'LIBTOOL\' is undefined", lambda m: MissingLibtool(), ), # libtoolize (r"libtoolize: error: \'(.*)\' does not exist.", file_not_found), # Seen in python-cogent ( "(OSError|RuntimeError): (.*) required but not found.", lambda m: MissingVagueDependency(m.group(2)) ), ( r'RuntimeError: The (.*) executable cannot be found\. ' r'Please check if it is in the system path\.', lambda m: MissingCommand(m.group(1).lower()) ), ( r'.*: [0-9]+: cannot open (.*): No such file', file_not_found, ), ( r'Cannot find Git. Git is required for .*', lambda m: MissingCommand('git') ), ( r'E ImportError: Bad (.*) executable\.', lambda m: MissingCommand('git') ), ( "RuntimeError: (.*) is missing", lambda m: MissingVagueDependency(m.group(1)), ), ( r"(OSError|RuntimeError): Could not find (.*) library\..*", lambda m: MissingVagueDependency(m.group(2)) ), ( r'(OSError|RuntimeError): We need package (.*), but not importable', lambda m: MissingPythonDistribution(m.group(2)) ), ( r'(OSError|RuntimeError): No (.*) was found: .*', lambda m: MissingVagueDependency(m.group(2)) ), ( r"(.*)meson.build:[0-9]+:[0-9]+: ERROR: " r"Meson version is (.+) but project requires >=\s*(.+)", lambda m: MissingVagueDependency( "meson", minimum_version=m.group(3).rstrip('.'), current_version=m.group(2)) ), # Seen in cpl-plugin-giraf ( r"ImportError: Numpy version (.*) or later must be " r"installed to use .*", lambda m: MissingPythonModule("numpy", minimum_version=m.group(1)), ), # Seen in mayavi2 (r"\w+Numpy is required to build.*", lambda m: MissingPythonModule("numpy")), # autoconf (r"configure.ac:[0-9]+: error: required file \'(.*)\' not found", file_not_found), (r'/usr/bin/m4:(.*):([0-9]+): cannot open `(.*)\': ' r'No such file or directory', lambda m: MissingFile(m.group(3))), # automake (r"Makefile.am: error: required file \'(.*)\' not found", file_not_found), # sphinx (r"config directory doesn\'t contain a conf.py file \((.*)\)", None), # vcversioner ( r"vcversioner: no VCS could be detected in \'/<>\' " r"and \'/<>/version.txt\' isn\'t present.", None, ), # rst2html (and other Python?) (r" InputError: \[Errno 2\] No such file or directory: \'(.*)\'", file_not_found), # gpg (r"gpg: can\'t connect to the agent: File name too long", None), (r"(.*.lua):[0-9]+: assertion failed", None), (r"\s+\^\-\-\-\-\^ SC[0-4][0-9][0-9][0-9]: .*", None), ( r"Error: (.*) needs updating from (.*)\. " r"Run \'pg_buildext updatecontrol\'.", lambda m: NeedPgBuildExtUpdateControl(m.group(1), m.group(2)), ), (r"Patch (.*) does not apply \(enforce with -f\)", lambda m: PatchApplicationFailed(m.group(1))), ( r"java.io.FileNotFoundException: (.*) \(No such file or directory\)", file_not_found, ), # Pytest (r"INTERNALERROR> PluginValidationError: (.*)", None), (r"[0-9]+ out of [0-9]+ hunks FAILED -- saving rejects to file (.*\.rej)", None), (r"pkg_resources.UnknownExtra: (.*) has no such extra feature \'(.*)\'", None), ( r"dh_auto_configure: invalid or non-existing path " r"to the source directory: .*", None, ), # Sphinx ( r"sphinx_rtd_theme is no longer a hard dependency since version (.*). " r"Please install it manually.\(pip install (.*)\)", lambda m: MissingPythonModule("sphinx_rtd_theme"), ), (r"There is a syntax error in your configuration file: (.*)", None), ( r"E: The Debian version (.*) cannot be used as an ELPA version.", lambda m: DebianVersionRejected(m.group(1)), ), (r'"(.*)" is not exported by the ExtUtils::MakeMaker module', None), ( r"E: Please add appropriate interpreter package to Build-Depends, " r"see pybuild\(1\) for details\..*", lambda m: DhAddonLoadFailure("pybuild", "Debian/Debhelper/Buildsystem/pybuild.pm"), ), (r"dpkg: error: .*: No space left on device", lambda m: NoSpaceOnDevice()), ( r"You need the GNU readline library\(ftp://ftp.gnu.org/gnu/readline/\s+\) " r"to build", lambda m: MissingLibrary("readline"), ), ( r'configure: error: Could not find lib(.*)', lambda m: MissingLibrary(m.group(1)) ), ( r" Could not find module ‘(.*)’", lambda m: MissingHaskellModule(m.group(1)), ), (r'E: session: (.*): Chroot not found', lambda m: ChrootNotFound(m.group(1))), HaskellMissingDependencyMatcher(), SetupPyCommandMissingMatcher(), CMakeErrorMatcher(), ( r"error: failed to select a version for the requirement `(.*)`", cargo_missing_requirement, ), (r"^Environment variable \$SOURCE_DATE_EPOCH: No digits were found: $", None), ( r"\[ERROR\] LazyFont - Failed to read font file (.*) " r"\" r"java.io.FileNotFoundException: (.*) \(No such file or directory\)", lambda m: MissingFile(m.group(1)), ), (r"qt.qpa.xcb: could not connect to display", lambda m: MissingXDisplay()), (r'\(.*:[0-9]+\): Gtk-WARNING \*\*: [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}: cannot open display: ', lambda m: MissingXDisplay()), ( r"\s*Package (.*) was not found in the pkg-config search path.", lambda m: MissingPkgConfig(m.group(1)), ), ( r"Can't open display", lambda m: MissingXDisplay(), ), ( r"Can't open (.+): No such file or directory.*", file_not_found, ), ( r'pkg-config does not know (.*) at .*\.', lambda m: MissingPkgConfig(m.group(1)), ), ( r'\*\*\* Please install (.*) \(atleast version (.*)\) or adjust', lambda m: MissingPkgConfig(m.group(1), m.group(2)) ), ( r"go runtime is required: https://golang.org/doc/install", lambda m: MissingGoRuntime(), ), ( r"\%Error: '(.*)' must be installed to build", lambda m: MissingCommand(m.group(1)), ), ( r'configure: error: "Could not find (.*) in PATH"', lambda m: MissingCommand(m.group(1)), ), ( r'Could not find executable (.*)', lambda m: MissingCommand(m.group(1)) ), ( r"go: .*: Get \"(.*)\": x509: certificate signed by unknown authority", lambda m: UnknownCertificateAuthority(m.group(1)), ), ( r".*.go:[0-9]+:[0-9]+: .*: Get \"(.*)\": x509: certificate signed by unknown authority", lambda m: UnknownCertificateAuthority(m.group(1)), ), ( r"fatal: unable to access '(.*)': server certificate verification failed. CAfile: none CRLfile: none", lambda m: UnknownCertificateAuthority(m.group(1)), ), ( r'curl: \(77\) error setting certificate verify locations: CAfile: (.*) CApath: (.*)', lambda m: MissingFile(m.group(1)) ), ( r"\t\(Do you need to predeclare (.*)\?\)", lambda m: MissingPerlPredeclared(m.group(1)), ), ( r"Bareword \"(.*)\" not allowed while \"strict subs\" in use at " r"Makefile.PL line ([0-9]+).", lambda m: MissingPerlPredeclared(m.group(1)), ), ( r'String found where operator expected at Makefile.PL line ([0-9]+), ' 'near "([a-z0-9_]+).*"', lambda m: MissingPerlPredeclared(m.group(2)), ), (r" vignette builder 'knitr' not found", lambda m: MissingRPackage("knitr")), ( r"fatal: unable to auto-detect email address \(got \'.*\'\)", lambda m: MissingGitIdentity(), ), ( r"E fatal: unable to auto-detect email address \(got \'.*\'\)", lambda m: MissingGitIdentity(), ), (r"gpg: no default secret key: No secret key", lambda m: MissingSecretGpgKey()), ( r"ERROR: FAILED--Further testing stopped: " r"Test requires module \'(.*)\' but it\'s not found", lambda m: MissingPerlModule(None, m.group(1)), ), ( r"(subprocess.CalledProcessError|error): " r"Command \'\[\'/usr/bin/python([0-9.]*)\', \'-m\', \'pip\', " r"\'--disable-pip-version-check\', \'wheel\', \'--no-deps\', \'-w\', " r".*, \'([^-][^\']+)\'\]\' " r"returned non-zero exit status 1.", lambda m: MissingPythonDistribution.from_requirement_str( m.group(3), python_version=(int(m.group(2)[0]) if m.group(2) else None) ), ), ( r"vcversioner: \[\'git\', .*, \'describe\', \'--tags\', \'--long\'\] " r"failed and \'(.*)/version.txt\' isn\'t present\.", lambda m: MissingVcVersionerVersion(), ), ( r"vcversioner: no VCS could be detected in '(.*)' and " r"'(.*)/version.txt' isn't present\.", lambda m: MissingVcVersionerVersion(), ), ( r"You don't have a working TeX binary \(tex\) installed anywhere in", lambda m: MissingCommand("tex"), ), ( r"# Module \'(.*)\' is not installed", lambda m: MissingPerlModule(None, m.group(1)), ), ( r'Base class package "(.*)" is empty.', lambda m: MissingPerlModule(None, m.group(1)), ), ( r" \! (.*::.*) is not installed", lambda m: MissingPerlModule(None, m.group(1)), ), ( r'Cannot find (.*) in @INC at (.*) line ([0-9]+)\.', lambda m: MissingPerlModule(None, m.group(1)), ), ( r'(.*::.*) (.*) is required to configure our .* dependency, ' r'please install it manually or upgrade your CPAN/CPANPLUS', lambda m: MissingPerlModule(None, m.group(1), minimum_version=m.group(2)) ), ( r"configure: error: Missing lib(.*)\.", lambda m: MissingLibrary(m.group(1)), ), ( r"OSError: (.*): cannot open shared object file: No such file or directory", lambda m: MissingFile(m.group(1)), ), ( r'The "(.*)" executable has not been found\.', lambda m: MissingCommand(m.group(1)), ), ( r" '\! LaTeX Error: File `(.*)' not found.'", lambda m: MissingLatexFile(m.group(1)), ), ( r"\! LaTeX Error: File `(.*)\' not found\.", lambda m: MissingLatexFile(m.group(1)), ), ( r"(\!|.*:[0-9]+:) Package fontspec Error: The font \"(.*)\" cannot be found\.", lambda m: MissingFontspec(m.group(2)), ), (r" vignette builder \'(.*)\' not found", lambda m: MissingRPackage(m.group(1))), ( r"Error: package [‘'](.*)[’'] (.*) was found, but >= (.*) is required by [‘'](.*)[’']", lambda m: MissingRPackage(m.group(1), m.group(3)), ), ( r'there is no package called \'(.*)\'', lambda m: MissingRPackage(m.group(1)) ), (r"Error in .*: there is no package called ‘(.*)’", lambda m: MissingRPackage(m.group(1))), (r" there is no package called \'(.*)\'", lambda m: MissingRPackage(m.group(1))), ( r"Exception: cannot execute command due to missing interpreter: (.*)", command_missing, ), ( r'E: Build killed with signal TERM after ([0-9]+) minutes of inactivity', lambda m: InactiveKilled(int(m.group(1))) ), (r'\[.*Authority\] PAUSE credentials not found in "config.ini" or "dist.ini" or "~/.pause"\! ' r'Please set it or specify an authority for this plugin. at inline delegation in ' r'Dist::Zilla::Plugin::Authority for logger->log_fatal \(attribute declared in ' r'/usr/share/perl5/Dist/Zilla/Role/Plugin.pm at line [0-9]+\) line [0-9]+\.', lambda m: MissingPauseCredentials()), ( r'npm ERR\! ERROR: \[Errno 2\] No such file or directory: \'(.*)\'', file_not_found ), ( r'\*\*\* error: gettext infrastructure mismatch: using a Makefile\.in\.in ' r'from gettext version ([0-9.]+) but the autoconf macros are from gettext ' r'version ([0-9.]+)', lambda m: MismatchGettextVersions(m.group(1), m.group(2))), ( r'You need to install the (.*) package to use this program\.', lambda m: MissingVagueDependency(m.group(1)) ), ( r'You need to install (.*)', lambda m: MissingVagueDependency(m.group(1))), ( r"configure: error: You don't seem to have the (.*) library installed\..*", lambda m: MissingVagueDependency(m.group(1))), ( r'configure: error: You need (.*) installed', lambda m: MissingVagueDependency(m.group(1)) ), ( r'open3: exec of cme (.*) failed: No such file or directory ' r'at .*/Dist/Zilla/Plugin/Run/Role/Runner.pm line [0-9]+\.', lambda m: MissingPerlModule(None, 'App::Cme::Command::' + m.group(1)) ), ( r'pg_ctl: cannot be run as (.*)', lambda m: InvalidCurrentUser(m.group(1)), ), ( r'([^ ]+) \(for section ([^ ]+)\) does not appear to be installed', lambda m: MissingPerlModule(None, m.group(1))), ( r'(.*) version (.*) required--this is only version (.*) ' r'at .*\.pm line [0-9]+\.', lambda m: MissingPerlModule(None, m.group(1), minimum_version=m.group(2)), ), ( r'Bailout called\. Further testing stopped: ' r'YOU ARE MISSING REQUIRED MODULES: \[ ([^,]+)(.*) \]:', lambda m: MissingPerlModule(None, m.group(1)) ), ( r'CMake Error: CMake was unable to find a build program corresponding' r' to "(.*)". CMAKE_MAKE_PROGRAM is not set\. You probably need to ' r'select a different build tool\.', lambda m: MissingVagueDependency(m.group(1)) ), ( r"Dist currently only works with Git or Mercurial repos", lambda m: VcsControlDirectoryNeeded(['git', 'hg']), ), ( r'GitHubMeta: need a .git\/config file, and you don\'t have one', lambda m: VcsControlDirectoryNeeded(['git']) ), ( r"Exception: Versioning for this project requires either an sdist " r"tarball, or access to an upstream git repository\. It's also " r"possible that there is a mismatch between the package name " r"in setup.cfg and the argument given to pbr\.version\.VersionInfo\. " r"Project name .* was given, but was not able to be found\.", lambda m: VcsControlDirectoryNeeded(["git"]) ), (r'configure: error: no suitable Python interpreter found', lambda m: MissingCommand('python')), (r'Could not find external command "(.*)"', lambda m: MissingCommand(m.group(1))), (r' Failed to find (.*) development headers\.', lambda m: MissingVagueDependency(m.group(1))), (r'\*\*\* \Subdirectory \'(.*)\' does not yet exist. ' r'Use \'./gitsub.sh pull\' to create it, or set the ' r'environment variable GNULIB_SRCDIR\.', lambda m: MissingGnulibDirectory(m.group(1)) ), (r'configure: error: Cap\'n Proto compiler \(capnp\) not found.', lambda m: MissingCommand('capnp')), (r'lua: (.*):(\d+): module \'(.*)\' not found:', lambda m: MissingLuaModule(m.group(3))), (r'Unknown key\(s\) in sphinx_gallery_conf:', None), (r'(.+\.gir):In (.*): error: (.*)', None), (r'(.+\.gir):[0-9]+\.[0-9]+-[0-9]+\.[0-9]+: error: (.*)', None), (r'psql:.*\.sql:[0-9]+: ERROR: (.*)', None), (r'intltoolize: \'(.*)\' is out of date: use \'--force\' to overwrite', None), (r"E: pybuild pybuild:[0-9]+: cannot detect build system, please " r"use --system option or set PYBUILD_SYSTEM env\. variable", None), (r'-- Requested \'(.*) >= (.*)\' but version of (.*) is (.*)', lambda m: MissingPkgConfig(m.group(1), minimum_version=m.group(2))), (r'.*Could not find (.*) lib/headers, please set ' r'.* or ensure (.*).pc is in PKG_CONFIG_PATH\.', lambda m: MissingPkgConfig(m.group(2))), (r'go: go.mod file not found in current directory or any parent directory; ' r'see \'go help modules\'', lambda m: MissingGoModFile()), (r'go: cannot find main module, but found Gopkg.lock in (.*)', lambda m: MissingGoModFile()), (r'go: updates to go.mod needed; to update it:', lambda m: OutdatedGoModFile()), (r'(c\+\+|collect2|cc1|g\+\+): fatal error: .*', None), (r'fatal: making (.*): failed to create tests\/decode.trs', None), # ocaml (r'Please specify at most one of .*', None), # Python lint (r'.*\.py:[0-9]+:[0-9]+: [A-Z][0-9][0-9][0-9] .*', None), (r'PHPUnit requires the \"(.*)\" extension\.', lambda m: MissingPHPExtension(m.group(1))), (r' \[exec\] PHPUnit requires the "(.*)" extension\.', lambda m: MissingPHPExtension(m.group(1))), (r".*/gnulib-tool: \*\*\* minimum supported autoconf version is (.*)\. ", lambda m: MinimumAutoconfTooOld(m.group(1))), (r"configure.(ac|in):[0-9]+: error: Autoconf version (.*) or higher is required", lambda m: MissingVagueDependency("autoconf", minimum_version=m.group(2))), (r'# Error: The file "(MANIFEST|META.yml)" is missing from ' 'this distribution\\. .*', lambda m: MissingPerlDistributionFile(m.group(1)), ), (r"^ ([^ ]+) does not exist$", file_not_found), (r'\s*> Cannot find \'\.git\' directory', lambda m: VcsControlDirectoryNeeded(['git'])), (r'Unable to find the \'(.*)\' executable\. .*', lambda m: MissingCommand(m.group(1))), (r'\[@RSRCHBOY\/CopyrightYearFromGit\] - ' r'412 No \.git subdirectory found', lambda m: VcsControlDirectoryNeeded(['git'])), (r'Couldn\'t find version control data \(git/hg/bzr/svn supported\)', lambda m: VcsControlDirectoryNeeded(['git', 'hg', 'bzr', 'svn'])), (r'RuntimeError: Unable to determine package version. ' r'No local Git clone detected, and no version file found at .*', lambda m: VcsControlDirectoryNeeded(['git'])), (r'"(.*)" failed to start: "No such file or directory" ' r'at .*.pm line [0-9]+\.', lambda m: MissingCommand(m.group(1))), (r'Can\'t find ([^ ]+)\.', lambda m: MissingCommand(m.group(1))), (r'Error: spawn (.*) ENOENT', lambda m: MissingCommand(m.group(1))), (r'E ImportError: Failed to initialize: Bad (.*) executable\.', lambda m: MissingCommand(m.group(1))), (r'ESLint couldn\'t find the config "(.*)" to extend from\. ' r'Please check that the name of the config is correct\.', None), ( r'E OSError: no library called "cairo-2" was found', lambda m: MissingLibrary(m.group(1)) ), ( r"ERROR: \[Errno 2\] No such file or directory: '(.*)'", file_not_found_maybe_executable, ), ( r"error: \[Errno 2\] No such file or directory: '(.*)'", file_not_found_maybe_executable, ), ( r'We need the Python library (.+) to be installed\. .*', lambda m: MissingPythonDistribution(m.group(1)) ), # Waf ( r'Checking for header (.+\.h|.+\.hpp)\s+: not found ', lambda m: MissingCHeader(m.group(1)) ), ( r'000: File does not exist (.*)', file_not_found, ), ( r'ERROR: Coverage for lines \(([0-9.]+)%\) does not meet ' r'global threshold \(([0-9]+)%\)', lambda m: CodeCoverageTooLow(float(m.group(1)), float(m.group(2))) ), ( r'Error \[ERR_REQUIRE_ESM\]: ' r'Must use import to load ES Module: (.*)', lambda m: ESModuleMustUseImport(m.group(1)), ), (r".* (/<>/.*): No such file or directory", file_not_found), (r"Cannot open file `(.*)' in mode `(.*)' \(No such file or directory\)", file_not_found), (r"[^:]+: cannot stat \'(.*)\': No such file or directory", file_not_found), (r"cat: (.*): No such file or directory", file_not_found), (r"ls: cannot access \'(.*)\': No such file or directory", file_not_found), (r"Problem opening (.*): No such file or directory at (.*) line ([0-9]+)\.", file_not_found), (r"/bin/bash: (.*): No such file or directory", file_not_found), (r'\(The package \"(.*)\" was not found when loaded as a Node module ' r'from the directory \".*\"\.\)', lambda m: MissingNodePackage(m.group(1))), (r'\+\-\- UNMET DEPENDENCY (.*)', lambda m: MissingNodePackage(m.group(1))), (r'Project ERROR: Unknown module\(s\) in QT: (.*)', lambda m: MissingQtModules(m.group(1).split())), (r'(.*):(\d+):(\d+): ' r'ERROR: Vala compiler \'.*\' can not compile programs', lambda m: ValaCompilerCannotCompile()), (r'(.*):(\d+):(\d+): ERROR: Problem encountered: ' r'Cannot load ([^ ]+) library\. (.*)', lambda m: MissingLibrary(m.group(4))), (r"go: (.*)@(.*): missing go.sum entry; to add it:", lambda m: MissingGoSumEntry(m.group(1), m.group(2))), (r'E: pybuild pybuild:(.*): configure: plugin (.*) failed with: ' r'PEP517 plugin dependencies are not available\. ' r'Please Build-Depend on (.*)\.', lambda m: MissingDebianBuildDep(m.group(1))), # ADD NEW REGEXES ABOVE THIS LINE (r'configure: error: Can not find "(.*)" .* in your PATH', lambda m: MissingCommand(m.group(1))), # Intentionally at the bottom of the list. (r'([^ ]+) package not found\. Please install from (https://[^ ]+)', lambda m: MissingVagueDependency(m.group(1), url=m.group(2))), (r'([^ ]+) package not found\. Please use \'pip install .*\' first', lambda m: MissingPythonDistribution(m.group(1))), (r".*: No space left on device", lambda m: NoSpaceOnDevice()), (r".*(No space left on device).*", lambda m: NoSpaceOnDevice()), (r'ocamlfind: Package `(.*)\' not found', lambda m: MissingOCamlPackage(m.group(1))), # Not a very unique ocaml-specific pattern :( (r'Error: Library "(.*)" not found.', lambda m: MissingOCamlPackage(m.group(1))), # ADD NEW REGEXES ABOVE THIS LINE # Intentionally at the bottom of the list, since they're quite broad. (r'configure: error: ([^ ]+) development files not found', lambda m: MissingVagueDependency(m.group(1))), (r'Exception: ([^ ]+) development files not found\..*', lambda m: MissingVagueDependency(m.group(1))), (r'Exception: Couldn\'t find (.*) source libs\!', lambda m: MissingVagueDependency(m.group(1))), ('configure: error: \'(.*)\' command was not found', lambda m: MissingCommand(m.group(1))), ( r"configure: error: (.*) not present.*", lambda m: MissingVagueDependency(m.group(1)) ), ( r"configure: error: (.*) >= (.*) not found", lambda m: MissingVagueDependency(m.group(1), minimum_version=m.group(2)) ), ( r"configure: error: (.*) headers (could )?not (be )?found", lambda m: MissingVagueDependency(m.group(1)), ), ( r"configure: error: (.*) ([0-9].*) (could )?not (be )?found", lambda m: MissingVagueDependency(m.group(1), minimum_version=m.group(2)), ), ( r"configure: error: (.*) (could )?not (be )?found", lambda m: MissingVagueDependency(m.group(1)), ), ( r"configure: error: (.*) ([0-9.]+) is required to build.*", lambda m: MissingVagueDependency(m.group(1), minimum_version=m.group(2)), ), ( ".*meson.build:([0-9]+):([0-9]+): ERROR: Problem encountered: (.*) (.*) or later required", lambda m: MissingVagueDependency(m.group(3), minimum_version=m.group(4)), ), ( r"configure: error: Please install (.*) from (http:\/\/[^ ]+)", lambda m: MissingVagueDependency(m.group(1), url=m.group(2)), ), ( r"configure: error: Required package (.*) (is ?)not available\.", lambda m: MissingVagueDependency(m.group(1)), ), ( r"Error\! You need to have (.*) \((.*)\) around.", lambda m: MissingVagueDependency(m.group(1), url=m.group(2)), ), ( r"configure: error: You don\'t have (.*) installed", lambda m: MissingVagueDependency(m.group(1)), ), ( r"configure: error: Could not find a recent version of (.*)", lambda m: MissingVagueDependency(m.group(1)), ), ( r"configure: error: Unable to locate (.*)", lambda m: MissingVagueDependency(m.group(1)), ), ( r"configure: error: Missing the (.* library)", lambda m: MissingVagueDependency(m.group(1)), ), ( r"configure: error: (.*) requires (.* libraries), .*", lambda m: MissingVagueDependency(m.group(2)), ), ( r"configure: error: (.*) requires ([^ ]+)\.", lambda m: MissingVagueDependency(m.group(2)) ), ( r"(.*) cannot be discovered in ([^ ]+)", lambda m: MissingVagueDependency(m.group(1)) ), ( r"configure: error: Missing required program '(.*)'.*", lambda m: MissingVagueDependency(m.group(1)), ), ( r"configure: error: Missing (.*)\.", lambda m: MissingVagueDependency(m.group(1)), ), ( r"configure: error: Unable to find (.*), please install (.*)", lambda m: MissingVagueDependency(m.group(2)), ), (r"configure: error: (.*) Not found", lambda m: MissingVagueDependency(m.group(1))), ( r"configure: error: You need to install (.*)", lambda m: MissingVagueDependency(m.group(1)), ), ( r'configure: error: (.*) \((.*)\) not found\.', lambda m: MissingVagueDependency(m.group(2)) ), ( r'configure: error: (.*) libraries are required for compilation', lambda m: MissingVagueDependency(m.group(1)) ), ( r'configure: error: .*Make sure you have (.*) installed\.', lambda m: MissingVagueDependency(m.group(1)) ), ( r'error: Cannot find (.*) in the usual places. .*', lambda m: MissingVagueDependency(m.group(1))), ( r'Makefile:[0-9]+: \*\*\* "(.*) was not found"\. Stop\.', lambda m: MissingVagueDependency(m.group(1)) ), ( r'Makefile:[0-9]+: \*\*\* ' r'\"At least (.*) version (.*) is needed to build (.*)\.". Stop\.', lambda m: MissingVagueDependency(m.group(1), minimum_version=m.group(2)) ), (r"([a-z0-9A-Z]+) not found", lambda m: MissingVagueDependency(m.group(1))), (r'ERROR: Unable to locate (.*)\.', lambda m: MissingVagueDependency(m.group(1))), ('\x1b\\[1;31merror: (.*) not found\x1b\\[0;32m', lambda m: MissingVagueDependency(m.group(1))), (r'You do not have (.*) correctly installed\. .*', lambda m: MissingVagueDependency(m.group(1))), (r'Error: (.*) is not available on your system', lambda m: MissingVagueDependency(m.group(1)), ), (r'ERROR: (.*) (.*) or later is required', lambda m: MissingVagueDependency(m.group(1), minimum_version=m.group(2))), (r'configure: error: .*Please install the \'(.*)\' package\.', lambda m: MissingVagueDependency(m.group(1))), (r'Error: Please install ([^ ]+) package', lambda m: MissingVagueDependency(m.group(1))), (r'configure: error: <(.*\.h)> is required', lambda m: MissingCHeader(m.group(1))), (r'configure: error: ([^ ]+) is required', lambda m: MissingVagueDependency(m.group(1))), (r'configure: error: you should install ([^ ]+) first', lambda m: MissingVagueDependency(m.group(1))), (r'configure: error: .*You need (.*) installed.', lambda m: MissingVagueDependency(m.group(1))), (r'To build (.*) you need (.*)', lambda m: MissingVagueDependency(m.group(1))), (r'.*Can\'t ([^\. ]+)\. (.*)', lambda m: MissingVagueDependency(m.group(1))), (r'([^ ]+) >= (.*) is required', lambda m: MissingVagueDependency(m.group(1), m.group(2))), (r'.*: ERROR: (.*) needs to be installed to run these tests', lambda m: MissingVagueDependency(m.group(1))), (r'ERROR: Unable to locate (.*)\.', lambda m: MissingVagueDependency(m.group(1))), (r'ERROR: Cannot find command \'(.*)\' - do you ' r'have \'(.*)\' installed and in your PATH\?', lambda m: MissingCommand(m.group(1))), (r'ValueError: no ([^ ]+) installed, .*', lambda m: MissingVagueDependency(m.group(1))), (r'This project needs (.*) in order to build\. .*', lambda m: MissingVagueDependency(m.group(1))), (r'ValueError: Unable to find (.+)', lambda m: MissingVagueDependency(m.group(1))), (r'([^ ]+) executable not found\. .*', lambda m: MissingCommand(m.group(1))), (r'ERROR: InvocationError for command could not find executable (.*)', lambda m: MissingCommand(m.group(1))), (r'E ImportError: Unable to find ([^ ]+) shared library', lambda m: MissingLibrary(m.group(1))), (r'\s*([^ ]+) library not found on the system', lambda m: MissingLibrary(m.group(1))), (r'\s*([^ ]+) library not found(\.?)', lambda m: MissingLibrary(m.group(1))), (r'.*Please install ([^ ]+) libraries\.', lambda m: MissingVagueDependency(m.group(1))), (r'Error: Please install (.*) package', lambda m: MissingVagueDependency(m.group(1))), (r'Please get ([^ ]+) from (www\..*)\.', lambda m: MissingVagueDependency(m.group(1), url=m.group(2))), (r'Please install ([^ ]+) so that it is on the PATH and try again\.', lambda m: MissingCommand(m.group(1))), (r'configure: error: No (.*) binary found in (.*)', lambda m: MissingCommand(m.group(1))), (r'Could not find ([A-Za-z-]+)', lambda m: MissingVagueDependency(m.group(1))), (r'No ([^ ]+) includes and libraries found', lambda m: MissingVagueDependency(m.group(1))), (r'Required library (.*) not found\.', lambda m: MissingVagueDependency(m.group(1))), (r'Missing ([^ ]+) boost library, .*', lambda m: MissingLibrary(m.group(1))), (r'configure: error: ([^ ]+) needed\!', lambda m: MissingVagueDependency(m.group(1))), (r'\*\*\* (.*) not found, please install it \*\*\*', lambda m: MissingVagueDependency(m.group(1))), ( r"configure: error: could not find ([^ ]+)", lambda m: MissingVagueDependency(m.group(1)), ), (r'([^ ]+) is required for ([^ ]+)\.', lambda m: MissingVagueDependency(m.group(1))), (r'configure: error: \*\*\* No ([^.])\! ' r'Install (.*) development headers/libraries! \*\*\*', lambda m: MissingVagueDependency(m.group(1))), (r'configure: error: \'(.*)\' cannot be found', lambda m: MissingVagueDependency(m.group(1))), (r'No (.*) includes and libraries found', lambda m: MissingVagueDependency(m.group(1))), (r'\s*No (.*) version could be found in your system\.', lambda m: MissingVagueDependency(m.group(1))), (r'You need (.+)', lambda m: MissingVagueDependency(m.group(1))), (r'configure: error: ([^ ]+) is needed', lambda m: MissingVagueDependency(m.group(1))), (r'configure: error: Cannot find ([^ ]+)\.', lambda m: MissingVagueDependency(m.group(1))), (r'configure: error: ([^ ]+) requested but not installed\.', lambda m: MissingVagueDependency(m.group(1))), (r'We need the Python library (.+) to be installed\..*', lambda m: MissingPythonDistribution(m.group(1))), (r'(.*) uses (.*) \(.*\) for installation but (.*) was not found', lambda m: MissingVagueDependency(m.group(1))), (r'ERROR: could not locate the \'([^ ]+)\' utility', lambda m: MissingCommand(m.group(1))), (r'Can\'t find (.*) libs. Exiting', lambda m: MissingLibrary(m.group(1))), ] compiled_build_failure_regexps = [] for entry in build_failure_regexps: try: matcher: Matcher if isinstance(entry, tuple): (regexp, cb) = entry matcher = SingleLineMatcher(regexp, cb) else: matcher = entry # type: ignore compiled_build_failure_regexps.append(matcher) except re.error as e: raise Exception(f"Error in {regexp}: {e}") from e # Regexps that hint at an error of some sort, but not the error itself. secondary_build_failure_regexps = [ r"E: pybuild pybuild:[0-9]+: test: plugin [^ ]+ failed with:", r"[^:]+: error: (.*)", r"[^:]+:[0-9]+: error: (.*)", r"[^:]+:[0-9]+:[0-9]+: error: (.*)", r"error TS[0-9]+: (.*)", r'mount: .*: mount failed: Operation not permitted\.', r" [0-9]+:[0-9]+\s+error\s+.+", r"fontmake: Error: In '(.*)': (.*)", r'# Failed test at t\/.*\.t line [0-9]+\.', r'Gradle build daemon disappeared unexpectedly ' r'\(it may have been killed or may have crashed\)', # ocaml r"\*\*\* omake error:", r".*ocamlc.*: OCam has been configured with -force-safe-string: " r"-unsafe-string is not available\.", # latex r"\! LaTeX Error: .*", r"Killed", # Java r'Exception in thread "(.*)" (.*): (.*);', r"error: Unrecognized option: \'.*\'", r"Segmentation fault", r"\[ERROR\] (.*\.java):\[[0-9]+,[0-9]+\] (.*)", r"make: \*\*\* No targets specified and no makefile found\. Stop\.", r"make\[[0-9]+\]: \*\*\* No targets specified and no makefile found\. Stop\.", r"make: \*\*\* No rule to make target " r"\'(.*)\'\. Stop\.", r"make\[[0-9]+\]: (.*): No such file or directory", r"make\[[0-9]+\]: \*\*\* \[.*:[0-9]+: .*\] Segmentation fault", ( r"make\[[0-9]+\]: \*\*\* No rule to make target " r"\'(?!maintainer-clean)(?!clean)(.*)\'\. Stop\." ), r".*:[0-9]+: \*\*\* empty variable name. Stop.", r"error: can't copy '(.*)': doesn't exist or not a regular file", r"error: ([0-9]+) test executed, ([0-9]+) fatal tests failed, " r"([0-9]+) nonfatal test failed\.", r'.*\.rst:toctree contains ref to nonexisting file \'.*\'', r'.*\.rst:[0-9]+:term not in glossary: .*', r"Try adding AC_PREREQ\(\[(.*)\]\) to your configure\.ac\.", # Erlang r' (.*_test): (.+)\.\.\.\*failed\*', r'(.*\.erl):[0-9]+:[0-9]+: erlang:.*', # Clojure r"Could not locate (.*) or (.*) on classpath\.", # QMake r"Project ERROR: .*", # pdflatex r"\! ==> Fatal error occurred, no output PDF file produced\!", # latex r"\! Undefined control sequence\.", r"\! Emergency stop\.", r"\!pdfTeX error: pdflatex: fwrite\(\) failed", # inkscape r"Unknown option ((?!ignoring).)*", # CTest r'not ok [0-9]+ .*', r"Errors while running CTest", r"dh_auto_install: error: .*", r"dh_quilt_patch: error: (.*)", r"dh.*: Aborting due to earlier error", r"dh.*: unknown option or error during option parsing; aborting", r"Could not import extension .* \(exception: .*\)", r"configure.ac:[0-9]+: error: (.*)", r"Reconfigure the source tree (via './config' or 'perl Configure'), please.", r"dwz: Too few files for multifile optimization", r"\[CJM/MatchManifest\] Aborted because of MANIFEST mismatch", r"dh_dwz: dwz -q -- .* returned exit code [0-9]+", r"help2man: can\'t get `-?-help\' info from .*", r"[^:]+: line [0-9]+:\s+[0-9]+ Segmentation fault.*", r"dpkg-gencontrol: error: (.*)", r".*:[0-9]+:[0-9]+: (error|ERROR): (.*)", r".*[.]+FAILED .*", r"FAIL: (.*)", r"FAIL\! : (.*)", r"\s*FAIL (.*) \(.*\)", r"FAIL\s+(.*) \[.*\] ?", r"([0-9]+)% tests passed, ([0-9]+) tests failed out of ([0-9]+)", r"TEST FAILURE", r"make\[[0-9]+\]: \*\*\* \[.*\] Error [0-9]+", r"make\[[0-9]+\]: \*\*\* \[.*\] Aborted", r"exit code=[0-9]+: .*", r"chmod: cannot access \'.*\': .*", r"dh_autoreconf: autoreconf .* returned exit code [0-9]+", r"make: \*\*\* \[.*\] Error [0-9]+", r".*:[0-9]+: \*\*\* missing separator\. Stop\.", r"[0-9]+ tests: [0-9]+ ok, [0-9]+ failure\(s\), [0-9]+ test\(s\) skipped", r"\*\*Error:\*\* (.*)", r"^Error: (.*)", r"Failed [0-9]+ tests? out of [0-9]+, [0-9.]+% okay.", r"Failed [0-9]+\/[0-9]+ test programs. [0-9]+/[0-9]+ subtests failed.", r"Original error was: (.*)", r"-- Error \(.*\.R:[0-9]+:[0-9]+\): \(.*\) [-]*", r"^Error \[ERR_.*\]: .*", r"^FAILED \(.*\)", r"FAILED .*", # Random Python errors "^(E +)?(SyntaxError|TypeError|ValueError|AttributeError|NameError|" r"django.core.exceptions..*|RuntimeError|subprocess.CalledProcessError|" r"testtools.matchers._impl.MismatchError|" r"PermissionError|IndexError|TypeError|AssertionError|IOError|ImportError|" r"SerialException|OSError|qtawesome.iconic_font.FontError|" r"redis.exceptions.ConnectionError|builtins.OverflowError|ArgumentError|" r"httptools.parser.errors.HttpParserInvalidURLError|HypothesisException|" r"SSLError|KeyError|Exception|rnc2rng.parser.ParseError|" r"pkg_resources.UnknownExtra|tarfile.ReadError|" r"numpydoc.docscrape.ParseError|distutils.errors.DistutilsOptionError|" r"datalad.support.exceptions.IncompleteResultsError|AssertionError|" r"Cython.Compiler.Errors.CompileError|UnicodeDecodeError|" r"UnicodeEncodeError): .*", # Rust r"error\[E[0-9]+\]: .*", "^E DeprecationWarning: .*", "^E fixture '(.*)' not found", # Rake r"[0-9]+ runs, [0-9]+ assertions, [0-9]+ failures, [0-9]+ errors, " r"[0-9]+ skips", # Node r"# failed [0-9]+ of [0-9]+ tests", # Pytest r"(.*).py:[0-9]+: AssertionError", r"============================ no tests ran in ([0-9.]+)s =============================", # Perl r" Failed tests: [0-9-]+", r"Failed (.*\.t): output changed", # Go r'no packages to test', "FAIL\t(.*)\t[0-9.]+s", r".*.go:[0-9]+:[0-9]+: (?!note:).*", r"can\'t load package: package \.: no Go files in /<>/(.*)", # Ld r"\/usr\/bin\/ld: cannot open output file (.*): No such file or directory", r"configure: error: (.+)", r"config.status: error: (.*)", r"E: Build killed with signal TERM after ([0-9]+) minutes of inactivity", r" \[javac\] [^: ]+:[0-9]+: error: (.*)", r"1\) TestChannelFeature: ([^:]+):([0-9]+): assert failed", r"cp: target \'(.*)\' is not a directory", r"cp: cannot create regular file \'(.*)\': No such file or directory", r"couldn\'t determine home directory at (.*)", r"ln: failed to create symbolic link \'(.*)\': File exists", r"ln: failed to create symbolic link \'(.*)\': No such file or directory", r"ln: failed to create symbolic link \'(.*)\': Permission denied", r"ln: invalid option -- .*", r"mkdir: cannot create directory [‘'](.*)['’]: No such file or directory", r"mkdir: cannot create directory [‘'](.*)['’]: File exists", r"mkdir: missing operand", r"rmdir: failed to remove '.*': No such file or directory", r"Fatal error: .*", "Fatal Error: (.*)", r"Alert: (.*)", r'ERROR: Test "(.*)" failed. Exiting.', # scons r"ERROR: test\(s\) failed in (.*)", r"./configure: line [0-9]+: syntax error near unexpected token `.*\'", r"scons: \*\*\* \[.*\] ValueError : unsupported pickle protocol: .*", # yarn r"ERROR: There are no scenarios; must have at least one.", # perl r"Execution of (.*) aborted due to compilation errors.", # Mocha r" AssertionError \[ERR_ASSERTION\]: Missing expected exception.", # lt (C++) r".*: .*:[0-9]+: .*: Assertion `.*\' failed.", r"(.*).xml: FAILED:", r" BROKEN .*", r'failed: [0-9]+-.*', # ninja r"ninja: build stopped: subcommand failed.", r".*\.s:[0-9]+: Error: .*", # rollup r"\[\!\] Error: Unexpected token", # glib r"\(.*:[0-9]+\): [a-zA-Z0-9]+-CRITICAL \*\*: [0-9:.]+: .*", r"tar: option requires an argument -- \'.\'", r"tar: .*: Cannot stat: No such file or directory", r"tar: .*: Cannot open: No such file or directory", # rsvg-convert r"Could not render file (.*.svg)", # pybuild tests r"ERROR: file not found: (.*)", # msgfmt r"/usr/bin/msgfmt: found [0-9]+ fatal errors", # Docker r"Cannot connect to the Docker daemon at " r"unix:///var/run/docker.sock. Is the docker daemon running\?", r"dh_makeshlibs: failing due to earlier errors", # Ruby r"([^:]+)\.rb:[0-9]+:in `([^\'])+\': (.*) \((.*)\)", r".*: \*\*\* ERROR: " r"There where errors/warnings in server logs after running test cases.", r"Errno::EEXIST: File exists @ dir_s_mkdir - .*", r"Test environment was found to be incomplete at configuration time,", r"libtool: error: cannot find the library \'(.*)\' or " r"unhandled argument \'(.*)\'", r"npm ERR\! (.*)", r"install: failed to access \'(.*)\': (.*)", r"MSBUILD: error MSBUILD[0-9]+: Project file \'(.*)\' not found.", r"E: (.*)", r"(.*)\(([0-9]+),([0-9]+)\): Error: .*", # C # r"(.*)\.cs\([0-9]+,[0-9]+\): error CS[0-9]+: .*", r".*Segmentation fault.*", r"a2x: ERROR: (.*) returned non-zero exit status ([0-9]+)", r"-- Configuring incomplete, errors occurred\!", r'Error opening link script "(.*)"', r"cc: error: (.*)", r"\[ERROR\] .*", r"dh_auto_(test|build): error: (.*)", r"tar: This does not look like a tar archive", r"\[DZ\] no (name|version) was ever set", r"\[Runtime\] No -phase or -relationship specified at .* line [0-9]+\.", r"diff: (.*): No such file or directory", r"gpg: signing failed: .*", # mh_install r"Cannot find the jar to install: (.*)", r"ERROR: .*", r"> error: (.*)", r"error: (.*)", r"(.*\.hs):[0-9]+:[0-9]+: error:", r"go1: internal compiler error: .*", ] compiled_secondary_build_failure_regexps = [] for regexp in secondary_build_failure_regexps: try: compiled_secondary_build_failure_regexps.append(re.compile(regexp)) except re.error as e: raise Exception(f"Error compiling {regexp!r}: {e}") from e def find_secondary_build_failure(lines, offset): for lineno in range(max(0, len(lines) - offset), len(lines)): line = lines[lineno].strip("\n") for regexp in compiled_secondary_build_failure_regexps: m = regexp.fullmatch(line) if m: logger.debug('Found match against %r on %r (line %d)', regexp, line, lineno + 1) return SingleLineMatch.from_lines( lines, lineno, origin=f"secondary regex {regexp.pattern}") return None def find_build_failure_description( # noqa: C901 lines: list[str], ) -> tuple[Optional[Match], Optional["Problem"]]: """Find the key failure line in build output. Returns: tuple with (match object, error object) """ OFFSET = 250 # Is this cmake-specific, or rather just kf5 / qmake ? cmake = False # We search backwards for clear errors. for i in range(1, OFFSET): lineno = len(lines) - i if lineno < 0: break if "cmake" in lines[lineno]: cmake = True m, err = _buildlog_consultant_rs.match_lines(lines, lineno) if m: return m, err for matcher in compiled_build_failure_regexps: linenos, err, origin = matcher.match(lines, lineno) if linenos: logger.debug('Found match against %r on %r (lines %r): %r', matcher, [lines[n] for n in linenos], linenos, err) return MultiLineMatch.from_lines(lines, linenos, origin=origin), err # TODO(jelmer): Remove this in favour of CMakeErrorMatcher above. if cmake: missing_file_pat = re.compile( r"\s*The imported target \"(.*)\" references the file" ) binary_pat = re.compile(r" Could NOT find (.*) \(missing: .*\)") cmake_files_pat = re.compile( "^ Could not find a package configuration file provided " 'by "(.*)" with any of the following names:' ) # Urgh, multi-line regexes--- for lineno in range(len(lines)): line = lines[lineno].rstrip("\n") m = re.fullmatch(binary_pat, line) if m: return ( SingleLineMatch.from_lines( lines, lineno, origin=f"direct regex ({binary_pat}"), MissingCommand(m.group(1).lower()), ) m = re.fullmatch(missing_file_pat, line) if m: lineno += 1 while lineno < len(lines) and not line: lineno += 1 if lines[lineno + 2].startswith(" but this file does not exist."): m = re.fullmatch(r'\s*"(.*)"', line) if m: filename = m.group(1) else: filename = line return ( SingleLineMatch.from_lines( lines, lineno, origin=f"direct regex {missing_file_pat}"), MissingFile(filename), ) continue if lineno + 1 < len(lines): m = re.fullmatch( cmake_files_pat, line + " " + lines[lineno + 1].lstrip(" ").strip("\n"), ) if m and lines[lineno + 2] == "\n": i = 3 filenames = [] while lines[lineno + i].strip(): filenames.append(lines[lineno + i].strip()) i += 1 return ( SingleLineMatch.from_lines( lines, lineno, origin="direct regex (cmake)"), CMakeFilesMissing(filenames), ) # And forwards for vague ("secondary") errors. match = find_secondary_build_failure(lines, OFFSET) if match: return match, None return None, None def as_json(m, problem): ret = {} if m: ret["lineno"] = m.lineno ret["line"] = m.line ret["origin"] = m.origin if problem: ret["problem"] = problem.kind try: ret["details"] = problem.json() except NotImplementedError: ret["details"] = None return ret def main(argv=None): import argparse import json parser = argparse.ArgumentParser("analyse-build-log") parser.add_argument("path", type=str, default="-", nargs="?") parser.add_argument("--context", "-c", type=int, default=5) parser.add_argument("--json", action="store_true", help="Output JSON.") parser.add_argument("--debug", action="store_true") parser.add_argument( "--version", action="version", version="%(prog)s " + version_string ) args = parser.parse_args(argv) if args.debug: loglevel = logging.DEBUG else: loglevel = logging.INFO logging.basicConfig(level=loglevel, format="%(message)s") if args.path == '-': args.path = '/dev/stdin' with open(args.path) as f: lines = list(f.readlines()) m, problem = find_build_failure_description(lines) if args.json: ret = as_json(m, problem) json.dump(ret, sys.stdout, indent=4) else: if not m: logging.info("No issues found") else: if len(m.linenos) == 1: logging.info("Issue found at line %d:", m.lineno) else: logging.info( "Issue found at lines %d-%d:", m.linenos[0], m.linenos[-1]) for i in range( max(0, m.offsets[0] - args.context), min(len(lines), m.offsets[-1] + args.context + 1), ): logging.info( " %s %s", ">" if i in m.offsets else " ", lines[i].rstrip("\n") ) if problem: logging.info("Identified issue: %s: %s", problem.kind, problem) if __name__ == "__main__": import sys sys.exit(main(sys.argv[1:])) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/buildlog_consultant/py.typed0000644000175000017500000000000014476651536022726 0ustar00jelmerjelmer././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/buildlog_consultant/sbuild.py0000644000175000017500000010656614476651536023113 0ustar00jelmerjelmer#!/usr/bin/python # Copyright (C) 2019-2021 Jelmer Vernooij # encoding: utf-8 # # 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 import logging import re from collections.abc import Iterator from dataclasses import dataclass from typing import BinaryIO, Optional, Union from . import Match, Problem, SingleLineMatch, version_string from .apt import ( find_apt_get_failure, find_apt_get_update_failure, find_install_deps_failure_description, ) from .autopkgtest import find_autopkgtest_failure_description from .common import ( ChrootNotFound, NoSpaceOnDevice, PatchApplicationFailed, find_build_failure_description, ) __all__ = [ "SbuildFailure", "parse_sbuild_log", "SbuildLog", ] logger = logging.getLogger(__name__) class SbuildFailure(Exception): """Sbuild failed to run.""" def __init__( self, stage: Optional[str], description: Optional[str], error: Optional["Problem"] = None, phase: Optional[Union[tuple[str], tuple[str, Optional[str]]]] = None, section: Optional["SbuildLogSection"] = None, match: Optional[Match] = None, ) -> None: self.stage = stage self.description = description self.error = error self.phase = phase self.section = section self.match = match def __repr__(self) -> str: return "{}({!r}, {!r}, error={!r}, phase={!r})".format( type(self).__name__, self.stage, self.description, self.error, self.phase, ) def json(self): ret = { "stage": self.stage, "phase": self.phase, "section": self.section.title if self.section else None, "origin": self.match.origin if self.match else None, "lineno": ( (self.section.offsets[0] if self.section else 0) + self.match.lineno) if self.match else None, } if self.error: ret["kind"] = self.error.kind try: ret["details"] = self.error.json() except NotImplementedError: ret["details"] = None return ret class DpkgSourceLocalChanges(Problem, kind="unexpected-local-upstream-changes"): diff_file: Optional[str] = None files: Optional[list[str]] = None def __repr__(self) -> str: if self.files is None: return f"<{type(self).__name__}()>" if len(self.files) < 5: return f"{type(self).__name__}({self.files!r})" return "<%s(%d files)>" % (type(self).__name__, len(self.files)) def __str__(self) -> str: if self.files and len(self.files) < 5: return "Tree has local changes: %r" % self.files elif self.files: return "Tree has local changes: %d files" % len(self.files) else: return "Tree has local changes" class DpkgSourceUnrepresentableChanges(Problem, kind="unrepresentable-local-changes"): def __str__(self) -> str: return "Tree has unrepresentable local changes." class DpkgUnwantedBinaryFiles(Problem, kind="unwanted-binary-files"): def __str__(self) -> str: return "Tree has unwanted binary files." class DpkgBinaryFileChanged(Problem, kind="changed-binary-files"): paths: list[str] def __str__(self) -> str: return "Tree has binary files with changes: %r" % self.paths class MissingControlFile(Problem, kind="missing-control-file"): path: str def __str__(self) -> str: return "Tree is missing control file %s" % self.path class UnableToFindUpstreamTarball( Problem, kind="unable-to-find-upstream-tarball"): package: str version: str def __str__(self) -> str: return ( "Unable to find the needed upstream tarball for " f"{self.package}, version {self.version}.") class SourceFormatUnbuildable(Problem, kind="source-format-unbuildable"): source_format: str reason: str def __str__(self) -> str: return "Source format {} unusable: {}".format( self.source_format, self.reason) class SourceFormatUnsupported(Problem, kind="unsupported-source-format"): source_format: str def __str__(self) -> str: return "Source format %r unsupported" % self.source_format class PatchFileMissing(Problem, kind="patch-file-missing"): path: str def __str__(self) -> str: return "Patch file %s missing" % self.path class UnknownMercurialExtraFields( Problem, kind="unknown-mercurial-extra-fields"): field: str def __str__(self) -> str: return "Unknown Mercurial extra fields: %s" % self.field class UpstreamPGPSignatureVerificationFailed( Problem, kind="upstream-pgp-signature-verification-failed"): def __str__(self) -> str: return "Unable to verify the PGP signature on the upstream source" class UScanRequestVersionMissing( Problem, kind="uscan-requested-version-missing"): version: str def __str__(self) -> str: return "UScan can not find requested version %s." % self.version class DebcargoFailure(Problem, kind="debcargo-failed"): reason: str def __str__(self) -> str: if self.reason: return "Debcargo failed: %s" % self.reason else: return "Debcargo failed" class ChangelogParseError(Problem, kind="changelog-parse-failed"): reason: str def __str__(self) -> str: return "Changelog failed to parse: %s" % self.reason class UScanFailed(Problem, kind="uscan-failed"): url: str reason: str def __str__(self) -> str: return f"UScan failed to download {self.url}: {self.reason}." class InconsistentSourceFormat(Problem, kind="inconsistent-source-format"): version: Optional[str] = None source_format: Optional[str] = None def __str__(self) -> str: return "Inconsistent source format between version and source format" class UpstreamMetadataFileParseError( Problem, kind="debian-upstream-metadata-invalid"): path: str reason: str def __str__(self) -> str: return "%s is invalid" % self.path class DpkgSourcePackFailed(Problem, kind="dpkg-source-pack-failed"): reason: Optional[str] = None def __str__(self) -> str: if self.reason: return "Packing source directory failed: %s" % self.reason else: return "Packing source directory failed." class DpkgBadVersion(Problem, kind="dpkg-bad-version"): version: str reason: Optional[str] = None def __str__(self) -> str: if self.reason: return f"Version ({self.version}) is invalid: {self.reason}" else: return "Version (%s) is invalid" % self.version class MissingDebcargoCrate(Problem, kind="debcargo-missing-crate"): crate: str version: Optional[str] = None @classmethod def from_string(cls, text): text = text.strip() if "=" in text: (crate, version) = text.split("=") return cls(crate.strip(), version.strip()) else: return cls(text) def __str__(self) -> str: ret = "debcargo can't find crate %s" % self.crate if self.version: ret += " (version: %s)" % self.version return ret def find_preamble_failure_description( # noqa: C901 lines: list[str], ) -> tuple[Optional[SingleLineMatch], Optional[Problem]]: ret: tuple[Optional[SingleLineMatch], Optional[Problem]] = (None, None) OFFSET = 100 err: Problem for i in range(1, OFFSET): lineno = len(lines) - i if lineno < 0: break line = lines[lineno].strip("\n") m = re.fullmatch( "dpkg-source: error: aborting due to unexpected upstream " "changes, see (.*)", line) if m: diff_file = m.group(1) j = lineno - 1 files: list[str] = [] while j > 0: if lines[j] == ( "dpkg-source: info: local changes detected, " "the modified files are:\n" ): err = DpkgSourceLocalChanges(diff_file, files) return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), err files.append(lines[j].strip()) j -= 1 err = DpkgSourceLocalChanges(diff_file) return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), err if line == "dpkg-source: error: unrepresentable changes to source": err = DpkgSourceUnrepresentableChanges() return SingleLineMatch.from_lines(lines, lineno, origin="direct match"), err if re.match( "dpkg-source: error: detected ([0-9]+) unwanted binary " "file.*", line ): err = DpkgUnwantedBinaryFiles() return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), err m = re.match( "dpkg-source: error: cannot read (.*/debian/control): " "No such file or directory", line, ) if m: err = MissingControlFile(m.group(1)) return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), err m = re.match("dpkg-source: error: .*: No space left on device", line) if m: err = NoSpaceOnDevice() return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), err m = re.match("tar: .*: Cannot write: No space left on device", line) if m: err = NoSpaceOnDevice() return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), err m = re.match( "dpkg-source: error: cannot represent change to (.*): " "binary file contents changed", line, ) if m: err = DpkgBinaryFileChanged([m.group(1)]) return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), err m = re.match( r"dpkg-source: error: source package format \'(.*)\' is not " r"supported: Can\'t locate (.*) in \@INC " r"\(you may need to install the (.*) module\) " r"\(\@INC contains: (.*)\) at \(eval [0-9]+\) line [0-9]+\.", line, ) if m: err = SourceFormatUnsupported(m.group(1)) return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), err m = re.match("E: Failed to package source directory (.*)", line) if m: err = DpkgSourcePackFailed() ret = SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), err m = re.match("E: Bad version unknown in (.*)", line) if m and lines[lineno - 1].startswith("LINE: "): m = re.match( r"dpkg-parsechangelog: warning: .*\(l[0-9]+\): " r"version \'(.*)\' is invalid: (.*)", lines[lineno - 2], ) if m: err = DpkgBadVersion(m.group(1), m.group(2)) return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), err m = re.match("Patch (.*) does not apply \\(enforce with -f\\)\n", line) if m: patchname = m.group(1).split("/")[-1] err = PatchApplicationFailed(patchname) return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), err m = re.match( r"dpkg-source: error: LC_ALL=C patch .* " r"--reject-file=- < .*\/debian\/patches\/([^ ]+) " r"subprocess returned exit status 1", line, ) if m: patchname = m.group(1) err = PatchApplicationFailed(patchname) return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), err m = re.match( "dpkg-source: error: " "can't build with source format '(.*)': " "(.*)", line, ) if m: err = SourceFormatUnbuildable(m.group(1), m.group(2)) return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), err m = re.match( "dpkg-source: error: cannot read (.*): " "No such file or directory", line, ) if m: err = PatchFileMissing(m.group(1).split("/", 1)[1]) return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), err m = re.match( "dpkg-source: error: " "source package format '(.*)' is not supported: " "(.*)", line, ) if m: (unused_match, p) = find_build_failure_description([m.group(2)]) if p is None: p = SourceFormatUnsupported(m.group(1)) return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), p m = re.match( "breezy.errors.NoSuchRevision: " "(.*) has no revision b'(.*)'", line, ) if m: err = MissingRevision(m.group(2).encode()) return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), err m = re.match( r'fatal: ambiguous argument \'(.*)\': ' r'unknown revision or path not in the working tree.', line) if m: err = PristineTarTreeMissing(m.group(1)) return SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), err m = re.match("dpkg-source: error: (.*)", line) if m: err = DpkgSourcePackFailed(m.group(1)) ret = SingleLineMatch.from_lines(lines, lineno, origin="direct regex"), err return ret class DebcargoUnacceptablePredicate(Problem, kind="debcargo-unacceptable-predicate"): crate: str predicate: str def __str__(self) -> str: return "Cannot represent prerelease part of dependency: %s" % (self.predicate) class DebcargoUnacceptableComparator(Problem, kind="debcargo-unacceptable-comparator"): crate: str comparator: str def __str__(self) -> str: return "Cannot represent prerelease part of dependency: %s" % (self.comparator) def _parse_debcargo_failure(m, pl): MORE_TAIL = "\x1b[0m\n" MORE_HEAD1 = "\x1b[1;31mSomething failed: " MORE_HEAD2 = "\x1b[1;31mdebcargo failed: " if pl[-1].endswith(MORE_TAIL): extra = [pl[-1][: -len(MORE_TAIL)]] for line in reversed(pl[:-1]): if extra[0].startswith(MORE_HEAD1): extra[0] = extra[0][len(MORE_HEAD1) :] break if extra[0].startswith(MORE_HEAD2): extra[0] = extra[0][len(MORE_HEAD2) :] break extra.insert(0, line) else: extra = [] if extra and extra[-1].strip() == ( "Try `debcargo update` to update the crates.io index." ): n = re.match(r"Couldn\'t find any crate matching (.*)", extra[-2]) if n: return MissingDebcargoCrate.from_string(n.group(1)) else: return DpkgSourcePackFailed(extra[-2]) elif extra: m = re.match( r"Cannot represent prerelease part of dependency: (.*) Predicate \{ (.*) \}", extra[0], ) if m: return DebcargoUnacceptablePredicate(m.group(1), m.group(2)) m = re.match( r"Cannot represent prerelease part of dependency: (.*) Comparator \{ (.*) \}", extra[0], ) if m: return DebcargoUnacceptableComparator(m.group(1), m.group(2)) else: return DebcargoFailure("".join(extra)) return DebcargoFailure("Debcargo failed to run") class UScanTooManyRequests(Problem, kind="uscan-too-many-requests"): url: str def __str__(self) -> str: return "UScan: %s: too many requests" % self.url BRZ_ERRORS = [ ( "Unable to find the needed upstream tarball for " "package (.*), version (.*)\\.", lambda m, pl: UnableToFindUpstreamTarball(m.group(1), m.group(2)), ), ( "Unknown mercurial extra fields in (.*): b'(.*)'.", lambda m, pl: UnknownMercurialExtraFields(m.group(2)), ), ( r"UScan failed to run: In watchfile (.*), reading webpage " r"(.*) failed: 429 too many requests\.", lambda m, pl: UScanTooManyRequests(m.group(2)) ), ( "UScan failed to run: OpenPGP signature did not verify..", lambda m, pl: UpstreamPGPSignatureVerificationFailed(), ), ( r"Inconsistency between source format and version: " r"version is( not)? native, format is( not)? native\.", lambda m, pl: InconsistentSourceFormat(), ), ( r"Inconsistency between source format and version: " r"version (.*) is( not)? native, format '(.*)' is( not)? native\.", lambda m, pl: InconsistentSourceFormat(m.group(1), m.group(2)), ), ( r"UScan failed to run: In (.*) no matching hrefs " "for version (.*) in watch line", lambda m, pl: UScanRequestVersionMissing(m.group(2)), ), ( r"UScan failed to run: In directory ., downloading \s+" r"(.*) failed: (.*)", lambda m, pl: UScanFailed(m.group(1), m.group(2)), ), ( r"UScan failed to run: In watchfile debian/watch, " r"reading webpage\n (.*) failed: (.*)", lambda m, pl: UScanFailed(m.group(1), m.group(2)), ), ( r"Unable to parse upstream metadata file (.*): (.*)", lambda m, pl: UpstreamMetadataFileParseError(m.group(1), m.group(2)), ), (r"Debcargo failed to run\.", _parse_debcargo_failure), ( r"\[Errno 28\] No space left on device", lambda m, pl: NoSpaceOnDevice(), ), ] _BRZ_ERRORS = [(re.compile(r), fn) for (r, fn) in BRZ_ERRORS] def parse_brz_error(line: str, prior_lines: list[str]) -> tuple[Optional[Problem], str]: error: Problem line = line.strip() for search_re, fn in _BRZ_ERRORS: m = search_re.match(line) if m: error = fn(m, prior_lines) return (error, str(error)) if line.startswith("UScan failed to run"): return (UScanFailed(None, line[len("UScan failed to run: "):]), line) if line.startswith('Unable to parse changelog: '): return (ChangelogParseError(line[len("Unable to parse changelog: "):]), line) return (None, line.split("\n")[0]) class MissingRevision(Problem, kind="missing-revision"): revision: bytes def json(self): return {'revision': self.revision.decode('utf-8')} @classmethod def from_json(cls, json): return cls(revision=json['revision'].encode('utf-8')) def __str__(self) -> str: return "Missing revision: %r" % self.revision class PristineTarTreeMissing(Problem, kind="pristine-tar-missing-tree"): treeish: str def __str__(self) -> str: return "pristine-tar can not find tree %r" % self.treeish def find_creation_session_error(lines): ret = None, None for i in range(len(lines) - 1, 0, -1): line = lines[i] if line.startswith("E: "): ret = SingleLineMatch.from_lines(lines, i, origin="direct regex"), None m = re.fullmatch( "E: Chroot for distribution (.*), architecture (.*) not found\n", line ) if m: return SingleLineMatch.from_lines(lines, i, origin="direct regex"), ChrootNotFound( f"{m.group(1)}-{m.group(2)}-sbuild" ) if line.endswith(": No space left on device\n"): return SingleLineMatch.from_lines(lines, i, origin="direct regex"), NoSpaceOnDevice() return ret def find_brz_build_error(lines): for i in range(len(lines) - 1, 0, -1): line = lines[i] if line.startswith("brz: ERROR: "): rest = [line[len("brz: ERROR: ") :]] for n in lines[i + 1 :]: if n.startswith(" "): rest.append(n) return parse_brz_error("".join(rest), lines[:i]) return (None, None) @dataclass class SbuildLogSection: title: Optional[str] offsets: tuple[int, int] lines: list[str] @dataclass class SbuildLog: sections: list[SbuildLogSection] def get_section(self, title): for section in self.sections: if section.title is None and title is None: return section if section.title and title and section.title.lower() == title.lower(): return section def get_section_lines(self, title): section = self.get_section(title) if section: return section.lines return [] def section_titles(self): return [section.title for section in self.sections] @classmethod def parse(cls, f: BinaryIO): sections = [] for section in parse_sbuild_log(f): logging.debug( "Section %s (lines %d-%d)" % (section.title, section.offsets[0], section.offsets[1]) ) sections.append(section) return cls(sections) def get_failed_stage(self) -> Optional[str]: return find_failed_stage(self.get_section_lines("summary")) def find_failure_fetch_src(sbuildlog, failed_stage): section = sbuildlog.get_section("fetch source files") if not section: logging.warning("expected section: fetch source files") return None section_lines = section.lines if not section_lines[0].strip(): section_lines = section_lines[1:] match: Optional[Match] if len(section_lines) == 1 and section_lines[0].startswith("E: Could not find "): match, error = find_preamble_failure_description( sbuildlog.get_section_lines(None) ) return SbuildFailure("unpack", str(error), error, section=section, match=match) (match, error) = find_apt_get_failure(section.lines) description = "build failed stage %s" % failed_stage return SbuildFailure( failed_stage, description, error=error, phase=None, section=section, match=match ) def find_failure_create_session(sbuildlog, failed_stage): section = sbuildlog.get_section(None) match, error = find_creation_session_error(section.lines) phase = ("create-session",) description = "build failed stage %s" % failed_stage return SbuildFailure( failed_stage, description, error=error, phase=phase, section=section, match=match, ) def find_failure_unpack(sbuildlog, failed_stage): section = sbuildlog.get_section("build") match, error = find_preamble_failure_description(section.lines) if error: return SbuildFailure( failed_stage, str(error), error, section=section, match=match ) description = "build failed stage %s" % failed_stage return SbuildFailure( failed_stage, description, error=error, phase=None, section=section, match=match ) def find_failure_build(sbuildlog, failed_stage): section = sbuildlog.get_section("build") phase = ("build",) section_lines, files = strip_build_tail(section.lines) match, error = find_build_failure_description(section_lines) if error: description = str(error) elif match: description = match.line.rstrip("\n") else: description = "build failed stage %s" % failed_stage return SbuildFailure( failed_stage, description, error=error, phase=phase, section=section, match=match, ) def find_failure_autopkgtest(sbuildlog, failed_stage): focus_section = { "run-post-build-commands": "post build commands", "post-build": "post build", "autopkgtest": "autopkgtest", }[failed_stage] section = sbuildlog.get_section(focus_section) if section is not None: ( match, testname, error, description, ) = find_autopkgtest_failure_description(section.lines) if not description: description = str(error) phase = ("autopkgtest", testname) else: description = None error = None match = None phase = None if not description: description = "build failed stage %s" % failed_stage return SbuildFailure( failed_stage, description, error=error, phase=phase, section=section, match=match, ) def find_failure_apt_get_update(sbuildlog, failed_stage): focus_section, match, error = find_apt_get_update_failure(sbuildlog) if error: description = str(error) elif match: description = match.line.rstrip("\n") else: description = "build failed stage %s" % failed_stage return SbuildFailure( failed_stage, description, error=error, phase=None, section=sbuildlog.get_section(focus_section), match=match, ) def find_failure_arch_check(sbuildlog, failed_stage): section = sbuildlog.get_section( "check architectures", ) (match, error) = find_arch_check_failure_description(section.lines) if error: description = str(error) else: description = "build failed stage %s" % failed_stage return SbuildFailure( failed_stage, description, error=error, phase=None, section=section, match=match ) def find_failure_check_space(sbuildlog, failed_stage): section = sbuildlog.get_section("cleanup") (match, error) = find_check_space_failure_description(section.lines) if error: description = str(error) else: description = "build failed stage %s" % failed_stage return SbuildFailure( failed_stage, description, error=error, phase=None, section=section, match=match ) def find_failure_install_deps(sbuildlog, failed_stage): (focus_section, match, error) = find_install_deps_failure_description(sbuildlog) if error: description = str(error) elif match: if match.line.startswith("E: "): description = match.line[3:].rstrip("\n") else: description = match.line.rstrip("\n") else: description = "build failed stage %s" % failed_stage phase = ("build",) return SbuildFailure( failed_stage, description, error=error, phase=phase, section=sbuildlog.get_section(focus_section), match=match, ) FAILED_STAGE_FAIL_FINDERS = { "fetch-src": find_failure_fetch_src, "create-session": find_failure_create_session, "unpack": find_failure_unpack, "build": find_failure_build, "apt-get-update": find_failure_apt_get_update, "arch-check": find_failure_arch_check, "check-space": find_failure_check_space, "install-deps": find_failure_install_deps, "explain-bd-uninstallable": find_failure_install_deps, "autopkgtest": find_failure_autopkgtest, # We run autopkgtest as only post-build step at the moment. "run-post-build-commands": find_failure_autopkgtest, "post-build": find_failure_autopkgtest, } def worker_failure_from_sbuild_log(f: Union[SbuildLog, BinaryIO]) -> SbuildFailure: match: Optional[Match] if isinstance(f, SbuildLog): sbuildlog = f else: sbuildlog = SbuildLog.parse(f) # TODO(jelmer): Doesn't this do the same thing as the tail? if len(sbuildlog.sections) == 1: match, error = find_preamble_failure_description(sbuildlog.sections[0].lines) if error: return SbuildFailure( "unpack", str(error), error, section=sbuildlog.sections[0], match=match ) failed_stage = sbuildlog.get_failed_stage() try: if failed_stage is None: raise KeyError overall_failure = FAILED_STAGE_FAIL_FINDERS[failed_stage]( sbuildlog, failed_stage ) except KeyError: if failed_stage is not None: logging.warning("unknown failed stage: %s", failed_stage) description = "build failed stage %s" % failed_stage return SbuildFailure( failed_stage, description, error=None, phase=None, section=None, match=None ) else: if overall_failure is not None: return overall_failure description = "build failed" phase = ("buildenv",) if sbuildlog.section_titles() == [None]: section = sbuildlog.sections[0] match, error = find_preamble_failure_description(section.lines) if error is not None: description = str(error) else: (match, error) = find_build_failure_description(section.lines) if match is None: error, description = find_brz_build_error(section.lines) else: description = match.line.rstrip("\n") return SbuildFailure( failed_stage, description, error=error, phase=phase, section=section, match=match, ) return SbuildFailure( failed_stage, description, error=None, phase=phase, section=None, match=None, ) def parse_sbuild_log(f: BinaryIO) -> Iterator[SbuildLogSection]: begin_offset = 1 lines: list[str] = [] title = None sep = b"+" + (b"-" * 78) + b"+" lineno = 0 line = f.readline() lineno += 1 while line: if line.strip() == sep: l1 = f.readline() l2 = f.readline() lineno += 2 if l1.startswith(b"|") and l1.strip().endswith(b"|") and l2.strip() == sep: end_offset = lineno - 3 # Drop trailing empty lines while lines and lines[-1] == "\n": lines.pop(-1) end_offset -= 1 if lines: yield SbuildLogSection(title, (begin_offset, end_offset), lines) title = l1.rstrip()[1:-1].strip().decode(errors="replace") lines = [] begin_offset = lineno else: lines.extend( [ line.decode(errors="replace"), l1.decode(errors="replace"), l2.decode(errors="replace"), ] ) else: lines.append(line.decode(errors="replace")) line = f.readline() lineno += 1 yield SbuildLogSection(title, (begin_offset, lineno), lines) def find_failed_stage(lines: list[str]) -> Optional[str]: for line in lines: if not line.startswith("Fail-Stage: "): continue (key, value) = line.split(": ", 1) return value.strip() return None DEFAULT_LOOK_BACK = 50 def strip_build_tail(lines, look_back=None): if look_back is None: look_back = DEFAULT_LOOK_BACK # Strip off unuseful tail for i, line in enumerate(lines[-look_back:]): if line.startswith("Build finished at "): lines = lines[: len(lines) - (look_back - i)] if lines and lines[-1] == ("-" * 80 + "\n"): lines = lines[:-1] break files = {} current_contents: list[str] = [] header_re = re.compile(r"==\> (.*) \<==\n") for i in range(len(lines) - 1, -1, -1): m = header_re.match(lines[i]) if m: files[m.group(1)] = current_contents current_contents = [] lines = lines[:i] continue return lines, files class ArchitectureNotInList(Problem, kind="arch-not-in-list"): arch: str arch_list: list[str] def __str__(self) -> str: return f"Architecture {self.arch} not a build arch" def find_arch_check_failure_description( lines: list[str], ) -> tuple[SingleLineMatch, Optional[Problem]]: for offset, line in enumerate(lines): m = re.match( r"E: dsc: (.*) not in arch list or does not match any arch " r"wildcards: (.*) -- skipping", line, ) if m: error = ArchitectureNotInList(m.group(1), m.group(2)) return SingleLineMatch.from_lines(lines, offset, origin="direct regex"), error return SingleLineMatch.from_lines(lines, len(lines) - 1, origin="direct regex"), None class InsufficientDiskSpace(Problem, kind="insufficient-disk-space"): needed: int free: int def __str__(self) -> str: return "Insufficient disk space for build. " "Need: %d KiB, free: %s KiB" % ( self.needed, self.free, ) def find_check_space_failure_description( lines, ) -> tuple[Optional[SingleLineMatch], Optional[Problem]]: for offset, line in enumerate(lines): if line == "E: Disk space is probably not sufficient for building.\n": m = re.fullmatch( r"I: Source needs ([0-9]+) KiB, " r"while ([0-9]+) KiB is free.\)\n", lines[offset + 1], ) if m: return ( SingleLineMatch.from_lines(lines, offset, origin="direct regex"), InsufficientDiskSpace(int(m.group(1)), int(m.group(2))), ) return SingleLineMatch.from_lines(lines, offset, origin="direct"), None return None, None def main(argv=None): import argparse import json parser = argparse.ArgumentParser("analyse-sbuild-log") parser.add_argument("--debug", action="store_true", help="Display debug output.") parser.add_argument("--json", action="store_true", help="Output JSON.") parser.add_argument( "--context", "-c", type=int, default=5, help="Number of context lines to print." ) parser.add_argument( "--version", action="version", version="%(prog)s " + version_string ) parser.add_argument("path", type=str) args = parser.parse_args() if args.debug: loglevel = logging.DEBUG elif args.json: loglevel = logging.WARNING else: loglevel = logging.INFO logging.basicConfig(level=loglevel, format="%(message)s") with open(args.path, "rb") as f: sbuildlog = SbuildLog.parse(f) failed_stage = sbuildlog.get_failed_stage() if failed_stage: logging.info("Failed stage: %s" % failed_stage) failure = worker_failure_from_sbuild_log(sbuildlog) if args.json: json.dump(failure.json(), sys.stdout, indent=4) if failure.error: logging.info("Error: %s" % failure.error) if failure.match and failure.section: logging.info( "Failed line: %d:" % (failure.section.offsets[0] + failure.match.lineno) ) for i in range( max(0, failure.match.offset - args.context), min(len(failure.section.lines), failure.match.offset + args.context + 1), ): logging.info( " %s %s", ">" if failure.match.offset == i else " ", failure.section.lines[i].rstrip("\n"), ) if __name__ == "__main__": import sys sys.exit(main(sys.argv)) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1694192519.7426732 buildlog-consultant-0.0.34/buildlog_consultant.egg-info/0000755000175000017500000000000014476651610022724 5ustar00jelmerjelmer././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192519.0 buildlog-consultant-0.0.34/buildlog_consultant.egg-info/PKG-INFO0000644000175000017500000000121014476651607024021 0ustar00jelmerjelmerMetadata-Version: 2.1 Name: buildlog-consultant Version: 0.0.34 Summary: buildlog parser and analyser Author-email: Jelmer Vernooij Project-URL: Homepage, https://github.com/jelmer/buildlog-consultant Project-URL: Repository, https://github.com/jelmer/buildlog-consultant.git Requires-Python: >=3.9 Description-Content-Type: text/markdown Provides-Extra: chatgpt License-File: LICENSE License-File: AUTHORS The 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). ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192519.0 buildlog-consultant-0.0.34/buildlog_consultant.egg-info/SOURCES.txt0000644000175000017500000000200614476651607024614 0ustar00jelmerjelmer.flake8 .gitignore AUTHORS CODE_OF_CONDUCT.md Cargo.lock Cargo.toml LICENSE MANIFEST.in Makefile README.md SECURITY.md disperse.conf pyproject.toml setup.py tox.ini .github/workflows/disperse.yml .github/workflows/pythonpackage.yml .github/workflows/wheels.yaml buildlog-consultant-py/Cargo.lock buildlog-consultant-py/Cargo.toml buildlog-consultant-py/src/lib.rs buildlog_consultant/__init__.py buildlog_consultant/__main__.py buildlog_consultant/apt.py buildlog_consultant/autopkgtest.py buildlog_consultant/chatgpt.py buildlog_consultant/common.py buildlog_consultant/py.typed buildlog_consultant/sbuild.py buildlog_consultant.egg-info/PKG-INFO buildlog_consultant.egg-info/SOURCES.txt buildlog_consultant.egg-info/dependency_links.txt buildlog_consultant.egg-info/entry_points.txt buildlog_consultant.egg-info/requires.txt buildlog_consultant.egg-info/top_level.txt src/common.rs src/lib.rs src/match.rs tests/__init__.py tests/test_apt.py tests/test_autopkgtest.py tests/test_base.py tests/test_common.py tests/test_sbuild.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192519.0 buildlog-consultant-0.0.34/buildlog_consultant.egg-info/dependency_links.txt0000644000175000017500000000000114476651607027000 0ustar00jelmerjelmer ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192519.0 buildlog-consultant-0.0.34/buildlog_consultant.egg-info/entry_points.txt0000644000175000017500000000027214476651607026231 0ustar00jelmerjelmer[console_scripts] analyse-autopkgtest-log = buildlog_consultant.autopkgtest:main analyse-build-log = buildlog_consultant.common:main analyse-sbuild-log = buildlog_consultant.sbuild:main ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192519.0 buildlog-consultant-0.0.34/buildlog_consultant.egg-info/requires.txt0000644000175000017500000000007314476651607025332 0ustar00jelmerjelmerpython_debian PyYAML requirements-parser [chatgpt] openai ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192519.0 buildlog-consultant-0.0.34/buildlog_consultant.egg-info/top_level.txt0000644000175000017500000000002414476651607025460 0ustar00jelmerjelmerbuildlog_consultant ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/disperse.conf0000644000175000017500000000032014476651536017646 0ustar00jelmerjelmername: "buildlog-consultant" timeout_days: 5 tag_name: "v$VERSION" update_version { path: "buildlog_consultant/__init__.py" match: "^__version__ = \\((.*)\\)" new_line: "__version__ = $TUPLED_VERSION" } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/pyproject.toml0000644000175000017500000000311514476651536020102 0ustar00jelmerjelmer[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"] [project.scripts] analyse-sbuild-log = "buildlog_consultant.sbuild:main" analyse-build-log = "buildlog_consultant.common:main" analyse-autopkgtest-log = "buildlog_consultant.autopkgtest:main" [tool.setuptools] packages = ["buildlog_consultant"] include-package-data = false [tool.setuptools.package-data] buildlog_consultant = ["py.typed"] [tool.setuptools.dynamic] version = {attr = "buildlog_consultant.__version__"} [tool.ruff] select = [ "ANN", "D", "E", "F", "I", "UP", ] target-version = "py37" ignore = [ "ANN001", "ANN002", "ANN003", "ANN101", "ANN102", "ANN201", "ANN202", "ANN204", "ANN206", "D100", "D101", "D102", "D103", "D104", "D105", "D107", "E501", ] [tool.ruff.pydocstyle] convention = "google" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1694192519.7426732 buildlog-consultant-0.0.34/setup.cfg0000644000175000017500000000004614476651610017000 0ustar00jelmerjelmer[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/setup.py0000755000175000017500000000037414476651536016707 0ustar00jelmerjelmer#!/usr/bin/python3 from setuptools import setup from setuptools_rust import Binding, RustExtension setup( rust_extensions=[RustExtension("buildlog_consultant._buildlog_consultant_rs", "buildlog-consultant-py/Cargo.toml", binding=Binding.PyO3)], ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1694192519.7426732 buildlog-consultant-0.0.34/src/0000755000175000017500000000000014476651610015746 5ustar00jelmerjelmer././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/src/common.rs0000644000175000017500000022620014476651536017615 0ustar00jelmerjelmeruse crate::r#match::{Error, Matcher, MatcherGroup, RegexLineMatcher}; use crate::regex_line_matcher; use crate::{Match, Problem}; use crate::{MultiLineMatch, Origin, SingleLineMatch}; use pyo3::prelude::*; use regex::Captures; use serde::{Deserialize, Serialize}; use std::borrow::Cow; use std::fmt::Display; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] struct MissingFile { path: std::path::PathBuf, } 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(), }) } } impl Display for MissingFile { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing file: {}", self.path.display()) } } #[derive(Clone, Debug, PartialEq, Eq, Serialize)] struct MissingBuildFile { filename: String, } impl Problem for MissingBuildFile { fn kind(&self) -> Cow { "missing-build-file".into() } fn json(&self) -> serde_json::Value { serde_json::json!({ "filename": self.filename, }) } } impl Display for MissingBuildFile { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing build file: {}", self.filename) } } #[derive(Clone, Debug, PartialEq, Eq)] struct MissingCommandOrBuildFile { 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, }) } } 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)] struct VcsControlDirectoryNeeded { 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, }) } } struct MissingPythonModule { module: String, python_version: Option, 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 { 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, }) } } struct MissingCommand(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, }) } } struct MissingPythonDistribution { distribution: String, python_version: Option, 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, }) } } impl MissingPythonDistribution { pub fn from_requirement_str( text: &str, python_version: Option, ) -> PyResult { Python::with_gil(|py| { let requirement = py .import("requirements.requirement")? .getattr("Requirement")? .call_method1("parse", (text,))?; let distribution = requirement.getattr("name")?.extract::()?; let specs = requirement .getattr("specs")? .extract::>()?; Ok(if specs.len() == 1 && specs[0].0 == ">=" { MissingPythonDistribution { distribution, python_version, minimum_version: Some(specs[0].1.clone()), } } else { MissingPythonDistribution { distribution, python_version, 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(", ")) } } 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(c: &Captures) -> Result>, Error> { let p = c.get(1).unwrap().as_str(); 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) } struct MissingLibrary(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, }) } } struct MissingIntrospectionTypelib(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, }) } } struct MissingPytestFixture(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, }) } } struct UnsupportedPytestConfigOption(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, }) } } struct UnsupportedPytestArguments(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, }) } } struct MissingRPackage { package: String, minimum_version: Option, } impl MissingRPackage { pub fn simple(package: String) -> Self { Self { package, 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, }) } } struct MissingGoPackage { 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, }) } } impl Display for MissingGoPackage { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing Go package: {}", self.package) } } struct MissingCHeader { 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, }) } } impl Display for MissingCHeader { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing C header: {}", self.header) } } impl MissingCHeader { fn new(header: String) -> Self { Self { header } } } struct MissingNodeModule(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, }) } } impl Display for MissingNodeModule { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing Node module: {}", self.0) } } struct MissingNodePackage(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, }) } } impl Display for MissingNodePackage { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing Node package: {}", self.0) } } 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(), )))) } struct MissingConfigure; impl Problem for MissingConfigure { fn kind(&self) -> Cow { "missing-configure".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } } impl Display for MissingConfigure { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing ./configure") } } 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())))) } struct MissingVagueDependency { name: String, url: Option, minimum_version: Option, current_version: Option, } impl MissingVagueDependency { 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, }) } } impl Display for MissingVagueDependency { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing dependency: {}", self.name) } } struct MissingQt; impl Problem for MissingQt { fn kind(&self) -> Cow { "missing-qt".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } } impl Display for MissingQt { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing Qt") } } struct MissingX11; impl Problem for MissingX11 { fn kind(&self) -> Cow { "missing-x11".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } } impl Display for MissingX11 { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing X11") } } struct MissingAutoconfMacro { r#macro: String, need_rebuild: bool, } impl MissingAutoconfMacro { 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, }) } } impl Display for MissingAutoconfMacro { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing autoconf macro: {}", self.r#macro) } } struct DirectoryNonExistant(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, }) } } impl Display for DirectoryNonExistant { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Directory does not exist: {}", self.0) } } 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(), )))); } struct MissingPostgresExtension(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, }) } } impl Display for MissingPostgresExtension { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing PostgreSQL extension: {}", self.0) } } struct MissingPkgConfig { module: String, 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, }) } } 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 { fn new(module: String, minimum_version: Option) -> Self { Self { module, minimum_version, } } fn simple(module: String) -> Self { Self { module, minimum_version: None, } } } 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) } 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, }))) ), ]); } 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.iter().enumerate().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))) } } struct MissingPerlModule { filename: Option, module: String, inc: Option>, 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, }) } } 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 { fn simple(module: &str) -> Self { Self { filename: None, module: module.to_string(), inc: None, minimum_version: None, } } } 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().to_string()))))), regex_line_matcher!(r"^Package '(.*)' required.*", |m| Ok(Some(Box::new(MissingRPackage::simple(m.get(1).unwrap().as_str().to_string()))))), regex_line_matcher!(r"^The '(.*)' package must be installed.*", |m| Ok(Some(Box::new(MissingRPackage::simple(m.get(1).unwrap().as_str().to_string()))))), ]); } 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, ))) } } struct MissingCSharpCompiler; impl Problem for MissingCSharpCompiler { fn kind(&self) -> Cow { "missing-c#-compiler".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } } impl Display for MissingCSharpCompiler { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing C# compiler") } } struct MissingRustCompiler; impl Problem for MissingRustCompiler { fn kind(&self) -> Cow { "missing-rust-compiler".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } } impl Display for MissingRustCompiler { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing Rust compiler") } } struct MissingAssembler; impl Problem for MissingAssembler { fn kind(&self) -> Cow { "missing-assembler".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } } impl Display for MissingAssembler { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing assembler") } } struct AutoconfUnexpectedMacroMatcher; impl Matcher for AutoconfUnexpectedMacroMatcher { fn extract_from_lines( &self, lines: &[&str], offset: usize, ) -> Result, Option>)>, Error> { let regexp1 = regex::Regex::new( r"\./configure: line [0-9]+: syntax error near unexpected token `.+'", ) .unwrap(); if !regexp1.is_match(lines[offset]) { return Ok(None); } let regexp2 = regex::Regex::new(r"^\./configure: line [0-9]+: `[\s\t]*([A-Z0-9_]+)\(.*").unwrap(); let c = regexp2.captures(lines[offset + 1]).unwrap(); if c.len() != 2 { return Ok(None); } let m = MultiLineMatch::new( Origin("autoconf unexpected macro".into()), vec![offset + 1, offset], vec![lines[offset + 1].to_string(), lines[offset].to_string()], ); Ok(Some(( Box::new(m), Some(Box::new(MissingAutoconfMacro { r#macro: c.get(1).unwrap().as_str().to_string(), need_rebuild: true, })), ))) } } struct MissingCargoCrate { crate_name: String, 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 }) } } impl MissingCargoCrate { 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) } } } struct DhWithOrderIncorrect; impl Problem for DhWithOrderIncorrect { fn kind(&self) -> Cow { "debhelper-argument-order".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } } impl Display for DhWithOrderIncorrect { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "dh argument order is incorrect") } } struct NoSpaceOnDevice; impl Problem for NoSpaceOnDevice { fn kind(&self) -> Cow { "no-space-on-device".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } } impl Display for NoSpaceOnDevice { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "No space left on device") } } struct MissingJRE; impl Problem for MissingJRE { fn kind(&self) -> Cow { "missing-jre".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } } impl Display for MissingJRE { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing JRE") } } struct MissingJDK { 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 }) } } impl Display for MissingJDK { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Missing JDK at {}", self.jdk_path) } } struct MissingJDKFile { jdk_path: String, 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 }) } } 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) } } struct MissingPerlFile { filename: String, 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 }) } } 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) } } } struct UnsupportedDebhelperCompatLevel { oldest_supported: u32, 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 }) } } impl Display for UnsupportedDebhelperCompatLevel { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "Request debhlper compat level {} lower than supported {}", self.requested, self.oldest_supported ) } } struct SetuptoolScmVersionIssue; impl Problem for SetuptoolScmVersionIssue { fn kind(&self) -> Cow { "setuptools-scm-version-issue".into() } fn json(&self) -> serde_json::Value { serde_json::json!({}) } } impl Display for SetuptoolScmVersionIssue { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "setuptools_scm was unable to find version") } } lazy_static::lazy_static! { static ref COMMON_MATCHERS: MatcherGroup = MatcherGroup::new(vec![ regex_line_matcher!( r"^make\[[0-9]+\]: \*\*\* No rule to make target '(.*)', needed by '.*'\. Stop\.$", file_not_found ), regex_line_matcher!(r"^[^:]+:\d+: (.*): No such file or directory$", file_not_found_maybe_executable), 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).unwrap()))) }), 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).unwrap())))), 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).unwrap())))), 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: '(.*)'", file_not_found_maybe_executable ), 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 (.*)", file_not_found_maybe_executable), 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))))), ]); } pub fn match_lines( lines: &[&str], offset: usize, ) -> Result, Option>)>, Error> { COMMON_MATCHERS.extract_from_lines(lines, offset) } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/src/lib.rs0000644000175000017500000001454514476651536017102 0ustar00jelmerjelmeruse pyo3::prelude::*; use std::borrow::Cow; use std::collections::HashMap; pub trait Match: Send + Sync + std::fmt::Debug { fn line(&self) -> String; fn origin(&self) -> Origin; fn offset(&self) -> usize; fn lineno(&self) -> usize { self.offset() + 1 } } #[derive(Clone, Debug)] pub struct Origin(String); impl ToString for Origin { fn to_string(&self) -> String { self.0.clone() } } 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 } } impl std::fmt::Debug for SingleLineMatch { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}:{}: {}", self.origin.0, self.lineno(), self.line) } } 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, } } } impl Match for MultiLineMatch { fn line(&self) -> String { self.lines[0].clone() } fn origin(&self) -> Origin { self.origin.clone() } fn offset(&self) -> usize { self.offsets[0] } fn lineno(&self) -> usize { self.offset() + 1 } } impl std::fmt::Debug 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 { fn kind(&self) -> Cow; fn json(&self) -> serde_json::Value; } pub struct PyMatch(PyObject); impl Match for PyMatch { fn line(&self) -> String { Python::with_gil(|py| { let line = self.0.getattr(py, "line").unwrap(); line.extract::(py).unwrap() }) } fn origin(&self) -> Origin { Python::with_gil(|py| { let origin = self.0.getattr(py, "origin").unwrap(); let origin = origin.extract::(py).unwrap(); Origin(origin) }) } fn offset(&self) -> usize { Python::with_gil(|py| { let offset = self.0.getattr(py, "offset").unwrap(); offset.extract::(py).unwrap() }) } } impl std::fmt::Debug for PyMatch { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Python::with_gil(|py| { let s = self .0 .call_method0(py, "__repr__") .unwrap() .extract::(py) .unwrap(); write!(f, "{}", s) }) } } impl PartialEq for PyMatch { fn eq(&self, other: &Self) -> bool { Python::with_gil(|py| { let eq = self .0 .call_method1(py, "__eq__", (other.0.clone(),)) .unwrap(); eq.extract::(py).unwrap() }) } } #[derive(Clone, Debug)] pub struct PyProblem(PyObject); fn py_to_json(py: Python, json: PyObject) -> PyResult { if let Ok(s) = json.extract::(py) { Ok(serde_json::Value::String(s)) } else if let Ok(n) = json.extract::(py) { Ok(serde_json::Value::Number(n.into())) } else if let Ok(b) = json.extract::(py) { Ok(serde_json::Value::Bool(b)) } else if let Ok(l) = json.extract::>(py) { let mut v = Vec::new(); for x in l { v.push(py_to_json(py, x)?); } Ok(serde_json::Value::Array(v)) } else if let Ok(d) = json.extract::>(py) { let mut m = serde_json::Map::new(); for (k, v) in d { let v = py_to_json(py, v)?; m.insert(k, v); } Ok(serde_json::Value::Object(m)) } else { Err(PyErr::new::( "unsupported type", )) } } impl Problem for PyProblem { fn kind(&self) -> Cow { Python::with_gil(|py| { let kind = self.0.getattr(py, "kind").unwrap(); kind.extract::(py).unwrap().into() }) } fn json(&self) -> serde_json::Value { Python::with_gil(|py| { let json = self.0.getattr(py, "json").unwrap(); py_to_json(py, json).unwrap() }) } } impl PartialEq for PyProblem { fn eq(&self, other: &Self) -> bool { Python::with_gil(|py| { let eq = self .0 .call_method1(py, "__eq__", (other.0.clone(),)) .unwrap(); eq.extract::(py).unwrap() }) } } impl std::fmt::Display for PyProblem { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Python::with_gil(|py| { let s = self .0 .call_method0(py, "__str__") .unwrap() .extract::(py) .unwrap(); write!(f, "{}", s) }) } } pub fn find_build_failure_description( lines: &[&str], ) -> (Option>, Option>) { Python::with_gil(|py| { let module = py.import("buildlog_consultant.common").unwrap(); let find_build_failure_description = module.getattr("find_build_failure_description").unwrap(); let result = find_build_failure_description .call1((lines.to_vec(),)) .unwrap(); let (m, p) = result .extract::<(Option, Option)>() .unwrap(); ( m.map(|m| Box::new(PyMatch(m)) as Box), p.map(|p| Box::new(PyProblem(p)) as Box), ) }) } #[cfg(test)] mod test { #[test] fn test_simple() { let (m, p) = super::find_build_failure_description(&[ "make[1]: *** No rule to make target 'nno.autopgen.bin', needed by 'dan-nno.autopgen.bin'. Stop."]); assert!(m.is_some()); assert!(p.is_some()); } } pub mod common; pub mod r#match; ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/src/match.rs0000644000175000017500000000602614476651536017423 0ustar00jelmerjelmeruse 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)), )) }; } 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) } } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1694192519.7426732 buildlog-consultant-0.0.34/tests/0000755000175000017500000000000014476651610016321 5ustar00jelmerjelmer././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/tests/__init__.py0000644000175000017500000000206714476651536020446 0ustar00jelmerjelmer#!/usr/bin/python # Copyright (C) 2019 Jelmer Vernooij # # 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 import unittest def test_suite(): names = [ "apt", "base", "autopkgtest", "common", "sbuild", ] module_names = [__name__ + ".test_" + name for name in names] loader = unittest.TestLoader() return loader.loadTestsFromNames(module_names) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/tests/test_apt.py0000644000175000017500000000636114476651536020533 0ustar00jelmerjelmer#!/usr/bin/python # Copyright (C) 2019-2021 Jelmer Vernooij # # 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 import unittest from buildlog_consultant.apt import ( AptFetchFailure, AptMissingReleaseFile, UnsatisfiedAptDependencies, find_apt_get_failure, ) class FindAptGetFailureDescriptionTests(unittest.TestCase): def run_test(self, lines, lineno, err=None): (match, actual_err) = find_apt_get_failure(lines) if lineno is not None: self.assertEqual(match.line, lines[lineno - 1]) self.assertEqual(match.lineno, lineno) else: self.assertIsNone(match) if err: self.assertEqual(actual_err, err) else: self.assertIs(None, actual_err) def test_make_missing_rule(self): self.run_test( [ """\ E: Failed to fetch http://janitor.debian.net/blah/Packages.xz \ File has unexpected size (3385796 != 3385720). Mirror sync in progress? [IP]\ """ ], 1, AptFetchFailure( "http://janitor.debian.net/blah/Packages.xz", "File has unexpected size (3385796 != 3385720). " "Mirror sync in progress? [IP]", ), ) def test_missing_release_file(self): self.run_test( [ """\ E: The repository 'https://janitor.debian.net blah/ Release' \ does not have a Release file.\ """ ], 1, AptMissingReleaseFile("http://janitor.debian.net/ blah/ Release"), ) def test_vague(self): self.run_test(["E: Stuff is broken"], 1, None) class UnsatisfiedAptDependenciesTests(unittest.TestCase): def test_old_deserialise(self): unsat = UnsatisfiedAptDependencies.from_json( {'relations': [[{"name": "libclang-14-dev", "archqual": "amd64", "restrictions": None, "version": None, "arch": None}]]}) self.assertEqual(unsat, UnsatisfiedAptDependencies([[{"name": "libclang-14-dev", "archqual": "amd64", "version": None, "arch": None, "restrictions": None}]])) def test_new_deserialise(self): unsat = UnsatisfiedAptDependencies.from_json("libclang-14-dev:amd64") self.assertEqual(unsat, UnsatisfiedAptDependencies([[{"name": "libclang-14-dev", "archqual": "amd64", "version": None, "restrictions": None, "arch": None}]])) def test_serialise(self): unsat = UnsatisfiedAptDependencies([[{"name": "libclang-14-dev", "archqual": "amd64", "version": None, "arch": None, "restrictions": None}]]) self.assertEqual(unsat.json(), 'libclang-14-dev:amd64') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/tests/test_autopkgtest.py0000644000175000017500000005054214476651536022321 0ustar00jelmerjelmer#!/usr/bin/python # Copyright (C) 2019-2021 Jelmer Vernooij # # 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 import unittest from buildlog_consultant import SingleLineMatch from buildlog_consultant.autopkgtest import ( AutopkgtestDepChrootDisappeared, AutopkgtestDepsUnsatisfiable, AutopkgtestStderrFailure, AutopkgtestTestbedFailure, AutopkgtestTimedOut, find_autopkgtest_failure_description, ) from buildlog_consultant.common import MissingCommand, MissingFile class FindAutopkgtestFailureDescriptionTests(unittest.TestCase): def test_empty(self): self.assertEqual( (None, None, None, None), find_autopkgtest_failure_description([]) ) def test_no_match(self): self.assertEqual( (SingleLineMatch(0, "blalblala\n"), "blalblala\n", None, None), find_autopkgtest_failure_description(["blalblala\n"]), ) def test_unknown_error(self): self.assertEqual( ( SingleLineMatch(1, "python-bcolz FAIL some error\n"), "python-bcolz", None, "Test python-bcolz failed: some error", ), find_autopkgtest_failure_description( [ "autopkgtest [07:58:03]: @@@@@@@@@@@@@@@@@@@@ summary\n", "python-bcolz FAIL some error\n", ] ), ) def test_timed_out(self): error = AutopkgtestTimedOut() self.assertEqual( ( SingleLineMatch(1, "unit-tests FAIL timed out"), "unit-tests", error, "timed out", ), find_autopkgtest_failure_description( [ "autopkgtest [07:58:03]: @@@@@@@@@@@@@@@@@@@@ summary\n", "unit-tests FAIL timed out", ] ), ) def test_deps(self): error = AutopkgtestDepsUnsatisfiable( [ ( "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"), ( None, "/home/janitor/tmp/tmppvupofwl/build-area/" "bcolz_1.2.1+ds2-4~jan+lint1.dsc", ), ] ) self.assertEqual( ( SingleLineMatch( 2, "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", ), "python-bcolz", error, "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.", ), find_autopkgtest_failure_description( [ "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", ] ), ) error = AutopkgtestDepsUnsatisfiable( [ ( "arg", "/home/janitor/tmp/tmpgbn5jhou/build-area/cmake" "-extras_1.3+17.04.20170310-6~jan+unchanged1_all.deb", ), ("deb", "cmake-extras"), ( None, "/home/janitor/tmp/tmpgbn5jhou/" "build-area/cmake-extras_1.3+17.04.20170310-6~jan.dsc", ), ] ) self.assertEqual( ( SingleLineMatch( 2, "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", ), "intltool", error, "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.", ), find_autopkgtest_failure_description( [ "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.", ] ), ) def test_session_disappeared(self): error = AutopkgtestDepChrootDisappeared() self.assertEqual( ( SingleLineMatch( 3, ": 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", ), None, error, ": 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", ), find_autopkgtest_failure_description( """\ autopkgtest [22:52:18]: starting date: 2021-04-01 autopkgtest [22:52:18]: version 5.16 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 : 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 ' autopkgtest [22:52:19]: ERROR: testbed failure: cannot send to testbed: [Errno 32] Broken pipe """.splitlines( False ) ), ) def test_stderr(self): error = AutopkgtestStderrFailure("some output") self.assertEqual( ( SingleLineMatch(2, "some output"), "intltool", error, "Test intltool failed due to unauthorized stderr output: " "some output", ), find_autopkgtest_failure_description( [ "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", ] ), ) self.assertEqual( ( SingleLineMatch(1, "/tmp/bla: 12: ss: not found"), "intltool", MissingCommand("ss"), "/tmp/bla: 12: ss: not found", ), find_autopkgtest_failure_description( [ "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", ] ), ) self.assertEqual( ( SingleLineMatch( 1, 'command10 FAIL stderr: Can\'t exec "uptime": No such file or directory at ' "/usr/lib/nagios/plugins/check_uptime line 529.", ), "command10", MissingCommand("uptime"), 'Can\'t exec "uptime": No such file or directory at ' "/usr/lib/nagios/plugins/check_uptime line 529.", ), find_autopkgtest_failure_description( [ "autopkgtest [07:58:03]: @@@@@@@@@@@@@@@@@@@@ summary\n", 'command10 FAIL stderr: Can\'t exec "uptime": ' "No such file or directory at " "/usr/lib/nagios/plugins/check_uptime line 529.", ] ), ) def test_testbed_failure(self): error = AutopkgtestTestbedFailure( "sent `copyup /tmp/autopkgtest.9IStGJ/build.0Pm/src/ " "/tmp/autopkgtest.output.icg0g8e6/tests-tree/', got " "`timeout', expected `ok...'" ) self.assertEqual( ( SingleLineMatch( 0, "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", ), None, error, None, ), find_autopkgtest_failure_description( [ "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" ] ), ) def test_testbed_failure_with_test(self): error = AutopkgtestTestbedFailure("testbed auxverb failed with exit code 255") self.assertEqual( ( SingleLineMatch( 3, "autopkgtest [06:59:01]: ERROR: testbed failure: testbed auxverb failed with exit code 255\n", ), "phpunit", error, None, ), find_autopkgtest_failure_description( """\ Removing autopkgtest-satdep (0) ... autopkgtest [06:59:00]: test phpunit: [----------------------- 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 autopkgtest [06:59:01]: ERROR: testbed failure: testbed auxverb \ failed with exit code 255 Exiting with 16 """.splitlines( True ) ), ) def test_test_command_failure(self): self.assertEqual( ( SingleLineMatch( 5, 'Cannot open file "/usr/share/php/Pimple/autoload.php".\n' ), "command2", MissingFile("/usr/share/php/Pimple/autoload.php"), 'Cannot open file "/usr/share/php/Pimple/autoload.php".\n', ), find_autopkgtest_failure_description( """\ Removing autopkgtest-satdep (0) ... autopkgtest [01:30:11]: test command2: phpunit --bootstrap /usr/autoload.php autopkgtest [01:30:11]: test command2: [----------------------- PHPUnit 8.5.2 by Sebastian Bergmann and contributors. Cannot open file "/usr/share/php/Pimple/autoload.php". autopkgtest [01:30:12]: test command2: -----------------------] autopkgtest [01:30:12]: test command2: \ - - - - - - - - - - results - - - - - - - - - - command2 FAIL non-zero exit status 1 autopkgtest [01:30:12]: @@@@@@@@@@@@@@@@@@@@ summary command1 PASS command2 FAIL non-zero exit status 1 Exiting with 4 """.splitlines( True ) ), ) def test_dpkg_failure(self): self.assertEqual( ( SingleLineMatch( 7, '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", ), "runtestsuite", AutopkgtestDepChrootDisappeared(), """\ W: /var/lib/schroot/session/unstable-amd64-\ sbuild-7fb1b836-14f9-4709-8584-cbbae284db97: \ Failed to stat file: No such file or directory""", ), find_autopkgtest_failure_description( """\ autopkgtest [19:19:19]: test require: [----------------------- autopkgtest [19:19:20]: test require: -----------------------] autopkgtest [19:19:20]: test require: \ - - - - - - - - - - results - - - - - - - - - - require PASS autopkgtest [19:19:20]: test runtestsuite: preparing testbed Get:1 file:/tmp/autopkgtest.hdIETy/binaries InRelease Ign:1 file:/tmp/autopkgtest.hdIETy/binaries InRelease 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 """.splitlines( True ) ), ) def test_last_stderr_line(self): self.assertEqual( ( SingleLineMatch( 10, "unmunge FAIL non-zero exit status 2\n" ), "unmunge", None, "Test unmunge failed: non-zero exit status 2", ), find_autopkgtest_failure_description( """\ autopkgtest [17:38:49]: test unmunge: [----------------------- munge: Error: Failed to access "/run/munge/munge.socket.2": \ No such file or directory unmunge: Error: No credential specified autopkgtest [17:38:50]: test unmunge: -----------------------] autopkgtest [17:38:50]: test unmunge: \ - - - - - - - - - - results - - - - - - - - - - unmunge FAIL non-zero exit status 2 autopkgtest [17:38:50]: test unmunge: \ - - - - - - - - - - stderr - - - - - - - - - - munge: Error: Failed to access "/run/munge/munge.socket.2": \ No such file or directory unmunge: Error: No credential specified autopkgtest [17:38:50]: @@@@@@@@@@@@@@@@@@@@ summary unmunge FAIL non-zero exit status 2 Exiting with 4 """.splitlines( True ) ), ) def test_python_error_in_output(self): self.assertEqual( ( SingleLineMatch( 5, "builtins.OverflowError: mktime argument out of range\n" ), "unit-tests-3", None, "builtins.OverflowError: mktime argument out of range\n", ), find_autopkgtest_failure_description( """\ 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 """.splitlines( True ) ), ) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/tests/test_base.py0000644000175000017500000000270314476651536020655 0ustar00jelmerjelmer#!/usr/bin/python # Copyright (C) 2022 Jelmer Vernooij # # 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 import unittest from buildlog_consultant import Problem, problem_clses class DummyProblem(Problem, kind='dummy-problem'): param: str class TestJson(unittest.TestCase): def test_json(self): self.assertEqual(DummyProblem(param="para").json(), {"param": "para"}) ret = DummyProblem.from_json({"param": 'parameter'}) self.assertEqual(ret.kind, "dummy-problem") self.assertEqual(ret.param, "parameter") def test_from_json(self): ret = problem_clses['dummy-problem'].from_json({'param': 'para'}) self.assertIsInstance(ret, DummyProblem) self.assertEqual(ret.kind, "dummy-problem") self.assertEqual(ret.param, "para") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/tests/test_common.py0000644000175000017500000021773014476651536021243 0ustar00jelmerjelmer#!/usr/bin/python # Copyright (C) 2019-2021 Jelmer Vernooij # # 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 import unittest from buildlog_consultant.common import ( CcacheError, CMakeFilesMissing, CMakeNeedExactVersion, DebhelperPatternNotFound, DhAddonLoadFailure, DhLinkDestinationIsDirectory, DhMissingUninstalled, DhUntilUnsupported, DhWithOrderIncorrect, DirectoryNonExistant, DisappearedSymbols, DuplicateDHCompatLevel, MismatchGettextVersions, MissingAssembler, MissingAutoconfMacro, MissingAutomakeInput, MissingBuildFile, MissingCHeader, MissingCMakeComponents, MissingCommand, MissingConfigStatusInput, MissingConfigure, MissingDHCompatLevel, MissingFile, MissingGitIdentity, MissingGoModFile, MissingGoPackage, MissingIntrospectionTypelib, MissingJavaClass, MissingJavaScriptRuntime, MissingJDK, MissingJDKFile, MissingJRE, MissingJVM, MissingLatexFile, MissingLibrary, MissingMavenArtifacts, MissingNodeModule, MissingOCamlPackage, MissingPerlFile, MissingPerlModule, MissingPerlPredeclared, MissingPhpClass, MissingPkgConfig, MissingPythonDistribution, MissingPythonModule, MissingRPackage, MissingRubyGem, MissingSetupPyCommand, MissingSprocketsFile, MissingVagueDependency, MissingValaPackage, MissingVcVersionerVersion, MissingX11, MissingXmlEntity, NeedPgBuildExtUpdateControl, NoSpaceOnDevice, UnknownCertificateAuthority, UnsupportedDebhelperCompatLevel, UnsupportedPytestArguments, UnsupportedPytestConfigOption, UpstartFilePresent, VcsControlDirectoryNeeded, find_build_failure_description, find_secondary_build_failure, ) class FindBuildFailureDescriptionTests(unittest.TestCase): def run_test(self, lines, lineno, err=None): self.maxDiff = None (match, actual_err) = find_build_failure_description(lines) if match is not None: self.assertEqual(match.line, lines[lineno - 1]) self.assertEqual(lineno, match.lineno) else: self.assertIsNone(match) if err: self.assertEqual(actual_err, err) else: self.assertIs(None, actual_err) def test_make_missing_rule(self): self.run_test( [ "make[1]: *** No rule to make target 'nno.autopgen.bin', " "needed by 'dan-nno.autopgen.bin'. Stop." ], 1, MissingBuildFile('nno.autopgen.bin'), ) self.run_test( [ "make[1]: *** No rule to make target '/usr/share/blah/blah', " "needed by 'dan-nno.autopgen.bin'. Stop." ], 1, MissingFile("/usr/share/blah/blah"), ) self.run_test( [ "debian/rules:4: /usr/share/openstack-pkg-tools/pkgos.make: " "No such file or directory" ], 1, MissingFile("/usr/share/openstack-pkg-tools/pkgos.make"), ) def test_git_identity(self): self.run_test( [ "fatal: unable to auto-detect email address " "(got 'jenkins@osuosl167-amd64.(none)')" ], 1, MissingGitIdentity(), ) def test_ioerror(self): self.run_test( [ "E IOError: [Errno 2] No such file or directory: " "'/usr/lib/python2.7/poly1305/rfc7539.txt'" ], 1, MissingFile("/usr/lib/python2.7/poly1305/rfc7539.txt"), ) def test_vignette(self): self.run_test( [ "Error: processing vignette 'uroot-intro.Rnw' failed with diagnostics:", "pdflatex is not available", ], 2, MissingVagueDependency("pdflatex")) def test_upstart_file_present(self): self.run_test( [ "dh_installinit: upstart jobs are no longer supported! " "Please remove debian/sddm.upstart and check if you " "need to add a conffile removal" ], 1, UpstartFilePresent("debian/sddm.upstart"), ) def test_missing_go_mod_file(self): self.run_test( [ "go: go.mod file not found in current directory or any " "parent directory; see 'go help modules'" ], 1, MissingGoModFile()) def test_missing_javascript_runtime(self): self.run_test( [ "ExecJS::RuntimeUnavailable: " "Could not find a JavaScript runtime. " "See https://github.com/rails/execjs for a list " "of available runtimes." ], 1, MissingJavaScriptRuntime(), ) def test_directory_missing(self): self.run_test( [ "debian/components/build: 19: cd: can't cd to rollup-plugin", ], 1, DirectoryNonExistant("rollup-plugin"), ) def test_vcs_control_directory(self): self.run_test( [" > Cannot find '.git' directory"], 1, VcsControlDirectoryNeeded(['git'])) def test_missing_sprockets_file(self): self.run_test( [ "Sprockets::FileNotFound: couldn't find file " "'activestorage' with type 'application/javascript'" ], 1, MissingSprocketsFile("activestorage", "application/javascript"), ) def test_gxx_missing_file(self): self.run_test( [ "g++: error: /usr/lib/x86_64-linux-gnu/libGL.so: " "No such file or directory" ], 1, MissingFile("/usr/lib/x86_64-linux-gnu/libGL.so"), ) def test_build_xml_missing_file(self): self.run_test( ["/<>/build.xml:59: " "/<>/lib does not exist."], 1, MissingBuildFile('lib') ) def test_vignette_builder(self): self.run_test( [" vignette builder 'R.rsp' not found"], 1, MissingRPackage("R.rsp") ) def test_dh_missing_addon(self): self.run_test( [ " 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, DhAddonLoadFailure("pybuild", "Debian/Debhelper/Buildsystem/pybuild.pm"), ) def test_libtoolize_missing_file(self): self.run_test( ["libtoolize: error: '/usr/share/aclocal/ltdl.m4' " "does not exist."], 1, MissingFile("/usr/share/aclocal/ltdl.m4"), ) def test_ruby_missing_file(self): self.run_test( [ "Error: Error: ENOENT: no such file or directory, " "open '/usr/lib/nodejs/requirejs/text.js'" ], 1, MissingFile("/usr/lib/nodejs/requirejs/text.js"), ) def test_vcversioner(self): self.run_test( [ "vcversioner: ['git', '--git-dir', '/build/tmp0tlam4pe/pyee/.git', " "'describe', '--tags', '--long'] failed and " "'/build/tmp0tlam4pe/pyee/version.txt' isn't present." ], 1, MissingVcVersionerVersion(), ) def test_python_missing_file(self): self.run_test( [ "python3.7: can't open file '/usr/bin/blah.py': " "[Errno 2] No such file or directory" ], 1, MissingFile("/usr/bin/blah.py"), ) self.run_test( [ "python3.7: can't open file 'setup.py': " "[Errno 2] No such file or directory" ], 1, MissingBuildFile('setup.py') ) self.run_test( [ "E FileNotFoundError: [Errno 2] " "No such file or directory: " "'/usr/share/firmware-microbit-micropython/firmware.hex'" ], 1, MissingFile("/usr/share/firmware-microbit-micropython/firmware.hex"), ) def test_vague(self): self.run_test( [ "configure: error: Please install gnu flex from http://www.gnu.org/software/flex/" ], 1, MissingVagueDependency("gnu flex", "http://www.gnu.org/software/flex/"), ) self.run_test( ["RuntimeError: cython is missing"], 1, MissingVagueDependency("cython")) self.run_test( [ "configure: error:", "", " Unable to find the Multi Emulator Super System (MESS).", ], 3, MissingVagueDependency("the Multi Emulator Super System (MESS)"), ) self.run_test( ["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, MissingVagueDependency("libwandio", minimum_version="4.0.0")) self.run_test( ["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, MissingVagueDependency("libpcap0.8")) self.run_test( ["Error: Please install xml2 package"], 1, MissingVagueDependency("xml2")) def test_gettext_mismatch(self): self.run_test( ["*** 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, MismatchGettextVersions('0.19', '0.20')) self.run_test( ["configure: error: *** " "No X11! Install X-Windows development headers/libraries! ***"], 1, MissingX11()) def test_multi_line_configure_error(self): self.run_test(["configure: error:", "", " Some other error."], 3, None) self.run_test([ "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, MissingVagueDependency("the Multi Emulator Super System (MESS)")) def test_interpreter_missing(self): self.run_test( [ "/bin/bash: /usr/bin/rst2man: /usr/bin/python: " "bad interpreter: No such file or directory" ], 1, MissingFile("/usr/bin/python"), ) self.run_test( ["env: ‘/<>/socket-activate’: " "No such file or directory"], 1, None, ) def test_webpack_missing(self): self.run_test( [ "ERROR in Entry module not found: " "Error: Can't resolve 'index.js' in '/<>'" ], 1, None, ) def test_installdocs_missing(self): self.run_test( [ 'dh_installdocs: Cannot find (any matches for) "README.txt" ' "(tried in ., debian/tmp)" ], 1, DebhelperPatternNotFound("README.txt", "installdocs", [".", "debian/tmp"]), ) def test_cmake_missing_file(self): self.run_test( """\ 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 """.splitlines( True ), 16, MissingFile("/usr/lib/x86_64-linux-gnu/libEGL.so"), ) def test_meson_missing_git(self): self.run_test( ["meson.build:13:0: ERROR: Git program not found."], 1, MissingCommand("git"), ) def test_meson_missing_lib(self): self.run_test( ["meson.build:85:0: ERROR: C++ shared or static library 'vulkan-1' not found"], 1, MissingLibrary("vulkan-1")) def test_ocaml_library_missing(self): self.run_test( ['Error: Library "camlp-streams" not found.'], 1, MissingOCamlPackage('camlp-streams')) def test_meson_version(self): self.run_test( ["meson.build:1:0: ERROR: Meson version is 0.49.2 but " "project requires >=0.50"], 1, MissingVagueDependency( "meson", minimum_version="0.50", current_version="0.49.2")) self.run_test( ["../meson.build:1:0: ERROR: Meson version is 0.49.2 but " "project requires >=0.50"], 1, MissingVagueDependency( "meson", minimum_version="0.50", current_version="0.49.2")) def test_need_pgbuildext(self): self.run_test( [ "Error: debian/control needs updating from debian/control.in. " "Run 'pg_buildext updatecontrol'." ], 1, NeedPgBuildExtUpdateControl("debian/control", "debian/control.in"), ) def test_cmake_missing_command(self): self.run_test( [ " Could NOT find Git (missing: GIT_EXECUTABLE)", "dh_auto_configure: cd obj-x86_64-linux-gnu && cmake with args", ], 1, MissingCommand("git"), ) def test_cmake_missing_include(self): self.run_test( """\ -- 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") """.splitlines(True), 8, CMakeFilesMissing(['KDEGitCommitHooks.cmake'])) def test_cmake_missing_cmake_files(self): self.run_test( """\ 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 """.splitlines( True ), 1, CMakeFilesMissing(["sensor_msgsConfig.cmake", "sensor_msgs-config.cmake"]), ) self.run_test( """\ 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") """.splitlines(True), 4, MissingCMakeComponents("KF5", ["Plasma", "PlasmaQuick", "Wayland", "ModemManagerQt", "NetworkManagerQt"])) def test_cmake_missing_exact_version(self): self.run_test( """\ 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) """.splitlines( True ), 4, CMakeNeedExactVersion( "SignalProtocol", "2.3.3", "2.3.2", "/usr/lib/x86_64-linux-gnu/libsignal-protocol-c.so", ), ) def test_cmake_missing_vague(self): self.run_test( ["CMake Error at CMakeLists.txt:84 (MESSAGE):", " alut not found"], 2, MissingVagueDependency("alut"), ) self.run_test( ["CMake Error at CMakeLists.txt:213 (message):", " could not find zlib"], 2, MissingVagueDependency("zlib")) self.run_test( """\ -- 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) """.splitlines(True), 5, MissingVagueDependency('GPGME')) def test_dh_compat_dupe(self): self.run_test( [ "dh_autoreconf: debhelper compat level specified both in " "debian/compat and via build-dependency on debhelper-compat" ], 1, DuplicateDHCompatLevel("dh_autoreconf"), ) def test_dh_compat_missing(self): self.run_test( ["dh_clean: Please specify the compatibility level in " "debian/compat"], 1, MissingDHCompatLevel("dh_clean"), ) def test_dh_compat_too_old(self): self.run_test([ "dh_clean: error: Compatibility levels before 7 are no longer " "supported (level 5 requested)"], 1, UnsupportedDebhelperCompatLevel(7, 5)) def test_dh_udeb_shared_library(self): self.run_test( [ "dh_makeshlibs: The udeb libepoxy0-udeb (>= 1.3) does not contain" " any shared libraries but --add-udeb=libepoxy0-udeb (>= 1.3) " "was passed!?" ], 1, ) def test_dh_systemd(self): self.run_test( [ "dh: unable to load addon systemd: dh: The systemd-sequence is " "no longer provided in compat >= 11, please rely on " "dh_installsystemd instead" ], 1, ) def test_dh_before(self): self.run_test( [ "dh: The --before option is not supported any longer (#932537). " "Use override targets instead." ], 1, ) def test_pytest_args(self): self.run_test( ['pytest: error: unrecognized arguments: --cov=janitor ' '--cov-report=html --cov-report=term-missing:skip-covered'], 1, UnsupportedPytestArguments([ '--cov=janitor', '--cov-report=html', '--cov-report=term-missing:skip-covered'])) def test_pytest_config(self): self.run_test( ['INTERNALERROR> pytest.PytestConfigWarning: ' 'Unknown config option: asyncio_mode'], 1, UnsupportedPytestConfigOption('asyncio_mode')) def test_distutils_missing(self): self.run_test( [ "distutils.errors.DistutilsError: Could not find suitable " "distribution for Requirement.parse('pytest-runner')" ], 1, MissingPythonDistribution("pytest-runner", None), ) self.run_test( [ "distutils.errors.DistutilsError: Could not find suitable " "distribution for Requirement.parse('certifi>=2019.3.9')" ], 1, MissingPythonDistribution("certifi", None, "2019.3.9"), ) self.run_test( [ "distutils.errors.DistutilsError: Could not find suitable " "distribution for Requirement.parse('cffi; " 'platform_python_implementation == "CPython"\')' ], 1, MissingPythonDistribution("cffi", None), ) self.run_test( [ "error: Could not find suitable distribution for " "Requirement.parse('gitlab')" ], 1, MissingPythonDistribution("gitlab", None), ) self.run_test( [ "pkg_resources.DistributionNotFound: The 'configparser>=3.5' " "distribution was not found and is required by importlib-metadata" ], 1, MissingPythonDistribution("configparser", None, "3.5"), ) self.run_test( [ "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, MissingPythonDistribution("asynctest", python_version=3), ) self.run_test( [ "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, MissingPythonDistribution("setuptools_scm"), ) def test_lazy_font(self): self.run_test( [ "[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, MissingFile( "/usr/share/texlive/texmf-dist/fonts/opentype/" "public/stix2-otf/STIX2Math.otf" ), ) def test_missing_latex_files(self): self.run_test( ["! LaTeX Error: File `fancyvrb.sty' not found."], 1, MissingLatexFile("fancyvrb.sty"), ) def test_pytest_import(self): self.run_test( ["E ImportError: cannot import name cmod"], 1, MissingPythonModule("cmod") ) self.run_test( ["E ImportError: No module named mock"], 1, MissingPythonModule("mock") ) self.run_test( [ "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, MissingPythonModule("pytest", 2, "4.4.0"), ) self.run_test( [ "ImportError: Error importing plugin " '"tests.plugins.mock_libudev": No module named mock' ], 1, MissingPythonModule("mock"), ) def test_sed(self): self.run_test( ["sed: can't read /etc/locale.gen: No such file or directory"], 1, MissingFile("/etc/locale.gen"), ) def test_python2_import(self): self.run_test( ["ImportError: No module named pytz"], 1, MissingPythonModule("pytz") ) self.run_test(["ImportError: cannot import name SubfieldBase"], 1, None) def test_python3_import(self): self.run_test( ["ModuleNotFoundError: No module named 'django_crispy_forms'"], 1, MissingPythonModule("django_crispy_forms", 3), ) self.run_test( [" ModuleNotFoundError: No module named 'Cython'"], 1, MissingPythonModule("Cython", 3), ) self.run_test( ["ModuleNotFoundError: No module named 'distro'"], 1, MissingPythonModule("distro", 3), ) self.run_test( ["E ModuleNotFoundError: No module named 'twisted'"], 1, MissingPythonModule("twisted", 3), ) self.run_test( [ "E ImportError: cannot import name 'async_poller' " "from 'msrest.polling' " "(/usr/lib/python3/dist-packages/msrest/polling/__init__.py)" ], 1, MissingPythonModule("msrest.polling.async_poller"), ) self.run_test( ["/usr/bin/python3: No module named sphinx"], 1, MissingPythonModule("sphinx", 3), ) self.run_test( [ "Could not import extension sphinx.ext.pngmath (exception: " "No module named pngmath)" ], 1, MissingPythonModule("pngmath"), ) self.run_test( [ "/usr/bin/python3: Error while finding module specification " "for 'pep517.build' " "(ModuleNotFoundError: No module named 'pep517')" ], 1, MissingPythonModule("pep517", python_version=3), ) def test_sphinx(self): self.run_test( [ "There is a syntax error in your configuration file: " "Unknown syntax: Constant" ], 1, None, ) def test_go_missing(self): self.run_test( [ "src/github.com/vuls/config/config.go:30:2: cannot find package " '"golang.org/x/xerrors" in any of:' ], 1, MissingGoPackage("golang.org/x/xerrors"), ) def test_c_header_missing(self): self.run_test( ["cdhit-common.h:39:9: fatal error: zlib.h: No such file " "or directory"], 1, MissingCHeader("zlib.h"), ) self.run_test( [ "/<>/Kernel/Operation_Vector.cpp:15:10: " "fatal error: petscvec.h: No such file or directory" ], 1, MissingCHeader("petscvec.h"), ) self.run_test( [ "src/bubble.h:27:10: fatal error: DBlurEffectWidget: " "No such file or directory" ], 1, MissingCHeader("DBlurEffectWidget"), ) def test_missing_jdk_file(self): self.run_test( [ "> Could not find tools.jar. Please check that " "/usr/lib/jvm/java-8-openjdk-amd64 contains a " "valid JDK installation.", ], 1, MissingJDKFile("/usr/lib/jvm/java-8-openjdk-amd64", "tools.jar"), ) def test_missing_jdk(self): self.run_test( [ "> 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, MissingJDK("/usr/lib/jvm/java-8-openjdk-amd64/jre"), ) def test_missing_jre(self): self.run_test( [ "ERROR: JAVA_HOME is not set and no 'java' command " "could be found in your PATH." ], 1, MissingJRE(), ) def test_node_module_missing(self): self.run_test( ["Error: Cannot find module 'tape'"], 1, MissingNodeModule("tape") ) self.run_test( [ "✖ ERROR: Cannot find module '/<>/test'", ], 1, None, ) self.run_test( ["npm ERR! [!] Error: Cannot find module '@rollup/plugin-buble'"], 1, MissingNodeModule("@rollup/plugin-buble"), ) self.run_test( ["npm ERR! Error: Cannot find module 'fs-extra'"], 1, MissingNodeModule("fs-extra"), ) self.run_test( ["\x1b[1m\x1b[31m[!] \x1b[1mError: Cannot find module '@rollup/plugin-buble'"], 1, MissingNodeModule('@rollup/plugin-buble')) def test_setup_py_command(self): self.run_test( """\ /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' """.splitlines( True ), 12, MissingSetupPyCommand("test"), ) def test_command_missing(self): self.run_test( ["./ylwrap: line 176: yacc: command not found"], 1, MissingCommand("yacc") ) self.run_test(["/bin/sh: 1: cmake: not found"], 1, MissingCommand("cmake")) self.run_test(["sh: 1: git: not found"], 1, MissingCommand("git")) self.run_test( ["/usr/bin/env: ‘python3’: No such file or directory"], 1, MissingCommand("python3"), ) self.run_test( ["%Error: 'flex' must be installed to build"], 1, MissingCommand("flex") ) self.run_test( ['pkg-config: exec: "pkg-config": executable file not found in $PATH'], 1, MissingCommand("pkg-config"), ) self.run_test( ['Can\'t exec "git": No such file or directory at ' "Makefile.PL line 25."], 1, MissingCommand("git"), ) self.run_test( [ "vcver.scm.git.GitCommandError: 'git describe --tags --match 'v*'" " --abbrev=0' returned an error code 127" ], 1, MissingCommand("git"), ) self.run_test( ["make[1]: docker: Command not found"], 1, MissingCommand("docker") ) self.run_test(["make[1]: git: Command not found"], 1, MissingCommand("git")) self.run_test(["make[1]: ./docker: Command not found"], 1, None) self.run_test( ["make: dh_elpa: Command not found"], 1, MissingCommand("dh_elpa") ) self.run_test( ["/bin/bash: valac: command not found"], 1, MissingCommand("valac") ) self.run_test( ["E: Failed to execute “python3”: No such file or directory"], 1, MissingCommand("python3"), ) self.run_test( [ 'Can\'t exec "cmake": No such file or directory at ' "/usr/share/perl5/Debian/Debhelper/Dh_Lib.pm line 484." ], 1, MissingCommand("cmake"), ) self.run_test( [ "Invalid gemspec in [unicorn.gemspec]: " "No such file or directory - git" ], 1, MissingCommand("git"), ) self.run_test( [ "dbus-run-session: failed to exec 'xvfb-run': " "No such file or directory" ], 1, MissingCommand("xvfb-run"), ) self.run_test(["/bin/sh: 1: ./configure: not found"], 1, MissingConfigure()) self.run_test( ["xvfb-run: error: xauth command not found"], 1, MissingCommand("xauth") ) self.run_test( [ "meson.build:39:2: ERROR: Program(s) ['wrc'] " "not found or not executable" ], 1, MissingCommand("wrc"), ) self.run_test( [ "/tmp/autopkgtest.FnbV06/build.18W/src/debian/tests/" "blas-testsuite: 7: dpkg-architecture: not found" ], 1, MissingCommand("dpkg-architecture"), ) self.run_test( [ "Traceback (most recent call last):", ' File "/usr/lib/python3/dist-packages/mesonbuild/mesonmain.py", line 140, in run', " return options.run_func(options)", ' 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)", ' File "/usr/lib/python3/dist-packages/mesonbuild/mdist.py", line 119, in create_dist_git', " git_clone(src_root, distdir)", ' File "/usr/lib/python3/dist-packages/mesonbuild/mdist.py", line 108, in git_clone', " if git_have_dirty_index(src_root):", ' 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'])", ' File "/usr/lib/python3.9/subprocess.py", line 349, in call', " with Popen(*popenargs, **kwargs) as p:", ' File "/usr/lib/python3.9/subprocess.py", line 951, in __init__', " self._execute_child(args, executable, preexec_fn, close_fds,", ' 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, MissingCommand("git"), ) self.run_test( ['> Cannot run program "git": error=2, No such file or directory'], 1, MissingCommand("git"), ) self.run_test( ["E ImportError: Bad git executable"], 1, MissingCommand("git")) self.run_test( ["E ImportError: Bad git executable."], 1, MissingCommand("git")) self.run_test( ["Could not find external command \"java\""], 1, MissingCommand("java")) def test_ts_error(self): self.run_test( [ "blah/tokenizer.ts(175,21): error TS2532: " "Object is possibly 'undefined'." ], 1, None, ) def test_nim_error(self): self.run_test( [ "/<>/msgpack4nim.nim(470, 6) " "Error: usage of 'isNil' is a user-defined error" ], 1, None, ) def test_scala_error(self): self.run_test( [ "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, None, ) def test_vala_error(self): self.run_test( [ "../src/Backend/FeedServer.vala:60.98-60.148: error: " "The name `COLLECTION_CREATE_NONE' does not exist in " "the context of `Secret.CollectionCreateFlags'" ], 1, None, ) self.run_test( [ "error: Package `glib-2.0' not found in specified Vala " "API directories or GObject-Introspection GIR directories" ], 1, MissingValaPackage("glib-2.0"), ) def test_gir(self): self.run_test( ["ValueError: Namespace GnomeDesktop not available"], 1, MissingIntrospectionTypelib("GnomeDesktop")) def test_missing_boost_components(self): self.run_test("""\ 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) """.splitlines(True), 4, MissingCMakeComponents("Boost", [ 'program_options', 'filesystem', 'system', 'graph', 'serialization', 'iostreams'])) def test_pkg_config_too_old(self): self.run_test( [ "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, MissingVagueDependency("pkg-config", minimum_version="0.9.0")) def test_pkg_config_missing(self): self.run_test( [ "configure: error: Package requirements " "(apertium-3.2 >= 3.2.0) were not met:" ], 1, MissingPkgConfig("apertium-3.2", "3.2.0"), ) self.run_test( ["checking for GLEW... configure: error: Package requirements (glew) were not met:"], 1, MissingPkgConfig("glew")) self.run_test( [ 'meson.build:10:0: ERROR: Dependency "gssdp-1.2" not ' "found, tried pkgconfig" ], 1, MissingPkgConfig("gssdp-1.2"), ) self.run_test( [ "src/plugins/sysprof/meson.build:3:0: " 'ERROR: Dependency "sysprof-3" not found, tried pkgconfig' ], 1, MissingPkgConfig("sysprof-3"), ) self.run_test( [ "meson.build:84:0: ERROR: Invalid version of dependency, " "need 'libpeas-1.0' ['>= 1.24.0'] found '1.22.0'." ], 1, MissingPkgConfig("libpeas-1.0", "1.24.0"), ) self.run_test( [ "meson.build:233:0: ERROR: Invalid version of dependency, need 'vte-2.91' ['>=0.63.0'] found '0.62.3'." ], 1, MissingPkgConfig("vte-2.91", "0.63.0"), ) self.run_test(["No package 'tepl-3' found"], 1, MissingPkgConfig("tepl-3")) self.run_test( ["Requested 'vte-2.91 >= 0.59.0' but version of vte is 0.58.2"], 1, MissingPkgConfig("vte-2.91", "0.59.0"), ) self.run_test( ["configure: error: x86_64-linux-gnu-pkg-config sdl2 couldn't " "be found"], 1, MissingPkgConfig("sdl2"), ) self.run_test( ["configure: error: No package 'libcrypto' found"], 1, MissingPkgConfig("libcrypto"), ) self.run_test( [ "-- Checking for module 'gtk+-3.0'", "-- Package 'gtk+-3.0', required by 'virtual:world', not found", ], 2, MissingPkgConfig("gtk+-3.0"), ) self.run_test( [ "configure: error: libfilezilla not found: Package dependency " "requirement 'libfilezilla >= 0.17.1' could not be satisfied." ], 1, MissingPkgConfig("libfilezilla", "0.17.1"), ) def test_pkgconf(self): self.run_test( [ "checking for LAPACK... " 'configure: error: "Cannot check for existence of module lapack without pkgconf"' ], 1, MissingCommand("pkgconf"), ) def test_dh_with_order(self): self.run_test( [ "dh: Unknown sequence --with " "(options should not come before the sequence)" ], 1, DhWithOrderIncorrect(), ) def test_no_disk_space(self): self.run_test( [ "/usr/bin/install: error writing '" "/<>/debian/tmp/usr/lib/gcc/" "x86_64-linux-gnu/8/cc1objplus': No space left on device" ], 1, NoSpaceOnDevice(), ) self.run_test( [ "OSError: [Errno 28] No space left on device", ], 1, NoSpaceOnDevice(), ) def test_segmentation_fault(self): self.run_test( [ "/bin/bash: line 3: 7392 Segmentation fault " 'itstool -m "${mo}" ${d}/C/index.docbook ${d}/C/legal.xml' ], 1, ) def test_missing_perl_plugin(self): self.run_test( [ "Required plugin bundle Dist::Zilla::PluginBundle::Git isn't " "installed." ], 1, MissingPerlModule(None, "Dist::Zilla::PluginBundle::Git", None), ) self.run_test( ["Required plugin Dist::Zilla::Plugin::PPPort isn't installed."], 1, MissingPerlModule(filename=None, module="Dist::Zilla::Plugin::PPPort"), ) def test_perl_expand(self): self.run_test( [">(error): Could not expand [ 'Dist::Inkt::Profile::TOBYINK'"], 1, MissingPerlModule(None, module="Dist::Inkt::Profile::TOBYINK"), ) def test_perl_missing_predeclared(self): self.run_test( [ "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'\"", '"strict subs" in use at Makefile.PL line 13.', ], 2, MissingPerlPredeclared("author_tests"), ) self.run_test( ["String found where operator expected at Makefile.PL line 8, near \"readme_from 'lib/URL/Encode.pod'\""], 1, MissingPerlPredeclared("readme_from")) self.run_test( [ 'Bareword "use_test_base" not allowed while "strict subs" in use at Makefile.PL line 12.' ], 1, MissingPerlPredeclared("use_test_base"), ) def test_unknown_cert_authority(self): self.run_test( [ "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, UnknownCertificateAuthority( "https://proxy.golang.org/github.com/golangci/" "golangci-lint/@v/v1.24.0.mod" ), ) def test_missing_perl_module(self): self.run_test( [ "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, MissingPerlModule( "String/Interpolate.pm", "String::Interpolate", [ "/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", ], ), ) self.run_test( [ "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, MissingPerlModule( "Test/Needs.pm", "Test::Needs", [ "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", ".", ], ), ) self.run_test( ["- ExtUtils::Depends ...missing. (would need 0.302)"], 1, MissingPerlModule(None, "ExtUtils::Depends", None, "0.302")) self.run_test( ['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, MissingPerlModule(None, "Dist::Inkt::Profile::TOBYINK", None)) self.run_test( ["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, MissingPerlModule( "ExtUtils/Depends.pm", "ExtUtils::Depends", [ "/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"])) self.run_test( ["Pod::Weaver::Plugin::WikiDoc (for section -WikiDoc) " "does not appear to be installed"], 1, MissingPerlModule(None, "Pod::Weaver::Plugin::WikiDoc")) self.run_test( ["List::Util version 1.56 required--this is only version 1.55 " "at /build/tmpttq5hhpt/package/blib/lib/List/AllUtils.pm line 8."], 1, MissingPerlModule(None, "List::Util", minimum_version="1.56")) def test_missing_perl_file(self): self.run_test( [ "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, MissingPerlFile( "debian/perldl.conf", [ "/<>/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", ], ), ) self.run_test( ['Can\'t open perl script "Makefile.PL": No such file or directory'], 1, MissingPerlFile("Makefile.PL"), ) def test_missing_maven_artifacts(self): self.run_test( [ "[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, MissingMavenArtifacts( [ "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", ] ), ) self.run_test( [ "[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, MissingMavenArtifacts(["org.apache.opennlp:opennlp-tools:jar:debian"]), ) self.run_test( [ "[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, MissingMavenArtifacts(["io.netty:netty:jar:debian"]), ) self.run_test( [ "[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, MissingMavenArtifacts(["org.apache.felix:maven-bundle-plugin:2.3.7"]), ) self.run_test( [ "[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, MissingMavenArtifacts(["org.apache.maven.plugins:maven-jar-plugin:2.6"]), ) self.run_test( [ "[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, MissingMavenArtifacts(["org.joda:joda-parent:pom:1.4.0"]), ) self.run_test( [ "[ivy:retrieve] \t\t:: " "com.carrotsearch.randomizedtesting#junit4-ant;" "${/com.carrotsearch.randomizedtesting/junit4-ant}: not found" ], 1, MissingMavenArtifacts( ["com.carrotsearch.randomizedtesting:junit4-ant:jar:debian"] ), ) self.run_test( ["[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, MissingMavenArtifacts( ["org.apache.maven.plugins:maven-compiler-plugin:3.10.1"])) def test_maven_errors(self): self.run_test( [ "[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, None, ) def test_dh_missing_uninstalled(self): self.run_test( [ "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, DhMissingUninstalled("usr/lib/x86_64-linux-gnu/libflorence-1.0.la"), ) def test_dh_until_unsupported(self): self.run_test( [ "dh: The --until option is not supported any longer (#932537). " "Use override targets instead." ], 1, DhUntilUnsupported(), ) def test_missing_xml_entity(self): self.run_test( [ "I/O error : Attempt to load network entity " "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" ], 1, MissingXmlEntity("http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd"), ) def test_ccache_error(self): self.run_test( [ "ccache: error: Failed to create directory " "/sbuild-nonexistent/.ccache/tmp: Permission denied" ], 1, CcacheError( "Failed to create directory " "/sbuild-nonexistent/.ccache/tmp: Permission denied" ), ) def test_dh_addon_load_failure(self): self.run_test( [ "dh: unable to load addon nodejs: " "Debian/Debhelper/Sequence/nodejs.pm did not return a true " "value at (eval 11) line 1." ], 1, DhAddonLoadFailure("nodejs", "Debian/Debhelper/Sequence/nodejs.pm"), ) def test_missing_library(self): self.run_test( ["/usr/bin/ld: cannot find -lpthreads"], 1, MissingLibrary("pthreads") ) self.run_test( [ "./testFortranCompiler.f:4: undefined reference to `sgemm_'", ], 1, ) self.run_test( [ "writer.d:59: error: undefined reference to 'sam_hdr_parse_'", ], 1, ) def test_assembler(self): self.run_test(['Found no assembler'], 1, MissingAssembler()) def test_fpic(self): self.run_test( [ "/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, None, ) def test_rspec(self): self.run_test( [ "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, None, ) def test_multiple_definition(self): self.run_test( [ "./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, ) self.run_test( [ "/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, ) def test_missing_ruby_gem(self): self.run_test( [ "Could not find gem 'childprocess (~> 0.5)', which is " "required by gem 'selenium-webdriver', in any of the sources." ], 1, MissingRubyGem("childprocess", "0.5"), ) self.run_test( [ "Could not find gem 'rexml', which is required by gem " "'rubocop', in any of the sources." ], 1, MissingRubyGem("rexml"), ) self.run_test( [ "/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, MissingRubyGem("http-parser", "1.2.0"), ) self.run_test( [ "/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, MissingRubyGem("celluloid", "0.17.3"), ) self.run_test( [ "/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, MissingRubyGem("i18n", "0.7"), ) self.run_test( [ "/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, MissingRubyGem("sassc", "2.0.0"), ) self.run_test( [ "/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, MissingRubyGem("rake-compiler"), ) self.run_test( [ "/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, MissingRubyGem("rdoc", "0.a"), ) def test_missing_php_class(self): self.run_test( [ "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, MissingPhpClass("PHPUnit_Framework_TestCase"), ) def test_missing_java_class(self): self.run_test( """\ 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 """.splitlines(), 1, MissingJavaClass("org.codehaus.Xpp3r$Builder"), ) def test_install_docs_link(self): self.run_test( """\ dh_installdocs: --link-doc not allowed between sympow and sympow-data (one is \ arch:all and the other not)""".splitlines(), 1, ) def test_r_missing(self): self.run_test( [ "ERROR: dependencies ‘ellipsis’, ‘pkgload’ are not available " "for package ‘testthat’" ], 1, MissingRPackage("ellipsis"), ) self.run_test( [ " namespace ‘DBI’ 1.0.0 is being loaded, " "but >= 1.0.0.9003 is required" ], 1, MissingRPackage("DBI", "1.0.0.9003"), ) self.run_test( [ " namespace ‘spatstat.utils’ 1.13-0 is already loaded, " "but >= 1.15.0 is required" ], 1, MissingRPackage("spatstat.utils", "1.15.0"), ) self.run_test( [ "Error in library(zeligverse) : there is no package called " "'zeligverse'" ], 1, MissingRPackage("zeligverse"), ) self.run_test( ["there is no package called 'mockr'"], 1, MissingRPackage("mockr") ) self.run_test( [ "ERROR: dependencies 'igraph', 'matlab', 'expm', 'RcppParallel' are not available for package 'markovchain'" ], 1, MissingRPackage("igraph"), ) self.run_test( [ "Error: package 'BH' 1.66.0-1 was found, but >= 1.75.0.0 is required by 'RSQLite'" ], 1, MissingRPackage("BH", "1.75.0.0"), ) self.run_test( [ "Error: package ‘AnnotationDbi’ 1.52.0 was found, but >= 1.53.1 is required by ‘GO.db’" ], 1, MissingRPackage("AnnotationDbi", "1.53.1")) self.run_test( [" namespace 'alakazam' 1.1.0 is being loaded, but >= 1.1.0.999 is required"], 1, MissingRPackage('alakazam', '1.1.0.999')) def test_mv_stat(self): self.run_test( ["mv: cannot stat '/usr/res/boss.png': No such file or directory"], 1, MissingFile("/usr/res/boss.png"), ) self.run_test(["mv: cannot stat 'res/boss.png': No such file or directory"], 1) def test_dh_link_error(self): self.run_test( [ "dh_link: link destination debian/r-cran-crosstalk/usr/lib/R/" "site-library/crosstalk/lib/ionrangeslider is a directory" ], 1, DhLinkDestinationIsDirectory( "debian/r-cran-crosstalk/usr/lib/R/site-library/crosstalk/" "lib/ionrangeslider" ), ) def test_go_test(self): self.run_test( ["FAIL\tgithub.com/edsrzf/mmap-go\t0.083s"], 1, None, ) def test_debhelper_pattern(self): self.run_test( [ "dh_install: Cannot find (any matches for) " '"server/etc/gnumed/gnumed-restore.conf" ' "(tried in ., debian/tmp)" ], 1, DebhelperPatternNotFound( "server/etc/gnumed/gnumed-restore.conf", "install", [".", "debian/tmp"] ), ) def test_symbols(self): self.run_test( [ "dpkg-gensymbols: error: some symbols or patterns disappeared in " "the symbols file: see diff output below" ], 1, DisappearedSymbols(), ) def test_autoconf_macro(self): self.run_test( ["configure.in:1802: error: possibly undefined macro: " "AC_CHECK_CCA"], 1, MissingAutoconfMacro("AC_CHECK_CCA"), ) self.run_test( ["./configure: line 12569: PKG_PROG_PKG_CONFIG: command not found"], 1, MissingAutoconfMacro("PKG_PROG_PKG_CONFIG"), ) self.run_test( [ "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, MissingAutoconfMacro("PKG_CHECK_MODULES", need_rebuild=True), ) self.run_test( [ "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, MissingAutoconfMacro("PKG_CHECK_MODULES", need_rebuild=True)) def test_autoconf_version(self): self.run_test( ["configure.ac:13: error: Autoconf version 2.71 or higher is required"], 1, MissingVagueDependency("autoconf", minimum_version="2.71")) def test_claws_version(self): self.run_test( ["configure: error: libetpan 0.57 not found"], 1, MissingVagueDependency( 'libetpan', minimum_version='0.57')) def test_config_status_input(self): self.run_test( ["config.status: error: cannot find input file: " "`po/Makefile.in.in'"], 1, MissingConfigStatusInput("po/Makefile.in.in"), ) def test_jvm(self): self.run_test( [ "ERROR: JAVA_HOME is set to an invalid " "directory: /usr/lib/jvm/default-java/" ], 1, MissingJVM(), ) def test_cp(self): self.run_test( [ "cp: cannot stat " "'/<>/debian/patches/lshw-gtk.desktop': " "No such file or directory" ], 1, MissingBuildFile("debian/patches/lshw-gtk.desktop"), ) def test_bash_redir_missing(self): self.run_test( ["/bin/bash: idna-tables-properties.csv: " "No such file or directory"], 1, MissingBuildFile("idna-tables-properties.csv")) def test_automake_input(self): self.run_test( [ "automake: error: cannot open < gtk-doc.make: " "No such file or directory" ], 1, MissingAutomakeInput("gtk-doc.make"), ) def test_shellcheck(self): self.run_test( [ " " * 40 + "^----^ SC2086: " "Double quote to prevent globbing and word splitting." ], 1, None, ) class SecondaryErrorFinder(unittest.TestCase): def assertMatches(self, line): m = find_secondary_build_failure([line], 100) self.assertIsNotNone(m) def assertNotMatches(self, line): m = find_secondary_build_failure([line], 100) self.assertIsNone(m) def test_unknown_option(self): self.assertMatches('Unknown option --foo') self.assertNotMatches('Unknown option --foo, ignoring.') ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/tests/test_sbuild.py0000644000175000017500000000543314476651536021230 0ustar00jelmerjelmer#!/usr/bin/python # Copyright (C) 2019-2021 Jelmer Vernooij # # 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 import unittest from buildlog_consultant.sbuild import ( InconsistentSourceFormat, MissingDebcargoCrate, find_brz_build_error, parse_brz_error, ) class ParseBrzErrorTests(unittest.TestCase): def test_inconsistent_source_format(self): self.assertEqual( ( InconsistentSourceFormat(), "Inconsistent source format between version and source " "format", ), parse_brz_error( "Inconsistency between source format and version: version " "is not native, format is native.", [], ), ) class FindBrzBuildErrorTests(unittest.TestCase): def test_missing_debcargo_crate(self): lines = [ "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", ] err, line = find_brz_build_error(lines) self.assertEqual( line, "debcargo can't find crate version-check (version: 0.9.2)" ) self.assertEqual(err, MissingDebcargoCrate("version-check", "0.9.2")) def test_missing_debcargo_crate2(self): lines = """\ Running 'sbuild -A -s -v' Building using working tree Building package in merge mode Using crate name: utf8parse, version 0.10.1+git20220116.1.dfac57e Updating crates.io index Updating crates.io index \x1b[1;31mdebcargo failed: Couldn't find any crate matching utf8parse =0.10.1 Try `debcargo update` to update the crates.io index.\x1b[0m brz: ERROR: Debcargo failed to run. """.splitlines(True) err, line = find_brz_build_error(lines) self.assertEqual( line, "debcargo can't find crate utf8parse (version: 0.10.1)" ) self.assertEqual(err, MissingDebcargoCrate("utf8parse", "0.10.1")) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1694192478.0 buildlog-consultant-0.0.34/tox.ini0000644000175000017500000000025414476651536016502 0ustar00jelmerjelmer[tox] downloadcache = {toxworkdir}/cache/ [testenv] recreate = True deps = setuptools-rust commands = python setup.py build_ext -i python -m unittest tests.test_suite