ognibuild-0.0.32/.cargo_vcs_info.json0000644000000001360000000000100130720ustar { "git": { "sha1": "98d5bd23b943f8af878f23cec4517a397141fb51" }, "path_in_vcs": "" }ognibuild-0.0.32/.codespellrc000064400000000000000000000000461046102023000141620ustar 00000000000000[codespell] ignore-words-list = crate ognibuild-0.0.32/.flake8000064400000000000000000000003221046102023000130320ustar 00000000000000banned-modules = "silver-platter = Should not use silver-platter" exclude = "build,.eggs/,target/" extend-ignore = E203, E266, E501, W293, W291 max-line-length = 88 max-complexity = 18 select = B,C,E,F,W,T4,B9 ognibuild-0.0.32/.github/CODEOWNERS000064400000000000000000000000121046102023000146060ustar 00000000000000* @jelmer ognibuild-0.0.32/.github/FUNDING.yml000064400000000000000000000000171046102023000150350ustar 00000000000000github: jelmer ognibuild-0.0.32/.github/dependabot.yml000064400000000000000000000012421046102023000160510ustar 00000000000000# Keep GitHub Actions up to date with GitHub's Dependabot... # https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot # https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem version: 2 updates: - package-ecosystem: "github-actions" directory: "/" groups: GitHub_Actions: patterns: - "*" # Group all Actions updates into a single larger pull request schedule: interval: "weekly" - package-ecosystem: "cargo" directory: "/" schedule: interval: "weekly" ognibuild-0.0.32/.github/workflows/disperse.yml000064400000000000000000000002741046102023000176230ustar 00000000000000--- name: Disperse configuration "on": - push jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: jelmer/action-disperse-validate@v2 ognibuild-0.0.32/.github/workflows/rust.yaml000064400000000000000000000026601046102023000171440ustar 00000000000000--- name: Rust on: push: pull_request: env: CARGO_TERM_COLOR: always jobs: rust: runs-on: ${{ matrix.os }} strategy: matrix: os: ['ubuntu-24.04', macos-latest] fail-fast: false steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: "3.x" - name: Update apt cache if: matrix.os == 'ubuntu-24.04' run: sudo apt-get update - name: Install Debian tools on Ubuntu if: matrix.os == 'ubuntu-24.04' run: sudo apt-get install -y mmdebstrap - name: Install system breezy and libapt-pkg-dev if: matrix.os == 'ubuntu-24.04' run: sudo apt-get install -y brz libapt-pkg-dev libpcre3-dev - name: Install breezy run: pip install breezy - name: Install breezy and brz-debian run: pip install \ bzr+https://launchpad.net/brz-debian \ python_apt@git+https://salsa.debian.org/apt-team/python-apt.git@2.5.0 if: matrix.os == 'ubuntu-24.04' - name: Build run: cargo build --verbose - name: Run tests # Exclude debian features: run: cargo test --verbose --no-default-features --features=breezy,dep-server,upstream if: matrix.os != 'ubuntu-24.04' - name: Run tests run: cargo test --verbose if: matrix.os == 'ubuntu-24.04' ognibuild-0.0.32/.gitignore000064400000000000000000000001471046102023000136540ustar 00000000000000.coverage build *~ ognibuild.egg-info dist __pycache__ .eggs *.swp *.swo *.swn .mypy_cache target *.so ognibuild-0.0.32/AUTHORS000064400000000000000000000000431046102023000127270ustar 00000000000000Jelmer Vernooij ognibuild-0.0.32/CODE_OF_CONDUCT.md000064400000000000000000000064231046102023000144660ustar 00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socioeconomic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project lead at jelmer@jelmer.uk. 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 ognibuild-0.0.32/Cargo.lock0000644000004015140000000000100110520ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "addr2line" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "once_cell", "version_check", "zerocopy", ] [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anstream" version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", "windows-sys 0.59.0", ] [[package]] name = "anyhow" version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "arbitrary" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" dependencies = [ "derive_arbitrary", ] [[package]] name = "ascii-canvas" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" dependencies = [ "term", ] [[package]] name = "async-trait" version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", "syn 2.0.89", ] [[package]] name = "atoi" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" dependencies = [ "num-traits", ] [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi 0.1.19", "libc", "winapi", ] [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" dependencies = [ "async-trait", "axum-core", "bytes", "futures-util", "http", "http-body", "http-body-util", "hyper", "hyper-util", "itoa", "matchit", "memchr", "mime", "percent-encoding", "pin-project-lite", "rustversion", "serde", "serde_json", "serde_path_to_error", "serde_urlencoded", "sync_wrapper 1.0.2", "tokio", "tower", "tower-layer", "tower-service", "tracing", ] [[package]] name = "axum-core" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" dependencies = [ "async-trait", "bytes", "futures-util", "http", "http-body", "http-body-util", "mime", "pin-project-lite", "rustversion", "sync_wrapper 1.0.2", "tower-layer", "tower-service", "tracing", ] [[package]] name = "backtrace" version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", "windows-targets 0.52.6", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bit-set" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ "bit-vec 0.6.3", ] [[package]] name = "bit-set" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec 0.8.0", ] [[package]] name = "bit-vec" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bit-vec" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" dependencies = [ "serde", ] [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "boxcar" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f839cdf7e2d3198ac6ca003fd8ebc61715755f41c1cad15ff13df67531e00ed" [[package]] name = "breezyshim" version = "0.1.227" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffe63ba40f1cf5aed5af3577f45cc5692e69f9bbf7b12950a87dd2469b9c3183" dependencies = [ "chrono", "ctor", "debian-changelog", "debian-control", "debversion", "difflib", "dirty-tracker", "lazy-regex", "lazy_static", "log", "patchkit", "percent-encoding", "pyo3", "pyo3-filelike", "serde", "tempfile", "url", ] [[package]] name = "bstr" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" dependencies = [ "memchr", "regex-automata 0.4.9", "serde", ] [[package]] name = "buildlog-consultant" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75ccd37b4516a71bb05059bbf95c2104b0164d5add3bd64b9a10436a4cf054e8" dependencies = [ "chrono", "clap", "debian-control", "debversion", "env_logger 0.11.5", "fancy-regex", "inventory", "lazy-regex", "lazy_static", "log", "maplit", "pep508_rs", "regex", "serde", "serde_json", "serde_yaml", "shlex", "text-size", "textwrap", ] [[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cc" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" dependencies = [ "shlex", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "charset" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1f927b07c74ba84c7e5fe4db2baeb3e996ab2688992e39ac68ce3220a677c7e" dependencies = [ "base64", "encoding_rs", ] [[package]] name = "chrono" version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", "windows-targets 0.52.6", ] [[package]] name = "chumsky" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" dependencies = [ "hashbrown 0.14.5", "stacker", ] [[package]] name = "clap" version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", "quote", "syn 2.0.89", ] [[package]] name = "clap_lex" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "colorchoice" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "concurrent-queue" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] [[package]] name = "configparser" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e57e3272f0190c3f1584272d613719ba5fc7df7f4942fe542e63d949cf3a649b" [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const-random" version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" dependencies = [ "const-random-macro", ] [[package]] name = "const-random-macro" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ "getrandom", "once_cell", "tiny-keccak", ] [[package]] name = "core-foundation" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "countme" version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" [[package]] name = "cpufeatures" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] [[package]] name = "crc" version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-queue" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "csv" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" dependencies = [ "csv-core", "itoa", "ryu", "serde", ] [[package]] name = "csv-core" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" dependencies = [ "memchr", ] [[package]] name = "ctor" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", "syn 2.0.89", ] [[package]] name = "data-encoding" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "deb822-derive" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b6e5cafe61e77421a090e2a33b8a2e4e2ff1b106fd906ebade111307064d981" dependencies = [ "proc-macro2", "quote", "syn 2.0.89", ] [[package]] name = "deb822-lossless" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812bb5c8052a89edc6d45d1bc3b3400e8186dd166e9b0a9520bfa5a2bd8477ee" dependencies = [ "deb822-derive", "pyo3", "regex", "rowan 0.16.1", "serde", ] [[package]] name = "debbugs" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6f8df1ed3ad28bd8380c05e317dcff67cd84957a334ccd713bfa81f9631f5dc" dependencies = [ "debversion", "lazy-regex", "log", "mailparse", "maplit", "reqwest", "tokio", "xmltree", ] [[package]] name = "debian-analyzer" version = "0.158.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a82b2f48997e6d74e716beb0bd364ed3678ba28f615c25f7841a32d115d71da" dependencies = [ "breezyshim", "chrono", "configparser", "deb822-lossless", "debian-changelog", "debian-control", "debian-copyright", "debversion", "dep3", "difflib", "distro-info", "filetime", "hex", "lazy-regex", "lazy_static", "log", "makefile-lossless", "maplit", "merge3", "patchkit", "pyo3", "quote", "reqwest", "semver", "serde", "serde_json", "sha1", "tempfile", "toml_edit", "url", ] [[package]] name = "debian-changelog" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98258e2066472d8d04bfe8a3fab2e7be77fe87b913dab2308a59f13061145814" dependencies = [ "chrono", "debversion", "lazy-regex", "log", "rowan 0.15.16", "textwrap", "whoami", ] [[package]] name = "debian-control" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8a22cedff98f1dde7406971869258ec8837728042cdcd9daf5795b6bc5becb5" dependencies = [ "chrono", "deb822-lossless", "debversion", "pyo3", "regex", "rowan 0.16.1", "url", ] [[package]] name = "debian-copyright" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e893a383c33c4e2689fd3c3121d6e82193211f65e7c483463c816f6b7c29857e" dependencies = [ "deb822-lossless", "debversion", "regex", ] [[package]] name = "debian-watch" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f692668701f5382bea79960cad4798f97689de3e22af638ee43552887c180f4f" dependencies = [ "debversion", "m_lexer", "rowan 0.16.1", "url", ] [[package]] name = "debversion" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b892997e53d52f9ac5c30bdac09cbea6bb1eeb3f93a204b8548774081a44b496" dependencies = [ "chrono", "lazy-regex", "pyo3", "serde", ] [[package]] name = "dep3" version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22009dcff95c439fb3317f9726556e9e3e5ece2ec7c249b667a66f8f35e05018" dependencies = [ "chrono", "deb822-lossless", "url", ] [[package]] name = "der" version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "pem-rfc7468", "zeroize", ] [[package]] name = "derive_arbitrary" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", "syn 2.0.89", ] [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "difflib" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "const-oid", "crypto-common", "subtle", ] [[package]] name = "dirs" version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-next" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" dependencies = [ "cfg-if", "dirs-sys-next", ] [[package]] name = "dirs-sys" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", "option-ext", "redox_users", "windows-sys 0.48.0", ] [[package]] name = "dirs-sys-next" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", "redox_users", "winapi", ] [[package]] name = "dirty-tracker" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57f673af5cabab0d10b822fae4b348c2f5fdc56d90474e26f5dcde0f94fce488" dependencies = [ "notify", "tempfile", ] [[package]] name = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn 2.0.89", ] [[package]] name = "distro-info" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef12237f2ced990e453ec0b69230752e73be0a357817448c50a62f8bbbe0ca71" dependencies = [ "chrono", "csv", "failure", ] [[package]] name = "dlv-list" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" dependencies = [ "const-random", ] [[package]] name = "document_tree" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee380fad9396cb284d9ce0aeccfe179f128ec87ae816186a073a8702b04f8879" dependencies = [ "anyhow", "regex", "serde", "serde_derive", "url", ] [[package]] name = "dotenvy" version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" dependencies = [ "serde", ] [[package]] name = "ena" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" dependencies = [ "log", ] [[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] name = "env_filter" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ "log", "regex", ] [[package]] name = "env_logger" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" dependencies = [ "atty", "humantime 1.3.0", "log", "regex", "termcolor", ] [[package]] name = "env_logger" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" dependencies = [ "atty", "humantime 2.1.0", "log", "regex", "termcolor", ] [[package]] name = "env_logger" version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" dependencies = [ "anstream", "anstyle", "env_filter", "humantime 2.1.0", "log", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "etcetera" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ "cfg-if", "home", "windows-sys 0.48.0", ] [[package]] name = "event-listener" version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ "concurrent-queue", "parking", "pin-project-lite", ] [[package]] name = "failure" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" dependencies = [ "backtrace", "failure_derive", ] [[package]] name = "failure_derive" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", "synstructure 0.12.6", ] [[package]] name = "fancy-regex" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" dependencies = [ "bit-set 0.8.0", "regex-automata 0.4.9", "regex-syntax 0.8.5", ] [[package]] name = "faster-hex" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" dependencies = [ "serde", ] [[package]] name = "fastrand" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "filetime" version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if", "libc", "libredox", "windows-sys 0.59.0", ] [[package]] name = "fixedbitset" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "flume" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ "futures-core", "futures-sink", "spin", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ "foreign-types-shared", ] [[package]] name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "fs-err" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" dependencies = [ "autocfg", ] [[package]] name = "fs_extra" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "fsevent-sys" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" dependencies = [ "libc", ] [[package]] name = "futf" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" dependencies = [ "mac", "new_debug_unreachable", ] [[package]] name = "futures" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-intrusive" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", "lock_api", "parking_lot", ] [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", "syn 2.0.89", ] [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "getopts" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" dependencies = [ "unicode-width 0.1.14", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "gix-actor" version = "0.33.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b24171f514cef7bb4dfb72a0b06dacf609b33ba8ad2489d4c4559a03b7afb3" dependencies = [ "bstr", "gix-date", "gix-utils", "itoa", "thiserror 2.0.3", "winnow", ] [[package]] name = "gix-config" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6649b406ca1f99cb148959cf00468b231f07950f8ec438cc0903cda563606f19" dependencies = [ "bstr", "gix-config-value", "gix-features", "gix-glob", "gix-path", "gix-ref", "gix-sec", "memchr", "once_cell", "smallvec", "thiserror 2.0.3", "unicode-bom", "winnow", ] [[package]] name = "gix-config-value" version = "0.14.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49aaeef5d98390a3bcf9dbc6440b520b793d1bf3ed99317dc407b02be995b28e" dependencies = [ "bitflags 2.6.0", "bstr", "gix-path", "libc", "thiserror 2.0.3", ] [[package]] name = "gix-date" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "691142b1a34d18e8ed6e6114bc1a2736516c5ad60ef3aa9bd1b694886e3ca92d" dependencies = [ "bstr", "itoa", "jiff", "thiserror 2.0.3", ] [[package]] name = "gix-features" version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d85d673f2e022a340dba4713bed77ef2cf4cd737d2f3e0f159d45e0935fd81f" dependencies = [ "gix-hash", "gix-trace", "gix-utils", "libc", "prodash", "sha1_smol", "walkdir", ] [[package]] name = "gix-fs" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34740384d8d763975858fa2c176b68652a6fcc09f616e24e3ce967b0d370e4d8" dependencies = [ "fastrand", "gix-features", "gix-utils", ] [[package]] name = "gix-glob" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aaf69a6bec0a3581567484bf99a4003afcaf6c469fd4214352517ea355cf3435" dependencies = [ "bitflags 2.6.0", "bstr", "gix-features", "gix-path", ] [[package]] name = "gix-hash" version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b5eccc17194ed0e67d49285e4853307e4147e95407f91c1c3e4a13ba9f4e4ce" dependencies = [ "faster-hex", "thiserror 2.0.3", ] [[package]] name = "gix-hashtable" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ef65b256631078ef733bc5530c4e6b1c2e7d5c2830b75d4e9034ab3997d18fe" dependencies = [ "gix-hash", "hashbrown 0.14.5", "parking_lot", ] [[package]] name = "gix-lock" version = "15.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cd3ab68a452db63d9f3ebdacb10f30dba1fa0d31ac64f4203d395ed1102d940" dependencies = [ "gix-tempfile", "gix-utils", "thiserror 2.0.3", ] [[package]] name = "gix-object" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65d93e2bbfa83a307e47f45e45de7b6c04d7375a8bd5907b215f4bf45237d879" dependencies = [ "bstr", "gix-actor", "gix-date", "gix-features", "gix-hash", "gix-hashtable", "gix-utils", "gix-validate", "itoa", "smallvec", "thiserror 2.0.3", "winnow", ] [[package]] name = "gix-path" version = "0.10.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afc292ef1a51e340aeb0e720800338c805975724c1dfbd243185452efd8645b7" dependencies = [ "bstr", "gix-trace", "home", "once_cell", "thiserror 2.0.3", ] [[package]] name = "gix-ref" version = "0.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1eae462723686272a58f49501015ef7c0d67c3e042c20049d8dd9c7eff92efde" dependencies = [ "gix-actor", "gix-features", "gix-fs", "gix-hash", "gix-lock", "gix-object", "gix-path", "gix-tempfile", "gix-utils", "gix-validate", "memmap2", "thiserror 2.0.3", "winnow", ] [[package]] name = "gix-sec" version = "0.10.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8b876ef997a955397809a2ec398d6a45b7a55b4918f2446344330f778d14fd6" dependencies = [ "bitflags 2.6.0", "gix-path", "libc", "windows-sys 0.52.0", ] [[package]] name = "gix-tempfile" version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2feb86ef094cc77a4a9a5afbfe5de626897351bbbd0de3cb9314baf3049adb82" dependencies = [ "gix-fs", "libc", "once_cell", "parking_lot", "tempfile", ] [[package]] name = "gix-trace" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04bdde120c29f1fc23a24d3e115aeeea3d60d8e65bab92cc5f9d90d9302eb952" [[package]] name = "gix-utils" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba427e3e9599508ed98a6ddf8ed05493db114564e338e41f6a996d2e4790335f" dependencies = [ "fastrand", "unicode-normalization", ] [[package]] name = "gix-validate" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd520d09f9f585b34b32aba1d0b36ada89ab7fefb54a8ca3fe37fc482a750937" dependencies = [ "bstr", "thiserror 2.0.3", ] [[package]] name = "h2" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", "http", "indexmap", "slab", "tokio", "tokio-util", "tracing", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", ] [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "hashlink" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" dependencies = [ "hashbrown 0.14.5", ] [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hermit-abi" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac", ] [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ "digest", ] [[package]] name = "home" version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "html5ever" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" dependencies = [ "log", "mac", "markup5ever 0.11.0", "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "html5ever" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e15626aaf9c351bc696217cbe29cb9b5e86c43f8a46b5e2f5c6c5cf7cb904ce" dependencies = [ "log", "mac", "markup5ever 0.14.0", "proc-macro2", "quote", "syn 2.0.89", ] [[package]] name = "http" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", ] [[package]] name = "http-body-util" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", "http", "http-body", "pin-project-lite", ] [[package]] name = "httparse" version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" dependencies = [ "quick-error", ] [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" dependencies = [ "bytes", "futures-channel", "futures-util", "h2", "http", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", "smallvec", "tokio", "want", ] [[package]] name = "hyper-rustls" version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http", "hyper", "hyper-util", "rustls", "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", ] [[package]] name = "hyper-tls" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", "hyper", "hyper-util", "native-tls", "tokio", "tokio-native-tls", "tower-service", ] [[package]] name = "hyper-util" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", "futures-util", "http", "http-body", "hyper", "pin-project-lite", "socket2", "tokio", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "icu_collections" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ "displaydoc", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locid" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_locid_transform" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ "displaydoc", "icu_locid", "icu_locid_transform_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_locid_transform_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" [[package]] name = "icu_normalizer" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "utf16_iter", "utf8_iter", "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" [[package]] name = "icu_properties" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ "displaydoc", "icu_collections", "icu_locid_transform", "icu_properties_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_properties_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" [[package]] name = "icu_provider" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ "displaydoc", "icu_locid", "icu_provider_macros", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_provider_macros" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", "syn 2.0.89", ] [[package]] name = "idna" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "indexmap" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown 0.15.2", "serde", ] [[package]] name = "indoc" version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "inotify" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" dependencies = [ "bitflags 1.3.2", "inotify-sys", "libc", ] [[package]] name = "inotify-sys" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" dependencies = [ "libc", ] [[package]] name = "inventory" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" [[package]] name = "ipnet" version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "is-terminal" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ "hermit-abi 0.4.0", "libc", "windows-sys 0.52.0", ] [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itertools" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jiff" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d9d414fc817d3e3d62b2598616733f76c4cc74fbac96069674739b881295c8" dependencies = [ "jiff-tzdb-platform", "windows-sys 0.59.0", ] [[package]] name = "jiff-tzdb" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91335e575850c5c4c673b9bd467b0e025f164ca59d0564f69d0c2ee0ffad4653" [[package]] name = "jiff-tzdb-platform" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9835f0060a626fe59f160437bc725491a6af23133ea906500027d1bd2f8f4329" dependencies = [ "jiff-tzdb", ] [[package]] name = "js-sys" version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] [[package]] name = "kqueue" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" dependencies = [ "kqueue-sys", "libc", ] [[package]] name = "kqueue-sys" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" dependencies = [ "bitflags 1.3.2", "libc", ] [[package]] name = "lalrpop" version = "0.19.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a1cbf952127589f2851ab2046af368fd20645491bb4b376f04b7f94d7a9837b" dependencies = [ "ascii-canvas", "bit-set 0.5.3", "diff", "ena", "is-terminal", "itertools 0.10.5", "lalrpop-util", "petgraph", "regex", "regex-syntax 0.6.29", "string_cache", "term", "tiny-keccak", "unicode-xid", ] [[package]] name = "lalrpop-util" version = "0.19.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3c48237b9604c5a4702de6b824e02006c3214327564636aef27c1028a8fa0ed" dependencies = [ "regex", ] [[package]] name = "lazy-regex" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d8e41c97e6bc7ecb552016274b99fbb5d035e8de288c582d9b933af6677bfda" dependencies = [ "lazy-regex-proc_macros", "once_cell", "regex", ] [[package]] name = "lazy-regex-proc_macros" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76e1d8b05d672c53cb9c7b920bbba8783845ae4f0b076e02a3db1d02c81b4163" dependencies = [ "proc-macro2", "quote", "regex", "syn 2.0.89", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ "spin", ] [[package]] name = "libc" version = "0.2.166" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2ccc108bbc0b1331bd061864e7cd823c0cab660bbe6970e66e2c0614decde36" [[package]] name = "libm" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libredox" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", "redox_syscall", ] [[package]] name = "libsqlite3-sys" version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", "vcpkg", ] [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "litemap" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "lock_api" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lz4_flex" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" dependencies = [ "twox-hash", ] [[package]] name = "lzma-rs" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" dependencies = [ "byteorder", "crc", ] [[package]] name = "m_lexer" version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7e51ebf91162d585a5bae05e4779efc4a276171cb880d61dd6fab11c98467a7" dependencies = [ "regex", ] [[package]] name = "mac" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" [[package]] name = "mailparse" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3da03d5980411a724e8aaf7b61a7b5e386ec55a7fb49ee3d0ff79efc7e5e7c7e" dependencies = [ "charset", "data-encoding", "quoted_printable", ] [[package]] name = "makefile-lossless" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d2abc0c3de4f7838c3fd8ca411030bc6d5504ea4ccda3791e496c49bd825d0" dependencies = [ "log", "rowan 0.16.1", ] [[package]] name = "maplit" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] name = "markup5ever" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" dependencies = [ "log", "phf 0.10.1", "phf_codegen 0.10.0", "string_cache", "string_cache_codegen", "tendril", ] [[package]] name = "markup5ever" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82c88c6129bd24319e62a0359cb6b958fa7e8be6e19bb1663bc396b90883aca5" dependencies = [ "log", "phf 0.11.2", "phf_codegen 0.11.2", "string_cache", "string_cache_codegen", "tendril", ] [[package]] name = "markup5ever_rcdom" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9521dd6750f8e80ee6c53d65e2e4656d7de37064f3a7a5d2d11d05df93839c2" dependencies = [ "html5ever 0.26.0", "markup5ever 0.11.0", "tendril", "xml5ever", ] [[package]] name = "matchers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ "regex-automata 0.1.10", ] [[package]] name = "matchit" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "md-5" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", "digest", ] [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ "libc", ] [[package]] name = "memoffset" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "merge3" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4b277bc3c7e2bc163abc6c0069f53715b52dc34442c0e807cc8758c7113524f" dependencies = [ "clap", "difflib", ] [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", ] [[package]] name = "mio" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", "wasi", "windows-sys 0.48.0", ] [[package]] name = "mio" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi 0.3.9", "libc", "wasi", "windows-sys 0.52.0", ] [[package]] name = "native-tls" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ "libc", "log", "openssl", "openssl-probe", "openssl-sys", "schannel", "security-framework", "security-framework-sys", "tempfile", ] [[package]] name = "new_debug_unreachable" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nix" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags 2.6.0", "cfg-if", "cfg_aliases", "libc", ] [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "notify" version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" dependencies = [ "bitflags 2.6.0", "crossbeam-channel", "filetime", "fsevent-sys", "inotify", "kqueue", "libc", "log", "mio 0.8.11", "walkdir", "windows-sys 0.48.0", ] [[package]] name = "nu-ansi-term" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ "overload", "winapi", ] [[package]] name = "num-bigint-dig" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" dependencies = [ "byteorder", "lazy_static", "libm", "num-integer", "num-iter", "num-traits", "rand", "smallvec", "zeroize", ] [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-iter" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", ] [[package]] name = "num_enum" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.89", ] [[package]] name = "object" version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "ognibuild" version = "0.0.32" dependencies = [ "axum", "breezyshim", "buildlog-consultant", "chrono", "clap", "deb822-lossless", "debian-analyzer", "debian-changelog", "debian-control", "debversion", "dirs", "env_logger 0.11.5", "flate2", "fs_extra", "inventory", "lazy-regex", "lazy_static", "libc", "log", "lz4_flex", "lzma-rs", "makefile-lossless", "maplit", "nix", "pep508_rs", "percent-encoding", "pyo3", "pyproject-toml", "r-description", "rand", "regex", "reqwest", "semver", "serde", "serde_json", "serde_yaml", "shlex", "sqlx", "stackdriver_logger", "tempfile", "test-log", "tokio", "toml 0.8.19", "toml_edit", "upstream-ontologist", "url", "whoami", "xmltree", ] [[package]] name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "opam-file-rs" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dc9fde26706c9170630772dd86981d874e9a3107cc456c811e1ee234e0c4863" dependencies = [ "lalrpop", "lalrpop-util", "thiserror 1.0.69", ] [[package]] name = "openssl" version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ "bitflags 2.6.0", "cfg-if", "foreign-types", "libc", "once_cell", "openssl-macros", "openssl-sys", ] [[package]] name = "openssl-macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", "syn 2.0.89", ] [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "ordered-multimap" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" dependencies = [ "dlv-list", "hashbrown 0.14.5", ] [[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets 0.52.6", ] [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "patchkit" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e00ed52656b51f535293e40caf6579b4cf09f25e660fc6e6936ab70e78972f" dependencies = [ "chrono", "lazy-regex", "lazy_static", "once_cell", "regex", ] [[package]] name = "pem-rfc7468" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" dependencies = [ "base64ct", ] [[package]] name = "pep440_rs" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0922a442c78611fa8c5ed6065d2d898a820cf12fa90604217fdb2d01675efec7" dependencies = [ "serde", "unicode-width 0.2.0", "unscanny", "version-ranges", ] [[package]] name = "pep508_rs" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c2feee999fa547bacab06a4881bacc74688858b92fa8ef1e206c748b0a76048" dependencies = [ "boxcar", "indexmap", "itertools 0.13.0", "once_cell", "pep440_rs", "regex", "rustc-hash 2.0.0", "serde", "smallvec", "thiserror 1.0.69", "unicode-width 0.2.0", "url", "urlencoding", "version-ranges", ] [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" dependencies = [ "memchr", "thiserror 1.0.69", "ucd-trie", ] [[package]] name = "pest_derive" version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" dependencies = [ "pest", "pest_generator", ] [[package]] name = "pest_generator" version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", "syn 2.0.89", ] [[package]] name = "pest_meta" version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" dependencies = [ "once_cell", "pest", "sha2", ] [[package]] name = "petgraph" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", "indexmap", ] [[package]] name = "phf" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" dependencies = [ "phf_shared 0.10.0", ] [[package]] name = "phf" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" dependencies = [ "phf_shared 0.11.2", ] [[package]] name = "phf_codegen" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" dependencies = [ "phf_generator 0.10.0", "phf_shared 0.10.0", ] [[package]] name = "phf_codegen" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" dependencies = [ "phf_generator 0.11.2", "phf_shared 0.11.2", ] [[package]] name = "phf_generator" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" dependencies = [ "phf_shared 0.10.0", "rand", ] [[package]] name = "phf_generator" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" dependencies = [ "phf_shared 0.11.2", "rand", ] [[package]] name = "phf_shared" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" dependencies = [ "siphasher", ] [[package]] name = "phf_shared" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ "siphasher", ] [[package]] name = "pin-project-lite" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkcs1" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ "der", "pkcs8", "spki", ] [[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", "spki", ] [[package]] name = "pkg-config" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "portable-atomic" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" [[package]] name = "ppv-lite86" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ "zerocopy", ] [[package]] name = "precomputed-hash" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "pretty_env_logger" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" dependencies = [ "env_logger 0.7.1", "log", ] [[package]] name = "proc-macro-crate" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "prodash" version = "29.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a266d8d6020c61a437be704c5e618037588e1985c7dbb7bf8d265db84cffe325" dependencies = [ "log", "parking_lot", ] [[package]] name = "psm" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810" dependencies = [ "cc", ] [[package]] name = "pulldown-cmark" version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f86ba2052aebccc42cbbb3ed234b8b13ce76f75c3551a303cb2bcffcff12bb14" dependencies = [ "bitflags 2.6.0", "getopts", "memchr", "pulldown-cmark-escape", "unicase", ] [[package]] name = "pulldown-cmark-escape" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae" [[package]] name = "pyo3" version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884" dependencies = [ "cfg-if", "chrono", "indoc", "libc", "memoffset", "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", "serde", "unindent", ] [[package]] name = "pyo3-build-config" version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38" dependencies = [ "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636" dependencies = [ "libc", "pyo3-build-config", ] [[package]] name = "pyo3-filelike" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4d4ba4774757be317f112fb7b09f9d2c157a77f07d229a08144f275223f06e8" dependencies = [ "pyo3", ] [[package]] name = "pyo3-macros" version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", "syn 2.0.89", ] [[package]] name = "pyo3-macros-backend" version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe" dependencies = [ "heck", "proc-macro2", "pyo3-build-config", "quote", "syn 2.0.89", ] [[package]] name = "pyproject-toml" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643af57c3f36ba90a8b53e972727d8092f7408a9ebfbaf4c3d2c17b07c58d835" dependencies = [ "indexmap", "pep440_rs", "pep508_rs", "serde", "thiserror 1.0.69", "toml 0.8.19", ] [[package]] name = "python-pkginfo" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3f3f0d552c7efdde2b6898bf21b49c4e76b3e6071ff196dfe52109804db896" dependencies = [ "flate2", "fs-err", "mailparse", "rfc2047-decoder", "tar", "thiserror 1.0.69", "zip", ] [[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "quoted_printable" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" [[package]] name = "r-description" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8ce9b776c5b0430f08ba021444cbb5868a2d8d706fd4ba987233e3729172da9" dependencies = [ "deb822-lossless", "rowan 0.16.1", "serde", "url", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "redox_syscall" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "redox_users" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", "thiserror 1.0.69", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata 0.4.9", "regex-syntax 0.8.5", ] [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ "regex-syntax 0.6.29", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax 0.8.5", ] [[package]] name = "regex-syntax" version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ "base64", "bytes", "encoding_rs", "futures-channel", "futures-core", "futures-util", "h2", "http", "http-body", "http-body-util", "hyper", "hyper-rustls", "hyper-tls", "hyper-util", "ipnet", "js-sys", "log", "mime", "native-tls", "once_cell", "percent-encoding", "pin-project-lite", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "sync_wrapper 1.0.2", "system-configuration", "tokio", "tokio-native-tls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "windows-registry", ] [[package]] name = "rfc2047-decoder" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc36545d1021456a751b573517cb52e8c339b2f662e6b2778ef629282678de29" dependencies = [ "base64", "charset", "chumsky", "memchr", "quoted_printable", "thiserror 2.0.3", ] [[package]] name = "ring" version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", "getrandom", "libc", "spin", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rowan" version = "0.15.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a542b0253fa46e632d27a1dc5cf7b930de4df8659dc6e720b647fc72147ae3d" dependencies = [ "countme", "hashbrown 0.14.5", "rustc-hash 1.1.0", "text-size", ] [[package]] name = "rowan" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "417a3a9f582e349834051b8a10c8d71ca88da4211e4093528e36b9845f6b5f21" dependencies = [ "countme", "hashbrown 0.14.5", "rustc-hash 1.1.0", "text-size", ] [[package]] name = "rsa" version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" dependencies = [ "const-oid", "digest", "num-bigint-dig", "num-integer", "num-traits", "pkcs1", "pkcs8", "rand_core", "signature", "spki", "subtle", "zeroize", ] [[package]] name = "rst_renderer" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30f290036cf1302ee0732fbc8b78c2f416ebd8cbeaeccb7e0802b008553bddd0" dependencies = [ "anyhow", "document_tree", "serde-xml-rs", "serde_json", ] [[package]] name = "rust-ini" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e310ef0e1b6eeb79169a1171daf9abcb87a2e17c03bee2c4bb100b55c75409f" dependencies = [ "cfg-if", "ordered-multimap", "trim-in-place", ] [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc-hash" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" [[package]] name = "rustix" version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "rustls" version = "0.23.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" dependencies = [ "once_cell", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-pemfile" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ "rustls-pki-types", ] [[package]] name = "rustls-pki-types" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" [[package]] name = "rustls-webpki" version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", "untrusted", ] [[package]] name = "rustversion" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "schannel" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "select" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f9da09dc3f4dfdb6374cbffff7a2cffcec316874d4429899eefdc97b3b94dcd" dependencies = [ "bit-set 0.5.3", "html5ever 0.26.0", "markup5ever_rcdom", ] [[package]] name = "semver" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" dependencies = [ "serde", ] [[package]] name = "serde" version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde-xml-rs" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65162e9059be2f6a3421ebbb4fef3e74b7d9e7c60c50a0e292c6239f19f1edfa" dependencies = [ "log", "serde", "thiserror 1.0.69", "xml-rs", ] [[package]] name = "serde_derive" version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", "syn 2.0.89", ] [[package]] name = "serde_json" version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "serde_path_to_error" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" dependencies = [ "itoa", "serde", ] [[package]] name = "serde_spanned" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] [[package]] name = "serde_urlencoded" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", "itoa", "ryu", "serde", ] [[package]] name = "serde_yaml" version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ "indexmap", "itoa", "ryu", "serde", "unsafe-libyaml", ] [[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sha1_smol" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" [[package]] name = "sha2" version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sharded-slab" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "signature" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", "rand_core", ] [[package]] name = "siphasher" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" dependencies = [ "serde", ] [[package]] name = "smawk" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" dependencies = [ "lock_api", ] [[package]] name = "spki" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", ] [[package]] name = "sqlformat" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" dependencies = [ "nom", "unicode_categories", ] [[package]] name = "sqlx" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" dependencies = [ "sqlx-core", "sqlx-macros", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", ] [[package]] name = "sqlx-core" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" dependencies = [ "atoi", "byteorder", "bytes", "crc", "crossbeam-queue", "either", "event-listener", "futures-channel", "futures-core", "futures-intrusive", "futures-io", "futures-util", "hashbrown 0.14.5", "hashlink", "hex", "indexmap", "log", "memchr", "native-tls", "once_cell", "paste", "percent-encoding", "serde", "serde_json", "sha2", "smallvec", "sqlformat", "thiserror 1.0.69", "tokio", "tokio-stream", "tracing", "url", ] [[package]] name = "sqlx-macros" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", "syn 2.0.89", ] [[package]] name = "sqlx-macros-core" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" dependencies = [ "dotenvy", "either", "heck", "hex", "once_cell", "proc-macro2", "quote", "serde", "serde_json", "sha2", "sqlx-core", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", "syn 2.0.89", "tempfile", "tokio", "url", ] [[package]] name = "sqlx-mysql" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" dependencies = [ "atoi", "base64", "bitflags 2.6.0", "byteorder", "bytes", "crc", "digest", "dotenvy", "either", "futures-channel", "futures-core", "futures-io", "futures-util", "generic-array", "hex", "hkdf", "hmac", "itoa", "log", "md-5", "memchr", "once_cell", "percent-encoding", "rand", "rsa", "serde", "sha1", "sha2", "smallvec", "sqlx-core", "stringprep", "thiserror 1.0.69", "tracing", "whoami", ] [[package]] name = "sqlx-postgres" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" dependencies = [ "atoi", "base64", "bitflags 2.6.0", "byteorder", "crc", "dotenvy", "etcetera", "futures-channel", "futures-core", "futures-io", "futures-util", "hex", "hkdf", "hmac", "home", "itoa", "log", "md-5", "memchr", "once_cell", "rand", "serde", "serde_json", "sha2", "smallvec", "sqlx-core", "stringprep", "thiserror 1.0.69", "tracing", "whoami", ] [[package]] name = "sqlx-sqlite" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" dependencies = [ "atoi", "flume", "futures-channel", "futures-core", "futures-executor", "futures-intrusive", "futures-util", "libsqlite3-sys", "log", "percent-encoding", "serde", "serde_urlencoded", "sqlx-core", "tracing", "url", ] [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "stackdriver_logger" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7935e3a17a0164bf9b908908b07514326119a677aab39370e957a4cd76d8dd7" dependencies = [ "chrono", "env_logger 0.9.3", "log", "pretty_env_logger", "serde_json", "toml 0.5.11", ] [[package]] name = "stacker" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b" dependencies = [ "cc", "cfg-if", "libc", "psm", "windows-sys 0.59.0", ] [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "string_cache" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" dependencies = [ "new_debug_unreachable", "once_cell", "parking_lot", "phf_shared 0.10.0", "precomputed-hash", "serde", ] [[package]] name = "string_cache_codegen" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" dependencies = [ "phf_generator 0.10.0", "phf_shared 0.10.0", "proc-macro2", "quote", ] [[package]] name = "stringprep" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" dependencies = [ "unicode-bidi", "unicode-normalization", "unicode-properties", ] [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[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.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sync_wrapper" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] [[package]] name = "synstructure" version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", "unicode-xid", ] [[package]] name = "synstructure" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", "syn 2.0.89", ] [[package]] name = "system-configuration" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.6.0", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "tar" version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6" dependencies = [ "filetime", "libc", "xattr", ] [[package]] name = "target-lexicon" version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", "once_cell", "rustix", "windows-sys 0.59.0", ] [[package]] name = "tendril" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" dependencies = [ "futf", "mac", "utf-8", ] [[package]] name = "term" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" dependencies = [ "dirs-next", "rustversion", "winapi", ] [[package]] name = "termcolor" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] name = "test-log" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dffced63c2b5c7be278154d76b479f9f9920ed34e7574201407f0b14e2bbb93" dependencies = [ "env_logger 0.11.5", "test-log-macros", "tracing-subscriber", ] [[package]] name = "test-log-macros" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" dependencies = [ "proc-macro2", "quote", "syn 2.0.89", ] [[package]] name = "text-size" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" [[package]] name = "textwrap" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" dependencies = [ "smawk", "unicode-linebreak", "unicode-width 0.1.14", ] [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" dependencies = [ "thiserror-impl 2.0.3", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn 2.0.89", ] [[package]] name = "thiserror-impl" version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" dependencies = [ "proc-macro2", "quote", "syn 2.0.89", ] [[package]] name = "thread_local" version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", ] [[package]] name = "tiny-keccak" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" dependencies = [ "crunchy", ] [[package]] name = "tinystr" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tinyvec" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", "bytes", "libc", "mio 1.0.2", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", "syn 2.0.89", ] [[package]] name = "tokio-native-tls" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", ] [[package]] name = "tokio-rustls" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ "rustls", "rustls-pki-types", "tokio", ] [[package]] name = "tokio-stream" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", "tokio", ] [[package]] name = "tokio-util" version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", ] [[package]] name = "toml" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] [[package]] name = "toml" version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "tower" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" dependencies = [ "futures-core", "futures-util", "pin-project-lite", "sync_wrapper 0.1.2", "tokio", "tower-layer", "tower-service", "tracing", ] [[package]] name = "tower-layer" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", "syn 2.0.89", ] [[package]] name = "tracing-core" version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", ] [[package]] name = "tracing-log" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ "log", "once_cell", "tracing-core", ] [[package]] name = "tracing-subscriber" version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term", "once_cell", "regex", "sharded-slab", "thread_local", "tracing", "tracing-core", "tracing-log", ] [[package]] name = "trim-in-place" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "twox-hash" version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", "static_assertions", ] [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicase" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" [[package]] name = "unicode-bidi" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-bom" version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-linebreak" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-normalization" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-properties" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-width" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "unicode_categories" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" [[package]] name = "unindent" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "unsafe-libyaml" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "unscanny" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9df2af067a7953e9c3831320f35c1cc0600c30d44d9f7a12b01db1cd88d6b47" [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "uo_rst_parser" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80277544afabbdd7ddd9117d7b5b2d61dba3819609f8810c619e590d8d1d8cdb" dependencies = [ "anyhow", "document_tree", "pest", "pest_derive", ] [[package]] name = "upstream-ontologist" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0b5a4643021ddac16ffb4f290449dfe303958a50b244ac614bf4de5d331aefc" dependencies = [ "async-trait", "breezyshim", "chrono", "configparser", "debbugs", "debian-changelog", "debian-control", "debian-copyright", "debian-watch", "debversion", "distro-info", "futures", "gix-config", "html5ever 0.29.0", "lazy-regex", "lazy_static", "log", "makefile-lossless", "maplit", "opam-file-rs", "percent-encoding", "pulldown-cmark", "pyo3", "pyproject-toml", "python-pkginfo", "quote", "r-description", "regex", "reqwest", "rst_renderer", "rust-ini", "select", "semver", "serde", "serde_json", "serde_yaml", "shlex", "textwrap", "tokio", "toml 0.8.19", "uo_rst_parser", "url", "xmltree", ] [[package]] name = "url" version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", ] [[package]] name = "urlencoding" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "utf-8" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "utf16_iter" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version-ranges" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8d079415ceb2be83fc355adbadafe401307d5c309c7e6ade6638e6f9f42f42d" dependencies = [ "smallvec", ] [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "want" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ "try-lock", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasite" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn 2.0.89", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" dependencies = [ "cfg-if", "js-sys", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "web-sys" version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "whoami" version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" dependencies = [ "redox_syscall", "wasite", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-registry" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" dependencies = [ "windows-result", "windows-strings", "windows-targets 0.52.6", ] [[package]] name = "windows-result" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-strings" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ "windows-result", "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] [[package]] name = "write16" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] name = "writeable" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "xattr" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" dependencies = [ "libc", "linux-raw-sys", "rustix", ] [[package]] name = "xml-rs" version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af310deaae937e48a26602b730250b4949e125f468f11e6990be3e5304ddd96f" [[package]] name = "xml5ever" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4034e1d05af98b51ad7214527730626f019682d797ba38b51689212118d8e650" dependencies = [ "log", "mac", "markup5ever 0.11.0", ] [[package]] name = "xmltree" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b619f8c85654798007fb10afa5125590b43b088c225a25fc2fec100a9fad0fc6" dependencies = [ "xml-rs", ] [[package]] name = "yoke" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", "syn 2.0.89", "synstructure 0.13.1", ] [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", "syn 2.0.89", ] [[package]] name = "zerofrom" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", "syn 2.0.89", "synstructure 0.13.1", ] [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] name = "zerovec" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", "syn 2.0.89", ] [[package]] name = "zip" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cc23c04387f4da0374be4533ad1208cbb091d5c11d070dfef13676ad6497164" dependencies = [ "arbitrary", "crc32fast", "crossbeam-utils", "displaydoc", "flate2", "indexmap", "num_enum", "thiserror 1.0.69", ] ognibuild-0.0.32/Cargo.toml0000644000000120540000000000100110720ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "ognibuild" version = "0.0.32" authors = ["Jelmer Vernooij "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false default-run = "ogni" description = "Detect and run any build system" homepage = "https://github.com/jelmer/ognibuild" readme = "README.md" license = "GPL-2.0+" repository = "https://github.com/jelmer/ognibuild.git" [lib] name = "ognibuild" path = "src/lib.rs" [[bin]] name = "deb-fix-build" path = "src/bin/deb-fix-build.rs" required-features = [ "debian", "cli", "breezy", ] [[bin]] name = "deb-upstream-deps" path = "src/bin/deb-upstream-deps.rs" required-features = [ "cli", "debian", "breezy", ] [[bin]] name = "dep-server" path = "src/bin/dep-server.rs" required-features = [ "dep-server", "cli", "debian", ] [[bin]] name = "ogni" path = "src/bin/ogni.rs" required-features = ["cli"] [[bin]] name = "ognibuild-deb" path = "src/bin/ognibuild-deb.rs" required-features = [ "cli", "debian", "breezy", ] [[bin]] name = "ognibuild-dist" path = "src/bin/ognibuild-dist.rs" required-features = [ "cli", "breezy", ] [[bin]] name = "report-apt-deps-status" path = "src/bin/report-apt-deps-status.rs" required-features = [ "cli", "debian", ] [[example]] name = "apt-file-search" path = "examples/apt-file-search.rs" required-features = [ "cli", "debian", ] [dependencies.axum] version = "0.7" features = [ "json", "http2", "tokio", ] optional = true [dependencies.breezyshim] version = "0.1" optional = true [dependencies.buildlog-consultant] version = ">=0.0.43" [dependencies.chrono] version = ">=0.4" [dependencies.clap] version = "4" features = [ "derive", "env", ] optional = true [dependencies.deb822-lossless] version = ">=0.2" [dependencies.debian-analyzer] version = "0.158.24" optional = true [dependencies.debian-changelog] version = ">=0.2" optional = true [dependencies.debian-control] version = ">=0.1.25" optional = true [dependencies.debversion] version = "0.4" optional = true [dependencies.dirs] version = "5.0.1" [dependencies.env_logger] version = ">=0.10" optional = true [dependencies.flate2] version = "1" optional = true [dependencies.fs_extra] version = "1.3.0" [dependencies.inventory] version = ">=0.3" [dependencies.lazy-regex] version = ">=2" [dependencies.lazy_static] version = ">=1.4" [dependencies.libc] version = "0.2.162" [dependencies.log] version = "0.4.20" [dependencies.lz4_flex] version = ">=0.11" optional = true [dependencies.lzma-rs] version = "0.3.0" optional = true [dependencies.makefile-lossless] version = "0.1.3" [dependencies.maplit] version = "1.0.2" [dependencies.nix] version = ">=0.27.0" features = ["user"] [dependencies.pep508_rs] version = "0.9" [dependencies.percent-encoding] version = "2.3.1" [dependencies.pyo3] version = "0.22" [dependencies.pyproject-toml] version = "0.13" [dependencies.r-description] version = ">=0.2" features = ["serde"] [dependencies.rand] version = "0.8.5" [dependencies.regex] version = "1.10.6" [dependencies.reqwest] version = ">=0.10" features = [ "blocking", "json", ] optional = true [dependencies.semver] version = ">=1" [dependencies.serde] version = "1.0" features = ["derive"] [dependencies.serde_json] version = "1.0" [dependencies.serde_yaml] version = "0.9.34" [dependencies.shlex] version = "1.3.0" [dependencies.sqlx] version = "0.8.1" features = [ "postgres", "runtime-tokio-native-tls", ] optional = true [dependencies.stackdriver_logger] version = "0.8.2" optional = true [dependencies.tempfile] version = ">=3" [dependencies.tokio] version = "1" features = ["full"] optional = true [dependencies.toml] version = ">=0.8" [dependencies.toml_edit] version = ">=0.22" [dependencies.upstream-ontologist] version = "0.2" optional = true [dependencies.url] version = ">=2" [dependencies.whoami] version = ">=1.4" default-features = false [dependencies.xmltree] version = ">=0.10" [dev-dependencies.lazy_static] version = "1" [dev-dependencies.test-log] version = "0.2" [features] breezy = ["dep:breezyshim"] cli = [ "dep:clap", "dep:env_logger", ] debian = [ "dep:debian-changelog", "dep:debversion", "dep:debian-control", "dep:flate2", "dep:lzma-rs", "dep:lz4_flex", "dep:reqwest", "breezyshim/debian", "dep:debian-analyzer", ] default = [ "debian", "cli", "udd", "upstream", "breezy", "dep-server", ] dep-server = [ "dep:axum", "dep:tokio", ] udd = [ "dep:sqlx", "dep:tokio", "debian", ] upstream = ["dep:upstream-ontologist"] ognibuild-0.0.32/Cargo.toml.orig000064400000000000000000000067071046102023000145630ustar 00000000000000[package] name = "ognibuild" version = "0.0.32" authors = [ "Jelmer Vernooij "] edition = "2021" license = "GPL-2.0+" description = "Detect and run any build system" repository = "https://github.com/jelmer/ognibuild.git" homepage = "https://github.com/jelmer/ognibuild" default-run = "ogni" [dependencies] pyo3 = "0.22" breezyshim = { version = "0.1", optional = true } buildlog-consultant = { version = ">=0.0.43" } upstream-ontologist = { version = "0.2", optional = true } axum = { version = "0.7", optional = true, features = ["json", "http2", "tokio"] } chrono = ">=0.4" clap = { version = "4", features = ["derive", "env"], optional = true } deb822-lossless = ">=0.2" debian-analyzer = { version = "0.158.24", optional = true } #debian-analyzer = { path = "../lintian-brush/analyzer", optional = true } debian-changelog = { version = ">=0.2", optional = true } debian-control = { version = ">=0.1.25", optional = true } debversion = { version = "0.4", optional = true } env_logger = { version = ">=0.10", optional = true } flate2 = { version = "1", optional = true } fs_extra = "1.3.0" inventory = ">=0.3" lazy-regex = ">=2" lazy_static = ">=1.4" libc = "0.2.162" log = "0.4.20" lz4_flex = { version = ">=0.11", optional = true } lzma-rs = { version = "0.3.0", optional = true } makefile-lossless = "0.1.3" maplit = "1.0.2" nix = { version = ">=0.27.0", features = ["user"] } pep508_rs = "0.9" percent-encoding = "2.3.1" pyproject-toml = "0.13" r-description = { version = ">=0.2", features = ["serde"] } rand = "0.8.5" regex = "1.10.6" reqwest = { version = ">=0.10", optional = true, features = ["blocking", "json"] } semver = ">=1" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_yaml = "0.9.34" shlex = "1.3.0" sqlx = { version = "0.8.1", optional = true, features = ["postgres", "runtime-tokio-native-tls"] } stackdriver_logger = { version = "0.8.2", optional = true } tempfile = ">=3" tokio = { version = "1", features = ["full"], optional = true } toml = ">=0.8" toml_edit = ">=0.22" url = ">=2" whoami = { version = ">=1.4", default-features = false } xmltree = ">=0.10" dirs = "5.0.1" [features] default = ["debian", "cli", "udd", "upstream", "breezy", "dep-server"] debian = ["dep:debian-changelog", "dep:debversion", "dep:debian-control", "dep:flate2", "dep:lzma-rs", "dep:lz4_flex", "dep:reqwest", "breezyshim/debian", "dep:debian-analyzer"] cli = ["dep:clap", "dep:env_logger"] udd = ["dep:sqlx", "dep:tokio", "debian"] dep-server = ["dep:axum", "dep:tokio"] upstream = ["dep:upstream-ontologist"] breezy = ["dep:breezyshim"] [dev-dependencies] lazy_static = "1" test-log = "0.2" [[bin]] name = "ognibuild-deb" path = "src/bin/ognibuild-deb.rs" required-features = ["cli", "debian", "breezy"] [[example]] name = "apt-file-search" path = "examples/apt-file-search.rs" required-features = ["cli", "debian"] [[bin]] name = "dep-server" path = "src/bin/dep-server.rs" required-features = ["dep-server", "cli", "debian"] [[bin]] name = "ognibuild-dist" path = "src/bin/ognibuild-dist.rs" required-features = ["cli", "breezy"] [[bin]] name = "ogni" path = "src/bin/ogni.rs" required-features = ["cli"] [[bin]] name = "deb-fix-build" path = "src/bin/deb-fix-build.rs" required-features = ["debian", "cli", "breezy"] [[bin]] name = "deb-upstream-deps" path = "src/bin/deb-upstream-deps.rs" required-features = ["cli", "debian", "breezy"] [[bin]] name = "report-apt-deps-status" path = "src/bin/report-apt-deps-status.rs" required-features = ["cli", "debian"] ognibuild-0.0.32/LICENSE000064400000000000000000000432541046102023000126770ustar 00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. ognibuild-0.0.32/Makefile000064400000000000000000000004651046102023000133270ustar 00000000000000check:: style style: ruff check py check:: testsuite build-inplace: python3 setup.py build_rust --inplace testsuite: cargo test check:: typing typing: mypy py tests coverage: PYTHONPATH=$(shell pwd)/py python3 -m coverage run -m unittest tests.test_suite coverage-html: python3 -m coverage html ognibuild-0.0.32/README.md000064400000000000000000000040301046102023000131360ustar 00000000000000# ognibuild Ognibuild is a simple wrapper with a common interface for invoking any kind of build tool. The ideas is that it can be run to build and install any source code directory by detecting the build system that is in use and invoking that with the correct parameters. It can also detect and install missing dependencies. ## Goals The goal of ognibuild is to provide a consistent CLI that can be used for any software package. It is mostly useful for automated building of large sets of diverse packages (e.g. different programming languages). It is not meant to expose all functionality that is present in the underlying build systems. To use that, invoke those build systems directly. ## Usage Ognibuild has a number of subcommands: * ``ogni clean`` - remove any built artifacts * ``ogni dist`` - create a source tarball * ``ogni build`` - build the package in-tree * ``ogni install`` - install the package * ``ogni test`` - run the testsuite in the source directory It also includes a subcommand that can fix up the build dependencies for Debian packages, called deb-fix-build. ### Examples ``` ogni -d https://gitlab.gnome.org/GNOME/fractal install ``` ## Status Ognibuild is functional, but sometimes rough around the edges. If you run into issues (or lack of support for a particular ecosystem), please file a bug. ### Supported Build Systems - Bazel - Cabal - Cargo - Golang - Gradle - Make, including various makefile generators: - autoconf/automake - CMake - Makefile.PL - qmake - Maven - ninja, including ninja file generators: - meson - Node - Octave - Perl - Module::Build::Tiny - Dist::Zilla - Minilla - PHP Pear - Python - setup.py/setup.cfg/pyproject.toml - R - Ruby gems - Waf ### Supported package repositories Package repositories are used to install missing dependencies. The following "native" repositories are supported: - pypi - cpan - hackage - npm - cargo - cran - golang\* As well one distribution repository: - apt ## License Ognibuild is licensed under the GNU GPL, v2 or later. ognibuild-0.0.32/SECURITY.md000064400000000000000000000004441046102023000134550ustar 00000000000000# Security Policy ## Supported Versions ognibuild 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 ognibuild-0.0.32/TODO000064400000000000000000000003171046102023000123530ustar 00000000000000- Need to be able to check up front whether a requirement is satisfied, before attempting to install it (which is more expensive) - Cache parsed Contents files during test suite runs and/or speed up reading ognibuild-0.0.32/disperse.toml000064400000000000000000000001011046102023000143650ustar 00000000000000tag-name = "v$VERSION" tarball-location = [] release-timeout = 5 ognibuild-0.0.32/examples/apt-file-search.rs000064400000000000000000000025211046102023000170120ustar 00000000000000use clap::Parser; use ognibuild::debian::file_search::{ get_apt_contents_file_searcher, get_packages_for_paths, FileSearcher, GENERATED_FILE_SEARCHER, }; use std::path::PathBuf; #[derive(Parser)] struct Args { #[clap(short, long)] /// Search for regex. regex: bool, /// Path to search for. path: Vec, #[clap(short, long)] /// Enable debug output. debug: bool, #[clap(short, long)] /// Case insensitive search. case_insensitive: bool, } pub fn main() -> Result<(), i8> { let args: Args = Args::parse(); env_logger::builder() .filter_level(if args.debug { log::LevelFilter::Debug } else { log::LevelFilter::Info }) .init(); let session = ognibuild::session::plain::PlainSession::new(); let main_searcher = get_apt_contents_file_searcher(&session).unwrap(); let searchers: Vec<&dyn FileSearcher> = vec![ main_searcher.as_ref(), &*GENERATED_FILE_SEARCHER as &dyn FileSearcher, ]; let packages = get_packages_for_paths( args.path .iter() .map(|x| x.as_path().to_str().unwrap()) .collect(), searchers.as_slice(), args.regex, args.case_insensitive, ); for package in packages { println!("{}", package); } Ok(()) } ognibuild-0.0.32/notes/design.md000064400000000000000000000041761046102023000146150ustar 00000000000000Ognibuild aims to build and extract build metadata from any software project. It does this through a variety of mechanisms: * Detecting know build systems * Ensuring necessary dependencies are present * Fixing other issues in the project A build system is anything that can create artefacts from source and/or test and install those artefacts. Some projects use multiple build systems which may or may not be tightly integrated. A build action is one of “clean”, “build”, “install” or “test”. DependencyCategory: Dependencies can be for different purposes: “build” (just necessary for building), “runtime” (to run after it has been built and possibly installed), “test” (for running tests - e.g. test frameworks, test runners), “dev” (necessary for development - e.g. listens, ide plugins, etc). When a build action is requested, ognibuild detects the build system(s) and then invokes the action. The action is run and if it failed the output is scanned for problems by buildlog-consultant. If a problem is found then the appropriate Fixer is invoked. This may take any of a number of steps, including changing the project source tree, configuring a tool locally for the user or installing more packages. If no appropriate Fixer can be found then no further action is taken. If the Fixer is successful then the original action is retried. When it comes to dependencies, there is usually only one relevant fixer loaded. Depending on the situation, this can either update the local project to reference the extra dependencies or install them on the system, invoking the appropriate Installer, Dependency fixers start by trying to derive the missing dependency from the problem that was found. Some second level dependency fixers may then try to coerce the dependency into a specific kind of dependency (e.g. a Debian dependency from a Python dependency). InstallationScope: Where dependencies are installed can vary from “user” (installed in the user’s home directory), “system” (installed globally on the system, usually in /usr), “vendor” (bundled with the project source code). Not all installers support all scopes. ognibuild-0.0.32/src/actions/build.rs000064400000000000000000000017351046102023000155640ustar 00000000000000use crate::buildsystem::{BuildSystem, Error}; use crate::fix_build::{iterate_with_build_fixers, BuildFixer, InterimError}; use crate::installer::{Error as InstallerError, Installer}; use crate::logs::{wrap, LogManager}; use crate::session::Session; pub fn run_build( session: &dyn Session, buildsystems: &[&dyn BuildSystem], installer: &dyn Installer, fixers: &[&dyn BuildFixer], log_manager: &mut dyn LogManager, ) -> Result<(), Error> { // Some things want to write to the user's home directory, e.g. pip caches in ~/.cache session.create_home()?; for buildsystem in buildsystems { return Ok(iterate_with_build_fixers( fixers, || -> Result<_, InterimError> { Ok(wrap(log_manager, || -> Result<_, Error> { Ok(buildsystem.build(session, installer)?) })?) }, None, )?); } Err(Error::NoBuildSystemDetected) } ognibuild-0.0.32/src/actions/clean.rs000064400000000000000000000017351046102023000155470ustar 00000000000000use crate::buildsystem::{BuildSystem, Error}; use crate::fix_build::{iterate_with_build_fixers, BuildFixer, InterimError}; use crate::installer::{Error as InstallerError, Installer}; use crate::logs::{wrap, LogManager}; use crate::session::Session; pub fn run_clean( session: &dyn Session, buildsystems: &[&dyn BuildSystem], installer: &dyn Installer, fixers: &[&dyn BuildFixer], log_manager: &mut dyn LogManager, ) -> Result<(), Error> { // Some things want to write to the user's home directory, e.g. pip caches in ~/.cache session.create_home()?; for buildsystem in buildsystems { return Ok(iterate_with_build_fixers( fixers, || -> Result<_, InterimError> { Ok(wrap(log_manager, || -> Result<_, Error> { Ok(buildsystem.clean(session, installer)?) })?) }, None, )?); } Err(Error::NoBuildSystemDetected) } ognibuild-0.0.32/src/actions/dist.rs000064400000000000000000000021251046102023000154220ustar 00000000000000use crate::buildsystem::{BuildSystem, Error}; use crate::fix_build::{iterate_with_build_fixers, BuildFixer, InterimError}; use crate::installer::{Error as InstallerError, Installer}; use crate::logs::{wrap, LogManager}; use crate::session::Session; use std::ffi::OsString; use std::path::Path; pub fn run_dist( session: &dyn Session, buildsystems: &[&dyn BuildSystem], installer: &dyn Installer, fixers: &[&dyn BuildFixer], target_directory: &Path, quiet: bool, log_manager: &mut dyn LogManager, ) -> Result { // Some things want to write to the user's home directory, e.g. pip caches in ~/.cache session.create_home()?; for buildsystem in buildsystems { return Ok(iterate_with_build_fixers( fixers, || -> Result<_, InterimError> { Ok(wrap(log_manager, || -> Result<_, Error> { Ok(buildsystem.dist(session, installer, target_directory, quiet)?) })?) }, None, )?); } Err(Error::NoBuildSystemDetected) } ognibuild-0.0.32/src/actions/info.rs000064400000000000000000000034001046102023000154070ustar 00000000000000use crate::buildsystem::{BuildSystem, Error}; use crate::fix_build::BuildFixer; use crate::installer::Error as InstallerError; use crate::session::Session; use std::collections::HashMap; pub fn run_info( session: &dyn Session, buildsystems: &[&dyn BuildSystem], fixers: Option<&[&dyn BuildFixer]>, ) -> Result<(), Error> { for buildsystem in buildsystems { log::info!("{:?}", buildsystem); let mut deps = HashMap::new(); match buildsystem.get_declared_dependencies(session, fixers) { Ok(declared_deps) => { for (category, dep) in declared_deps { deps.entry(category).or_insert_with(Vec::new).push(dep); } } Err(e) => { log::error!( "Failed to get declared dependencies from {:?}: {}", buildsystem, e ); } } if !deps.is_empty() { log::info!(" Declared dependencies:"); for (category, deps) in deps { for dep in deps { log::info!(" {}: {:?}", category, dep); } } } let outputs = match buildsystem.get_declared_outputs(session, fixers) { Ok(outputs) => outputs, Err(e) => { log::error!( "Failed to get declared outputs from {:?}: {}", buildsystem, e ); continue; } }; if !outputs.is_empty() { log::info!(" Outputs:"); for output in outputs { log::info!(" {:?}", output); } } } Ok(()) } ognibuild-0.0.32/src/actions/install.rs000064400000000000000000000023031046102023000161230ustar 00000000000000use crate::buildsystem::{BuildSystem, Error, InstallTarget}; use crate::fix_build::{iterate_with_build_fixers, BuildFixer, InterimError}; use crate::installer::{Error as InstallerError, InstallationScope, Installer}; use crate::logs::{wrap, LogManager}; use crate::session::Session; use std::path::Path; pub fn run_install( session: &dyn Session, buildsystems: &[&dyn BuildSystem], installer: &dyn Installer, fixers: &[&dyn BuildFixer], log_manager: &mut dyn LogManager, scope: InstallationScope, prefix: Option<&Path>, ) -> Result<(), Error> { // Some things want to write to the user's home directory, e.g. pip caches in ~/.cache session.create_home()?; let target = InstallTarget { scope, prefix: prefix.map(|p| p.to_path_buf()), }; for buildsystem in buildsystems { return Ok(iterate_with_build_fixers( fixers, || -> Result<_, InterimError> { Ok(wrap(log_manager, || -> Result<_, Error> { Ok(buildsystem.install(session, installer, &target)?) })?) }, None, )?); } Err(Error::NoBuildSystemDetected) } ognibuild-0.0.32/src/actions/mod.rs000064400000000000000000000001311046102023000152310ustar 00000000000000pub mod build; pub mod clean; pub mod dist; pub mod info; pub mod install; pub mod test; ognibuild-0.0.32/src/actions/test.rs000064400000000000000000000017331046102023000154420ustar 00000000000000use crate::buildsystem::{BuildSystem, Error}; use crate::fix_build::{iterate_with_build_fixers, BuildFixer, InterimError}; use crate::installer::{Error as InstallerError, Installer}; use crate::logs::{wrap, LogManager}; use crate::session::Session; pub fn run_test( session: &dyn Session, buildsystems: &[&dyn BuildSystem], installer: &dyn Installer, fixers: &[&dyn BuildFixer], log_manager: &mut dyn LogManager, ) -> Result<(), Error> { // Some things want to write to the user's home directory, e.g. pip caches in ~/.cache session.create_home()?; for buildsystem in buildsystems { return Ok(iterate_with_build_fixers( fixers, || -> Result<_, InterimError> { Ok(wrap(log_manager, || -> Result<_, Error> { Ok(buildsystem.test(session, installer)?) })?) }, None, )?); } Err(Error::NoBuildSystemDetected) } ognibuild-0.0.32/src/analyze.rs000064400000000000000000000120751046102023000144670ustar 00000000000000use crate::session::{run_with_tee, Error as SessionError, Session}; use buildlog_consultant::problems::common::MissingCommand; fn default_check_success(status: std::process::ExitStatus, _lines: Vec<&str>) -> bool { status.success() } #[derive(Debug)] pub enum AnalyzedError { MissingCommandError { command: String, }, IoError(std::io::Error), Detailed { retcode: i32, error: Box, }, Unidentified { retcode: i32, lines: Vec, secondary: Option>, }, } impl From for AnalyzedError { fn from(e: std::io::Error) -> Self { #[cfg(unix)] match e.raw_os_error() { Some(libc::ENOSPC) => { return AnalyzedError::Detailed { retcode: 1, error: Box::new(buildlog_consultant::problems::common::NoSpaceOnDevice), }; } Some(libc::EMFILE) => { return AnalyzedError::Detailed { retcode: 1, error: Box::new(buildlog_consultant::problems::common::TooManyOpenFiles), } } _ => {} } AnalyzedError::IoError(e) } } impl std::fmt::Display for AnalyzedError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { AnalyzedError::MissingCommandError { command } => { write!(f, "Command not found: {}", command) } AnalyzedError::IoError(e) => write!(f, "IO error: {}", e), AnalyzedError::Detailed { retcode, error } => { write!(f, "Command failed with code {}", retcode)?; write!(f, "\n{}", error) } AnalyzedError::Unidentified { retcode, lines, secondary, } => { write!(f, "Command failed with code {}", retcode)?; if let Some(secondary) = secondary { write!(f, "\n{}", secondary) } else { write!(f, "\n{}", lines.join("\n")) } } } } } impl std::error::Error for AnalyzedError {} /// Run a command and analyze the output for common build errors. /// /// # Arguments /// * `session`: Session to run the command in /// * `args`: Arguments to the command /// * `check_success`: Function to determine if the command was successful /// * `quiet`: Whether to log the command being run /// * `cwd`: Current working directory for the command /// * `user`: User to run the command as /// * `env`: Environment variables to set for the command /// * `stdin`: Stdin for the command pub fn run_detecting_problems( session: &dyn Session, args: Vec<&str>, check_success: Option<&dyn Fn(std::process::ExitStatus, Vec<&str>) -> bool>, quiet: bool, cwd: Option<&std::path::Path>, user: Option<&str>, env: Option<&std::collections::HashMap>, stdin: Option, ) -> Result, AnalyzedError> { let check_success = check_success.unwrap_or(&default_check_success); let (retcode, contents) = match run_with_tee(session, args.clone(), cwd, user, env, stdin, quiet) { Ok((retcode, contents)) => (retcode, contents), Err(SessionError::SetupFailure(..)) => unreachable!(), Err(SessionError::IoError(e)) if e.kind() == std::io::ErrorKind::NotFound => { let command = args[0].to_string(); return Err(AnalyzedError::Detailed { retcode: 127, error: Box::new(MissingCommand(command)) as Box, }); } Err(SessionError::IoError(e)) => { return Err(AnalyzedError::IoError(e)); } Err(SessionError::CalledProcessError(retcode)) => (retcode, vec![]), }; log::debug!( "Command returned code {}, with {} lines of output.", retcode.code().unwrap_or(1), contents.len() ); if check_success(retcode, contents.iter().map(|s| s.as_str()).collect()) { return Ok(contents); } let (r#match, error) = buildlog_consultant::common::find_build_failure_description( contents.iter().map(|x| x.as_str()).collect(), ); if let Some(error) = error { log::debug!("Identified error: {}", error); Err(AnalyzedError::Detailed { retcode: retcode.code().unwrap_or(1), error, }) } else { if let Some(r#match) = r#match.as_ref() { log::warn!("Build failed with unidentified error:"); log::warn!("{}", r#match.line().trim_end_matches('\n')); } else { log::warn!("Build failed without error being identified."); } Err(AnalyzedError::Unidentified { retcode: retcode.code().unwrap_or(1), lines: contents, secondary: r#match, }) } } ognibuild-0.0.32/src/bin/deb-fix-build.rs000064400000000000000000000145671046102023000162170ustar 00000000000000use clap::Parser; use ognibuild::debian::build::BuildOnceError; use ognibuild::debian::fix_build::{rescue_build_log, IterateBuildError}; #[cfg(target_os = "linux")] use ognibuild::session::schroot::SchrootSession; use ognibuild::session::{plain::PlainSession, Session}; use std::fmt::Write as _; use std::io::Write as _; use std::path::PathBuf; #[derive(Parser)] struct Args { /// Suffix to use for test builds #[clap(short, long, default_value = "fixbuild1")] suffix: String, /// Suite to target #[clap(short, long, default_value = "unstable")] suite: String, /// Committer string (name and email) #[clap(short, long)] committer: Option, /// Document changes in the changelog [default: auto-detect] #[arg(long, default_value_t = false, conflicts_with = "no_update_changelog")] update_changelog: bool, /// Do not document changes in the changelog (useful when using e.g. "gbp dch") [default: auto-detect] #[arg(long, default_value_t = false, conflicts_with = "update_changelog")] no_update_changelog: bool, /// Output directory. #[clap(short, long)] output_directory: Option, /// Build command #[clap(short, long, default_value = "sbuild -A -s -v")] build_command: String, /// Maximum number of issues to attempt to fix before giving up. #[clap(short, long, default_value = "10")] max_iterations: usize, #[cfg(target_os = "linux")] /// chroot to use. #[clap(short, long)] schroot: Option, /// ognibuild dep server to use #[clap(short, long, env = "OGNIBUILD_DEPS")] dep_server_url: Option, /// Be verbose #[clap(short, long)] verbose: bool, /// Directory to use #[clap(short, long, default_value = ".")] directory: PathBuf, } fn main() -> Result<(), i32> { let args = Args::parse(); let update_changelog: Option = if args.update_changelog { Some(true) } else if args.no_update_changelog { Some(false) } else { None }; env_logger::builder() .format(|buf, record| writeln!(buf, "{}", record.args())) .filter( None, if args.verbose { log::LevelFilter::Debug } else { log::LevelFilter::Info }, ) .init(); let mut temp_output_dir = None; let output_directory = if let Some(output_directory) = &args.output_directory { if !output_directory.is_dir() { log::error!("output directory {:?} is not a directory", output_directory); std::process::exit(1); } output_directory.clone() } else { temp_output_dir = Some(tempfile::tempdir().unwrap()); log::info!("Using output directory {:?}", temp_output_dir); temp_output_dir.as_ref().unwrap().path().to_path_buf() }; let (tree, subpath) = breezyshim::workingtree::open_containing(&args.directory).unwrap(); #[cfg(target_os = "linux")] let session = if let Some(schroot) = &args.schroot { Box::new(SchrootSession::new(schroot, Some("deb-fix-build")).unwrap()) as Box } else { Box::new(PlainSession::new()) }; #[cfg(not(target_os = "linux"))] let session = Box::new(PlainSession::new()); let apt = ognibuild::debian::apt::AptManager::new(session.as_ref(), None); let committer = if let Some(committer) = &args.committer { Some(breezyshim::config::parse_username(committer)) } else { None }; let packaging_context = ognibuild::debian::context::DebianPackagingContext::new( tree.clone(), &subpath, committer, args.update_changelog, Some(Box::new(breezyshim::commit::NullCommitReporter::new())), ); let fixers = ognibuild::debian::fixers::default_fixers(&packaging_context, &apt); match ognibuild::debian::fix_build::build_incrementally( &tree, Some(&args.suffix), Some(&args.suite), &output_directory, &args.build_command, fixers .iter() .map(|f| f.as_ref()) .collect::>() .as_slice(), None, Some(args.max_iterations), &subpath, None, None, None, None, update_changelog == Some(false), ) { Ok(build_result) => { log::info!( "Built {} - changes file at {:?}.", build_result.version, build_result.changes_names, ); return Ok(()); } Err(IterateBuildError::Persistent(phase, error)) => { log::error!("Error during {}: {}", phase, error); if let Some(output_directory) = args.output_directory { rescue_build_log(&output_directory, Some(&tree)).unwrap(); } return Err(1); } Err(IterateBuildError::Unidentified { phase, lines, secondary, .. }) => { let mut header = if let Some(phase) = phase { format!("Error during {}:", phase) } else { "Error:".to_string() }; if let Some(m) = secondary { let linenos = m.linenos(); write!( header, " on lines {}-{}", linenos[0], linenos[linenos.len() - 1] ) .unwrap(); } header.write_str(":").unwrap(); log::error!("{}", header); for line in lines { log::error!(" {}", line); } if let Some(output_directory) = args.output_directory { rescue_build_log(&output_directory, Some(&tree)).unwrap(); } return Err(1); } Err(IterateBuildError::FixerLimitReached(n)) => { log::error!("Fixer limit reached - {} attempts.", n); return Err(1); } Err(IterateBuildError::Other(o)) => { log::error!("Error: {}", o); return Err(1); } Err(IterateBuildError::MissingPhase) => { log::error!("Missing phase"); return Err(1); } Err(IterateBuildError::ResetTree(e)) => { log::error!("Error resetting tree: {}", e); return Err(1); } } } ognibuild-0.0.32/src/bin/deb-upstream-deps.rs000064400000000000000000000064441046102023000171200ustar 00000000000000use breezyshim::error::Error as BrzError; use breezyshim::workingtree; use clap::Parser; use debian_analyzer::editor::{Editor, MutableTreeEdit}; use debian_control::lossless::relations::Relations; use ognibuild::session::plain::PlainSession; use ognibuild::session::Session; use std::io::Write; use std::path::PathBuf; #[derive(Parser)] struct Args { #[clap(short, long)] /// Be verbose debug: bool, #[clap(short, long)] /// Update current package update: bool, #[clap(short, long, default_value = ".")] /// Directory to run in directory: PathBuf, } fn main() -> Result<(), i8> { let args = Args::parse(); env_logger::builder() .format(|buf, record| writeln!(buf, "{}", record.args())) .filter( None, if args.debug { log::LevelFilter::Debug } else { log::LevelFilter::Info }, ) .init(); let (wt, subpath) = match workingtree::open_containing(&args.directory) { Ok((wt, subpath)) => (wt, subpath), Err(e @ BrzError::NotBranchError { .. }) => { log::error!("please run deps in an existing branch: {}", e); return Err(1); } Err(e) => { log::error!("error opening working tree: {}", e); return Err(1); } }; let mut build_deps = vec![]; let mut test_deps = vec![]; let mut session: Box = Box::new(ognibuild::session::plain::PlainSession::new()); let project = session.project_from_vcs(&wt, None, None).unwrap(); for (bs_subpath, bs) in ognibuild::buildsystem::scan_buildsystems(&wt.abspath(&subpath).unwrap()) { session .chdir(&project.internal_path().join(&bs_subpath)) .unwrap(); let (bs_build_deps, bs_test_deps) = ognibuild::debian::upstream_deps::get_project_wide_deps(session.as_ref(), bs.as_ref()); build_deps.extend(bs_build_deps); test_deps.extend(bs_test_deps); } if !build_deps.is_empty() { println!( "Build-Depends: {}", build_deps .iter() .map(|x| x.relation_string()) .collect::>() .join(", ") ); } if !test_deps.is_empty() { println!( "Test-Depends: {}", test_deps .iter() .map(|x| x.relation_string()) .collect::>() .join(", ") ); } if args.update { let edit = wt .edit_file::(&subpath.join("debian/control"), true, true) .unwrap(); let mut source = edit.source().unwrap(); for build_dep in build_deps { for entry in build_dep.iter() { let mut relations = source.build_depends().unwrap_or_else(|| Relations::new()); let old_str = relations.to_string(); debian_analyzer::relations::ensure_relation(&mut relations, entry); if old_str != relations.to_string() { log::info!("Bumped to {}", relations.to_string()); source.set_build_depends(&relations); } } } edit.commit().unwrap(); } Ok(()) } ognibuild-0.0.32/src/bin/dep-server.rs000064400000000000000000000033271046102023000156500ustar 00000000000000use axum::{response::Json, routing::get, Router}; use clap::Parser; use ognibuild::debian::apt::AptManager; #[cfg(target_os = "linux")] use ognibuild::session::schroot::SchrootSession; use ognibuild::session::{plain::PlainSession, Session}; use std::io::Write; #[derive(Parser)] struct Args { #[clap(short, long)] listen_address: String, #[clap(short, long)] port: u16, #[cfg(target_os = "linux")] #[clap(short, long)] schroot: Option, #[clap(short, long)] debug: bool, } #[tokio::main] async fn main() -> Result<(), i8> { let args = Args::parse(); env_logger::builder() .format(|buf, record| writeln!(buf, "{}", record.args())) .filter( None, if args.debug { log::LevelFilter::Debug } else { log::LevelFilter::Info }, ) .init(); #[cfg(target_os = "linux")] let session: Box = if let Some(schroot) = args.schroot { Box::new(SchrootSession::new(&schroot, None).unwrap()) } else { Box::new(PlainSession::new()) }; #[cfg(not(target_os = "linux"))] let session: Box = Box::new(PlainSession::new()); let apt_mgr = AptManager::from_session(session.as_ref()); let app = Router::new() .route("/health", get(|| async { "ok" })) .route("/version", get(|| async { env!("CARGO_PKG_VERSION") })) .route("/ready", get(|| async { "ok" })); let listener = tokio::net::TcpListener::bind((args.listen_address.as_str(), args.port)) .await .unwrap(); log::info!("listening on {}", listener.local_addr().unwrap()); axum::serve(listener, app).await.unwrap(); Ok(()) } ognibuild-0.0.32/src/bin/ogni.rs000064400000000000000000000361101046102023000145240ustar 00000000000000use clap::{Parser, Subcommand}; use ognibuild::analyze::AnalyzedError; use ognibuild::buildsystem::{detect_buildsystems, BuildSystem, DependencyCategory, Error}; use ognibuild::dependency::Dependency; use ognibuild::fix_build::BuildFixer; use ognibuild::installer::{ auto_installer, select_installers, Error as InstallerError, Explanation, InstallationScope, Installer, }; use ognibuild::session::Session; use std::io::Write; use std::path::Path; use std::path::PathBuf; use url::Url; #[derive(Parser)] struct ExecArgs { #[clap(name = "subargv", trailing_var_arg = true)] subargv: Vec, } #[derive(Parser)] struct InstallArgs { #[clap(long)] prefix: Option, } #[derive(Subcommand)] enum Command { #[clap(name = "dist")] Dist, #[clap(name = "build")] Build, #[clap(name = "clean")] Clean, #[clap(name = "test")] Test, #[clap(name = "info")] Info, #[clap(name = "verify")] Verify, #[clap(name = "exec")] Exec(ExecArgs), #[clap(name = "install")] Install(InstallArgs), } #[derive(Parser)] struct Args { #[clap(subcommand)] command: Command, #[clap(long, short, default_value = ".")] directory: String, #[cfg(target_os = "linux")] #[clap(long)] schroot: Option, #[clap(long, short, default_value = "auto", use_value_delimiter = true)] installer: Vec, #[clap(long, hide = true)] apt: bool, #[clap(long, hide = true)] native: bool, #[clap(long)] /// Explain what needs to be done rather than making changes explain: bool, #[clap(long)] /// Ignore declared dependencies, follow build errors only ignore_declared_dependencies: bool, #[clap(long)] /// Scope to install in installation_scope: Option, #[clap(long, env = "OGNIBUILD_DEPS")] /// ognibuild dep server to use dep_server_url: Option, #[clap(long)] /// Print more verbose output debug: bool, } fn explain_missing_deps( session: &dyn Session, installer: &dyn Installer, scope: InstallationScope, deps: &[&dyn Dependency], ) -> Result, Error> { if deps.is_empty() { return Ok(vec![]); } let missing = deps .into_iter() .filter(|dep| !dep.present(session)) .collect::>(); if missing.is_empty() { return Ok(vec![]); } Ok(missing .into_iter() .map(|dep| installer.explain(*dep, scope)) .collect::>()?) } fn explain_necessary_declared_dependencies( session: &dyn Session, installer: &dyn Installer, fixers: &[&dyn BuildFixer], buildsystems: &[&dyn BuildSystem], categories: &[DependencyCategory], scope: InstallationScope, ) -> Result, Error> { let mut relevant: Vec> = vec![]; for buildsystem in buildsystems { let declared_deps = buildsystem.get_declared_dependencies(session, Some(fixers))?; for (category, dep) in declared_deps { if categories.contains(&category) { relevant.push(dep); } } } explain_missing_deps( session, installer, scope, relevant .iter() .map(|d| d.as_ref()) .collect::>() .as_slice(), ) } fn install_necessary_declared_dependencies( session: &dyn Session, installer: &dyn Installer, scopes: &[InstallationScope], fixers: &[&dyn BuildFixer], buildsystems: &[&dyn BuildSystem], categories: &[DependencyCategory], ) -> Result<(), Error> { for buildsystem in buildsystems { buildsystem.install_declared_dependencies( categories, scopes, session, installer, Some(fixers), )?; } Ok(()) } fn run_action( session: &dyn Session, scope: InstallationScope, external_dir: &Path, installer: &dyn Installer, fixers: &[&dyn BuildFixer], args: &Args, ) -> Result<(), Error> { if let Command::Exec(ExecArgs { subargv }) = &args.command { ognibuild::fix_build::run_fixing_problems::<_, Error>( fixers, None, session, subargv .iter() .map(|s| s.as_str()) .collect::>() .as_slice(), false, None, None, None, )?; return Ok(()); } let mut log_manager = ognibuild::logs::NoLogManager; let bss = detect_buildsystems(external_dir); if !args.ignore_declared_dependencies { let categories = match args.command { Command::Dist => vec![], Command::Build => vec![DependencyCategory::Universal, DependencyCategory::Build], Command::Clean => vec![], Command::Install(_) => vec![DependencyCategory::Universal, DependencyCategory::Build], Command::Test => vec![ DependencyCategory::Universal, DependencyCategory::Build, DependencyCategory::Test, ], Command::Info => vec![], Command::Verify => vec![ DependencyCategory::Universal, DependencyCategory::Build, DependencyCategory::Test, ], Command::Exec(_) => vec![], }; if !categories.is_empty() { log::info!("Checking that declared dependencies are present"); if !args.explain { match install_necessary_declared_dependencies( session, installer, &[scope, InstallationScope::Vendor], fixers, bss.iter() .map(|bs| bs.as_ref()) .collect::>() .as_slice(), &categories, ) { Ok(_) => {} Err(e) => { log::info!("Unable to install declared dependencies: {}", e); return Err(e); } } } else { match explain_necessary_declared_dependencies( session, installer, fixers, bss.iter() .map(|bs| bs.as_ref()) .collect::>() .as_slice(), &categories, scope, ) { Ok(explanations) => { for explanation in explanations { log::info!("{}", explanation); } } Err(e) => { log::info!("Unable to explain declared dependencies",); return Err(e); } } } } } match &args.command { Command::Exec(..) => unreachable!(), Command::Dist => { ognibuild::actions::dist::run_dist( session, bss.iter() .map(|bs| bs.as_ref()) .collect::>() .as_slice(), installer, fixers, Path::new("."), false, &mut log_manager, )?; } Command::Build => { ognibuild::actions::build::run_build( session, bss.iter() .map(|bs| bs.as_ref()) .collect::>() .as_slice(), installer, fixers, &mut log_manager, )?; } Command::Clean => { ognibuild::actions::clean::run_clean( session, bss.iter() .map(|bs| bs.as_ref()) .collect::>() .as_slice(), installer, fixers, &mut log_manager, )?; } Command::Install(install_args) => { ognibuild::actions::install::run_install( session, bss.iter() .map(|bs| bs.as_ref()) .collect::>() .as_slice(), installer, fixers, &mut log_manager, scope, install_args.prefix.as_deref(), )?; } Command::Test => { ognibuild::actions::test::run_test( session, bss.iter() .map(|bs| bs.as_ref()) .collect::>() .as_slice(), installer, fixers, &mut log_manager, )?; } Command::Info => { ognibuild::actions::info::run_info( session, bss.iter() .map(|bs| bs.as_ref()) .collect::>() .as_slice(), Some(fixers), )?; } Command::Verify => { ognibuild::actions::build::run_build( session, bss.iter() .map(|bs| bs.as_ref()) .collect::>() .as_slice(), installer, fixers, &mut log_manager, )?; ognibuild::actions::test::run_test( session, bss.iter() .map(|bs| bs.as_ref()) .collect::>() .as_slice(), installer, fixers, &mut log_manager, )?; } } Ok(()) } fn main() -> Result<(), i32> { let mut args = Args::parse(); env_logger::builder() .format(|buf, record| writeln!(buf, "{}", record.args())) .filter( None, if args.debug { log::LevelFilter::Debug } else { log::LevelFilter::Info }, ) .init(); #[cfg(target_os = "linux")] let mut session: Box = if let Some(schroot) = args.schroot.as_ref() { Box::new(ognibuild::session::schroot::SchrootSession::new(schroot, None).unwrap()) } else { Box::new(ognibuild::session::plain::PlainSession::new()) }; #[cfg(not(target_os = "linux"))] let mut session: Box = Box::new(ognibuild::session::plain::PlainSession::new()); let url = if let Ok(url) = args.directory.parse::() { url } else { let p = Path::new(&args.directory); url::Url::from_directory_path(&p.canonicalize().unwrap()).unwrap() }; let mut td: Option = None; // TODO(jelmer): Get a list of supported schemes from breezy? #[cfg(feature = "breezy")] let project = if ["git", "http", "https", "ssh"].contains(&url.scheme()) { let b = breezyshim::branch::open(&url).unwrap(); log::info!("Cloning {}", args.directory); td = Some(tempfile::tempdir().unwrap()); let to_dir = b .controldir() .sprout( Url::from_directory_path(td.as_ref().unwrap().path()).unwrap(), None, Some(true), None, None, ) .unwrap(); let wt = to_dir.open_workingtree().unwrap(); session.project_from_vcs(&wt, None, None).unwrap() } else { let directory = if url.scheme() == "file" { Path::new(url.path()).to_path_buf() } else { PathBuf::from(args.directory.clone()) }; log::info!("Preparing directory {}", directory.display()); session.project_from_directory(&directory, None).unwrap() }; #[cfg(not(feature = "breezy"))] let project = { let directory = PathBuf::from(args.directory.clone()); log::info!("Preparing directory {}", directory.display()); session.project_from_directory(&directory, None).unwrap() }; session.chdir(&project.internal_path()).unwrap(); std::env::set_current_dir(&project.external_path()).unwrap(); if !session.is_temporary() && matches!(args.command, Command::Info) { args.explain = true; } if args.apt { args.installer = vec!["apt".to_string()]; } if args.native { args.installer = vec!["native".to_string()]; } let scope = if let Some(scope) = args.installation_scope { scope } else if args.explain { InstallationScope::Global } else if args.installer.contains(&"apt".to_string()) { InstallationScope::Global } else { ognibuild::installer::auto_installation_scope(session.as_ref()) }; let installer: Box = if args.installer == ["auto"] { auto_installer(session.as_ref(), scope, args.dep_server_url.as_ref()) } else { select_installers( session.as_ref(), args.installer .iter() .map(|x| x.as_str()) .collect::>() .as_slice(), args.dep_server_url.as_ref(), ) .unwrap() }; let fixers: Vec>> = if !args.explain { vec![Box::new(ognibuild::fixers::InstallFixer::new( installer.as_ref(), scope, ))] } else { vec![] }; match run_action( session.as_ref(), scope, project.external_path(), installer.as_ref(), fixers .iter() .map(|f| f.as_ref()) .collect::>() .as_slice(), &args, ) { Ok(_) => {} Err(Error::NoBuildSystemDetected) => { log::info!("No build tools found."); return Err(1); } Err(Error::DependencyInstallError(e)) => { log::info!("Dependency installation failed: {}", e); return Err(1); } Err(Error::Unimplemented) => { log::info!("This command is not yet implemented."); return Err(1); } Err(Error::Error(AnalyzedError::Unidentified { .. })) => { log::info!( "If there is a clear indication of a problem in the build log, please consider filing a request to update the patterns in buildlog-consultant at https://github.com/jelmer/buildlog-consultant/issues/new"); return Err(1); } Err(Error::Error(AnalyzedError::Detailed { error, .. })) => { log::info!("Detailed error: {}", error); log::info!( "Please consider filing a bug report at https://github.com/jelmer/ognibuild/issues/new" ); } Err(e) => { log::info!("Error: {}", e); return Err(1); } } std::mem::drop(td); Ok(()) } ognibuild-0.0.32/src/bin/ognibuild-deb.rs000064400000000000000000000056541046102023000163050ustar 00000000000000use clap::Parser; use ognibuild::debian::build::{BuildOnceError, DEFAULT_BUILDER}; use std::io::Write; use std::path::PathBuf; #[derive(Parser)] struct Args { #[clap(short, long)] suffix: Option, #[clap(long, default_value = DEFAULT_BUILDER)] build_command: String, #[clap(short, long, default_value = "..")] output_directory: PathBuf, #[clap(short, long)] build_suite: Option, #[clap(long)] debug: bool, #[clap(long)] build_changelog_entry: Option, #[clap(short, long, default_value = ".")] /// The directory to build in directory: PathBuf, /// Use gbp dch to generate the changelog entry #[clap(long, default_value = "false")] gbp_dch: bool, } pub fn main() -> Result<(), i32> { let args = Args::parse(); let dir = args.directory; let (wt, subpath) = breezyshim::workingtree::open_containing(&dir).unwrap(); breezyshim::init(); breezyshim::plugin::load_plugins(); env_logger::builder() .format(|buf, record| writeln!(buf, "{}", record.args())) .filter( None, if args.debug { log::LevelFilter::Debug } else { log::LevelFilter::Info }, ) .init(); log::info!("Using output directory {}", args.output_directory.display()); if args.suffix.is_some() && args.build_changelog_entry.is_none() { log::warn!("--suffix is ignored without --build-changelog-entry"); } if args.build_changelog_entry.is_some() && args.build_suite.is_none() { log::error!("--build-changelog-entry requires --build-suite"); return Err(1); } let source_date_epoch = std::env::var("SOURCE_DATE_EPOCH") .ok() .map(|s| s.parse().unwrap()); match ognibuild::debian::build::attempt_build( &wt, args.suffix.as_deref(), args.build_suite.as_deref(), &args.output_directory, &args.build_command, args.build_changelog_entry.as_deref(), &subpath, source_date_epoch, args.gbp_dch, None, None, None, ) { Ok(_) => {} Err(BuildOnceError::Unidentified { phase, description, .. }) => { if let Some(phase) = phase { log::error!("build failed during {}: {}", phase, description); } else { log::error!("build failed: {}", description); } return Err(1); } Err(BuildOnceError::Detailed { phase, description, error, .. }) => { if let Some(phase) = phase { log::error!("build failed during {}: {}", phase, description); } else { log::error!("build failed: {}", description); } log::info!("error: {:?}", error); return Err(1); } } Ok(()) } ognibuild-0.0.32/src/bin/ognibuild-dist.rs000064400000000000000000000135111046102023000165050ustar 00000000000000use breezyshim::export::export; use breezyshim::tree::Tree; use breezyshim::workingtree::{self, WorkingTree}; use clap::Parser; #[cfg(feature = "debian")] use debian_control::Control; use ognibuild::analyze::AnalyzedError; use ognibuild::buildsystem::Error; use std::path::{Path, PathBuf}; #[derive(Clone, Default, PartialEq, Eq)] pub enum Mode { #[default] Auto, Vcs, Buildsystem, } impl std::str::FromStr for Mode { type Err = String; fn from_str(s: &str) -> Result { match s { "auto" => Ok(Mode::Auto), "vcs" => Ok(Mode::Vcs), "buildsystem" => Ok(Mode::Buildsystem), _ => Err(format!("Unknown mode: {}", s)), } } } impl std::fmt::Display for Mode { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Mode::Auto => write!(f, "auto"), Mode::Vcs => write!(f, "vcs"), Mode::Buildsystem => write!(f, "buildsystem"), } } } #[derive(Parser)] struct Args { #[clap(short, long, default_value = "unstable-amd64-sbuild")] /// Name of chroot to use chroot: String, #[clap(default_value = ".")] /// Directory with upstream source. directory: PathBuf, #[clap(long)] /// Path to packaging directory. packaging_directory: Option, #[clap(long, default_value = "..")] /// Target directory target_directory: PathBuf, #[clap(long)] /// Enable debug output. debug: bool, #[clap(long, default_value = "auto")] /// Mechanism to use to create buildsystem mode: Mode, #[clap(long)] /// Include control directory in tarball. include_controldir: bool, } pub fn main() -> Result<(), i32> { let args = Args::parse(); env_logger::builder() .filter_level(if args.debug { log::LevelFilter::Debug } else { log::LevelFilter::Info }) .init(); let (tree, subpath) = workingtree::open_containing(&args.directory).unwrap(); #[cfg(feature = "debian")] let (packaging_tree, packaging_subdir, package_name): ( Option, Option, Option, ) = if let Some(packaging_directory) = &args.packaging_directory { let (packaging_tree, packaging_subpath) = workingtree::open_containing(packaging_directory).unwrap(); let text = packaging_tree .get_file(Path::new("debian/control")) .unwrap(); let control: Control = Control::read(text).unwrap(); let package_name = control.source().unwrap().name().unwrap(); ( Some(packaging_tree), Some(packaging_subpath), Some(package_name), ) } else { (None, None, None) }; #[cfg(not(feature = "debian"))] let (packaging_tree, packaging_subdir): ( Option, Option, Option, ) = (None, None, None); match args.mode { Mode::Vcs => { export(&tree, Path::new("dist.tar.gz"), Some(&subpath)).unwrap(); Ok(()) } Mode::Auto | Mode::Buildsystem => { #[cfg(not(target_os = "linux"))] { log::error!("Unsupported mode: {}", args.mode); Err(1) } #[cfg(target_os = "linux")] match ognibuild::dist::create_dist_schroot( &tree, &args.target_directory.canonicalize().unwrap(), &args.chroot, packaging_tree.as_ref().map(|t| t as &dyn Tree), packaging_subdir.as_deref(), Some(args.include_controldir), &subpath, &mut ognibuild::logs::NoLogManager, None, package_name.as_deref(), ) { Ok(ret) => { log::info!("Created {}", ret.to_str().unwrap()); Ok(()) } Err(Error::IoError(e)) => { log::error!("IO error: {}", e); Err(1) } Err(Error::DependencyInstallError(e)) => { log::error!("Dependency install error: {}", e); Err(1) } Err(Error::NoBuildSystemDetected) => { if args.mode == Mode::Buildsystem { log::error!("No build system detected, unable to create tarball"); Err(1) } else { log::info!("No build system detected, falling back to simple export."); export(&tree, Path::new("dist.tar.gz"), Some(&subpath)).unwrap(); Ok(()) } } Err(Error::Unimplemented) => { if args.mode == Mode::Buildsystem { log::error!("Unable to ask buildsystem for tarball"); Err(1) } else { log::info!("Build system does not support dist tarball creation, falling back to simple export."); export(&tree, Path::new("dist.tar.gz"), Some(&subpath)).unwrap(); Ok(()) } } Err(Error::Error(AnalyzedError::Unidentified { lines, .. })) => { log::error!("Unidentified error: {:?}", lines); Err(1) } Err(Error::Error(AnalyzedError::Detailed { error, .. })) => { log::error!("Identified error during dist creation: {}", error); Err(1) } Err(e) => { log::error!("Error: {}", e); Err(1) } } } } } ognibuild-0.0.32/src/bin/report-apt-deps-status.rs000064400000000000000000000153661046102023000201510ustar 00000000000000use clap::Parser; use ognibuild::buildsystem::{detect_buildsystems, DependencyCategory}; use ognibuild::debian::apt::{dependency_to_deb_dependency, AptManager}; use ognibuild::dependencies::debian::{ default_tie_breakers, DebianDependency, DebianDependencyCategory, }; use ognibuild::dependency::Dependency; use ognibuild::session::plain::PlainSession; use ognibuild::session::Session; use std::collections::HashMap; use std::io::Write; use std::path::PathBuf; #[derive(Parser)] struct Args { #[clap(long)] detailed: bool, directory: PathBuf, #[clap(long)] debug: bool, } fn main() -> Result<(), i32> { let args = Args::parse(); let mut session = PlainSession::new(); env_logger::builder() .format(|buf, record| writeln!(buf, "{}", record.args())) .filter( None, if args.debug { log::LevelFilter::Debug } else { log::LevelFilter::Info }, ) .init(); let directory = args.directory.canonicalize().unwrap(); session.chdir(&directory).unwrap(); let bss = detect_buildsystems(&directory); if bss.is_empty() { eprintln!("No build tools found"); std::process::exit(1); } log::debug!("Detected buildsystems: {:?}", bss); let mut deps: HashMap>> = HashMap::new(); for buildsystem in bss { match buildsystem.get_declared_dependencies(&session, Some(&[])) { Ok(declared_reqs) => { for (stage, req) in declared_reqs { deps.entry(stage).or_default().push(req); } } Err(e) => { log::warn!( "Unable to get dependencies from buildsystem {:?}, skipping", buildsystem ); continue; } } } let tie_breakers = default_tie_breakers(&session); let apt = AptManager::new(&mut session, None); if args.detailed { let mut unresolved = false; for (stage, deps) in deps.iter() { log::info!("Stage: {}", stage); for dep in deps { if let Some(deb_dep) = dependency_to_deb_dependency(&apt, dep.as_ref(), &tie_breakers).unwrap() { log::info!("Dependency: {:?} → {}", dep, deb_dep.relation_string()); } else { log::warn!("Dependency: {:?} → ??", dep); unresolved = true; } } log::info!(""); } if unresolved { Err(1) } else { Ok(()) } } else { let mut dep_depends: HashMap> = HashMap::new(); let mut unresolved = vec![]; for (stage, reqs) in deps.iter() { for dep in reqs { if let Some(deb_dep) = dependency_to_deb_dependency(&apt, dep.as_ref(), &tie_breakers).unwrap() { match stage { DependencyCategory::Universal => { dep_depends .entry(DebianDependencyCategory::Build) .or_default() .push(deb_dep.clone()); dep_depends .entry(DebianDependencyCategory::Runtime) .or_default() .push(deb_dep); } DependencyCategory::Build => { dep_depends .entry(DebianDependencyCategory::Build) .or_default() .push(deb_dep); } DependencyCategory::Runtime => { dep_depends .entry(DebianDependencyCategory::Runtime) .or_default() .push(deb_dep); } DependencyCategory::BuildExtra(name) => { // TODO: handle build extra: build profile? } DependencyCategory::Test => { dep_depends .entry(DebianDependencyCategory::Test("test".to_string())) .or_default() .push(deb_dep); } DependencyCategory::Dev => {} DependencyCategory::RuntimeExtra(name) => { // TODO: handle runtime extra } } } else { unresolved.push(dep); } } } for (category, deps) in dep_depends.iter() { match category { DebianDependencyCategory::Build => { log::info!( "Build-Depends: {}", deps.iter() .map(|d| d.relation_string()) .collect::>() .join(", ") ); } DebianDependencyCategory::Runtime => { log::info!( "Depends: {}", deps.iter() .map(|d| d.relation_string()) .collect::>() .join(", ") ); } DebianDependencyCategory::Test(test) => { log::info!( "Test-Depends ({}): {}", test, deps.iter() .map(|d| d.relation_string()) .collect::>() .join(", ") ); } DebianDependencyCategory::Install => { log::info!( "Pre-Depends: {}", deps.iter() .map(|d| d.relation_string()) .collect::>() .join(", ") ); } } } if !unresolved.is_empty() { log::warn!("Unable to find apt packages for the following dependencies:"); for req in unresolved { log::warn!("* {:?}", req); } Err(1) } else { Ok(()) } } } ognibuild-0.0.32/src/buildlog.rs000064400000000000000000000101331046102023000146160ustar 00000000000000use crate::dependency::Dependency; use buildlog_consultant::problems::common::*; use buildlog_consultant::problems::debian::UnsatisfiedAptDependencies; use buildlog_consultant::Problem; pub trait ToDependency: Problem { fn to_dependency(&self) -> Option>; } macro_rules! try_problem_to_dependency { ($expr:expr, $type:ty) => { if let Some(p) = $expr .as_any() .downcast_ref::<$type>() .and_then(|p| p.to_dependency()) { return Some(p); } }; } pub fn problem_to_dependency(problem: &dyn Problem) -> Option> { // TODO(jelmer): Find a more idiomatic way to do this. try_problem_to_dependency!(problem, MissingAutoconfMacro); #[cfg(feature = "debian")] try_problem_to_dependency!(problem, UnsatisfiedAptDependencies); try_problem_to_dependency!(problem, MissingGoPackage); try_problem_to_dependency!(problem, MissingHaskellDependencies); try_problem_to_dependency!(problem, MissingJavaClass); try_problem_to_dependency!(problem, MissingJDK); try_problem_to_dependency!(problem, MissingJRE); try_problem_to_dependency!(problem, MissingJDKFile); try_problem_to_dependency!(problem, MissingLatexFile); try_problem_to_dependency!(problem, MissingCommand); try_problem_to_dependency!(problem, MissingCommandOrBuildFile); try_problem_to_dependency!(problem, VcsControlDirectoryNeeded); try_problem_to_dependency!(problem, MissingLuaModule); try_problem_to_dependency!(problem, MissingCargoCrate); try_problem_to_dependency!(problem, MissingRustCompiler); try_problem_to_dependency!(problem, MissingPkgConfig); try_problem_to_dependency!(problem, MissingFile); try_problem_to_dependency!(problem, MissingCHeader); try_problem_to_dependency!(problem, MissingJavaScriptRuntime); try_problem_to_dependency!(problem, MissingValaPackage); try_problem_to_dependency!(problem, MissingRubyGem); try_problem_to_dependency!(problem, DhAddonLoadFailure); try_problem_to_dependency!(problem, MissingLibrary); try_problem_to_dependency!(problem, MissingStaticLibrary); try_problem_to_dependency!(problem, MissingRubyFile); try_problem_to_dependency!(problem, MissingSprocketsFile); try_problem_to_dependency!(problem, CMakeFilesMissing); try_problem_to_dependency!(problem, MissingMavenArtifacts); try_problem_to_dependency!(problem, MissingGnomeCommonDependency); try_problem_to_dependency!(problem, MissingQtModules); try_problem_to_dependency!(problem, MissingQt); try_problem_to_dependency!(problem, MissingX11); try_problem_to_dependency!(problem, UnknownCertificateAuthority); try_problem_to_dependency!(problem, MissingLibtool); try_problem_to_dependency!(problem, MissingCMakeComponents); try_problem_to_dependency!(problem, MissingGnulibDirectory); try_problem_to_dependency!(problem, MissingIntrospectionTypelib); try_problem_to_dependency!(problem, MissingCSharpCompiler); try_problem_to_dependency!(problem, MissingXfceDependency); try_problem_to_dependency!(problem, MissingNodePackage); try_problem_to_dependency!(problem, MissingNodeModule); try_problem_to_dependency!(problem, MissingPerlPredeclared); try_problem_to_dependency!(problem, MissingPerlFile); try_problem_to_dependency!(problem, MissingPerlModule); try_problem_to_dependency!(problem, MissingPhpClass); try_problem_to_dependency!(problem, MissingPHPExtension); try_problem_to_dependency!(problem, MissingPytestFixture); try_problem_to_dependency!(problem, UnsupportedPytestArguments); try_problem_to_dependency!(problem, UnsupportedPytestConfigOption); try_problem_to_dependency!(problem, MissingPythonDistribution); try_problem_to_dependency!(problem, MissingPythonModule); try_problem_to_dependency!(problem, MissingSetupPyCommand); try_problem_to_dependency!(problem, MissingRPackage); try_problem_to_dependency!(problem, MissingVagueDependency); try_problem_to_dependency!(problem, MissingXmlEntity); try_problem_to_dependency!(problem, MissingMakeTarget); None } ognibuild-0.0.32/src/buildsystem.rs000064400000000000000000000561071046102023000153740ustar 00000000000000use crate::dependencies::BinaryDependency; use crate::dependency::Dependency; use crate::installer::{ install_missing_deps, Error as InstallerError, InstallationScope, Installer, }; use crate::output::Output; use crate::session::{which, Session}; use std::path::{Path, PathBuf}; /// The category of a dependency #[derive(Debug, Clone, PartialEq, Eq, std::hash::Hash)] pub enum DependencyCategory { /// A dependency that is required for the package to build and run Universal, /// Building of artefacts Build, /// For running artefacts after build or install Runtime, /// Test infrastructure, e.g. test frameworks or test runners Test, /// Needed for development, e.g. linters or IDE plugins Dev, /// Extra build dependencies, e.g. for optional features BuildExtra(String), /// Extra dependencies, e.g. for optional features RuntimeExtra(String), } impl std::fmt::Display for DependencyCategory { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { DependencyCategory::Universal => write!(f, "universal"), DependencyCategory::Build => write!(f, "build"), DependencyCategory::Runtime => write!(f, "runtime"), DependencyCategory::Test => write!(f, "test"), DependencyCategory::Dev => write!(f, "dev"), DependencyCategory::BuildExtra(s) => write!(f, "build-extra:{}", s), DependencyCategory::RuntimeExtra(s) => write!(f, "runtime-extra:{}", s), } } } #[derive(Debug)] pub enum Error { /// The build system could not be detected. NoBuildSystemDetected, DependencyInstallError(InstallerError), Error(crate::analyze::AnalyzedError), IoError(std::io::Error), Unimplemented, Other(String), } impl From for Error { fn from(e: InstallerError) -> Self { Error::DependencyInstallError(e) } } impl From for Error { fn from(e: std::io::Error) -> Self { Error::IoError(e) } } impl From for Error { fn from(e: crate::analyze::AnalyzedError) -> Self { Error::Error(e) } } impl From for Error { fn from(e: crate::session::Error) -> Self { match e { crate::session::Error::CalledProcessError(e) => { crate::analyze::AnalyzedError::Unidentified { retcode: e.code().unwrap(), lines: Vec::new(), secondary: None, } .into() } crate::session::Error::IoError(e) => e.into(), crate::session::Error::SetupFailure(_, _) => unreachable!(), } } } impl From> for Error { fn from(e: crate::fix_build::IterateBuildError) -> Self { match e { crate::fix_build::IterateBuildError::FixerLimitReached(n) => { Error::Other(format!("Fixer limit reached: {}", n)) } crate::fix_build::IterateBuildError::Persistent(e) => { crate::analyze::AnalyzedError::Detailed { error: e, retcode: 1, } .into() } crate::fix_build::IterateBuildError::Unidentified { retcode, lines, secondary, } => crate::analyze::AnalyzedError::Unidentified { retcode, lines, secondary, } .into(), crate::fix_build::IterateBuildError::Other(o) => o.into(), } } } impl From> for Error { fn from(e: crate::fix_build::IterateBuildError) -> Self { match e { crate::fix_build::IterateBuildError::FixerLimitReached(n) => { Error::Other(format!("Fixer limit reached: {}", n)) } crate::fix_build::IterateBuildError::Persistent(e) => { crate::analyze::AnalyzedError::Detailed { error: e, retcode: 1, } .into() } crate::fix_build::IterateBuildError::Unidentified { retcode, lines, secondary, } => crate::analyze::AnalyzedError::Unidentified { retcode, lines, secondary, } .into(), crate::fix_build::IterateBuildError::Other(o) => o, } } } impl From for crate::fix_build::InterimError { fn from(e: Error) -> Self { match e { Error::Error(crate::analyze::AnalyzedError::Detailed { error, retcode: _ }) => { crate::fix_build::InterimError::Recognized(error) } e => crate::fix_build::InterimError::Other(e), } } } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Error::NoBuildSystemDetected => write!(f, "No build system detected"), Error::DependencyInstallError(e) => write!(f, "Error installing dependency: {}", e), Error::Error(e) => write!(f, "Error: {}", e), Error::IoError(e) => write!(f, "IO Error: {}", e), Error::Other(e) => write!(f, "Error: {}", e), Error::Unimplemented => write!(f, "Unimplemented"), } } } impl std::error::Error for Error {} #[derive(Debug, Clone)] pub struct InstallTarget { pub scope: InstallationScope, pub prefix: Option, } impl DependencyCategory { pub fn all() -> [DependencyCategory; 5] { [ DependencyCategory::Universal, DependencyCategory::Build, DependencyCategory::Runtime, DependencyCategory::Test, DependencyCategory::Dev, ] } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum Action { Clean, Build, Test, Install, } /// Determine the path to a binary, installing it if necessary pub fn guaranteed_which( session: &dyn Session, installer: &dyn Installer, name: &str, ) -> Result { match which(session, name) { Some(path) => Ok(PathBuf::from(path)), None => { installer.install(&BinaryDependency::new(name), InstallationScope::Global)?; Ok(PathBuf::from(which(session, name).unwrap())) } } } /// A particular buildsystem. pub trait BuildSystem: std::fmt::Debug { /// The name of the buildsystem. fn name(&self) -> &str; fn dist( &self, session: &dyn Session, installer: &dyn Installer, target_directory: &Path, quiet: bool, ) -> Result; fn install_declared_dependencies( &self, categories: &[DependencyCategory], scopes: &[InstallationScope], session: &dyn Session, installer: &dyn Installer, fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result<(), Error> { let declared_deps = self.get_declared_dependencies(session, fixers)?; let relevant = declared_deps .into_iter() .filter(|(c, _d)| categories.contains(c)) .map(|(_, d)| d) .collect::>(); log::debug!("Declared dependencies: {:?}", relevant); install_missing_deps( session, installer, scopes, relevant .iter() .map(|d| d.as_ref()) .collect::>() .as_slice(), )?; Ok(()) } fn test(&self, session: &dyn Session, installer: &dyn Installer) -> Result<(), Error>; fn build(&self, session: &dyn Session, installer: &dyn Installer) -> Result<(), Error>; fn clean(&self, session: &dyn Session, installer: &dyn Installer) -> Result<(), Error>; fn install( &self, session: &dyn Session, installer: &dyn Installer, install_target: &InstallTarget, ) -> Result<(), Error>; fn get_declared_dependencies( &self, session: &dyn Session, fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result)>, Error>; fn get_declared_outputs( &self, session: &dyn Session, fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result>, Error>; fn as_any(&self) -> &dyn std::any::Any; } pub const PEAR_NAMESPACES: &[&str] = &[ "http://pear.php.net/dtd/package-2.0", "http://pear.php.net/dtd/package-2.1", ]; #[derive(Debug)] pub struct Pear(pub PathBuf); impl Pear { pub fn new(path: PathBuf) -> Self { Self(path) } pub fn probe(path: &Path) -> Option> { let package_xml_path = path.join("package.xml"); if !package_xml_path.exists() { return None; } use xmltree::Element; let root = Element::parse(std::fs::File::open(package_xml_path).unwrap()).unwrap(); // Check that the root tag is and that the namespace is one of the known PEAR // namespaces. if root .namespace .as_deref() .and_then(|ns| PEAR_NAMESPACES.iter().find(|&n| *n == ns)) .is_none() { log::warn!( "Namespace of package.xml is not recognized as a PEAR package: {:?}", root.namespace ); return None; } if root.name != "package" { log::warn!("Root tag of package.xml is not : {:?}", root.name); return None; } log::debug!( "Found package.xml with namespace {}, assuming pear package.", root.namespace.as_ref().unwrap() ); Some(Box::new(Self(PathBuf::from(path)))) } } impl BuildSystem for Pear { fn name(&self) -> &str { "pear" } fn dist( &self, session: &dyn Session, installer: &dyn Installer, target_directory: &Path, quiet: bool, ) -> Result { let dc = crate::dist_catcher::DistCatcher::new(vec![session.external_path(Path::new("."))]); let pear = guaranteed_which(session, installer, "pear")?; session .command(vec![pear.to_str().unwrap(), "package"]) .quiet(quiet) .run_detecting_problems()?; Ok(dc.copy_single(target_directory).unwrap().unwrap()) } fn test(&self, session: &dyn Session, installer: &dyn Installer) -> Result<(), Error> { let pear = guaranteed_which(session, installer, "pear")?; session .command(vec![pear.to_str().unwrap(), "run-tests"]) .run_detecting_problems()?; Ok(()) } fn build(&self, session: &dyn Session, installer: &dyn Installer) -> Result<(), Error> { let pear = guaranteed_which(session, installer, "pear")?; session .command(vec![ pear.to_str().unwrap(), "build", self.0.to_str().unwrap(), ]) .run_detecting_problems()?; Ok(()) } fn clean(&self, _session: &dyn Session, _installer: &dyn Installer) -> Result<(), Error> { todo!() } fn install( &self, session: &dyn Session, installer: &dyn Installer, _install_target: &InstallTarget, ) -> Result<(), Error> { let pear = guaranteed_which(session, installer, "pear")?; session .command(vec![ pear.to_str().unwrap(), "install", self.0.to_str().unwrap(), ]) .run_detecting_problems()?; Ok(()) } fn get_declared_dependencies( &self, _session: &dyn Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result)>, Error> { let path = self.0.join("package.xml"); use xmltree::Element; let root = Element::parse(std::fs::File::open(path).unwrap()).unwrap(); // Check that the root tag is and that the namespace is one of the known PEAR // namespaces. if root .namespace .as_deref() .and_then(|ns| PEAR_NAMESPACES.iter().find(|&n| *n == ns)) .is_none() { log::warn!( "Namespace of package.xml is not recognized as a PEAR package: {:?}", root.namespace ); return Ok(vec![]); } if root.name != "package" { log::warn!("Root tag of package.xml is not : {:?}", root.name); return Ok(vec![]); } let dependencies_tag = root .get_child("dependencies") .ok_or_else(|| Error::Other("No tag found in ".to_string()))?; let required_tag = dependencies_tag .get_child("required") .ok_or_else(|| Error::Other("No tag found in ".to_string()))?; Ok(required_tag .children .iter() .filter_map(|x| x.as_element()) .filter(|c| c.name.as_str() == "package") .filter_map( |package_tag| -> Option<(DependencyCategory, Box)> { let name = package_tag .get_child("name") .and_then(|n| n.get_text()) .unwrap() .into_owned(); let min_version = package_tag .get_child("min") .and_then(|m| m.get_text()) .map(|s| s.into_owned()); let max_version = package_tag .get_child("max") .and_then(|m| m.get_text()) .map(|s| s.into_owned()); let channel = package_tag .get_child("channel") .and_then(|c| c.get_text()) .map(|s| s.into_owned()); Some(( DependencyCategory::Universal, Box::new(crate::dependencies::php::PhpPackageDependency { package: name, channel, min_version, max_version, }) as Box, )) }, ) .collect()) } fn get_declared_outputs( &self, _session: &dyn Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result>, Error> { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } /// Detect build systems. pub fn scan_buildsystems(path: &Path) -> Vec<(PathBuf, Box)> { let mut ret = vec![]; ret.extend( detect_buildsystems(path) .into_iter() .map(|bs| (PathBuf::from(path), bs)), ); if ret.is_empty() { // Nothing found. Try the next level? for entry in std::fs::read_dir(path).unwrap() { let entry = entry.unwrap(); if entry.file_type().unwrap().is_dir() { ret.extend( detect_buildsystems(&entry.path()) .into_iter() .map(|bs| (entry.path(), bs)), ); } } } ret } #[derive(Debug)] pub struct Composer(pub PathBuf); impl Composer { pub fn new(path: PathBuf) -> Self { Self(path) } pub fn probe(path: &Path) -> Option> { if path.join("composer.json").exists() { Some(Box::new(Self(path.into()))) } else { None } } } impl BuildSystem for Composer { fn name(&self) -> &str { "composer" } fn dist( &self, _session: &dyn Session, _installer: &dyn Installer, _target_directory: &Path, _quiet: bool, ) -> Result { todo!() } fn test(&self, _session: &dyn Session, _installer: &dyn Installer) -> Result<(), Error> { todo!() } fn build(&self, _session: &dyn Session, _installer: &dyn Installer) -> Result<(), Error> { todo!() } fn clean(&self, _session: &dyn Session, _installer: &dyn Installer) -> Result<(), Error> { todo!() } fn install( &self, _session: &dyn Session, _installer: &dyn Installer, _install_target: &InstallTarget, ) -> Result<(), Error> { todo!() } fn get_declared_dependencies( &self, _session: &dyn Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result)>, Error> { todo!() } fn get_declared_outputs( &self, _session: &dyn Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result>, Error> { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } #[derive(Debug)] pub struct RunTests(pub PathBuf); impl RunTests { pub fn new(path: PathBuf) -> Self { Self(path) } pub fn probe(path: &Path) -> Option> { if path.join("runtests.sh").exists() { Some(Box::new(Self(path.into()))) } else { None } } } impl BuildSystem for RunTests { fn name(&self) -> &str { "runtests" } fn dist( &self, _session: &dyn Session, _installer: &dyn Installer, _target_directory: &Path, _quiet: bool, ) -> Result { todo!() } fn test(&self, session: &dyn Session, _installer: &dyn Installer) -> Result<(), Error> { let interpreter = crate::shebang::shebang_binary(&self.0.join("runtests.sh")).unwrap(); let argv = if interpreter.is_some() { vec!["./runtests.sh"] } else { vec!["/bin/bash", "./runtests.sh"] }; session.command(argv).run_detecting_problems()?; Ok(()) } fn build(&self, _session: &dyn Session, _installer: &dyn Installer) -> Result<(), Error> { todo!() } fn clean(&self, _session: &dyn Session, _installer: &dyn Installer) -> Result<(), Error> { todo!() } fn install( &self, _session: &dyn Session, _installer: &dyn Installer, _install_target: &InstallTarget, ) -> Result<(), Error> { todo!() } fn get_declared_dependencies( &self, _session: &dyn Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result)>, Error> { todo!() } fn get_declared_outputs( &self, _session: &dyn Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result>, Error> { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } pub fn detect_buildsystems(path: &std::path::Path) -> Vec> { if !path.exists() { log::error!("Path does not exist: {:?}", path); return vec![]; } let path = path.canonicalize().unwrap(); let mut ret = vec![]; for probe in [ Pear::probe, crate::buildsystems::python::SetupPy::probe, crate::buildsystems::node::Node::probe, crate::buildsystems::waf::Waf::probe, crate::buildsystems::ruby::Gem::probe, crate::buildsystems::meson::Meson::probe, crate::buildsystems::rust::Cargo::probe, crate::buildsystems::haskell::Cabal::probe, crate::buildsystems::java::Gradle::probe, crate::buildsystems::java::Maven::probe, crate::buildsystems::perl::DistZilla::probe, crate::buildsystems::perl::PerlBuildTiny::probe, crate::buildsystems::go::Golang::probe, crate::buildsystems::bazel::Bazel::probe, crate::buildsystems::r::R::probe, crate::buildsystems::octave::Octave::probe, crate::buildsystems::make::CMake::probe, crate::buildsystems::gnome::GnomeShellExtension::probe, // Make is intentionally at the end of the list. crate::buildsystems::make::Make::probe, Composer::probe, RunTests::probe, ] { let bs = probe(&path); if let Some(bs) = bs { ret.push(bs); } } ret } pub fn get_buildsystem(path: &Path) -> Option<(PathBuf, Box)> { scan_buildsystems(path).into_iter().next() } pub fn buildsystem_by_name(name: &str, path: &Path) -> Option> { match name { "pear" => Pear::probe(path), "composer" => Composer::probe(path), "runtests" => RunTests::probe(path), "setup.py" => crate::buildsystems::python::SetupPy::probe(path), "node" => crate::buildsystems::node::Node::probe(path), "waf" => crate::buildsystems::waf::Waf::probe(path), "gem" => crate::buildsystems::ruby::Gem::probe(path), "meson" => crate::buildsystems::meson::Meson::probe(path), "cargo" => crate::buildsystems::rust::Cargo::probe(path), "cabal" => crate::buildsystems::haskell::Cabal::probe(path), "gradle" => crate::buildsystems::java::Gradle::probe(path), "maven" => crate::buildsystems::java::Maven::probe(path), "distzilla" => crate::buildsystems::perl::DistZilla::probe(path), "perl-build-tiny" => crate::buildsystems::perl::PerlBuildTiny::probe(path), "go" => crate::buildsystems::go::Golang::probe(path), "bazel" => crate::buildsystems::bazel::Bazel::probe(path), "r" => crate::buildsystems::r::R::probe(path), "octave" => crate::buildsystems::octave::Octave::probe(path), "cmake" => crate::buildsystems::make::CMake::probe(path), "gnome-shell-extension" => crate::buildsystems::gnome::GnomeShellExtension::probe(path), "make" => crate::buildsystems::make::Make::probe(path), _ => None, } } #[cfg(test)] mod tests { use super::*; use crate::installer::NullInstaller; use crate::session::plain::PlainSession; #[test] fn test_guaranteed_which() { let session = PlainSession::new(); let installer = NullInstaller::new(); let _path = guaranteed_which(&session, &installer, "ls").unwrap(); } #[test] fn test_guaranteed_which_not_found() { let session = PlainSession::new(); let installer = NullInstaller::new(); assert!(matches!( guaranteed_which(&session, &installer, "this-does-not-exist").unwrap_err(), InstallerError::UnknownDependencyFamily, )); } } ognibuild-0.0.32/src/buildsystems/bazel.rs000064400000000000000000000056201046102023000166460ustar 00000000000000use crate::buildsystem::{BuildSystem, Error}; use std::path::{Path, PathBuf}; #[derive(Debug)] pub struct Bazel { #[allow(dead_code)] path: PathBuf, } impl Bazel { pub fn new(path: &Path) -> Self { Self { path: path.to_path_buf(), } } pub fn probe(path: &Path) -> Option> { if path.join("BUILD").exists() { Some(Box::new(Self::new(path))) } else { None } } pub fn exists(path: &Path) -> bool { path.join("BUILD").exists() } } impl BuildSystem for Bazel { fn name(&self) -> &str { "bazel" } fn dist( &self, _session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, _target_directory: &Path, _quiet: bool, ) -> Result { Err(Error::Unimplemented) } fn test( &self, session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { session .command(vec!["bazel", "test", "//..."]) .run_detecting_problems()?; Ok(()) } fn build( &self, session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { session .command(vec!["bazel", "build", "//..."]) .run_detecting_problems()?; Ok(()) } fn clean( &self, _session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { Err(Error::Unimplemented) } fn install( &self, session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, _install_target: &crate::buildsystem::InstallTarget, ) -> Result<(), crate::buildsystem::Error> { session .command(vec!["bazel", "build", "//..."]) .run_detecting_problems()?; Err(Error::Unimplemented) } fn get_declared_dependencies( &self, _session: &dyn crate::session::Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result< Vec<( crate::buildsystem::DependencyCategory, Box, )>, crate::buildsystem::Error, > { Err(Error::Unimplemented) } fn get_declared_outputs( &self, _session: &dyn crate::session::Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result>, crate::buildsystem::Error> { Err(Error::Unimplemented) } fn as_any(&self) -> &dyn std::any::Any { self } } ognibuild-0.0.32/src/buildsystems/gnome.rs000064400000000000000000000070531046102023000166600ustar 00000000000000use crate::buildsystem::{BuildSystem, DependencyCategory, Error}; use crate::dependencies::vague::VagueDependency; use std::path::{Path, PathBuf}; #[derive(Debug)] pub struct GnomeShellExtension { path: PathBuf, } #[derive(Debug, serde::Deserialize)] #[allow(dead_code)] struct Metadata { name: String, description: String, uuid: String, shell_version: String, version: String, url: String, license: String, authors: Vec, settings_schema: Option, gettext_domain: Option, extension: Option, _generated: Option, } impl GnomeShellExtension { pub fn new(path: PathBuf) -> Self { Self { path } } pub fn exists(path: &PathBuf) -> bool { path.join("metadata.json").exists() } pub fn probe(path: &Path) -> Option> { if Self::exists(&path.to_path_buf()) { log::debug!("Found metadata.json , assuming gnome-shell extension."); Some(Box::new(Self::new(path.to_path_buf()))) } else { None } } } impl BuildSystem for GnomeShellExtension { fn name(&self) -> &str { "gnome-shell-extension" } fn dist( &self, _session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, _target_directory: &std::path::Path, _quiet: bool, ) -> Result { Err(Error::Unimplemented) } fn test( &self, _session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { Ok(()) } fn build( &self, _session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { Ok(()) } fn clean( &self, _session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { Err(Error::Unimplemented) } fn install( &self, _session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, _install_target: &crate::buildsystem::InstallTarget, ) -> Result<(), crate::buildsystem::Error> { Err(Error::Unimplemented) } fn get_declared_dependencies( &self, _session: &dyn crate::session::Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result< Vec<( crate::buildsystem::DependencyCategory, Box, )>, crate::buildsystem::Error, > { let f = std::fs::File::open(self.path.join("metadata.json")).unwrap(); let metadata: Metadata = serde_json::from_reader(f).unwrap(); let deps: Vec<(DependencyCategory, Box)> = vec![( DependencyCategory::Universal, Box::new(VagueDependency::new( "gnome-shell", Some(&metadata.shell_version), )), )]; Ok(deps) } fn get_declared_outputs( &self, _session: &dyn crate::session::Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result>, crate::buildsystem::Error> { Err(Error::Unimplemented) } fn as_any(&self) -> &dyn std::any::Any { self } } ognibuild-0.0.32/src/buildsystems/go.rs000064400000000000000000000166571046102023000161720ustar 00000000000000use crate::buildsystem::{BuildSystem, Error}; use crate::dependencies::go::{GoDependency, GoPackageDependency}; use std::path::{Path, PathBuf}; #[derive(Debug)] pub struct Golang { path: PathBuf, } impl Golang { pub fn new(path: &Path) -> Self { Self { path: path.to_path_buf(), } } pub fn probe(path: &Path) -> Option> { if path.join("go.mod").exists() { return Some(Box::new(Self::new(path))); } if path.join("go.sum").exists() { return Some(Box::new(Self::new(path))); } for entry in path.read_dir().unwrap() { let entry = entry.unwrap(); if !entry.file_type().unwrap().is_dir() { continue; } match entry.path().read_dir() { Ok(d) => { for subentry in d { let subentry = subentry.unwrap(); if subentry.file_type().unwrap().is_file() && subentry.path().extension() == Some(std::ffi::OsStr::new("go")) { return Some(Box::new(Self::new(path))); } } } Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => { // Ignore permission denied errors. log::debug!("Permission denied reading {:?}", entry.path()); } Err(e) => { panic!("Error reading {:?}: {:?}", entry.path(), e); } } } None } } impl BuildSystem for Golang { fn name(&self) -> &str { "golang" } fn dist( &self, _session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, _target_directory: &Path, _quiet: bool, ) -> Result { Err(Error::Unimplemented) } fn test( &self, session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { session .command(vec!["go", "test", "./..."]) .run_detecting_problems()?; Ok(()) } fn build( &self, session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { session .command(vec!["go", "build"]) .run_detecting_problems()?; Ok(()) } fn clean( &self, session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { session.command(vec!["go", "clean"]).check_call()?; Ok(()) } fn install( &self, session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, _install_target: &crate::buildsystem::InstallTarget, ) -> Result<(), crate::buildsystem::Error> { session .command(vec!["go", "install"]) .run_detecting_problems()?; Ok(()) } fn get_declared_dependencies( &self, _session: &dyn crate::session::Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result< Vec<( crate::buildsystem::DependencyCategory, Box, )>, crate::buildsystem::Error, > { let mut ret = vec![]; let go_mod_path = self.path.join("go.mod"); if go_mod_path.exists() { let f = std::fs::File::open(go_mod_path).unwrap(); ret.extend( go_mod_dependencies(f) .into_iter() .map(|dep| (crate::buildsystem::DependencyCategory::Build, dep)), ); } Ok(ret) } fn get_declared_outputs( &self, _session: &dyn crate::session::Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result>, crate::buildsystem::Error> { Err(Error::Unimplemented) } fn as_any(&self) -> &dyn std::any::Any { self } } enum GoModEntry { Go(String), Require(String, String), Exclude(String, String), Replace(String, String, String, String), Retract(String, String), Toolchain(String), Module(String), } impl GoModEntry { fn parse(name: &str, args: &[&str]) -> Self { match name { "go" => GoModEntry::Go(args[0].to_string()), "require" => GoModEntry::Require(args[0].to_string(), args[1].to_string()), "exclude" => GoModEntry::Exclude(args[0].to_string(), args[1].to_string()), "replace" => { assert_eq!(args[2], "=>"); GoModEntry::Replace( args[0].to_string(), args[1].to_string(), args[3].to_string(), args[4].to_string(), ) } "retract" => GoModEntry::Retract(args[0].to_string(), args[1].to_string()), "toolchain" => GoModEntry::Toolchain(args[0].to_string()), "module" => GoModEntry::Module(args[0].to_string()), _ => panic!("unknown go.mod directive: {}", name), } } } fn parse_go_mod(f: R) -> Vec { let f = std::io::BufReader::new(f); let mut ret = vec![]; use std::io::BufRead; let lines = f .lines() .map(|l| l.unwrap()) .map(|l| l.split("//").next().unwrap().to_string()) .collect::>(); let mut line_iter = lines.iter(); while let Some(mut line) = line_iter.next() { let parts = line.trim().split(" ").collect::>(); if parts.is_empty() || parts == [""] { continue; } if parts.len() == 2 && parts[1] == "(" { line = line_iter.next().unwrap(); while line.trim() != ")" { ret.push(GoModEntry::parse( parts[0], line.trim().split(' ').collect::>().as_slice(), )); line = line_iter.next().expect("unexpected EOF"); } } else { ret.push(GoModEntry::parse(parts[0], &parts[1..])); } } ret } fn go_mod_dependencies(r: R) -> Vec> { let mut ret: Vec> = vec![]; for entry in parse_go_mod(r) { match entry { GoModEntry::Go(version) => { ret.push(Box::new(GoDependency::new(Some(&version)))); } GoModEntry::Require(name, version) => { ret.push(Box::new(GoPackageDependency::new( &name, Some(version.strip_prefix('v').unwrap()), ))); } GoModEntry::Exclude(_name, _version) => { // TODO(jelmer): Create conflicts? } GoModEntry::Module(_name) => {} GoModEntry::Retract(_name, _version) => {} GoModEntry::Toolchain(_name) => {} GoModEntry::Replace(_n1, _v1, _n2, _v2) => { // TODO(jelmer): do.. something? } } } ret } ognibuild-0.0.32/src/buildsystems/haskell.rs000064400000000000000000000071351046102023000171770ustar 00000000000000use crate::buildsystem::{BuildSystem, Error}; use std::path::{Path, PathBuf}; #[derive(Debug)] pub struct Cabal { #[allow(dead_code)] path: PathBuf, } impl Cabal { pub fn new(path: PathBuf) -> Self { Self { path } } fn run( &self, session: &dyn crate::session::Session, extra_args: Vec<&str>, ) -> Result<(), crate::analyze::AnalyzedError> { let mut args = vec!["runhaskell", "Setup.hs"]; args.extend(extra_args); match session.command(args.clone()).run_detecting_problems() { Ok(ls) => Ok(ls), Err(crate::analyze::AnalyzedError::Unidentified { lines, .. }) if lines.contains(&"Run the 'configure' command first.".to_string()) => { session .command(vec!["runhaskell", "Setup.hs", "configure"]) .run_detecting_problems()?; session.command(args).run_detecting_problems() } Err(e) => Err(e), } .map(|_| ()) } pub fn probe(path: &Path) -> Option> { if path.join("Setup.hs").exists() { Some(Box::new(Self::new(path.to_owned()))) } else { None } } } impl BuildSystem for Cabal { fn name(&self) -> &str { "cabal" } fn dist( &self, session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, target_directory: &std::path::Path, _quiet: bool, ) -> Result { let dc = crate::dist_catcher::DistCatcher::new(vec![ session.external_path(Path::new("dist-newstyle/sdist")), session.external_path(Path::new("dist")), ]); self.run(session, vec!["sdist"])?; Ok(dc.copy_single(target_directory).unwrap().unwrap()) } fn test( &self, session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { self.run(session, vec!["test"])?; Ok(()) } fn build( &self, _session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { Err(Error::Unimplemented) } fn clean( &self, _session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { Err(Error::Unimplemented) } fn install( &self, _session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, _install_target: &crate::buildsystem::InstallTarget, ) -> Result<(), crate::buildsystem::Error> { Err(Error::Unimplemented) } fn get_declared_dependencies( &self, _session: &dyn crate::session::Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result< Vec<( crate::buildsystem::DependencyCategory, Box, )>, crate::buildsystem::Error, > { Err(Error::Unimplemented) } fn get_declared_outputs( &self, _session: &dyn crate::session::Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result>, crate::buildsystem::Error> { Err(Error::Unimplemented) } fn as_any(&self) -> &dyn std::any::Any { self } } ognibuild-0.0.32/src/buildsystems/java.rs000064400000000000000000000221471046102023000164750ustar 00000000000000use crate::buildsystem::{BuildSystem, DependencyCategory, Error}; use crate::dependency::Dependency; use crate::installer::{InstallationScope, Installer}; use crate::session::Session; use std::os::unix::fs::PermissionsExt; use std::path::{Path, PathBuf}; #[derive(Debug)] pub struct Gradle { path: PathBuf, executable: String, } impl Gradle { pub fn new(path: PathBuf, executable: String) -> Self { Self { path, executable } } pub fn simple(path: PathBuf) -> Self { Self { path, executable: "gradle".to_string(), } } pub fn exists(path: &Path) -> bool { path.join("build.gradle").exists() || path.join("build.gradle.kts").exists() } pub fn from_path(path: &Path) -> Self { if path.join("gradlew").exists() { Self::new(path.to_path_buf(), "./gradlew".to_string()) } else { Self::simple(path.to_path_buf()) } } pub fn probe(path: &Path) -> Option> { if Self::exists(path) { log::debug!("Found build.gradle, assuming gradle package."); Some(Box::new(Self::from_path(path))) } else { None } } fn setup(&self, session: &dyn Session, installer: &dyn Installer) -> Result<(), Error> { if !self.executable.starts_with("./") { let binary_req = crate::dependencies::BinaryDependency::new(&self.executable); if !binary_req.present(session) { installer.install(&binary_req, InstallationScope::Global)?; } } Ok(()) } fn run( &self, session: &dyn Session, installer: &dyn Installer, task: &str, args: Vec<&str>, ) -> Result<(), Error> { self.setup(session, installer)?; let mut argv = vec![]; if self.executable.starts_with("./") && (!std::fs::metadata(self.path.join(&self.executable)) .unwrap() .permissions() .mode() & 0o111 != 0) { argv.push("sh".to_string()); } argv.extend(vec![self.executable.clone(), task.to_owned()]); argv.extend(args.iter().map(|x| x.to_string())); match session .command(argv.iter().map(|x| x.as_str()).collect()) .run_detecting_problems() { Err(crate::analyze::AnalyzedError::Unidentified { lines, .. }) if lines.iter().any(|l| { lazy_regex::regex_is_match!(r"Task '(.*)' not found in root project '.*'\.", l) }) => { unimplemented!("Task not found"); } other => other, } .map(|_| ()) .map_err(|e| e.into()) } } impl BuildSystem for Gradle { fn name(&self) -> &str { "gradle" } fn dist( &self, session: &dyn Session, installer: &dyn crate::installer::Installer, target_directory: &std::path::Path, _quiet: bool, ) -> Result { let dc = crate::dist_catcher::DistCatcher::new(vec![session.external_path(Path::new("."))]); self.run(session, installer, "distTar", [].to_vec())?; Ok(dc.copy_single(target_directory).unwrap().unwrap()) } fn test( &self, session: &dyn Session, installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { self.run(session, installer, "test", [].to_vec())?; Ok(()) } fn build( &self, session: &dyn Session, installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { self.run(session, installer, "build", [].to_vec())?; Ok(()) } fn clean( &self, session: &dyn Session, installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { self.run(session, installer, "clean", [].to_vec())?; Ok(()) } fn install( &self, _session: &dyn Session, _installer: &dyn crate::installer::Installer, _install_target: &crate::buildsystem::InstallTarget, ) -> Result<(), crate::buildsystem::Error> { return Err(crate::buildsystem::Error::Unimplemented); // TODO(jelmer): installDist just creates files under build/install/... // self.run(session, installer, "installDist", [].to_vec())?; // Ok(()) } fn get_declared_dependencies( &self, _session: &dyn Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result< Vec<( crate::buildsystem::DependencyCategory, Box, )>, crate::buildsystem::Error, > { Err(crate::buildsystem::Error::Unimplemented) } fn get_declared_outputs( &self, _session: &dyn Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result>, crate::buildsystem::Error> { Err(crate::buildsystem::Error::Unimplemented) } fn as_any(&self) -> &dyn std::any::Any { self } } #[derive(Debug)] pub struct Maven { path: PathBuf, } impl Maven { pub fn probe(path: &Path) -> Option> { if path.join("pom.xml").exists() { log::debug!("Found pom.xml, assuming maven package."); Some(Box::new(Self::new(path.join("pom.xml")))) } else { None } } pub fn new(path: PathBuf) -> Self { Self { path } } } impl BuildSystem for Maven { fn name(&self) -> &str { "maven" } fn dist( &self, _session: &dyn Session, _installer: &dyn Installer, _target_directory: &Path, _quiet: bool, ) -> Result { // TODO(jelmer): 'mvn generate-sources' creates a jar in target/. is that what we need? Err(Error::Unimplemented) } fn test(&self, session: &dyn Session, _installer: &dyn Installer) -> Result<(), Error> { session .command(vec!["mvn", "test"]) .run_detecting_problems()?; Ok(()) } fn build(&self, session: &dyn Session, _installer: &dyn Installer) -> Result<(), Error> { session .command(vec!["mvn", "compile"]) .run_detecting_problems()?; Ok(()) } fn clean(&self, session: &dyn Session, _installer: &dyn Installer) -> Result<(), Error> { session .command(vec!["mvn", "clean"]) .run_detecting_problems()?; Ok(()) } fn install( &self, session: &dyn Session, _installer: &dyn Installer, _install_target: &crate::buildsystem::InstallTarget, ) -> Result<(), Error> { session .command(vec!["mvn", "install"]) .run_detecting_problems()?; Ok(()) } fn get_declared_dependencies( &self, _session: &dyn Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result)>, Error> { let mut ret = vec![]; use xmltree::Element; let f = std::fs::File::open(&self.path).unwrap(); let root = Element::parse(f).unwrap(); if root.namespace != Some("http://maven.apache.org/POM/4.0.0".to_string()) { log::warn!("Unknown namespace in pom.xml: {:?}", root.namespace); return Ok(vec![]); } assert_eq!(root.name, "project"); if let Some(deps_tag) = root.get_child("dependencies") { for dep in deps_tag.children.iter().filter_map(|x| x.as_element()) { let version_tag = dep.get_child("version"); let group_id = dep .get_child("groupId") .unwrap() .get_text() .unwrap() .into_owned(); let artifact_id = dep .get_child("artifactId") .unwrap() .get_text() .unwrap() .into_owned(); let version = version_tag.map(|x| x.get_text().unwrap().into_owned()); ret.push(( DependencyCategory::Universal, Box::new(crate::dependencies::MavenArtifactDependency { group_id, artifact_id, version, kind: None, }) as Box, )); } } Ok(ret) } fn get_declared_outputs( &self, _session: &dyn Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result>, Error> { Err(Error::Unimplemented) } fn as_any(&self) -> &dyn std::any::Any { self } } ognibuild-0.0.32/src/buildsystems/make.rs000064400000000000000000000463761046102023000165030ustar 00000000000000use crate::analyze::AnalyzedError; use crate::buildsystem::{BuildSystem, DependencyCategory, Error, InstallTarget}; use crate::dependency::Dependency; use crate::installer::Installer; use crate::session::Session; use crate::shebang::shebang_binary; use std::path::{Path, PathBuf}; #[derive(Debug, Copy, Clone, PartialEq, Eq)] enum Kind { MakefilePL, Automake, Autoconf, Qmake, Make, } #[derive(Debug)] pub struct Make { path: PathBuf, kind: Kind, } /// Check if a Makefile exists in the current directory. fn makefile_exists(session: &dyn Session) -> bool { session.exists(Path::new("Makefile")) || session.exists(Path::new("GNUmakefile")) || session.exists(Path::new("makefile")) } impl Make { pub fn new(path: &Path) -> Self { let kind = if path.join("Makefile.PL").exists() { Kind::MakefilePL } else if path.join("Makefile.am").exists() { Kind::Automake } else if path.join("configure.ac").exists() || path.join("configure.in").exists() || path.join("autogen.sh").exists() { Kind::Autoconf } else if path .read_dir() .unwrap() .any(|n| n.unwrap().file_name().to_string_lossy().ends_with(".pro")) { Kind::Qmake } else { Kind::Make }; Self { path: path.to_path_buf(), kind, } } fn setup( &self, session: &dyn Session, _installer: &dyn Installer, prefix: Option<&Path>, ) -> Result<(), Error> { if self.kind == Kind::MakefilePL && !makefile_exists(session) { session .command(vec!["perl", "Makefile.PL"]) .run_detecting_problems()?; } if !makefile_exists(session) && !session.exists(Path::new("configure")) { if session.exists(Path::new("autogen.sh")) { if shebang_binary(&self.path.join("autogen.sh")) .unwrap() .is_none() { session .command(vec!["/bin/sh", "./autogen.sh"]) .run_detecting_problems()?; } match session .command(vec!["./autogen.sh"]) .run_detecting_problems() { Err(AnalyzedError::Unidentified { lines, .. }) if lines.contains( &"Gnulib not yet bootstrapped; run ./bootstrap instead.".to_string(), ) => { session .command(vec!["./bootstrap"]) .run_detecting_problems()?; session .command(vec!["./autogen.sh"]) .run_detecting_problems() } other => other, }?; } else if session.exists(Path::new("configure.ac")) || session.exists(Path::new("configure.in")) { session .command(vec!["autoreconf", "-i"]) .run_detecting_problems()?; } } if !makefile_exists(session) && session.exists(Path::new("configure")) { let args = [ vec!["./configure".to_string()], if let Some(p) = prefix { vec![format!("--prefix={}", p.to_str().unwrap())] } else { vec![] }, ] .concat(); session .command(args.iter().map(|s| s.as_str()).collect()) .run_detecting_problems()?; } if !makefile_exists(session) && session .read_dir(Path::new(".")) .unwrap() .iter() .any(|n| n.file_name().to_str().unwrap().ends_with(".pro")) { session.command(vec!["qmake"]).run_detecting_problems()?; } Ok(()) } fn run_make( &self, session: &dyn Session, args: Vec<&str>, prefix: Option<&Path>, ) -> Result<(), AnalyzedError> { fn wants_configure(line: &str) -> bool { if line.starts_with("Run ./configure") { return true; } if line == "Please run ./configure first" { return true; } if line.starts_with("Project not configured") { return true; } if line.starts_with("The project was not configured") { return true; } lazy_regex::regex_is_match!( r"Makefile:[0-9]+: \*\*\* You need to run \.\/configure .*", line ) } let cwd = if session.exists(Path::new("build/Makefile")) { Path::new("build") } else { Path::new(".") }; let args = [vec!["make"], args].concat(); match session .command(args.clone()) .cwd(cwd) .run_detecting_problems() { Err(AnalyzedError::Unidentified { lines, .. }) if lines.len() < 5 && lines.iter().any(|l| wants_configure(l)) => { session .command( [ vec!["./configure".to_string()], if let Some(p) = prefix.as_ref() { vec![format!("--prefix={}", p.to_str().unwrap())] } else { vec![] }, ] .concat() .iter() .map(|x| x.as_str()) .collect(), ) .run_detecting_problems()?; session.command(args).cwd(cwd).run_detecting_problems() } Err(AnalyzedError::Unidentified { lines, .. }) if lines.contains( &"Reconfigure the source tree (via './config' or 'perl Configure'), please." .to_string(), ) => { session.command(vec!["./config"]).run_detecting_problems()?; session.command(args).cwd(cwd).run_detecting_problems() } other => other, } .map(|_| ()) } pub fn probe(path: &Path) -> Option> { if [ "Makefile", "GNUmakefile", "makefile", "Makefile.PL", "autogen.sh", "configure.ac", "configure.in", ] .iter() .any(|p| path.join(p).exists()) { return Some(Box::new(Self::new(path))); } for n in path.read_dir().unwrap() { let n = n.unwrap(); if n.file_name().to_string_lossy().ends_with(".pro") { return Some(Box::new(Self::new(path))); } } None } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingMakeTarget { fn to_dependency(&self) -> Option> { if let Some(_local_path) = self.0.strip_prefix("/<>/") { // Local file or target None } else if self.0.starts_with('/') { Some(Box::new(crate::dependencies::PathDependency::from( PathBuf::from(&self.0), ))) } else { None } } } impl BuildSystem for Make { fn name(&self) -> &str { "make" } fn dist( &self, session: &dyn crate::session::Session, installer: &dyn Installer, target_directory: &std::path::Path, _quiet: bool, ) -> Result { self.setup(session, installer, None)?; let dc = crate::dist_catcher::DistCatcher::default(&session.external_path(Path::new("."))); match self.run_make(session, vec!["dist"], None) { Err(AnalyzedError::Unidentified { lines, .. }) if lines.contains(&"make: *** No rule to make target 'dist'. Stop.".to_string()) => { unimplemented!(); } Err(AnalyzedError::Unidentified { lines, .. }) if lines.contains(&"make[1]: *** No rule to make target 'dist'. Stop.".to_string()) => { unimplemented!(); } Err(AnalyzedError::Unidentified { lines, .. }) if lines.contains(&"ninja: error: unknown target 'dist', did you mean 'dino'?".to_string()) => { unimplemented!(); } Err(AnalyzedError::Unidentified { lines, .. }) if lines.contains(&"Please try running 'make manifest' and then run 'make dist' again.".to_string()) => { session.command(vec!["make", "manifest"]).run_detecting_problems()?; session.command(vec!["make", "dist"]).run_detecting_problems().map(|_| ()) } Err(AnalyzedError::Unidentified { lines, .. }) if lines.iter().any(|l| lazy_regex::regex_is_match!(r"(Makefile|GNUmakefile|makefile):[0-9]+: \*\*\* Missing 'Make.inc' Run './configure \[options\]' and retry. Stop.", l)) => { session.command(vec!["./configure"]).run_detecting_problems()?; session.command(vec!["make", "dist"]).run_detecting_problems().map(|_| ()) } Err(AnalyzedError::Unidentified { lines, .. }) if lines.iter().any(|l| lazy_regex::regex_is_match!(r"Problem opening MANIFEST: No such file or directory at .* line [0-9]+\.", l)) => { session.command(vec!["make", "manifest"]).run_detecting_problems()?; session.command(vec!["make", "dist"]).run_detecting_problems().map(|_| ()) } other => other }?; Ok(dc.copy_single(target_directory).unwrap().unwrap()) } fn test( &self, session: &dyn crate::session::Session, installer: &dyn Installer, ) -> Result<(), Error> { self.setup(session, installer, None)?; for target in ["check", "test"] { match self.run_make(session, vec![target], None) { Err(AnalyzedError::Unidentified { lines, .. }) if lines.contains(&format!( "make: *** No rule to make target '{}'. Stop.", target )) => { continue; } Err(AnalyzedError::Unidentified { lines, .. }) if lines.contains(&format!( "make[1]: *** No rule to make target '{}'. Stop.", target )) => { continue; } other => other, }?; return Ok(()); } if self.path.join("t").exists() { // See https://perlmaven.com/how-to-run-the-tests-of-a-typical-perl-module session .command(vec!["prove", "-b", "t/"]) .run_detecting_problems()?; } else { log::warn!("No test target found"); } Ok(()) } fn build( &self, session: &dyn crate::session::Session, installer: &dyn Installer, ) -> Result<(), Error> { self.setup(session, installer, None)?; let default_target = match self.kind { Kind::Qmake => None, _ => Some("all"), }; let args = if let Some(target) = default_target { vec![target] } else { vec![] }; self.run_make(session, args, None)?; Ok(()) } fn clean( &self, session: &dyn crate::session::Session, installer: &dyn Installer, ) -> Result<(), Error> { self.setup(session, installer, None)?; self.run_make(session, vec!["clean"], None)?; Ok(()) } fn install( &self, session: &dyn crate::session::Session, installer: &dyn Installer, install_target: &InstallTarget, ) -> Result<(), Error> { self.setup(session, installer, install_target.prefix.as_deref())?; self.run_make(session, vec!["install"], install_target.prefix.as_deref())?; Ok(()) } fn get_declared_dependencies( &self, session: &dyn crate::session::Session, fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result< Vec<( crate::buildsystem::DependencyCategory, Box, )>, Error, > { // TODO(jelmer): Split out the perl-specific stuff? let mut ret = vec![]; let meta_yml = self.path.join("META.yml"); if meta_yml.exists() { let mut f = std::fs::File::open(meta_yml).unwrap(); ret.extend( crate::buildsystems::perl::declared_deps_from_meta_yml(&mut f) .into_iter() .map(|d| (d.0, Box::new(d.1) as Box)), ); } let cpanfile = self.path.join("cpanfile"); if cpanfile.exists() { ret.extend( crate::buildsystems::perl::declared_deps_from_cpanfile( session, fixers.unwrap_or(&[]), ) .into_iter() .map(|d| (d.0, Box::new(d.1) as Box)), ); } Ok(ret) } fn get_declared_outputs( &self, _session: &dyn crate::session::Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result>, Error> { Err(Error::Unimplemented) } fn as_any(&self) -> &dyn std::any::Any { self } } #[derive(Debug)] pub struct CMake { path: PathBuf, builddir: String, } impl CMake { pub fn new(path: &Path) -> Self { Self { path: path.to_path_buf(), builddir: "build".to_string(), } } fn setup( &self, session: &dyn Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), Error> { if !session.exists(Path::new(&self.builddir)) { session.mkdir(Path::new(&self.builddir))?; } match session .command(vec!["cmake", ".", &format!("-B{}", self.builddir)]) .run_detecting_problems() { Ok(_) => Ok(()), Err(e) => { session.rmtree(Path::new(&self.builddir))?; Err(e.into()) } } } pub fn probe(path: &Path) -> Option> { if path.join("CMakeLists.txt").exists() { return Some(Box::new(Self::new(path))); } None } } impl crate::buildsystem::BuildSystem for CMake { fn name(&self) -> &str { "cmake" } fn dist( &self, _session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, _target_directory: &std::path::Path, _quiet: bool, ) -> Result { Err(crate::buildsystem::Error::Unimplemented) } fn build( &self, session: &dyn crate::session::Session, installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { self.setup(session, installer)?; session .command(vec!["cmake", "--build", &self.builddir]) .run_detecting_problems()?; Ok(()) } fn install( &self, session: &dyn crate::session::Session, installer: &dyn crate::installer::Installer, _install_target: &crate::buildsystem::InstallTarget, ) -> Result<(), crate::buildsystem::Error> { self.setup(session, installer)?; session .command(vec!["cmake", "--install", &self.builddir]) .run_detecting_problems()?; Ok(()) } fn clean( &self, session: &dyn crate::session::Session, installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { self.setup(session, installer)?; session .command(vec![ "cmake", "--build", &self.builddir, ".", "--target", "clean", ]) .run_detecting_problems()?; Ok(()) } fn get_declared_dependencies( &self, _session: &dyn crate::session::Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result< Vec<( crate::buildsystem::DependencyCategory, Box, )>, crate::buildsystem::Error, > { // TODO(jelmer): Find a proper parser for CMakeLists.txt somewhere? use std::io::BufRead; let f = std::fs::File::open(self.path.join("CMakeLists.txt")).unwrap(); let mut ret: Vec<(DependencyCategory, Box)> = vec![]; for line in std::io::BufReader::new(f).lines() { let line = line.unwrap(); if let Some((_, m)) = lazy_regex::regex_captures!( r"cmake_minimum_required\(\s*VERSION\s+(.*)\s*\)", &line ) { ret.push(( crate::buildsystem::DependencyCategory::Build, Box::new(crate::dependencies::vague::VagueDependency::new( "CMake", Some(m), )), )); } } Ok(ret) } fn test(&self, _session: &dyn Session, _installer: &dyn Installer) -> Result<(), Error> { Err(Error::Unimplemented) } fn get_declared_outputs( &self, _session: &dyn Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result>, Error> { Err(Error::Unimplemented) } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(test)] mod tests { use super::*; use test_log::test; #[test] fn test_exists() { let mut session = crate::session::plain::PlainSession::new(); let td = tempfile::tempdir().unwrap(); session.chdir(td.path()).unwrap(); assert!(!makefile_exists(&session)); std::fs::write(td.path().join("Makefile"), b"").unwrap(); assert!(makefile_exists(&session)); } #[test] fn test_simple() { let mut session = crate::session::plain::PlainSession::new(); let td = tempfile::tempdir().unwrap(); session.chdir(td.path()).unwrap(); std::fs::write( td.path().join("Makefile"), r###" all: test: check: "###, ) .unwrap(); let make = Make::probe(td.path()).expect("make"); make.build(&session, &crate::installer::NullInstaller) .unwrap(); std::mem::drop(td); } } ognibuild-0.0.32/src/buildsystems/meson.rs000064400000000000000000000147541046102023000167020ustar 00000000000000use crate::analyze::AnalyzedError; use crate::buildsystem::{BuildSystem, DependencyCategory, Error}; use crate::dependencies::vague::VagueDependency; use crate::dependency::Dependency; use crate::dist_catcher::DistCatcher; use crate::fix_build::BuildFixer; use crate::installer::Error as InstallerError; use crate::session::Session; use std::path::{Path, PathBuf}; #[derive(Debug)] pub struct Meson { #[allow(dead_code)] path: PathBuf, } #[derive(Debug, serde::Deserialize)] #[allow(dead_code)] struct MesonDependency { pub name: String, pub version: Vec, pub required: bool, pub has_fallback: bool, pub conditional: bool, } #[derive(Debug, serde::Deserialize)] #[allow(dead_code)] struct MesonTarget { r#type: String, installed: bool, filename: Vec, } impl Meson { pub fn new(path: &Path) -> Self { Self { path: path.to_path_buf(), } } fn setup(&self, session: &dyn Session) -> Result<(), Error> { if !session.exists(Path::new("build")) { session.mkdir(Path::new("build")).unwrap(); } session .command(vec!["meson", "setup", "build"]) .run_detecting_problems()?; Ok(()) } fn introspect serde::Deserialize<'a>>( &self, session: &dyn Session, fixers: Option<&[&dyn BuildFixer]>, args: &[&str], ) -> Result { let args = [&["meson", "introspect"], args, &["./meson.build"]].concat(); let ret = if let Some(fixers) = fixers { session .command(args) .quiet(true) .run_fixing_problems::<_, Error>(fixers) .unwrap() } else { session.command(args).run_detecting_problems()? }; let text = ret.concat(); Ok(serde_json::from_str(&text).unwrap()) } pub fn probe(path: &Path) -> Option> { let path = path.join("meson.build"); if path.exists() { log::debug!("Found meson.build, assuming meson package."); Some(Box::new(Self::new(&path))) } else { None } } } impl BuildSystem for Meson { fn name(&self) -> &str { "meson" } fn dist( &self, session: &dyn Session, _installer: &dyn crate::installer::Installer, target_directory: &Path, _quiet: bool, ) -> Result { self.setup(session)?; let dc = DistCatcher::new(vec![session.external_path(Path::new("build/meson-dist"))]); match session .command(vec!["ninja", "-C", "build", "dist"]) .run_detecting_problems() { Ok(_) => {} Err(AnalyzedError::Unidentified { lines, .. }) if lines.contains( &"ninja: error: unknown target 'dist', did you mean 'dino'?".to_string(), ) => { unimplemented!(); } Err(e) => return Err(e.into()), } Ok(dc.copy_single(target_directory).unwrap().unwrap()) } fn test( &self, session: &dyn Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), Error> { self.setup(session)?; session .command(vec!["ninja", "-C", "build", "test"]) .run_detecting_problems()?; Ok(()) } fn build( &self, session: &dyn Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), Error> { self.setup(session)?; session .command(vec!["ninja", "-C", "build"]) .run_detecting_problems()?; Ok(()) } fn clean( &self, session: &dyn Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), Error> { self.setup(session)?; session .command(vec!["ninja", "-C", "build", "clean"]) .run_detecting_problems()?; Ok(()) } fn install( &self, session: &dyn Session, _installer: &dyn crate::installer::Installer, _install_target: &crate::buildsystem::InstallTarget, ) -> Result<(), Error> { self.setup(session)?; session .command(vec!["ninja", "-C", "build", "install"]) .run_detecting_problems()?; Ok(()) } fn get_declared_dependencies( &self, session: &dyn Session, fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result)>, Error> { let mut ret: Vec<(DependencyCategory, Box)> = Vec::new(); let resp = self.introspect::>(session, fixers, &["--scan-dependencies"])?; for entry in resp { let mut minimum_version = None; if entry.version.len() == 1 { if let Some(rest) = entry.version[0].strip_prefix(">=") { minimum_version = Some(rest.trim().to_string()); } } else if entry.version.len() > 1 { log::warn!("Unable to parse version constraints: {:?}", entry.version); } // TODO(jelmer): Include entry['required'] ret.push(( DependencyCategory::Universal, Box::new(VagueDependency { name: entry.name.to_string(), minimum_version, }), )); } Ok(ret) } fn get_declared_outputs( &self, session: &dyn Session, fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result>, Error> { let mut ret: Vec> = Vec::new(); let resp = self.introspect::>(session, fixers, &["--targets"])?; for entry in resp { if !entry.installed { continue; } if entry.r#type == "executable" { for p in entry.filename { ret.push(Box::new(crate::output::BinaryOutput::new( p.file_name().unwrap().to_str().unwrap(), ))); } } // TODO(jelmer): Handle other types } Ok(ret) } fn as_any(&self) -> &dyn std::any::Any { self } } ognibuild-0.0.32/src/buildsystems/mod.rs000064400000000000000000000003261046102023000163260ustar 00000000000000pub mod bazel; pub mod gnome; pub mod go; pub mod haskell; pub mod java; pub mod make; pub mod meson; pub mod node; pub mod octave; pub mod perl; pub mod python; pub mod r; pub mod ruby; pub mod rust; pub mod waf; ognibuild-0.0.32/src/buildsystems/node.rs000064400000000000000000000124711046102023000165000ustar 00000000000000use crate::buildsystem::{BuildSystem, DependencyCategory, Error}; use crate::dependencies::node::NodePackageDependency; use crate::dependencies::BinaryDependency; use crate::dependency::Dependency; use crate::installer::{Error as InstallerError, InstallationScope, Installer}; use crate::session::Session; use serde::Deserialize; use std::collections::HashMap; use std::path::PathBuf; #[derive(Debug)] #[allow(dead_code)] pub struct Node { path: PathBuf, package: NodePackage, } #[derive(Debug, Deserialize)] struct NodePackage { dependencies: HashMap, #[serde(rename = "devDependencies")] dev_dependencies: HashMap, scripts: HashMap, } impl Node { pub fn new(path: PathBuf) -> Self { let package = path.join("package.json"); let package = std::fs::read_to_string(&package).unwrap(); let package: NodePackage = serde_json::from_str(&package).unwrap(); Self { path, package } } fn setup(&self, session: &dyn Session, installer: &dyn Installer) -> Result<(), Error> { let binary_req = BinaryDependency::new("npm"); if !binary_req.present(session) { installer.install(&binary_req, InstallationScope::Global)?; } Ok(()) } pub fn probe(path: &std::path::Path) -> Option> { let path = path.join("package.json"); if path.exists() { log::debug!("Found package.json, assuming node package."); return Some(Box::new(Self::new(path))); } None } } impl BuildSystem for Node { fn get_declared_dependencies( &self, _session: &dyn Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result)>, Error> { let mut dependencies: Vec<(DependencyCategory, Box)> = vec![]; for (name, _version) in self.package.dependencies.iter() { // TODO(jelmer): Look at version dependencies.push(( DependencyCategory::Universal, Box::new(NodePackageDependency::new(name)), )); } for (name, _version) in self.package.dev_dependencies.iter() { // TODO(jelmer): Look at version dependencies.push(( DependencyCategory::Build, Box::new(NodePackageDependency::new(name)), )); } Ok(dependencies) } fn name(&self) -> &str { "node" } fn dist( &self, session: &dyn Session, installer: &dyn crate::installer::Installer, target_directory: &std::path::Path, quiet: bool, ) -> Result { self.setup(session, installer)?; let dc = crate::dist_catcher::DistCatcher::new(vec![ session.external_path(std::path::Path::new(".")) ]); session .command(vec!["npm", "pack"]) .quiet(quiet) .run_detecting_problems()?; Ok(dc.copy_single(target_directory).unwrap().unwrap()) } fn test( &self, session: &dyn crate::session::Session, installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { self.setup(session, installer)?; if let Some(test_script) = self.package.scripts.get("test") { session .command(vec!["bash", "-c", test_script]) .run_detecting_problems()?; } else { log::info!("No test command defined in package.json"); } Ok(()) } fn build( &self, session: &dyn crate::session::Session, installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { self.setup(session, installer)?; if let Some(build_script) = self.package.scripts.get("build") { session .command(vec!["bash", "-c", build_script]) .run_detecting_problems()?; } else { log::info!("No build command defined in package.json"); } Ok(()) } fn clean( &self, session: &dyn crate::session::Session, installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { self.setup(session, installer)?; if let Some(clean_script) = self.package.scripts.get("clean") { session .command(vec!["bash", "-c", clean_script]) .run_detecting_problems()?; } else { log::info!("No clean command defined in package.json"); } Ok(()) } fn install( &self, _session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, _install_target: &crate::buildsystem::InstallTarget, ) -> Result<(), crate::buildsystem::Error> { Err(Error::Unimplemented) } fn get_declared_outputs( &self, _session: &dyn crate::session::Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result>, crate::buildsystem::Error> { Err(Error::Unimplemented) } fn as_any(&self) -> &dyn std::any::Any { self } } ognibuild-0.0.32/src/buildsystems/octave.rs000064400000000000000000000204311046102023000170270ustar 00000000000000use crate::buildsystem::{BuildSystem, Error}; use crate::dependencies::octave::OctavePackageDependency; use crate::dependency::Dependency; use crate::session::Session; use std::path::{Path, PathBuf}; #[derive(Debug)] pub struct Octave { path: PathBuf, } #[allow(dead_code)] pub struct Version { major: u32, minor: u32, patch: u32, } impl std::str::FromStr for Version { type Err = std::num::ParseIntError; fn from_str(s: &str) -> Result { let mut parts = s.splitn(3, '.'); let major = parts.next().unwrap().parse()?; let minor = parts.next().unwrap().parse()?; let patch = parts.next().unwrap().parse()?; Ok(Self { major, minor, patch, }) } } #[derive(Default)] pub struct Description { name: Option, version: Option, description: Option, date: Option, author: Option, maintainer: Option, title: Option, categories: Option>, problems: Option>, url: Option>, depends: Option>, license: Option, system_requirements: Option>, build_requires: Option>, } fn read_description_fields( r: R, ) -> Result, std::io::Error> { let mut fields = Vec::new(); let mut lines = r.lines(); let line = lines.next().unwrap()?; loop { if line.is_empty() { break; } if line.starts_with('#') { continue; } let mut parts = line.splitn(2, ": "); let key = parts.next().unwrap().to_string(); let mut value = parts.next().unwrap().to_string(); while let Some(line) = lines.next() { let line = line?; if line.starts_with(' ') { value.push_str(line.trim_start()); } else if line.starts_with('#') { } else { fields.push((key, value)); break; } } } Ok(fields) } pub fn read_description(r: R) -> Result { let mut description = Description::default(); for (key, value) in read_description_fields(r)?.into_iter() { match key.as_str() { "Package" => description.name = Some(value), "Version" => description.version = Some(value.parse().unwrap()), "Description" => description.description = Some(value), "Date" => description.date = Some(value), "Author" => description.author = Some(value), "Maintainer" => description.maintainer = Some(value), "Title" => description.title = Some(value), "Categories" => { description.categories = Some(value.split(',').map(|s| s.trim().to_string()).collect()) } "Problems" => { description.problems = Some(value.split(',').map(|s| s.trim().to_string()).collect()) } "URL" => { description.url = Some( value .split(',') .map(|s| s.trim().to_string()) .map(|s| s.parse().unwrap()) .collect::>(), ) } "Depends" => { description.depends = Some(value.split(',').map(|s| s.trim().to_string()).collect()) } "License" => description.license = Some(value), "SystemRequirements" => { description.system_requirements = Some(value.split(',').map(|s| s.trim().to_string()).collect()) } "BuildRequires" => { description.build_requires = Some(value.split(',').map(|s| s.trim().to_string()).collect()) } name => log::warn!("Unknown field in DESCRIPTION: {}", name), } } Ok(description) } impl Octave { pub fn new(path: PathBuf) -> Self { Self { path } } pub fn exists(path: &Path) -> bool { if path.join("DESCRIPTION").exists() { return false; } // Urgh, isn't there a better way to see if this is an octave package? for entry in path.read_dir().unwrap() { let entry = entry.unwrap(); if entry.file_name().to_string_lossy().ends_with(".m") { return true; } if !entry.file_type().unwrap().is_dir() { continue; } match entry.path().read_dir() { Ok(subentries) => { for subentry in subentries { let subentry = subentry.unwrap(); if subentry.file_name().to_string_lossy().ends_with(".m") { return true; } } } Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => { log::debug!( "Permission denied while reading directory: {}", entry.path().display() ); } Err(e) => { panic!("Error reading directory: {}", e); } } } false } pub fn probe(path: &Path) -> Option> { if Self::exists(path) { log::debug!("Found DESCRIPTION, assuming octave package."); Some(Box::new(Self::new(path.to_path_buf()))) } else { None } } } impl BuildSystem for Octave { fn name(&self) -> &str { "octave" } fn dist( &self, _session: &dyn Session, _installer: &dyn crate::installer::Installer, _target_directory: &Path, _quiet: bool, ) -> Result { Err(Error::Unimplemented) } fn test( &self, _session: &dyn Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { Err(Error::Unimplemented) } fn build( &self, _session: &dyn Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { Err(Error::Unimplemented) } fn clean( &self, _session: &dyn Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { Err(Error::Unimplemented) } fn install( &self, _session: &dyn Session, _installer: &dyn crate::installer::Installer, _install_target: &crate::buildsystem::InstallTarget, ) -> Result<(), crate::buildsystem::Error> { Err(Error::Unimplemented) } fn get_declared_dependencies( &self, _session: &dyn Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result< Vec<(crate::buildsystem::DependencyCategory, Box)>, crate::buildsystem::Error, > { let f = std::fs::File::open(self.path.join("DESCRIPTION")).unwrap(); let description = read_description(std::io::BufReader::new(f)).unwrap(); let mut ret: Vec<(crate::buildsystem::DependencyCategory, Box)> = Vec::new(); for depend in description.depends.unwrap_or_default() { let d: OctavePackageDependency = depend.parse().unwrap(); ret.push((crate::buildsystem::DependencyCategory::Build, Box::new(d))); } for build_require in description.build_requires.unwrap_or_default() { let d: OctavePackageDependency = build_require.parse().unwrap(); ret.push((crate::buildsystem::DependencyCategory::Build, Box::new(d))); } Ok(ret) } fn get_declared_outputs( &self, _session: &dyn Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result>, crate::buildsystem::Error> { Err(Error::Unimplemented) } fn as_any(&self) -> &dyn std::any::Any { self } } ognibuild-0.0.32/src/buildsystems/perl.rs000064400000000000000000000377351046102023000165270ustar 00000000000000use crate::analyze::AnalyzedError; use crate::buildsystem::{guaranteed_which, BuildSystem, DependencyCategory, Error}; use crate::dependencies::perl::PerlModuleDependency; use crate::fix_build::{BuildFixer, IterateBuildError}; use crate::installer::Error as InstallerError; use crate::session::Session; use std::collections::HashMap; use std::io::Read; use std::path::{Path, PathBuf}; fn read_cpanfile( session: &dyn Session, args: Vec<&str>, category: DependencyCategory, fixers: &[&dyn BuildFixer], ) -> impl Iterator { let mut argv = vec!["cpanfile-dump"]; argv.extend(args); session .command(argv) .run_fixing_problems::<_, crate::buildsystem::Error>(fixers) .unwrap() .into_iter() .filter_map(move |line| { let line = line.trim(); if !line.is_empty() { Some((category.clone(), PerlModuleDependency::simple(line))) } else { None } }) } pub fn declared_deps_from_cpanfile( session: &dyn Session, fixers: &[&dyn BuildFixer], ) -> Vec<(DependencyCategory, PerlModuleDependency)> { read_cpanfile( session, vec!["--configure", "--build"], DependencyCategory::Build, fixers, ) .chain(read_cpanfile( session, vec!["--test"], DependencyCategory::Test, fixers, )) .collect() } #[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)] pub struct Meta { name: String, #[serde(rename = "abstract")] r#abstract: String, version: String, license: String, author: Vec, distribution_type: String, requires: HashMap, recommends: HashMap, build_requires: HashMap, resources: HashMap, #[serde(rename = "meta-spec")] meta_spec: HashMap, generated_by: String, configure_requires: HashMap, } pub fn declared_deps_from_meta_yml( f: R, ) -> Vec<(DependencyCategory, PerlModuleDependency)> { // See http://module-build.sourceforge.net/META-spec-v1.4.html for the specification of the format. let data: Meta = serde_yaml::from_reader(f).unwrap(); let mut ret = vec![]; // TODO: handle versions for (name, _version) in &data.requires { ret.push(( DependencyCategory::Universal, PerlModuleDependency::simple(name), )); } for (name, _version) in &data.build_requires { ret.push(( DependencyCategory::Build, PerlModuleDependency::simple(name), )); } for (name, _version) in &data.configure_requires { ret.push(( DependencyCategory::Build, PerlModuleDependency::simple(name), )); } // TODO(jelmer): recommends ret } #[derive(Debug)] pub struct DistZilla { path: PathBuf, dist_inkt_class: Option, } impl DistZilla { pub fn new(path: PathBuf) -> Self { let mut dist_inkt_class = None; let mut f = std::fs::File::open(&path).unwrap(); let mut contents = String::new(); f.read_to_string(&mut contents).unwrap(); for line in contents.lines() { let rest = if let Some(rest) = line.strip_prefix(";;") { rest } else { continue; }; let (key, value) = if let Some((key, value)) = rest.split_once('=') { (key.trim(), value.trim()) } else { continue; }; if key == "class" && value.starts_with("'Dist::Inkt") { dist_inkt_class = Some(value[1..value.len() - 1].to_string()); break; } } Self { path, dist_inkt_class, } } pub fn setup( &self, installer: &dyn crate::installer::Installer, ) -> Result<(), crate::installer::Error> { let dep = crate::dependencies::perl::PerlModuleDependency::simple("Dist::Inkt"); installer.install(&dep, crate::installer::InstallationScope::Global)?; Ok(()) } pub fn probe(path: &Path) -> Option> { let dist_ini_path = path.join("dist.ini"); if dist_ini_path.exists() && !path.join("Makefile.PL").exists() { Some(Box::new(Self::new(dist_ini_path))) } else { None } } } impl BuildSystem for DistZilla { fn name(&self) -> &str { "Dist::Zilla" } fn dist( &self, session: &dyn Session, installer: &dyn crate::installer::Installer, target_directory: &Path, quiet: bool, ) -> Result { self.setup(installer)?; let dc = crate::dist_catcher::DistCatcher::default(&session.external_path(Path::new("."))); if self.dist_inkt_class.is_some() { session .command(vec![guaranteed_which(session, installer, "distinkt-dist") .unwrap() .to_str() .unwrap()]) .quiet(quiet) .run_detecting_problems()?; } else { // Default to invoking Dist::Zilla session .command(vec![ guaranteed_which(session, installer, "dzil") .unwrap() .to_str() .unwrap(), "build", "--tgz", ]) .quiet(quiet) .run_detecting_problems()?; } Ok(dc.copy_single(target_directory).unwrap().unwrap()) } fn test( &self, session: &dyn Session, installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { // see also https://perlmaven.com/how-to-run-the-tests-of-a-typical-perl-module self.setup(installer)?; session .command(vec![ guaranteed_which(session, installer, "dzil") .unwrap() .to_str() .unwrap(), "test", ]) .run_detecting_problems()?; Ok(()) } fn build( &self, session: &dyn Session, installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { self.setup(installer)?; session .command(vec![ guaranteed_which(session, installer, "dzil") .unwrap() .to_str() .unwrap(), "build", ]) .run_detecting_problems()?; Ok(()) } fn clean( &self, _session: &dyn Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { Err(Error::Unimplemented) } fn install( &self, _session: &dyn Session, _installerr: &dyn crate::installer::Installer, _install_target: &crate::buildsystem::InstallTarget, ) -> Result<(), crate::buildsystem::Error> { Err(Error::Unimplemented) } fn get_declared_dependencies( &self, session: &dyn Session, fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result< Vec<(DependencyCategory, Box)>, crate::buildsystem::Error, > { let mut ret = vec![]; if self.path.exists() { let lines = session .command(vec!["dzil", "authordeps"]) .run_fixing_problems::<_, crate::buildsystem::Error>(fixers.unwrap_or(&[])) .unwrap(); for entry in lines { ret.push(( DependencyCategory::Build, Box::new(PerlModuleDependency::simple(entry.trim())) as Box, )); } } if self.path.parent().unwrap().join("cpanfile").exists() { ret.extend( declared_deps_from_cpanfile(session, fixers.unwrap_or(&[])) .into_iter() .map(|(category, dep)| { ( category, Box::new(dep) as Box, ) }), ); } Ok(ret) } fn get_declared_outputs( &self, _session: &dyn Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result>, crate::buildsystem::Error> { Err(Error::Unimplemented) } fn as_any(&self) -> &dyn std::any::Any { self } } #[derive(Debug)] pub struct PerlBuildTiny { path: PathBuf, minilla: bool, } impl PerlBuildTiny { pub fn new(path: PathBuf) -> Self { let minilla = path.join("minil.toml").exists(); Self { path, minilla } } fn setup( &self, session: &dyn Session, fixers: Option<&[&dyn BuildFixer]>, ) -> Result<(), crate::buildsystem::Error> { let fixers = fixers.unwrap_or(&[]); let argv = vec!["perl", "Build.PL"]; session .command(argv) .run_fixing_problems::<_, crate::buildsystem::Error>(fixers)?; Ok(()) } pub fn probe(path: &Path) -> Option> { if path.join("Build.PL").exists() { log::debug!("Found Build.PL, assuming Module::Build::Tiny package."); Some(Box::new(Self::new(path.to_path_buf()))) } else { None } } } impl BuildSystem for PerlBuildTiny { fn name(&self) -> &str { "Module::Build::Tiny" } fn dist( &self, session: &dyn Session, _installer: &dyn crate::installer::Installer, target_directory: &Path, quiet: bool, ) -> Result { self.setup(session, None)?; let dc = crate::dist_catcher::DistCatcher::default(&session.external_path(Path::new("."))); if self.minilla { // minil seems to return 0 even if it didn't produce a tarball :( crate::analyze::run_detecting_problems( session, vec!["minil", "dist"], Some(&|_, _| !dc.find_files().is_some()), quiet, None, None, None, None, )?; } else { match session .command(vec!["./Build", "dist"]) .run_detecting_problems() { Err(AnalyzedError::Unidentified { lines, .. }) if lines.iter().any(|l| { l.contains("Can't find dist packages without a MANIFEST file") }) => { session .command(vec!["./Build", "manifest"]) .run_detecting_problems()?; session .command(vec!["./Build", "dist"]) .run_detecting_problems() } Err(AnalyzedError::Unidentified { lines, .. }) if lines.iter().any(|l| l.contains("No such action 'dist'")) => { unimplemented!("Module::Build::Tiny dist command not supported"); } other => other, }?; } Ok(dc.copy_single(target_directory).unwrap().unwrap()) } fn test( &self, session: &dyn Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { self.setup(session, None)?; if self.minilla { session .command(vec!["minil", "test"]) .run_detecting_problems()?; } else { session .command(vec!["./Build", "test"]) .run_detecting_problems()?; } Ok(()) } fn build( &self, session: &dyn Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { self.setup(session, None)?; session .command(vec!["./Build", "build"]) .run_detecting_problems()?; Ok(()) } fn clean( &self, session: &dyn Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { self.setup(session, None)?; session .command(vec!["./Build", "clean"]) .run_detecting_problems()?; Ok(()) } fn install( &self, session: &dyn Session, _installer: &dyn crate::installer::Installer, _install_target: &crate::buildsystem::InstallTarget, ) -> Result<(), crate::buildsystem::Error> { self.setup(session, None)?; if self.minilla { session .command(vec!["minil", "install"]) .run_detecting_problems()?; } else { session .command(vec!["./Build", "install"]) .run_detecting_problems()?; } Ok(()) } fn get_declared_dependencies( &self, session: &dyn Session, fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result< Vec<(DependencyCategory, Box)>, crate::buildsystem::Error, > { self.setup(session, fixers)?; if self.minilla { // Minilla doesn't seem to have a way to just regenerate the metadata :( } else { let cmd = session.command(vec!["./Build", "distmeta"]); if let Some(fixers) = fixers { match cmd.run_fixing_problems::<_, crate::buildsystem::Error>(fixers) { Err(IterateBuildError::Unidentified { lines, .. }) if lines .iter() .any(|l| l.contains("No such action 'distmeta'")) => { // Module::Build::Tiny doesn't have a distmeta action Ok(Vec::new()) } Err(IterateBuildError::Unidentified { lines, .. }) if lines.iter().any(|l| { l.contains( "Do not run distmeta. Install Minilla and `minil install` instead.", ) }) => { log::warn!( "did not detect minilla, but it is required to get the dependencies" ); Ok(Vec::new()) } other => other, }?; } else { cmd.run_detecting_problems()?; } } let meta_yml_path = self.path.join("META.yml"); if meta_yml_path.exists() { let f = std::fs::File::open(&meta_yml_path).unwrap(); Ok(declared_deps_from_meta_yml(f) .into_iter() .map(|(category, dep)| { ( category, Box::new(dep) as Box, ) }) .collect()) } else { Ok(vec![]) } } fn get_declared_outputs( &self, _session: &dyn Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result>, crate::buildsystem::Error> { Err(Error::Unimplemented) } fn as_any(&self) -> &dyn std::any::Any { self } } ognibuild-0.0.32/src/buildsystems/python.rs000064400000000000000000000667731046102023000171120ustar 00000000000000use crate::analyze::{run_detecting_problems, AnalyzedError}; use crate::buildsystem::{BuildSystem, DependencyCategory, Error, InstallTarget}; use crate::dependencies::python::{PythonDependency, PythonPackageDependency}; use crate::dependency::Dependency; use crate::dist_catcher::DistCatcher; use crate::fix_build::BuildFixer; use crate::installer::{Error as InstallerError, InstallationScope, Installer}; use crate::output::{BinaryOutput, Output, PythonPackageOutput}; use crate::session::Session; use pyo3::exceptions::{ PyFileNotFoundError, PyImportError, PyModuleNotFoundError, PyRuntimeError, PySystemExit, }; use pyo3::prelude::*; use pyo3::types::PyDict; use serde::Deserialize; use std::collections::{HashMap, HashSet}; use std::io::Seek; use std::path::{Path, PathBuf}; use toml; #[derive(Debug, Deserialize)] #[allow(dead_code)] struct Distribution { name: Option, requires: Vec, setup_requires: Vec, install_requires: Vec, tests_require: Vec, scripts: Vec, packages: Vec, entry_points: HashMap>, } fn load_toml(path: &Path) -> Result { let path = path.join("pyproject.toml"); let text = std::fs::read_to_string(path).unwrap(); Ok(toml::from_str(&text).unwrap()) } #[derive(Debug)] pub struct SetupCfg(PyObject); impl SetupCfg { fn has_section(&self, section: &str) -> bool { Python::with_gil(|py| { self.0 .call_method1(py, "__contains__", (section,)) .unwrap() .extract::(py) .unwrap() }) } fn get_section(&self, section: &str) -> Option { Python::with_gil(|py| { if self.has_section(section) { let section: Option = self .0 .call_method1(py, "get", (section, py.None())) .unwrap() .extract(py) .ok(); Some(SetupCfgSection(section.to_object(py))) } else { None } }) } } pub struct SetupCfgSection(PyObject); impl Default for SetupCfg { fn default() -> Self { Python::with_gil(|py| SetupCfg(py.None())) } } impl SetupCfgSection { fn get FromPyObject<'a>>(&self, key: &str) -> Option { Python::with_gil(|py| { self.0 .call_method1(py, "get", (key, py.None())) .unwrap() .extract::>(py) .unwrap() }) } pub fn has_key(&self, key: &str) -> bool { Python::with_gil(|py| { self.0 .call_method1(py, "__contains__", (key,)) .unwrap() .extract::(py) .unwrap() }) } } fn load_setup_cfg(path: &Path) -> Result, PyErr> { Python::with_gil(|py| { let m = py.import_bound("setuptools.config.setupcfg")?; let read_configuration = m.getattr("read_configuration")?; let p = path.join("setup.cfg"); if p.exists() { let config = read_configuration.call1((p,))?; Ok(Some(SetupCfg(config.to_object(py)))) } else { Ok(None) } }) } // run_setup, but setting __name__ // Imported from Python's distutils.core, Copyright (C) PSF fn run_setup(py: Python, script_name: &Path, stop_after: &str) -> PyResult { assert!( stop_after == "init" || stop_after == "config" || stop_after == "commandline" || stop_after == "run" ); // Import setuptools, just in case it decides to replace distutils let _ = py.import_bound("setuptools"); let core = match py.import_bound("distutils.core") { Ok(m) => m, Err(e) if e.is_instance_of::(py) => { // Importing distutils failed, but that's fine. match py.import_bound("setuptools._distutils.core") { Ok(m) => m, Err(e) => return Err(e), } } Err(e) => return Err(e), }; core.setattr("_setup_stop_after", stop_after)?; let sys = py.import_bound("sys")?; let os = py.import_bound("os")?; let save_argv = sys.getattr("argv")?; let g = PyDict::new_bound(py); g.set_item("__file__", script_name)?; g.set_item("__name__", "__main")?; let old_cwd = os.getattr("getcwd")?.call0()?.extract::()?; os.call_method1( "chdir", (os.getattr("path")? .call_method1("dirname", (script_name,))?,), )?; sys.setattr("argv", vec![script_name])?; let text = std::fs::read_to_string(script_name)?; let r = py.eval_bound(text.as_str(), Some(&g), None); os.call_method1("chdir", (old_cwd,))?; sys.setattr("argv", save_argv)?; core.setattr("_setup_stop_after", py.None())?; match r { Ok(_) => Ok(core.getattr("_setup_distribution")?.to_object(py)), Err(e) if e.is_instance_of::(py) => { Ok(core.getattr("_setup_distribution")?.to_object(py)) } Err(e) => Err(e), } } const SETUP_WRAPPER: &str = r#""" try: import setuptools except ImportError: pass import distutils from distutils import core import sys import os script_name = "%(script_name)s" os.chdir(os.path.dirname(script_name)) g = {"__file__": os.path.basename(script_name), "__name__": "__main__"} try: core._setup_stop_after = "init" sys.argv[0] = script_name with open(script_name, "rb") as f: exec(f.read(), g) except SystemExit: # Hmm, should we do something if exiting with a non-zero code # (ie. error)? pass if core._setup_distribution is None: raise RuntimeError( ( "'distutils.core.setup()' was never called -- " "perhaps '%s' is not a Distutils setup script?" ) % script_name ) d = core._setup_distribution r = { 'name': getattr(d, "name", None) or None, 'setup_requires': getattr(d, "setup_requires", []), 'install_requires': getattr(d, "install_requires", []), 'tests_require': getattr(d, "tests_require", []) or [], 'scripts': getattr(d, "scripts", []) or [], 'entry_points': getattr(d, "entry_points", None) or {}, 'packages': getattr(d, "packages", []) or [], 'requires': d.get_requires() or [], } import os import json with open(%(output_path)s, 'w') as f: json.dump(r, f) """#; #[derive(Debug)] pub struct SetupPy { path: PathBuf, has_setup_py: bool, config: Option, pyproject: Option, #[allow(dead_code)] buildsystem: Option, } impl SetupPy { pub fn new(path: &Path) -> Self { let has_setup_py = path.join("setup.py").exists(); Python::with_gil(|py| { let config = match load_setup_cfg(path) { Ok(config) => config, Err(e) if e.is_instance_of::(py) => None, Err(e) if e.is_instance_of::(py) => { log::warn!("Error parsing setup.cfg: {}", e); None } Err(e) => { panic!("Error parsing setup.cfg: {}", e); } }; let (pyproject, buildsystem) = match load_toml(path) { Ok(pyproject) => { let buildsystem = pyproject .build_system .as_ref() .and_then(|bs| bs.build_backend.clone()); (Some(pyproject), buildsystem) } Err(e) if e.is_instance_of::(py) => (None, None), Err(e) => { panic!("Error parsing pyproject.toml: {}", e); } }; Self { has_setup_py, path: path.to_owned(), config, pyproject, buildsystem, } }) } pub fn probe(path: &Path) -> Option> { if path.join("setup.py").exists() { log::debug!("Found setup.py, assuming python project."); return Some(Box::new(Self::new(path))); } if path.join("pyproject.toml").exists() { log::debug!("Found pyproject.toml, assuming python project."); return Some(Box::new(Self::new(path))); } None } fn extract_setup_direct(&self) -> Option { let p = self.path.join("setup.py").canonicalize().unwrap(); Python::with_gil(|py| { let d = match run_setup(py, &p, "init") { Err(e) if e.is_instance_of::(py) => { log::warn!("Unable to load setup.py metadata: {}", e); return None; } Ok(d) => d, Err(e) => { panic!("Unable to load setup.py metadata: {}", e); } }; let name: Option = d.getattr(py, "name").unwrap().extract(py).unwrap(); let setup_requires: Vec = d .call_method1(py, "get", ("setup_requires", Vec::::new())) .unwrap() .extract(py) .unwrap(); let install_requires: Vec = d .call_method1(py, "get", ("install_requires", Vec::::new())) .unwrap() .extract(py) .unwrap(); let tests_require: Vec = d .call_method1(py, "get", ("tests_require", Vec::::new())) .unwrap() .extract(py) .unwrap(); let scripts: Vec = d .call_method1(py, "get", ("scripts", Vec::::new())) .unwrap() .extract(py) .unwrap(); let entry_points: HashMap> = d .call_method1( py, "get", ("entry_points", HashMap::>::new()), ) .unwrap() .extract(py) .unwrap(); let packages: Vec = d .call_method1(py, "get", ("packages", Vec::::new())) .unwrap() .extract(py) .unwrap(); let requires: Vec = d .call_method0(py, "get_requires") .unwrap() .extract(py) .unwrap(); Some(Distribution { name, setup_requires, install_requires, tests_require, scripts, entry_points, packages, requires, }) }) } fn determine_interpreter(&self) -> String { if let Some(config) = self.config.as_ref() { let python_requires: Option = config .get_section("options") .and_then(|s| s.get::("python_requires")); if python_requires .map(|pr| !pr.contains("2.7")) .unwrap_or(true) { return "python3".to_owned(); } } let path = self.path.join("setup.py"); let shebang_binary = crate::shebang::shebang_binary(&path).unwrap(); shebang_binary.unwrap_or("python3".to_owned()) } fn extract_setup_in_session( &self, session: &dyn Session, fixers: Option<&[&dyn BuildFixer]>, ) -> Option { let interpreter = self.determine_interpreter(); let mut output_f = tempfile::NamedTempFile::new_in(session.location().join("tmp")).unwrap(); let argv: Vec = vec![ interpreter, "-c".to_string(), SETUP_WRAPPER .replace( "%(script_name)s", session.pwd().join("setup.py").to_str().unwrap(), ) .replace( "%(output_path)s", &format!( "\"/{}\"", output_f .path() .to_str() .unwrap() .strip_prefix(session.location().to_str().unwrap()) .unwrap() ), ), ]; let r = if let Some(fixers) = fixers { session .command(argv.iter().map(|x| x.as_str()).collect::>()) .quiet(true) .run_fixing_problems::<_, Error>(fixers) .map(|_| ()) .map_err(|e| e.to_string()) } else { session .command(argv.iter().map(|x| x.as_str()).collect()) .check_call() .map_err(|e| e.to_string()) }; match r { Ok(_) => (), Err(e) => { log::warn!("Unable to load setup.py metadata: {}", e); return None; } } output_f.seek(std::io::SeekFrom::Start(0)).unwrap(); Some(serde_json::from_reader(output_f).unwrap()) } fn extract_setup( &self, session: Option<&dyn Session>, fixers: Option<&[&dyn BuildFixer]>, ) -> Option { if !self.has_setup_py { return None; } if let Some(session) = session { self.extract_setup_in_session(session, fixers) } else { self.extract_setup_direct() } } fn setup_requires(&self) -> Vec { let mut ret = vec![]; if let Some(build_system) = self .pyproject .as_ref() .and_then(|p| p.build_system.as_ref()) { let requires = &build_system.requires; for require in requires { ret.push(PythonPackageDependency::from(require.clone())); } } if let Some(config) = &self.config { let options = config.get_section("options"); let setup_requires = options .and_then(|os| os.get::>("setup_requires")) .unwrap_or(vec![]); for require in &setup_requires { ret.push(PythonPackageDependency::try_from(require.clone()).unwrap()); } } ret } fn run_setup( &self, session: &dyn Session, installer: &dyn Installer, args: Vec<&str>, ) -> Result<(), Error> { // Install the setup_requires beforehand, since otherwise // setuptools might fetch eggs instead of our preferred installer. let setup_requires = self .setup_requires() .into_iter() .map(|x| Box::new(x) as Box) .collect::>(); crate::installer::install_missing_deps( session, installer, &[crate::installer::InstallationScope::Global], setup_requires .iter() .map(|x| x.as_ref()) .collect::>() .as_slice(), )?; let interpreter = self.determine_interpreter().clone(); let mut args = args.clone(); args.insert(0, &interpreter); args.insert(1, "setup.py"); // TODO(jelmer): Perhaps this should be additive? session.command(args).run_detecting_problems()?; Ok(()) } } impl BuildSystem for SetupPy { fn test(&self, session: &dyn Session, installer: &dyn Installer) -> Result<(), Error> { if self.path.join("tox.ini").exists() { run_detecting_problems( session, vec!["tox", "--skip-missing-interpreters"], None, false, None, None, None, None, )?; return Ok(()); } if self .config .as_ref() .map(|c| c.has_section("tool:pytest") || c.has_section("pytest")) .unwrap_or(false) { session.command(vec!["pytest"]).run_detecting_problems()?; return Ok(()); } if self.has_setup_py { // Pre-emptively install setuptools, since distutils doesn't provide // a 'test' subcommand and some packages fall back to distutils // if setuptools is not available. let setuptools_dep = PythonPackageDependency::simple("setuptools"); if !setuptools_dep.present(session) { installer.install(&setuptools_dep, InstallationScope::Global)?; } match self.run_setup(session, installer, vec!["test"]) { Ok(_) => { return Ok(()); } Err(Error::Error(AnalyzedError::Unidentified { lines, .. })) if lines.contains(&"error: invalid command 'test'".to_string()) => { return Ok(()); } Err(e) => { return Err(e); } } } unimplemented!(); } fn build(&self, session: &dyn Session, installer: &dyn Installer) -> Result<(), Error> { if self.has_setup_py { self.run_setup(session, installer, vec!["build"]) } else { unimplemented!(); } } fn dist( &self, session: &dyn Session, installer: &dyn Installer, target_directory: &Path, quiet: bool, ) -> Result { // TODO(jelmer): Look at self.build_backend let dc = DistCatcher::new(vec![session.external_path(Path::new("dist"))]); if self.has_setup_py { let mut preargs = vec![]; if quiet { preargs.push("--quiet"); } // Preemptively install setuptools since some packages fail in some way without it. let setuptools_req = PythonPackageDependency::simple("setuptools"); if !setuptools_req.present(session) { installer.install(&setuptools_req, InstallationScope::Global)?; } preargs.push("sdist"); self.run_setup(session, installer, preargs)?; } else if self.pyproject.is_some() { run_detecting_problems( session, vec!["python3", "-m", "build", "--sdist", "."], None, false, None, None, None, None, )?; } else { panic!("No setup.py or pyproject.toml"); } Ok(dc.copy_single(target_directory).unwrap().unwrap()) } fn clean(&self, session: &dyn Session, installer: &dyn Installer) -> Result<(), Error> { if self.has_setup_py { self.run_setup(session, installer, vec!["clean"]) } else { unimplemented!(); } } fn install( &self, session: &dyn Session, installer: &dyn Installer, install_target: &InstallTarget, ) -> Result<(), Error> { if self.has_setup_py { let mut args = vec![]; if install_target.scope == InstallationScope::User { args.push("--user".to_string()); } if let Some(prefix) = install_target.prefix.as_ref() { args.push(format!("--prefix={}", prefix.to_str().unwrap())); } args.insert(0, "install".to_owned()); self.run_setup( session, installer, args.iter().map(|x| x.as_str()).collect(), )?; Ok(()) } else { unimplemented!(); } } fn get_declared_dependencies( &self, session: &dyn Session, fixers: std::option::Option<&[&dyn BuildFixer]>, ) -> Result)>, Error> { let mut ret: Vec<(DependencyCategory, Box)> = vec![]; let distribution = self.extract_setup(Some(session), fixers); if let Some(distribution) = distribution { for require in &distribution.requires { ret.push(( DependencyCategory::Universal, Box::new(PythonPackageDependency::try_from(require.clone()).unwrap()), )); } // Not present for distutils-only packages for require in &distribution.setup_requires { ret.push(( DependencyCategory::Build, Box::new(PythonPackageDependency::try_from(require.clone()).unwrap()), )); } // Not present for distutils-only packages for require in &distribution.install_requires { ret.push(( DependencyCategory::Universal, Box::new(PythonPackageDependency::try_from(require.clone()).unwrap()), )); } // Not present for distutils-only packages for require in &distribution.tests_require { ret.push(( DependencyCategory::Test, Box::new(PythonPackageDependency::try_from(require.clone()).unwrap()), )); } } if let Some(pyproject) = self.pyproject.as_ref() { if let Some(build_system) = pyproject.build_system.as_ref() { for require in &build_system.requires { ret.push(( DependencyCategory::Build, Box::new(PythonPackageDependency::from(require.clone())), )); } } } if let Some(options) = self.config.as_ref().and_then(|c| c.get_section("options")) { for require in options .get::>("setup_requires") .unwrap_or_default() { ret.push(( DependencyCategory::Build, Box::new(PythonPackageDependency::try_from(require).unwrap()), )); } for require in options .get::>("install_requires") .unwrap_or_default() { ret.push(( DependencyCategory::Universal, Box::new(PythonPackageDependency::try_from(require).unwrap()), )); } } if let Some(pyproject_toml) = self.pyproject.as_ref() { if let Some(build_system) = pyproject_toml.build_system.as_ref() { for require in &build_system.requires { ret.push(( DependencyCategory::Build, Box::new(PythonPackageDependency::from(require.clone())), )); } } if let Some(dependencies) = pyproject_toml .project .as_ref() .and_then(|p| p.dependencies.as_ref()) { for dep in dependencies { ret.push(( DependencyCategory::Universal, Box::new(PythonPackageDependency::from(dep.clone())), )); } } if let Some(extras) = pyproject_toml .project .as_ref() .and_then(|p| p.optional_dependencies.as_ref()) { for (name, deps) in extras { for dep in deps { ret.push(( DependencyCategory::RuntimeExtra(name.clone()), Box::new(PythonPackageDependency::from(dep.clone())), )); } } } if let Some(requires_python) = pyproject_toml .project .as_ref() .and_then(|p| p.requires_python.as_ref()) { ret.push(( DependencyCategory::Universal, Box::new(PythonDependency::from(requires_python)), )); } } Ok(ret) } fn get_declared_outputs( &self, session: &dyn Session, fixers: Option<&[&dyn BuildFixer]>, ) -> Result>, Error> { let mut ret: Vec> = vec![]; let distribution = self.extract_setup(Some(session), fixers); let mut all_packages = HashSet::new(); if let Some(distribution) = distribution { for script in &distribution.scripts { ret.push(Box::new(BinaryOutput( Path::new(script) .file_name() .unwrap() .to_str() .unwrap() .to_owned(), ))); } for script in distribution .entry_points .get("console_scripts") .unwrap_or(&vec![]) { ret.push(Box::new(BinaryOutput( script.split_once('=').unwrap().0.to_string().to_owned(), ))); } all_packages.extend(distribution.packages); } if let Some(options) = self.config.as_ref().and_then(|c| c.get_section("options")) { all_packages.extend(options.get::>("packages").unwrap_or_default()); for script in options.get::>("scripts").unwrap_or_default() { let p = Path::new(&script); ret.push(Box::new(BinaryOutput( p.file_name().unwrap().to_str().unwrap().to_owned(), ))); } let entry_points = options .get::>>("entry_points") .unwrap_or_default(); for script in entry_points.get("console_scripts").unwrap_or(&vec![]) { ret.push(Box::new(BinaryOutput( script.split_once('=').unwrap().0.to_string().to_owned(), ))); } } for package in all_packages { ret.push(Box::new(PythonPackageOutput::new( &package, Some("cpython3"), ))); } if let Some(pyproject) = self.pyproject.as_ref().and_then(|p| p.project.as_ref()) { if let Some(scripts) = pyproject.scripts.as_ref() { for (script, _from) in scripts { ret.push(Box::new(BinaryOutput(script.to_string()))); } } if let Some(gui_scripts) = pyproject.gui_scripts.as_ref() { for (script, _from) in gui_scripts { ret.push(Box::new(BinaryOutput(script.to_string()))); } } ret.push(Box::new(PythonPackageOutput::new( &pyproject.name, pyproject.version.as_ref().map(|v| v.to_string()).as_deref(), ))); } Ok(ret) } fn name(&self) -> &str { "setup.py" } fn as_any(&self) -> &dyn std::any::Any { self } } ognibuild-0.0.32/src/buildsystems/r.rs000064400000000000000000000141731046102023000160150ustar 00000000000000use crate::buildsystem::guaranteed_which; use crate::buildsystem::{BuildSystem, DependencyCategory}; use crate::dependencies::r::RPackageDependency; use crate::dependency::Dependency; use crate::dist_catcher::DistCatcher; use crate::output::RPackageOutput; use std::path::{Path, PathBuf}; #[derive(Debug)] pub struct R { path: PathBuf, } impl R { pub fn new(path: PathBuf) -> Self { Self { path } } pub fn lint( &self, session: &dyn crate::session::Session, installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { let r_path = guaranteed_which(session, installer, "R").unwrap(); session .command(vec![r_path.to_str().unwrap(), "CMD", "check"]) .run_detecting_problems()?; Ok(()) } pub fn probe(path: &Path) -> Option> { if path.join("DESCRIPTION").exists() && path.join("NAMESPACE").exists() { Some(Box::new(Self::new(path.to_path_buf()))) } else { None } } } impl BuildSystem for R { fn name(&self) -> &str { "R" } fn dist( &self, session: &dyn crate::session::Session, installer: &dyn crate::installer::Installer, target_directory: &Path, _quiet: bool, ) -> Result { let dc = DistCatcher::new(vec![session.external_path(Path::new("."))]); let r_path = guaranteed_which(session, installer, "R").unwrap(); session .command(vec![r_path.to_str().unwrap(), "CMD", "build", "."]) .run_detecting_problems()?; Ok(dc.copy_single(target_directory).unwrap().unwrap()) } fn test( &self, session: &dyn crate::session::Session, installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { let r_path = guaranteed_which(session, installer, "R").unwrap(); if session.exists(Path::new("run_tests.sh")) { session .command(vec!["./run_tests.sh"]) .run_detecting_problems()?; } else if session.exists(Path::new("tests/testthat")) { session .command(vec![ r_path.to_str().unwrap(), "-e", "testthat::test_dir('tests')", ]) .run_detecting_problems()?; } Ok(()) } fn build( &self, _session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { // Nothing to do here Ok(()) } fn clean( &self, _session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), crate::buildsystem::Error> { Err(crate::buildsystem::Error::Unimplemented) } fn install( &self, session: &dyn crate::session::Session, installer: &dyn crate::installer::Installer, install_target: &crate::buildsystem::InstallTarget, ) -> Result<(), crate::buildsystem::Error> { let r_path = guaranteed_which(session, installer, "R").unwrap(); let mut args = vec![ r_path.to_str().unwrap().to_string(), "CMD".to_string(), "INSTALL".to_string(), ".".to_string(), ]; if let Some(prefix) = &install_target.prefix.as_ref() { args.push(format!("--prefix={}", prefix.to_str().unwrap())); } session .command(args.iter().map(|s| s.as_str()).collect()) .run_detecting_problems()?; Ok(()) } fn get_declared_dependencies( &self, _session: &dyn crate::session::Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result< Vec<( crate::buildsystem::DependencyCategory, Box, )>, crate::buildsystem::Error, > { let mut ret: Vec<(DependencyCategory, Box)> = vec![]; let f = std::fs::File::open(self.path.join("DESCRIPTION")).unwrap(); let description = read_description(f).unwrap(); for s in description.suggests().unwrap_or_default().iter() { ret.push(( DependencyCategory::Build, /* TODO */ Box::new(RPackageDependency::from(s)), )); } for s in description.depends().unwrap_or_default().iter() { ret.push(( DependencyCategory::Build, Box::new(RPackageDependency::from(s)), )); } for s in description.imports().unwrap_or_default().iter() { ret.push(( DependencyCategory::Build, Box::new(RPackageDependency::from_str(&s)), )); } for s in description.linking_to().unwrap_or_default() { ret.push(( DependencyCategory::Build, Box::new(RPackageDependency::from_str(&s)), )); } Ok(ret) } fn get_declared_outputs( &self, _session: &dyn crate::session::Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result>, crate::buildsystem::Error> { let mut ret = vec![]; let f = std::fs::File::open(self.path.join("DESCRIPTION")).unwrap(); let description = read_description(f).unwrap(); if let Some(package) = description.package() { ret.push(Box::new(RPackageOutput::new(&package)) as Box); } Ok(ret) } fn as_any(&self) -> &dyn std::any::Any { self } } fn read_description( mut r: R, ) -> Result { // See https://r-pkgs.org/description.html let mut s = String::new(); r.read_to_string(&mut s).unwrap(); let p: r_description::lossless::RDescription = s.parse().unwrap(); Ok(p) } ognibuild-0.0.32/src/buildsystems/ruby.rs000064400000000000000000000066531046102023000165410ustar 00000000000000use crate::buildsystem::{guaranteed_which, BuildSystem, Error}; use std::path::{Path, PathBuf}; #[derive(Debug)] pub struct Gem { path: PathBuf, } impl Gem { pub fn new(path: PathBuf) -> Self { Self { path } } pub fn probe(path: &Path) -> Option> { let mut gemfiles = std::fs::read_dir(path) .unwrap() .filter_map(|entry| entry.ok().map(|entry| entry.path())) .filter(|path| path.extension().unwrap_or_default() == "gem") .collect::>(); if !gemfiles.is_empty() { Some(Box::new(Self::new(gemfiles.remove(0)))) } else { None } } } impl BuildSystem for Gem { fn name(&self) -> &str { "gem" } fn dist( &self, session: &dyn crate::session::Session, installer: &dyn crate::installer::Installer, target_directory: &std::path::Path, quiet: bool, ) -> Result { let mut gemfiles = std::fs::read_dir(&self.path) .unwrap() .filter_map(|entry| entry.ok().map(|entry| entry.path())) .filter(|path| path.extension().unwrap_or_default() == "gem") .collect::>(); assert!(!gemfiles.is_empty()); if gemfiles.len() > 1 { log::warn!("More than one gemfile. Trying the first?"); } let dc = crate::dist_catcher::DistCatcher::default(&session.external_path(Path::new("."))); session .command(vec![ guaranteed_which(session, installer, "gem2tgz")? .to_str() .unwrap(), gemfiles.remove(0).to_str().unwrap(), ]) .quiet(quiet) .run_detecting_problems()?; Ok(dc.copy_single(target_directory).unwrap().unwrap()) } fn test( &self, _session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), Error> { Err(Error::Unimplemented) } fn build( &self, _session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), Error> { Err(Error::Unimplemented) } fn clean( &self, _session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), Error> { Err(Error::Unimplemented) } fn install( &self, _session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, _install_target: &crate::buildsystem::InstallTarget, ) -> Result<(), Error> { Err(Error::Unimplemented) } fn get_declared_dependencies( &self, _session: &dyn crate::session::Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result< Vec<( crate::buildsystem::DependencyCategory, Box, )>, Error, > { Err(Error::Unimplemented) } fn get_declared_outputs( &self, _session: &dyn crate::session::Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result>, Error> { Err(Error::Unimplemented) } fn as_any(&self) -> &dyn std::any::Any { self } } ognibuild-0.0.32/src/buildsystems/rust.rs000064400000000000000000000157011046102023000165470ustar 00000000000000use crate::analyze::AnalyzedError; use crate::buildsystem::{BuildSystem, DependencyCategory, Error}; use crate::dependencies::CargoCrateDependency; use crate::dependency::Dependency; use std::path::{Path, PathBuf}; #[derive(serde::Deserialize, Debug)] #[allow(dead_code)] struct Package { name: String, } #[derive(serde::Deserialize, Debug)] #[serde(untagged)] #[allow(dead_code)] enum CrateDependency { Version(String), Details { version: Option, optional: Option, features: Option>, workspace: Option, git: Option, branch: Option, #[serde(rename = "default-features")] default_features: Option, }, } #[allow(dead_code)] impl CrateDependency { fn version(&self) -> Option<&str> { match self { Self::Version(v) => Some(v.as_str()), Self::Details { version, .. } => version.as_deref(), } } fn features(&self) -> Option<&[String]> { match self { Self::Version(_) => None, Self::Details { features, .. } => features.as_ref().map(|v| v.as_slice()), } } } #[derive(serde::Deserialize, Debug)] #[allow(dead_code)] pub struct CrateBinary { name: String, path: Option, #[serde(rename = "required-features")] required_features: Option>, } #[derive(serde::Deserialize, Debug)] pub struct CrateLibrary {} #[derive(serde::Deserialize, Debug)] #[allow(dead_code)] struct CargoToml { package: Option, dependencies: Option>, bin: Option>, lib: Option, } #[derive(Debug)] pub struct Cargo { #[allow(dead_code)] path: PathBuf, local_crate: CargoToml, } impl Cargo { pub fn new(path: PathBuf) -> Self { let cargo_toml = std::fs::read_to_string(path.join("Cargo.toml")).unwrap(); let local_crate: CargoToml = toml::from_str(&cargo_toml).unwrap(); Self { path, local_crate } } pub fn probe(path: &Path) -> Option> { if path.join("Cargo.toml").exists() { log::debug!("Found Cargo.toml, assuming rust cargo package."); Some(Box::new(Cargo::new(path.to_path_buf()))) } else { None } } } impl BuildSystem for Cargo { fn name(&self) -> &str { "cargo" } fn dist( &self, _session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, _target_directory: &std::path::Path, _quiet: bool, ) -> Result { Err(Error::Unimplemented) } fn test( &self, session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), Error> { session .command(vec!["cargo", "test"]) .run_detecting_problems()?; Ok(()) } fn build( &self, session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), Error> { match session .command(vec!["cargo", "generate"]) .run_detecting_problems() { Ok(_) => {} Err(AnalyzedError::Unidentified { lines, .. }) if lines == ["error: no such subcommand: `generate`"] => {} Err(e) => return Err(e.into()), } session .command(vec!["cargo", "build"]) .run_detecting_problems()?; Ok(()) } fn clean( &self, session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, ) -> Result<(), Error> { session .command(vec!["cargo", "clean"]) .run_detecting_problems()?; Ok(()) } fn install( &self, session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, install_target: &crate::buildsystem::InstallTarget, ) -> Result<(), Error> { let mut args = vec![ "cargo".to_string(), "install".to_string(), "--path=.".to_string(), ]; if let Some(prefix) = install_target.prefix.as_ref() { args.push(format!("-root={}", prefix.to_str().unwrap())); } session .command(args.iter().map(|x| x.as_str()).collect()) .run_detecting_problems()?; Ok(()) } fn get_declared_dependencies( &self, _session: &dyn crate::session::Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result< Vec<( crate::buildsystem::DependencyCategory, Box, )>, Error, > { let mut ret: Vec<(DependencyCategory, Box)> = vec![]; for (name, details) in self .local_crate .dependencies .as_ref() .unwrap_or(&std::collections::HashMap::new()) { ret.push(( DependencyCategory::Build, Box::new(CargoCrateDependency { name: name.clone(), features: Some(details.features().unwrap_or(&[]).to_vec()), api_version: None, minimum_version: None, }), )); } Ok(ret) } fn get_declared_outputs( &self, _session: &dyn crate::session::Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result>, Error> { let mut ret: Vec> = vec![]; if let Some(bins) = &self.local_crate.bin { for bin in bins { ret.push(Box::new(crate::output::BinaryOutput::new(&bin.name))); } } // TODO: library output Ok(ret) } fn install_declared_dependencies( &self, _categories: &[crate::buildsystem::DependencyCategory], scopes: &[crate::installer::InstallationScope], session: &dyn crate::session::Session, _installer: &dyn crate::installer::Installer, fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result<(), Error> { if !scopes.contains(&crate::installer::InstallationScope::Vendor) { return Err(crate::installer::Error::UnsupportedScopes(scopes.to_vec()).into()); } if let Some(fixers) = fixers { session .command(vec!["cargo", "fetch"]) .run_fixing_problems::<_, Error>(fixers) .unwrap(); } else { session .command(vec!["cargo", "fetch"]) .run_detecting_problems()?; } Ok(()) } fn as_any(&self) -> &dyn std::any::Any { self } } ognibuild-0.0.32/src/buildsystems/waf.rs000064400000000000000000000072721046102023000163330ustar 00000000000000use crate::buildsystem::{BuildSystem, Error}; use crate::dependency::Dependency; use crate::installer::{InstallationScope, Installer}; use crate::session::Session; use std::path::PathBuf; #[derive(Debug)] pub struct Waf { #[allow(dead_code)] path: PathBuf, } impl Waf { pub fn new(path: PathBuf) -> Self { Self { path } } fn setup(&self, session: &dyn Session, installer: &dyn Installer) -> Result<(), Error> { let binary_req = crate::dependencies::BinaryDependency::new("python3"); if !binary_req.present(session) { installer.install(&binary_req, InstallationScope::Global)?; } Ok(()) } pub fn probe(path: &std::path::Path) -> Option> { let path = path.join("waf"); if path.exists() { log::debug!("Found waf, assuming waf package."); Some(Box::new(Self::new(path))) } else { None } } } impl BuildSystem for Waf { fn name(&self) -> &str { "waf" } fn dist( &self, session: &dyn Session, installer: &dyn Installer, target_directory: &std::path::Path, quiet: bool, ) -> Result { self.setup(session, installer)?; let dc = crate::dist_catcher::DistCatcher::default( &session.external_path(std::path::Path::new(".")), ); session .command(vec!["./waf", "dist"]) .quiet(quiet) .run_detecting_problems()?; Ok(dc.copy_single(target_directory).unwrap().unwrap()) } fn test(&self, session: &dyn Session, installer: &dyn Installer) -> Result<(), Error> { self.setup(session, installer)?; session .command(vec!["./waf", "test"]) .run_detecting_problems()?; Ok(()) } fn build(&self, session: &dyn Session, installer: &dyn Installer) -> Result<(), Error> { self.setup(session, installer)?; match session .command(vec!["./waf", "build"]) .run_detecting_problems() { Err(crate::analyze::AnalyzedError::Unidentified { lines, .. }) if lines.contains( &"The project was not configured: run \"waf configure\" first!".to_string(), ) => { session .command(vec!["./waf", "configure"]) .run_detecting_problems()?; session .command(vec!["./waf", "build"]) .run_detecting_problems() } other => other, }?; Ok(()) } fn clean(&self, _session: &dyn Session, _installer: &dyn Installer) -> Result<(), Error> { Err(Error::Unimplemented) } fn install( &self, _session: &dyn Session, _installer: &dyn Installer, _install_target: &crate::buildsystem::InstallTarget, ) -> Result<(), Error> { Err(Error::Unimplemented) } fn get_declared_dependencies( &self, _session: &dyn Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result< Vec<( crate::buildsystem::DependencyCategory, Box, )>, Error, > { Err(Error::Unimplemented) } fn get_declared_outputs( &self, _session: &dyn Session, _fixers: Option<&[&dyn crate::fix_build::BuildFixer]>, ) -> Result>, Error> { Err(Error::Unimplemented) } fn as_any(&self) -> &dyn std::any::Any { self } } ognibuild-0.0.32/src/debian/apt.rs000064400000000000000000000432651046102023000150370ustar 00000000000000use crate::dependencies::debian::{ default_tie_breakers, DebianDependency, IntoDebianDependency, TieBreaker, }; use crate::dependency::Dependency; use crate::installer::{Error as InstallerError, Explanation, InstallationScope, Installer}; use crate::session::{get_user, Session}; use debversion::Version; use std::sync::RwLock; pub enum Error { Unidentified { retcode: i32, args: Vec, lines: Vec, secondary: Option>, }, Detailed { retcode: i32, args: Vec, error: Option>, }, Session(crate::session::Error), } impl std::fmt::Debug for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Error::Unidentified { retcode, args, lines, secondary: _, } => { write!( f, "Unidentified error: apt failed with retcode {}: {:?}\n{}", retcode, args, lines.join("\n") ) } Error::Detailed { retcode, args, error, } => { write!( f, "Detailed error: apt failed with retcode {}: {:?}\n{}", retcode, args, error.as_ref().map_or("".to_string(), |e| e.to_string()) ) } Error::Session(error) => write!(f, "{:?}", error), } } } impl From for Error { fn from(error: crate::session::Error) -> Self { Error::Session(error) } } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Error::Unidentified { retcode, args, lines, secondary: _, } => { write!( f, "apt failed with retcode {}: {:?}\n{}", retcode, args, lines.join("\n") ) } Error::Detailed { retcode, args, error, } => { write!( f, "apt failed with retcode {}: {:?}\n{}", retcode, args, error.as_ref().map_or("".to_string(), |e| e.to_string()) ) } Error::Session(error) => write!(f, "{}", error), } } } impl std::error::Error for Error {} pub struct AptManager<'a> { session: &'a dyn Session, prefix: Vec, searchers: RwLock + 'a>>>>, } pub enum SatisfyEntry { Required(String), Conflict(String), } impl<'a> AptManager<'a> { pub fn session(&self) -> &'a dyn Session { self.session } pub fn new(session: &'a dyn Session, prefix: Option>) -> Self { Self { session, prefix: prefix.unwrap_or_default(), searchers: RwLock::new(None), } } pub fn set_searchers( &self, searchers: Vec + 'a>>, ) { *self.searchers.write().unwrap() = Some(searchers); } pub fn from_session(session: &'a dyn Session) -> Self { let prefix = if get_user(session).as_str() != "root" { vec!["sudo".to_string()] } else { vec![] }; return Self::new(session, Some(prefix)); } fn run_apt(&self, args: Vec<&str>) -> Result<(), Error> { run_apt( self.session, args, self.prefix.iter().map(|s| s.as_str()).collect(), ) } pub fn satisfy(&self, deps: Vec) -> Result<(), Error> { let mut args = vec!["satisfy".to_string()]; args.extend(deps.iter().map(|dep| match dep { SatisfyEntry::Required(s) => s.clone(), SatisfyEntry::Conflict(s) => format!("Conflict: {}", s), })); self.run_apt(args.iter().map(|s| s.as_str()).collect()) } pub fn satisfy_command<'b>(&'b self, deps: Vec<&'b str>) -> Vec<&'b str> { let mut args = self .prefix .iter() .map(|s| s.as_str()) .collect::>(); args.push("apt"); args.push("satisfy"); args.extend(deps); args } pub fn get_packages_for_paths( &self, paths: Vec<&str>, regex: bool, case_insensitive: bool, ) -> Result, Error> { if regex { log::debug!("Searching for packages containing regexes {:?}", paths); } else { log::debug!("Searching for packages containing {:?}", paths); } if self.searchers.read().unwrap().is_none() { *self.searchers.write().unwrap() = Some(vec![ crate::debian::file_search::get_apt_contents_file_searcher(self.session).unwrap(), Box::new(crate::debian::file_search::GENERATED_FILE_SEARCHER.clone()), ]); } Ok(crate::debian::file_search::get_packages_for_paths( paths, self.searchers .read() .unwrap() .as_ref() .unwrap() .iter() .map(|s| s.as_ref()) .collect::>() .as_slice(), regex, case_insensitive, )) } } pub fn find_deps_simple( apt_mgr: &AptManager, paths: Vec<&str>, regex: bool, case_insensitive: bool, ) -> Result, Error> { let packages = apt_mgr.get_packages_for_paths(paths, regex, case_insensitive)?; Ok(packages .iter() .map(|package| DebianDependency::simple(package)) .collect()) } pub fn find_deps_with_min_version( apt_mgr: &AptManager, paths: Vec<&str>, regex: bool, minimum_version: &Version, case_insensitive: bool, ) -> Result, Error> { let packages = apt_mgr.get_packages_for_paths(paths, regex, case_insensitive)?; Ok(packages .iter() .map(|package| DebianDependency::new_with_min_version(package, minimum_version)) .collect()) } pub fn run_apt(session: &dyn Session, args: Vec<&str>, prefix: Vec<&str>) -> Result<(), Error> { let args = [prefix, vec!["apt", "-y"], args].concat(); log::info!("apt: running {:?}", args); let (status, mut lines) = session .command(args.clone()) .cwd(std::path::Path::new("/")) .user("root") .run_with_tee()?; if status.success() { return Ok(()); } let (r#match, error) = buildlog_consultant::apt::find_apt_get_failure(lines.iter().map(|s| s.as_str()).collect()); if let Some(error) = error { return Err(Error::Detailed { retcode: status.code().unwrap_or(1), args: args.iter().map(|s| s.to_string()).collect(), error: Some(error), }); } while lines.last().map_or(false, |line| line.trim().is_empty()) { lines.pop(); } return Err(Error::Unidentified { retcode: status.code().unwrap_or(1), args: args.iter().map(|s| s.to_string()).collect(), lines, secondary: r#match, }); } fn pick_best_deb_dependency( mut dependencies: Vec, tie_breakers: &[Box], ) -> Option { if dependencies.is_empty() { return None; } if dependencies.len() == 1 { return Some(dependencies.remove(0)); } log::warn!("Multiple candidates for dependency {:?}", dependencies); for tie_breaker in tie_breakers { let winner = tie_breaker.break_tie(dependencies.iter().collect::>().as_slice()); if let Some(winner) = winner { return Some(winner.clone()); } } log::info!( "No tie breaker could determine a winner for dependency {:?}", dependencies ); Some(dependencies.remove(0)) } pub fn dependency_to_possible_deb_dependencies( apt: &AptManager, dep: &dyn Dependency, ) -> Vec { let mut candidates = vec![]; macro_rules! try_into_debian_dependency { ($apt:expr, $dep:expr, $type:ty) => { if let Some(dep) = $dep.as_any().downcast_ref::<$type>() { if let Some(alts) = dep.try_into_debian_dependency($apt) { candidates.extend(alts); } } }; } // TODO: More idiomatic way to do this? try_into_debian_dependency!(apt, dep, crate::dependencies::go::GoPackageDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::go::GoDependency); try_into_debian_dependency!( apt, dep, crate::dependencies::haskell::HaskellPackageDependency ); try_into_debian_dependency!(apt, dep, crate::dependencies::java::JavaClassDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::java::JDKDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::java::JREDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::java::JDKFileDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::BinaryDependency); try_into_debian_dependency!( apt, dep, crate::dependencies::pytest::PytestPluginDependency ); try_into_debian_dependency!( apt, dep, crate::dependencies::VcsControlDirectoryAccessDependency ); try_into_debian_dependency!(apt, dep, crate::dependencies::CargoCrateDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::PkgConfigDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::PathDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::CHeaderDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::JavaScriptRuntimeDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::ValaPackageDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::RubyGemDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::DhAddonDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::LibraryDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::StaticLibraryDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::RubyFileDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::xml::XmlEntityDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::SprocketsFileDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::CMakeFileDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::MavenArtifactDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::GnomeCommonDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::QtModuleDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::QTDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::X11Dependency); try_into_debian_dependency!( apt, dep, crate::dependencies::CertificateAuthorityDependency ); try_into_debian_dependency!( apt, dep, crate::dependencies::autoconf::AutoconfMacroDependency ); try_into_debian_dependency!(apt, dep, crate::dependencies::LibtoolDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::BoostComponentDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::KF5ComponentDependency); try_into_debian_dependency!( apt, dep, crate::dependencies::IntrospectionTypelibDependency ); try_into_debian_dependency!(apt, dep, crate::dependencies::node::NodePackageDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::node::NodeModuleDependency); try_into_debian_dependency!( apt, dep, crate::dependencies::octave::OctavePackageDependency ); try_into_debian_dependency!( apt, dep, crate::dependencies::perl::PerlPreDeclaredDependency ); try_into_debian_dependency!(apt, dep, crate::dependencies::perl::PerlModuleDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::perl::PerlFileDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::php::PhpClassDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::php::PhpExtensionDependency); try_into_debian_dependency!( apt, dep, crate::dependencies::python::PythonModuleDependency ); try_into_debian_dependency!( apt, dep, crate::dependencies::python::PythonPackageDependency ); try_into_debian_dependency!(apt, dep, crate::dependencies::python::PythonDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::r::RPackageDependency); try_into_debian_dependency!(apt, dep, crate::dependencies::vague::VagueDependency); candidates } pub fn dependency_to_deb_dependency( apt: &AptManager, dep: &dyn Dependency, tie_breakers: &[Box], ) -> Result, InstallerError> { let mut candidates = dependency_to_possible_deb_dependencies(apt, dep); if candidates.is_empty() { log::debug!("No Debian dependency candidates for dependency {:?}", dep); Ok(None) } else if candidates.len() == 1 { let deb_dep = candidates.remove(0); log::debug!( "Only one Debian dependency candidate for dependency {:?}: {:?}", dep, deb_dep ); Ok(Some(deb_dep)) } else { Ok(pick_best_deb_dependency(candidates, tie_breakers)) } } pub struct AptInstaller<'a> { apt: AptManager<'a>, tie_breakers: Vec>, } impl<'a> AptInstaller<'a> { pub fn new(apt: AptManager<'a>) -> Self { let tie_breakers = default_tie_breakers(apt.session); Self { apt, tie_breakers } } pub fn new_with_tie_breakers( apt: AptManager<'a>, tie_breakers: Vec>, ) -> Self { Self { apt, tie_breakers } } /// Create a new AptInstaller from a session pub fn from_session(session: &'a dyn Session) -> Self { Self::new(AptManager::from_session(session)) } } impl<'a> Installer for AptInstaller<'a> { fn install( &self, dep: &dyn Dependency, scope: InstallationScope, ) -> Result<(), InstallerError> { match scope { InstallationScope::User => { return Err(InstallerError::UnsupportedScope(scope)); } InstallationScope::Global => {} InstallationScope::Vendor => { return Err(InstallerError::UnsupportedScope(scope)); } } let apt_deb = if let Some(apt_deb) = dependency_to_deb_dependency(&self.apt, dep, self.tie_breakers.as_slice())? { apt_deb } else { return Err(InstallerError::UnknownDependencyFamily); }; match self .apt .satisfy(vec![SatisfyEntry::Required(apt_deb.relation_string())]) { Ok(_) => {} Err(e) => { return Err(InstallerError::Other(e.to_string())); } } Ok(()) } fn explain( &self, dep: &dyn Dependency, _scope: InstallationScope, ) -> Result { let apt_deb = if let Some(apt_deb) = dependency_to_deb_dependency(&self.apt, dep, self.tie_breakers.as_slice())? { apt_deb } else { return Err(InstallerError::UnknownDependencyFamily); }; let apt_deb_str = apt_deb.relation_string(); let cmd = self.apt.satisfy_command(vec![apt_deb_str.as_str()]); Ok(Explanation { message: format!( "Install {}", apt_deb .package_names() .iter() .map(|x| x.as_str()) .collect::>() .join(", ") ), command: Some(cmd.iter().map(|s| s.to_string()).collect()), }) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_pick_best_deb_dependency() { struct DummyTieBreaker; impl crate::dependencies::debian::TieBreaker for DummyTieBreaker { fn break_tie<'a>(&self, reqs: &[&'a DebianDependency]) -> Option<&'a DebianDependency> { reqs.iter().next().cloned() } } let mut tie_breakers = vec![Box::new(DummyTieBreaker) as Box]; let dep1 = DebianDependency::new("libssl-dev"); let dep2 = DebianDependency::new("libssl1.1-dev"); // Single dependency assert_eq!( pick_best_deb_dependency(vec![dep1.clone()], tie_breakers.as_mut_slice()), Some(dep1.clone()) ); // No dependencies assert_eq!( pick_best_deb_dependency(vec![], tie_breakers.as_mut_slice()), None ); // Multiple dependencies assert_eq!( pick_best_deb_dependency( vec![dep1.clone(), dep2.clone()], tie_breakers.as_mut_slice() ), Some(dep1.clone()) ); } } ognibuild-0.0.32/src/debian/build.rs000064400000000000000000000557321046102023000153540ustar 00000000000000use breezyshim::tree::{MutableTree, Tree, WorkingTree}; use buildlog_consultant::Problem; use debian_changelog::{ChangeLog, Urgency}; use debversion::Version; use std::io::Seek; use std::path::{Path, PathBuf}; pub fn get_build_architecture() -> String { std::process::Command::new("dpkg-architecture") .arg("-qDEB_BUILD_ARCH") .output() .map(|output| String::from_utf8(output.stdout).unwrap().trim().to_string()) .unwrap() } pub const DEFAULT_BUILDER: &str = "sbuild --no-clean-source"; fn python_command() -> String { pyo3::Python::with_gil(|py| { use pyo3::types::PyAnyMethods; let sys_module = py.import_bound("sys").unwrap(); sys_module .getattr("executable") .unwrap() .extract::() .unwrap() }) } pub fn builddeb_command( build_command: Option<&str>, result_dir: Option<&std::path::Path>, apt_repository: Option<&str>, apt_repository_key: Option<&str>, extra_repositories: Option<&Vec<&str>>, ) -> Vec { let mut build_command = build_command.unwrap_or(DEFAULT_BUILDER).to_string(); if let Some(extra_repositories) = extra_repositories { for repo in extra_repositories { build_command.push_str(&format!( " --extra-repository={}", shlex::try_quote(repo).unwrap() )); } } let mut args = vec![ python_command(), "-m".to_string(), "breezy".to_string(), "builddeb".to_string(), "--guess-upstream-branch-url".to_string(), format!("--builder={}", build_command), ]; if let Some(apt_repository) = apt_repository { args.push(format!("--apt-repository={}", apt_repository)); } if let Some(apt_repository_key) = apt_repository_key { args.push(format!("--apt-repository-key={}", apt_repository_key)); } if let Some(result_dir) = result_dir { args.push(format!("--result-dir={}", result_dir.to_string_lossy())); } args } #[derive(Debug)] pub struct BuildFailedError(pub i32); impl std::fmt::Display for BuildFailedError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Build failed: {}", self.0) } } impl std::error::Error for BuildFailedError {} pub fn build( local_tree: &WorkingTree, outf: std::fs::File, build_command: &str, result_dir: Option<&std::path::Path>, distribution: Option<&str>, subpath: &std::path::Path, source_date_epoch: Option>, apt_repository: Option<&str>, apt_repository_key: Option<&str>, extra_repositories: Option<&Vec<&str>>, ) -> Result<(), BuildFailedError> { let args = builddeb_command( Some(build_command), result_dir, apt_repository, apt_repository_key, extra_repositories, ); // Make a copy of the environment variables let mut env = std::env::vars().collect::>(); if let Some(distribution) = distribution { env.insert("DISTRIBUTION".to_owned(), distribution.to_owned()); } if let Some(source_date_epoch) = source_date_epoch { env.insert( "SOURCE_DATE_EPOCH".to_owned(), format!("{}", source_date_epoch.timestamp()), ); } log::info!("Building debian packages, running {}.", build_command); match std::process::Command::new(&args[0]) .args(&args[1..]) .current_dir(local_tree.abspath(subpath).unwrap()) .stdout(outf.try_clone().unwrap()) .stderr(outf) .envs(env) .status() { Ok(status) => { if status.success() { log::info!("Build succeeded."); Ok(()) } else { Err(BuildFailedError(status.code().unwrap_or(1))) } } Err(e) => { log::error!("Failed to run build command: {}", e); Err(BuildFailedError(1)) } } } pub const BUILD_LOG_FILENAME: &str = "build.log"; #[derive(Debug)] pub enum BuildOnceError { Detailed { stage: Option, phase: Option, retcode: i32, command: Vec, error: Box, description: String, }, Unidentified { stage: Option, phase: Option, retcode: i32, command: Vec, description: String, }, } pub struct BuildOnceResult { pub source_package: String, pub version: debversion::Version, pub changes_names: Vec, } pub fn build_once( local_tree: &WorkingTree, build_suite: Option<&str>, output_directory: &Path, build_command: &str, subpath: &Path, source_date_epoch: Option>, apt_repository: Option<&str>, apt_repository_key: Option<&str>, extra_repositories: Option<&Vec<&str>>, ) -> Result { use buildlog_consultant::problems::debian::DpkgSourceLocalChanges; use buildlog_consultant::sbuild::{worker_failure_from_sbuild_log, SbuildLog}; let build_log_path = output_directory.join(BUILD_LOG_FILENAME); log::debug!("Writing build log to {}", build_log_path.display()); let mut logf = std::fs::File::create(&build_log_path).unwrap(); match build( local_tree, logf.try_clone().unwrap(), build_command, Some(output_directory), build_suite, subpath, source_date_epoch, apt_repository, apt_repository_key, extra_repositories, ) { Ok(()) => (), Err(e) => { logf.sync_all().unwrap(); logf.seek(std::io::SeekFrom::Start(0)).unwrap(); let sbuildlog = SbuildLog::try_from(std::fs::File::open(&build_log_path).unwrap()).unwrap(); let sbuild_failure = worker_failure_from_sbuild_log(&sbuildlog); // Preserve the diff for later inspection if let Some(error) = sbuild_failure .error .as_ref() .and_then(|e| e.as_any().downcast_ref::()) { if let Some(diff_file) = error.diff_file.as_ref() { let diff_file_name = output_directory.join(Path::new(&diff_file).file_name().unwrap()); std::fs::copy(diff_file, &diff_file_name).unwrap(); } } let retcode = e.0; if let Some(error) = sbuild_failure.error { return Err(BuildOnceError::Detailed { stage: sbuild_failure.stage, phase: sbuild_failure.phase, retcode, command: shlex::split(build_command).unwrap(), error, description: sbuild_failure.description.unwrap_or_default(), }); } else { return Err(BuildOnceError::Unidentified { stage: sbuild_failure.stage, phase: sbuild_failure.phase, retcode, command: shlex::split(build_command).unwrap(), description: sbuild_failure .description .unwrap_or_else(|| format!("Build failed with exit code {}", retcode)), }); } } } let (package, version) = get_last_changelog_entry(local_tree, subpath); let mut changes_names = vec![]; for (_kind, entry) in find_changes_files(output_directory, &package, &version) { changes_names.push(entry.path()); } Ok(BuildOnceResult { source_package: package, version, changes_names, }) } fn control_files_in_root(tree: &dyn MutableTree, subpath: &std::path::Path) -> bool { let debian_path = subpath.join("debian"); if tree.has_filename(&debian_path) { return false; } let control_path = subpath.join("control"); if tree.has_filename(&control_path) { return true; } tree.has_filename(std::path::Path::new(&format!( "{}.in", control_path.to_string_lossy() ))) } fn get_last_changelog_entry( local_tree: &WorkingTree, subpath: &std::path::Path, ) -> (String, debversion::Version) { let path = if control_files_in_root(local_tree, subpath) { subpath.join("changelog") } else { subpath.join("debian/changelog") }; let f = local_tree.get_file(&path).unwrap(); let cl = ChangeLog::read_relaxed(f).unwrap(); let e = cl.iter().next().unwrap(); (e.package().unwrap(), e.version().unwrap()) } pub fn gbp_dch(path: &std::path::Path) -> Result<(), std::io::Error> { let cmd = std::process::Command::new("gbp-dch") .arg("--ignore-branch") .current_dir(path) .output()?; if !cmd.status.success() { return Err(std::io::Error::new( std::io::ErrorKind::Other, "gbp-dch failed", )); } Ok(()) } pub fn find_changes_files( path: &std::path::Path, package: &str, version: &debversion::Version, ) -> impl Iterator { let mut non_epoch_version = version.upstream_version.to_string(); if let Some(debian_version) = version.debian_revision.as_ref() { non_epoch_version.push_str(&format!("-{}", debian_version)); } let regex = format!( "{}_{}_(.*)", regex::escape(package), regex::escape(&non_epoch_version) ); let c = regex::Regex::new(®ex).unwrap(); std::fs::read_dir(path).unwrap().filter_map(move |entry| { let entry = entry.unwrap(); c.captures(entry.file_name().to_str().unwrap()) .map(|m| (m.get(1).unwrap().as_str().to_owned(), entry)) }) } /// Attempt a build, with a custom distribution set. /// /// # Arguments /// * `local_tree` - The tree to build in. /// * `suffix` - Suffix to add to version string. /// * `build_suite` - Name of suite (i.e. distribution) to build for. /// * `output_directory` - Directory to write output to. /// * `build_command` - Build command to build package. /// * `build_changelog_entry` - Changelog entry to use. /// * `subpath` - Sub path in tree where package lives. /// * `source_date_epoch` - Source date epoch to set. /// * `run_gbp_dch` - Whether to run gbp-dch. /// * `apt_repository` - APT repository to use. /// * `apt_repository_key` - APT repository key to use. /// * `extra_repositories` - Extra repositories to use. pub fn attempt_build( local_tree: &WorkingTree, suffix: Option<&str>, build_suite: Option<&str>, output_directory: &std::path::Path, build_command: &str, build_changelog_entry: Option<&str>, subpath: &std::path::Path, source_date_epoch: Option>, run_gbp_dch: bool, apt_repository: Option<&str>, apt_repository_key: Option<&str>, extra_repositories: Option<&Vec<&str>>, ) -> Result { if run_gbp_dch && subpath == std::path::Path::new("") && pyo3::Python::with_gil(|py| { use pyo3::ToPyObject; local_tree .controldir() .to_object(py) .getattr(py, "_git") .is_ok() }) { gbp_dch(&local_tree.abspath(subpath).unwrap()).unwrap(); } if let Some(build_changelog_entry) = build_changelog_entry { assert!( suffix.is_some(), "build_changelog_entry specified, but suffix is None" ); assert!( build_suite.is_some(), "build_changelog_entry specified, but build_suite is None" ); add_dummy_changelog_entry( local_tree, subpath, suffix.unwrap(), build_suite.unwrap(), build_changelog_entry, None, None, ); } build_once( local_tree, build_suite, output_directory, build_command, subpath, source_date_epoch, apt_repository, apt_repository_key, extra_repositories, ) } pub fn version_add_suffix(version: &Version, suffix: &str) -> Version { fn add_suffix(v: &str, suffix: &str) -> String { if let Some(m) = regex::Regex::new(&format!("(.*)({})([0-9]+)", regex::escape(suffix))) .unwrap() .captures(v) { let main = m.get(1).unwrap().as_str(); let suffix = m.get(2).unwrap().as_str(); let revision = m.get(3).unwrap().as_str(); format!("{}{}{}", main, suffix, revision.parse::().unwrap() + 1) } else { format!("{}{}1", v, suffix) } } let mut version = version.clone(); if let Some(r) = version.debian_revision { version.debian_revision = Some(add_suffix(&r, suffix)); } else { version.upstream_version = add_suffix(&version.upstream_version, suffix); } version } /// Add a dummy changelog entry to a package. /// /// # Arguments /// * `tree` - The tree to add the entry to. /// * `subpath` - Sub path in tree where package lives. /// * `suffix` - Suffix to add to version string. /// * `suite` - Name of suite (i.e. distribution) to build for. /// * `message` - Changelog message to use. /// * `timestamp` - Timestamp to use. /// * `maintainer` - Maintainer to use. /// * `allow_reformatting` - Whether to allow reformatting. /// /// # Returns /// The version of the newly added entry. pub fn add_dummy_changelog_entry( tree: &dyn MutableTree, subpath: &Path, suffix: &str, suite: &str, message: &str, timestamp: Option>, maintainer: Option<(String, String)>, ) -> Version { let path = if control_files_in_root(tree, subpath) { subpath.join("changelog") } else { subpath.join("debian/changelog") }; let mut cl = ChangeLog::read_relaxed(tree.get_file(&path).unwrap()).unwrap(); let prev_entry = cl.iter().next().unwrap(); let prev_version = prev_entry.version().unwrap(); let version = version_add_suffix(&prev_version, suffix); log::debug!("Adding dummy changelog entry {} for build", &version); let mut entry = cl.auto_add_change( &[&format!("* {}", message)], maintainer.unwrap_or_else(|| debian_changelog::get_maintainer().unwrap()), timestamp.map(|t| t.into()), Some(Urgency::Low), ); entry.set_version(&version); entry.set_distributions(vec![suite.to_string()]); tree.put_file_bytes_non_atomic(&path, cl.to_string().as_bytes()) .unwrap(); entry.version().unwrap() } #[cfg(test)] mod tests { use super::*; use breezyshim::tree::MutableTree; #[test] fn test_get_build_architecture() { let arch = get_build_architecture(); assert!(!arch.is_empty() && arch.len() < 10); } #[test] fn test_builddeb_command() { let command = builddeb_command( Some("sbuild --no-clean-source"), Some(std::path::Path::new("/tmp")), Some("ppa:my-ppa/ppa"), Some("my-ppa-key"), Some(&vec!["deb http://example.com/debian buster main"]), ); assert_eq!(command, vec![ python_command(), "-m".to_string(), "breezy".to_string(), "builddeb".to_string(), "--guess-upstream-branch-url".to_string(), "--builder=sbuild --no-clean-source --extra-repository='deb http://example.com/debian buster main'".to_string(), "--apt-repository=ppa:my-ppa/ppa".to_string(), "--apt-repository-key=my-ppa-key".to_string(), "--result-dir=/tmp".to_string(), ]); } #[test] fn test_python_command() { let _ = python_command(); } #[test] fn test_control_files_not_in_root() { let td = tempfile::tempdir().unwrap(); let tree = breezyshim::controldir::create_standalone_workingtree( td.path(), &breezyshim::controldir::ControlDirFormat::default(), ) .unwrap(); let subpath = std::path::Path::new(""); tree.mkdir(&subpath.join("debian")).unwrap(); tree.put_file_bytes_non_atomic(&subpath.join("debian/control"), b"") .unwrap(); assert!(!control_files_in_root(&tree, subpath)); } #[test] fn test_control_files_in_root() { let td = tempfile::tempdir().unwrap(); let tree = breezyshim::controldir::create_standalone_workingtree( td.path(), &breezyshim::controldir::ControlDirFormat::default(), ) .unwrap(); let subpath = std::path::Path::new(""); tree.put_file_bytes_non_atomic(&subpath.join("control"), b"") .unwrap(); assert!(control_files_in_root(&tree, subpath)); } mod test_version_add_suffix { use super::*; #[test] fn test_native() { assert_eq!( "1.0~jan+lint4".parse::().unwrap(), version_add_suffix(&"1.0~jan+lint3".parse().unwrap(), "~jan+lint"), ); assert_eq!( "1.0~jan+lint1".parse::().unwrap(), version_add_suffix(&"1.0".parse().unwrap(), "~jan+lint"), ); } #[test] fn test_normal() { assert_eq!( "1.0-1~jan+lint4".parse::().unwrap(), version_add_suffix(&"1.0-1~jan+lint3".parse().unwrap(), "~jan+lint"), ); assert_eq!( "1.0-1~jan+lint1".parse::().unwrap(), version_add_suffix(&"1.0-1".parse().unwrap(), "~jan+lint"), ); assert_eq!( "0.0.12-1~jan+lint1".parse::().unwrap(), version_add_suffix(&"0.0.12-1".parse().unwrap(), "~jan+lint"), ); assert_eq!( "0.0.12-1~jan+unchanged1~jan+lint1" .parse::() .unwrap(), version_add_suffix(&"0.0.12-1~jan+unchanged1".parse().unwrap(), "~jan+lint"), ); } } mod test_add_dummy_changelog { use super::*; use std::path::Path; #[test] fn test_simple() { let td = tempfile::tempdir().unwrap(); let tree = breezyshim::controldir::create_standalone_workingtree( td.path(), &breezyshim::controldir::ControlDirFormat::default(), ) .unwrap(); std::fs::create_dir(td.path().join("debian")).unwrap(); std::fs::write( td.path().join("debian/changelog"), r#"janitor (0.1-1) UNRELEASED; urgency=medium * Initial release. (Closes: #XXXXXX) -- Jelmer Vernooij Sat, 05 Sep 2020 12:35:04 -0000 "#, ) .unwrap(); tree.add(&[Path::new("debian"), Path::new("debian/changelog")]) .unwrap(); add_dummy_changelog_entry( &tree, Path::new(""), "jan+some", "some-fixes", "Dummy build.", Some( chrono::DateTime::parse_from_rfc3339("2020-09-05T12:35:04Z") .unwrap() .to_utc(), ), Some(("Jelmer Vernooij".to_owned(), "jelmer@debian.org".to_owned())), ); let contents = std::fs::read_to_string(td.path().join("debian/changelog")).unwrap(); assert_eq!( r#"janitor (0.1-1jan+some1) some-fixes; urgency=medium * Initial release. (Closes: #XXXXXX) * Dummy build. -- Jelmer Vernooij Sat, 05 Sep 2020 12:35:04 -0000 "#, contents ); } #[test] fn test_native() { let td = tempfile::tempdir().unwrap(); let tree = breezyshim::controldir::create_standalone_workingtree( td.path(), &breezyshim::controldir::ControlDirFormat::default(), ) .unwrap(); std::fs::create_dir(td.path().join("debian")).unwrap(); std::fs::write( td.path().join("debian/changelog"), r#"janitor (0.1) UNRELEASED; urgency=medium * Initial release. (Closes: #XXXXXX) -- Jelmer Vernooij Sat, 05 Sep 2020 12:35:04 -0000 "#, ) .unwrap(); tree.add(&[Path::new("debian"), Path::new("debian/changelog")]) .unwrap(); add_dummy_changelog_entry( &tree, Path::new(""), "jan+some", "some-fixes", "Dummy build.", Some( chrono::DateTime::parse_from_rfc3339("2020-09-05T12:35:04Z") .unwrap() .to_utc(), ), Some(("Jelmer Vernooij".to_owned(), "jelmer@debian.org".to_owned())), ); let contents = std::fs::read_to_string(td.path().join("debian/changelog")).unwrap(); assert_eq!( r#"janitor (0.1jan+some1) some-fixes; urgency=medium * Initial release. (Closes: #XXXXXX) * Dummy build. -- Jelmer Vernooij Sat, 05 Sep 2020 12:35:04 -0000 "#, contents ); } #[test] fn test_exists() { let td = tempfile::tempdir().unwrap(); let tree = breezyshim::controldir::create_standalone_workingtree( td.path(), &breezyshim::controldir::ControlDirFormat::default(), ) .unwrap(); std::fs::create_dir(td.path().join("debian")).unwrap(); std::fs::write( td.path().join("debian/changelog"), r#"janitor (0.1-1jan+some1) UNRELEASED; urgency=medium * Initial release. (Closes: #XXXXXX) -- Jelmer Vernooij Sat, 05 Sep 2020 12:35:04 -0000 "#, ) .unwrap(); tree.add(&[Path::new("debian"), Path::new("debian/changelog")]) .unwrap(); add_dummy_changelog_entry( &tree, Path::new(""), "jan+some", "some-fixes", "Dummy build.", Some( chrono::DateTime::parse_from_rfc3339("2020-09-05T12:35:04Z") .unwrap() .to_utc(), ), Some(("Jelmer Vernooij".to_owned(), "jelmer@debian.org".to_owned())), ); let contents = std::fs::read_to_string(td.path().join("debian/changelog")).unwrap(); assert_eq!( r#"janitor (0.1-1jan+some2) some-fixes; urgency=medium * Initial release. (Closes: #XXXXXX) * Dummy build. -- Jelmer Vernooij Sat, 05 Sep 2020 12:35:04 -0000 "#, contents ); } } } ognibuild-0.0.32/src/debian/build_deps.rs000064400000000000000000000042461046102023000163610ustar 00000000000000use crate::dependencies::debian::DebianDependency; use crate::dependencies::debian::TieBreaker; use crate::session::Session; use breezyshim::debian::apt::{Apt, LocalApt}; use std::cell::RefCell; use std::collections::HashMap; pub struct BuildDependencyTieBreaker { apt: LocalApt, counts: RefCell>>, } impl BuildDependencyTieBreaker { pub fn from_session(session: &dyn Session) -> Self { Self { apt: LocalApt::new(Some(&session.location())).unwrap(), counts: RefCell::new(None), } } fn count(&self) -> HashMap { let mut counts = HashMap::new(); for source in self.apt.iter_sources() { source .build_depends() .into_iter() .chain(source.build_depends_indep().into_iter()) .chain(source.build_depends_arch().into_iter()) .for_each(|r| { for e in r.entries() { e.relations().for_each(|r| { let count = counts.entry(r.name().clone()).or_insert(0); *count += 1; }); } }); } counts } } impl TieBreaker for BuildDependencyTieBreaker { fn break_tie<'a>(&self, reqs: &[&'a DebianDependency]) -> Option<&'a DebianDependency> { if self.counts.borrow().is_none() { let counts = self.count(); self.counts.replace(Some(counts)); } let c = self.counts.borrow(); let count = c.clone().unwrap(); let mut by_count = HashMap::new(); for req in reqs { let name = req.package_names().into_iter().next().unwrap(); by_count.insert(req, count[&name]); } if by_count.is_empty() { return None; } let top = by_count.iter().max_by_key(|k| k.1).unwrap(); log::info!( "Breaking tie between [{:?}] to {:?} based on build-depends count", reqs.iter().map(|r| r.relation_string()).collect::>(), top.0.relation_string(), ); Some(*top.0) } } ognibuild-0.0.32/src/debian/context.rs000064400000000000000000000267101046102023000157330ustar 00000000000000use crate::dependencies::debian::DebianDependency; use breezyshim::commit::CommitReporter; use breezyshim::debian::debcommit::debcommit; use breezyshim::error::Error as BrzError; use breezyshim::tree::{MutableTree, Tree}; use breezyshim::workingtree::WorkingTree; pub use buildlog_consultant::sbuild::Phase; use debian_analyzer::abstract_control::AbstractControlEditor; use debian_analyzer::editor::{Editor, EditorError, Marshallable, MutableTreeEdit, TreeEditor}; use std::path::{Path, PathBuf}; #[derive(Debug)] pub enum Error { CircularDependency(String), /// No source stanza MissingSource, BrzError(BrzError), EditorError(debian_analyzer::editor::EditorError), IoError(std::io::Error), InvalidField(String, String), } impl From for Error { fn from(e: BrzError) -> Self { Error::BrzError(e) } } impl From for Error { fn from(e: debian_analyzer::editor::EditorError) -> Self { Error::EditorError(e) } } impl From for Error { fn from(e: std::io::Error) -> Self { Error::IoError(e) } } impl From for crate::fix_build::InterimError { fn from(e: Error) -> crate::fix_build::InterimError { crate::fix_build::InterimError::Other(e) } } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Error::CircularDependency(pkg) => write!(f, "Circular dependency on {}", pkg), Error::MissingSource => write!(f, "No source stanza"), Error::BrzError(e) => write!(f, "{}", e), Error::EditorError(e) => write!(f, "{}", e), Error::IoError(e) => write!(f, "{}", e), Error::InvalidField(field, e) => write!(f, "Invalid field {}: {}", field, e), } } } impl std::error::Error for Error {} pub struct DebianPackagingContext { pub tree: WorkingTree, pub subpath: PathBuf, pub committer: (String, String), pub update_changelog: bool, pub commit_reporter: Option>, } impl DebianPackagingContext { pub fn new( tree: WorkingTree, subpath: &Path, committer: Option<(String, String)>, update_changelog: bool, commit_reporter: Option>, ) -> Self { Self { tree, subpath: subpath.to_path_buf(), committer: committer.unwrap_or_else(|| debian_changelog::get_maintainer().unwrap()), update_changelog, commit_reporter, } } pub fn has_filename(&self, path: &Path) -> bool { self.tree.has_filename(&self.subpath.join(path)) } pub fn abspath(&self, path: &Path) -> PathBuf { self.tree.abspath(&self.subpath.join(path)).unwrap() } pub fn edit_file( &self, path: &std::path::Path, allow_generated: bool, ) -> Result, EditorError> { let path = self.subpath.join(path); self.tree.edit_file(&path, allow_generated, true) } pub fn commit(&self, summary: &str, update_changelog: Option) -> Result { let update_changelog = update_changelog.unwrap_or(self.update_changelog); let committer = format!("{} <{}>", self.committer.0, self.committer.1); let lock_write = self.tree.lock_write(); let r = if update_changelog { let mut cl = self .edit_file::(Path::new("debian/changelog"), false)?; cl.auto_add_change(&[summary], self.committer.clone(), None, None); cl.commit()?; debcommit( &self.tree, Some(&committer), &self.subpath, None, self.commit_reporter.as_deref(), None, ) } else { let mut builder = self .tree .build_commit() .message(summary) .committer(&committer) .specific_files(&[&self.subpath]); if let Some(commit_reporter) = self.commit_reporter.as_ref() { builder = builder.reporter(commit_reporter.as_ref()); } builder.commit() }; std::mem::drop(lock_write); match r { Ok(_) => Ok(true), Err(BrzError::PointlessCommit) => Ok(false), Err(e) => Err(e.into()), } } pub fn add_dependency( &self, phase: &Phase, requirement: &DebianDependency, ) -> Result { match phase { Phase::AutoPkgTest(n) => self.add_test_dependency(n, requirement), Phase::Build => self.add_build_dependency(requirement), Phase::BuildEnv => { // TODO(jelmer): Actually, we probably just want to install it on the host system? log::warn!("Unknown phase {:?}", phase); Ok(false) } Phase::CreateSession => { log::warn!("Unknown phase {:?}", phase); Ok(false) } } } pub fn edit_control<'a>(&'a self) -> Result, Error> { if self .tree .has_filename(&self.subpath.join("debian/debcargo.toml")) { Ok(Box::new( debian_analyzer::debcargo::DebcargoEditor::from_directory( &self.tree.abspath(&self.subpath).unwrap(), )?, )) } else { let control_path = Path::new("debian/control"); Ok( Box::new(self.edit_file::(control_path, false)?) as Box, ) } } fn add_build_dependency(&self, requirement: &DebianDependency) -> Result { assert!(!requirement.is_empty()); let mut control = self.edit_control()?; for binary in control.binaries() { if requirement.touches_package(&binary.name().unwrap()) { return Err(Error::CircularDependency(binary.name().unwrap())); } } let mut source = if let Some(source) = control.source() { source } else { return Err(Error::MissingSource); }; for rel in requirement.iter() { source.ensure_build_dep(rel); } std::mem::drop(source); let desc = requirement.relation_string(); if !control.commit() { log::info!("Giving up; build dependency {} was already present.", desc); return Ok(false); } log::info!("Adding build dependency: {}", desc); self.commit(&format!("Add missing build dependency on {}.", desc), None)?; Ok(true) } pub fn edit_tests_control(&self) -> Result, Error> { Ok(self.edit_file::(Path::new("debian/tests/control"), false)?) } pub fn edit_rules(&self) -> Result, Error> { Ok(self.edit_file::(Path::new("debian/rules"), false)?) } fn add_test_dependency( &self, testname: &str, requirement: &DebianDependency, ) -> Result { // TODO(jelmer): If requirement is for one of our binary packages but "@" is already // present then don't do anything. let editor = self.edit_tests_control()?; let mut command_counter = 1; for mut para in editor.paragraphs() { let name = para.get("Tests").unwrap_or_else(|| { let name = format!("command{}", command_counter); command_counter += 1; name }); if name != testname { continue; } for rel in requirement.iter() { let depends = para.get("Depends").unwrap_or_default(); let mut rels: debian_control::lossless::relations::Relations = depends.parse().map_err(|e| { Error::InvalidField(format!("Test Depends for {}", testname), e) })?; debian_analyzer::relations::ensure_relation(&mut rels, rel); para.insert("Depends", &rels.to_string()); } } let desc = requirement.relation_string(); if editor.commit()?.is_empty() { log::info!( "Giving up; dependency {} for test {} was already present.", desc, testname, ); return Ok(false); } log::info!("Adding dependency to test {}: {}", testname, desc); self.commit( &format!("Add missing dependency for test {} on {}.", testname, desc), None, )?; Ok(true) } } #[cfg(test)] mod tests { use super::*; use breezyshim::controldir::{create_standalone_workingtree, ControlDirFormat}; pub const COMMITTER: &str = "ognibuild "; fn setup(path: &Path) -> DebianPackagingContext { let tree = create_standalone_workingtree(path, &ControlDirFormat::default()).unwrap(); std::fs::create_dir_all(path.join("debian")).unwrap(); std::fs::write( path.join("debian/control"), r###"Source: blah Build-Depends: libc6 Package: python-blah Depends: ${python3:Depends} Description: A python package Foo "###, ) .unwrap(); std::fs::write( path.join("debian/changelog"), r###"blah (0.1) UNRELEASED; urgency=medium * Initial release. (Closes: #XXXXXX) -- Jelmer Vernooij Sat, 04 Apr 2020 14:12:13 +0000 "###, ) .unwrap(); tree.add(&[ Path::new("debian"), Path::new("debian/control"), Path::new("debian/changelog"), ]) .unwrap(); tree.build_commit() .message("Initial commit") .committer(COMMITTER) .commit() .unwrap(); DebianPackagingContext::new( tree, Path::new(""), Some(("ognibuild".to_owned(), "".to_owned())), false, Some(Box::new(breezyshim::commit::NullCommitReporter::new())), ) } #[test] fn test_already_present() { let td = tempfile::tempdir().unwrap(); let context = setup(td.path()); let dep = DebianDependency::simple("libc6"); assert!(!context.add_build_dependency(&dep).unwrap()); } #[test] fn test_basic() { let td = tempfile::tempdir().unwrap(); let context = setup(td.path()); let dep = DebianDependency::simple("foo"); assert!(context.add_build_dependency(&dep).unwrap()); let control = std::fs::read_to_string(td.path().join("debian/control")).unwrap(); assert_eq!( control, r###"Source: blah Build-Depends: libc6, foo Package: python-blah Depends: ${python3:Depends} Description: A python package Foo "### ); } #[test] fn test_circular() { let td = tempfile::tempdir().unwrap(); let context = setup(td.path()); let dep = DebianDependency::simple("python-blah"); assert!(matches!( context.add_build_dependency(&dep), Err(Error::CircularDependency(_)) )); } } ognibuild-0.0.32/src/debian/dep_server.rs000064400000000000000000000107421046102023000164030ustar 00000000000000use crate::debian::apt::AptManager; use crate::dependencies::debian::DebianDependency; use crate::dependency::Dependency; use crate::installer::{Error, Explanation, InstallationScope, Installer}; use crate::session::Session; use reqwest::StatusCode; use tokio::runtime::Runtime; use url::Url; /// Resolve a requirement to an APT requirement with a dep server. /// /// # Arguments /// * `url` - Dep server URL /// * `req` - Dependency to resolve /// /// # Returns /// List of APT requirements. async fn resolve_apt_requirement_dep_server( url: &url::Url, dep: &dyn Dependency, ) -> Result, Error> { let client = reqwest::Client::new(); let response = client .post(url.join("resolve-apt").unwrap()) .json(&serde_json::json!( { "requirement": { // TODO: Use the actual dependency } })) .send() .await .unwrap(); match response.status() { StatusCode::NOT_FOUND => { if response .headers() .get("Reason") .map(|x| x.to_str().unwrap()) == Some("family-unknown") { return Err(Error::UnknownDependencyFamily); } Ok(None) } StatusCode::OK => { let body = response.json::().await.unwrap(); Ok(Some(body)) } _ => { panic!("Unexpected response status: {}", response.status()); } } } pub struct DepServerAptInstaller<'a> { apt: AptManager<'a>, dep_server_url: Url, } impl<'a> DepServerAptInstaller<'a> { pub fn new(apt: AptManager<'a>, dep_server_url: &Url) -> Self { Self { apt, dep_server_url: dep_server_url.clone(), } } pub fn from_session(session: &'a dyn Session, dep_server_url: &'_ Url) -> Self { let apt = AptManager::from_session(session); Self::new(apt, dep_server_url) } pub fn resolve(&self, req: &dyn Dependency) -> Result, Error> { let rt = Runtime::new().unwrap(); match rt.block_on(resolve_apt_requirement_dep_server( &self.dep_server_url, req, )) { Ok(deps) => Ok(deps), Err(o) => { log::warn!("Falling back to resolving error locally"); Err(Error::Other(o.to_string())) } } } } impl<'a> Installer for DepServerAptInstaller<'a> { fn install( &self, dep: &dyn Dependency, scope: crate::installer::InstallationScope, ) -> Result<(), Error> { match scope { InstallationScope::User => { return Err(Error::UnsupportedScope(scope)); } InstallationScope::Global => {} InstallationScope::Vendor => { return Err(Error::UnsupportedScope(scope)); } } let dep = self.resolve(dep)?; if let Some(dep) = dep { match self .apt .satisfy(vec![crate::debian::apt::SatisfyEntry::Required( dep.relation_string(), )]) { Ok(_) => {} Err(e) => { return Err(Error::Other(e.to_string())); } } Ok(()) } else { Err(Error::UnknownDependencyFamily) } } fn explain( &self, dep: &dyn Dependency, scope: crate::installer::InstallationScope, ) -> Result { match scope { InstallationScope::User => { return Err(Error::UnsupportedScope(scope)); } InstallationScope::Global => {} InstallationScope::Vendor => { return Err(Error::UnsupportedScope(scope)); } } let dep = self.resolve(dep)?; let dep = dep.ok_or_else(|| Error::UnknownDependencyFamily)?; let apt_deb_str = dep.relation_string(); let cmd = self.apt.satisfy_command(vec![apt_deb_str.as_str()]); Ok(Explanation { message: format!( "Install {}", dep.package_names() .iter() .map(|x| x.as_str()) .collect::>() .join(", ") ), command: Some(cmd.iter().map(|s| s.to_string()).collect()), }) } } ognibuild-0.0.32/src/debian/file_search.rs000064400000000000000000000575421046102023000165220ustar 00000000000000use crate::debian::sources_list::{SourcesEntry, SourcesList}; use crate::session::{Error as SessionError, Session}; use debian_control::apt::Release; use flate2::read::GzDecoder; use lzma_rs::lzma_decompress; use std::collections::{HashMap, HashSet}; use std::fs::File; use std::io::{BufRead, BufReader, Read}; use std::path::{Path, PathBuf}; use url::Url; #[derive(Debug)] pub enum Error { AptFileAccessError(String), FileNotFoundError(String), IoError(std::io::Error), } impl From for Error { fn from(e: std::io::Error) -> Error { Error::IoError(e) } } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Error::AptFileAccessError(e) => write!(f, "AptFileAccessError: {}", e), Error::FileNotFoundError(e) => write!(f, "FileNotFoundError: {}", e), Error::IoError(e) => write!(f, "IoError: {}", e), } } } impl std::error::Error for Error {} pub trait FileSearcher<'b> { fn search_files<'a>( &'a self, path: &'a Path, case_insensitive: bool, ) -> Box + 'a>; fn search_files_regex<'a>( &'a self, path: &'a str, case_insensitive: bool, ) -> Box + 'a>; } pub fn read_contents_file(f: R) -> impl Iterator { BufReader::new(f).lines().map(|line| { let line = line.unwrap(); let (path, rest) = line.rsplit_once(' ').unwrap(); (path.to_string(), rest.to_string()) }) } pub fn contents_urls_from_sources_entry<'a>( entry: &'a SourcesEntry, arches: Vec<&'a str>, load_url: impl Fn(&url::Url) -> Result, Error>, ) -> Box + 'a> { match entry { SourcesEntry::Deb { uri, dist, comps } => { let base_url = uri.trim_end_matches('/'); let name = dist.trim_end_matches('/'); let dists_url: url::Url = if comps.is_empty() { base_url.to_string() } else { format!("{}/dists", base_url) } .parse() .unwrap(); let inrelease_url: Url = dists_url.join(&format!("{}/InRelease", name)).unwrap(); let mut response = match load_url(&inrelease_url) { Ok(response) => response, Err(_) => { let release_url = dists_url.join(&format!("{}/Release", name)).unwrap(); match load_url(&release_url) { Ok(response) => response, Err(e) => { log::warn!( "Unable to download {} or {}: {}", inrelease_url, release_url, e ); return Box::new(vec![].into_iter()); } } } }; let mut release = String::new(); response.read_to_string(&mut release).unwrap(); let mut existing_names = HashMap::new(); let release: Release = release.parse().unwrap(); for name in release .checksums_md5() .into_iter() .map(|x| x.filename) .chain(release.checksums_sha256().into_iter().map(|x| x.filename)) .chain(release.checksums_sha1().into_iter().map(|x| x.filename)) .chain(release.checksums_sha512().into_iter().map(|x| x.filename)) { existing_names.insert( std::path::PathBuf::from(name.clone()) .file_stem() .unwrap() .to_owned(), name, ); } let mut contents_files = HashSet::new(); if comps.is_empty() { for arch in arches { contents_files.insert(format!("Contents-{}", arch)); } } else { for comp in comps { for arch in &arches { contents_files.insert(format!("{}/Contents-{}", comp, arch)); } } } return Box::new(contents_files.into_iter().filter_map(move |f| { if let Some(name) = existing_names.get(&std::path::Path::new(&f).file_stem().unwrap().to_owned()) { return Some(dists_url.join(name).unwrap().join(&f).unwrap()); } None })); } SourcesEntry::DebSrc { .. } => Box::new(vec![].into_iter()), } } pub fn contents_urls_from_sourceslist<'a>( sl: &'a SourcesList, arch: &'a str, load_url: impl Fn(&'_ url::Url) -> Result, Error> + 'a + Copy, ) -> impl Iterator + 'a { // TODO(jelmer): Verify signatures, etc. let arches = vec![arch, "all"]; sl.iter() .flat_map(move |source| contents_urls_from_sources_entry(source, arches.clone(), load_url)) } pub fn unwrap<'a, R: Read + 'a>(f: R, ext: &str) -> Box { match ext { ".gz" => Box::new(GzDecoder::new(f)), ".xz" => { let mut compressed_reader = BufReader::new(f); let mut decompressed_data = Vec::new(); lzma_decompress(&mut compressed_reader, &mut decompressed_data).unwrap(); Box::new(std::io::Cursor::new(decompressed_data.into_iter())) } ".lz4" => Box::new(lz4_flex::frame::FrameDecoder::new(f)), _ => Box::new(f), } } pub fn load_direct_url(url: &url::Url) -> Result, Error> { for ext in [".xz", ".gz", ""] { let response = match reqwest::blocking::get(url.to_string() + ext) { Ok(response) => response, Err(e) => { if e.status() == Some(reqwest::StatusCode::NOT_FOUND) { continue; } return Err(Error::AptFileAccessError(format!( "Unable to access apt URL {}{}: {}", url, ext, e ))); } }; return Ok(unwrap(response, ext)); } Err(Error::FileNotFoundError(format!("{} not found", url))) } pub fn load_url_with_cache(url: &url::Url, cache_dirs: &[&Path]) -> Result, Error> { for cache_dir in cache_dirs { match load_apt_cache_file(url, cache_dir) { Ok(f) => return Ok(Box::new(f)), Err(e) => { if e.kind() != std::io::ErrorKind::NotFound { return Err(e.into()); } } } } load_direct_url(url) } /// Convert a URI into a safe filename. It quotes all unsafe characters and converts / to _ and removes the scheme identifier. pub fn uri_to_filename(url: &url::Url) -> String { let mut url = url.clone(); url.set_username("").unwrap(); url.set_password(None).unwrap(); use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; // Define the set of characters that need to be percent-encoded const BAD_CHARS: &AsciiSet = &CONTROLS .add(b' ') // Add space .add(b'\"') // Add " .add(b'\\') // Add \ .add(b'{') .add(b'}') .add(b'[') .add(b']') .add(b'<') .add(b'>') .add(b'^') .add(b'~') .add(b'_') .add(b'=') .add(b'!') .add(b'@') .add(b'#') .add(b'$') .add(b'%') .add(b'^') .add(b'&') .add(b'*'); let mut u = url.to_string(); if let Some(pos) = u.find("://") { u = u[(pos + 3)..].to_string(); // Remove the scheme } // Percent-encode the bad characters let encoded_uri = utf8_percent_encode(&u, BAD_CHARS).to_string(); // Replace '/' with '_' encoded_uri.replace('/', "_") } pub fn load_apt_cache_file( url: &url::Url, cache_dir: &Path, ) -> Result, std::io::Error> { let f = uri_to_filename(url); for ext in [".xz", ".gz", ".lz4", ""] { let p = cache_dir.join([&f, ext].concat()); if !p.exists() { continue; } log::debug!("Loading cached contents file {}", p.display()); // return os.popen('/usr/lib/apt/apt-helper cat-file %s' % p) let f = File::open(p)?; return Ok(unwrap(f, ext)); } Err(std::io::Error::new( std::io::ErrorKind::NotFound, format!("{} not found", url), )) } lazy_static::lazy_static! { pub static ref CACHE_IS_EMPTY_PATH: &'static Path = Path::new("/usr/share/apt-file/is-cache-empty"); } pub struct AptFileFileSearcher<'a> { session: &'a dyn Session, } impl<'a> AptFileFileSearcher<'a> { pub fn has_cache(session: &dyn Session) -> Result { if !session.exists(&CACHE_IS_EMPTY_PATH) { return Ok(false); } match session .command(vec![&CACHE_IS_EMPTY_PATH.to_str().unwrap()]) .check_call() { Ok(_) => Ok(true), Err(SessionError::CalledProcessError(status)) => { if status.code() == Some(1) { Ok(true) } else { Ok(false) } } Err(e) => Err(e), } } pub fn from_session(session: &dyn Session) -> AptFileFileSearcher { log::debug!("Using apt-file to search apt contents"); if !session.exists(&CACHE_IS_EMPTY_PATH) { crate::debian::apt::AptManager::from_session(session) .satisfy(vec![crate::debian::apt::SatisfyEntry::Required( "apt-file".to_string(), )]) .unwrap(); } if !Self::has_cache(session).unwrap() { session .command(vec!["apt-file", "update"]) .user("root") .check_call() .unwrap(); } AptFileFileSearcher { session } } fn search_files_ex( &self, path: &str, regex: bool, case_insensitive: bool, ) -> Result, Error> { let mut args = vec!["apt-file", "search", "--stream-results"]; if regex { args.push("-x"); } else { args.push("-F"); } if case_insensitive { args.push("-i"); } args.push(path); let output = self .session .command(args) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .output() .map_err(|e| { Error::AptFileAccessError(format!( "Unable to search for files matching {}: {}", path, e )) })?; match output.status.code() { Some(0) | Some(1) => { // At least one search result let output_str = std::str::from_utf8(&output.stdout).unwrap(); let entries = output_str .split('\n') .filter_map(|line| { if line.is_empty() { return None; } let (pkg, _path) = line.split_once(": ").unwrap(); Some(pkg.to_string()) }) .collect::>(); log::debug!("Found entries {:?} for {}", entries, path); Ok(entries.into_iter()) } Some(2) => { // Error Err(Error::AptFileAccessError(format!( "Error searching for files matching {}: {}", path, std::str::from_utf8(&output.stderr).unwrap() ))) } Some(3) => Err(Error::AptFileAccessError( "apt-file cache is empty".to_owned(), )), Some(4) => Err(Error::AptFileAccessError( "apt-file has no entries matching restrictions".to_owned(), )), _ => Err(Error::AptFileAccessError( "apt-file returned an unknown error".to_owned(), )), } } } impl<'b> FileSearcher<'b> for AptFileFileSearcher<'b> { fn search_files<'a>( &'a self, path: &'a Path, case_insensitive: bool, ) -> Box + 'a> { return Box::new( self.search_files_ex(path.to_str().unwrap(), false, case_insensitive) .unwrap(), ); } fn search_files_regex<'a>( &'a self, path: &'a str, case_insensitive: bool, ) -> Box + 'a> { Box::new(self.search_files_ex(path, true, case_insensitive).unwrap()) } } pub fn get_apt_contents_file_searcher<'a>( session: &'a dyn Session, ) -> Result + 'a>, Error> { if AptFileFileSearcher::has_cache(session).unwrap() { Ok(Box::new(AptFileFileSearcher::from_session(session)) as Box>) } else { Ok(Box::new(RemoteContentsFileSearcher::from_session(session)?) as Box>) } } pub struct RemoteContentsFileSearcher { db: HashMap>, } impl RemoteContentsFileSearcher { pub fn from_session(session: &dyn Session) -> Result { log::debug!("Loading apt contents information"); let mut ret = RemoteContentsFileSearcher { db: HashMap::new() }; ret.load_from_session(session)?; Ok(ret) } pub fn load_local(&mut self) -> Result<(), Error> { let sl = SourcesList::default(); let arch = crate::debian::build::get_build_architecture(); let cache_dirs = vec![Path::new("/var/lib/apt/lists")]; let load_url = |url: &url::Url| load_url_with_cache(url, cache_dirs.as_slice()); let urls = contents_urls_from_sourceslist(&sl, &arch, load_url); self.load_urls(urls, load_url) } pub fn load_from_session(&mut self, session: &dyn Session) -> Result<(), Error> { // TODO(jelmer): what about sources.list.d? let sl = SourcesList::from_apt_dir(&session.external_path(Path::new("/etc/apt"))); let arch = crate::debian::build::get_build_architecture(); let cache_dirs = [session.external_path(Path::new("/var/lib/apt/lists"))]; let load_url = |url: &url::Url| { load_url_with_cache( url, cache_dirs .iter() .map(|p| p.as_ref()) .collect::>() .as_slice(), ) }; let urls = contents_urls_from_sourceslist(&sl, &arch, load_url); self.load_urls(urls, load_url) } fn load_urls( &mut self, urls: impl Iterator, load_url: impl Fn(&url::Url) -> Result, Error>, ) -> Result<(), Error> { for url in urls { let f = load_url(&url)?; self.load_file(f, url); } Ok(()) } pub fn search_files_ex<'a>( &'a self, mut matches: impl FnMut(&Path) -> bool + 'a, ) -> Box + 'a> { Box::new( self.db .iter() .filter(move |(p, _)| matches(Path::new(p))) .map(|(_, rest)| { std::str::from_utf8(rest.split(|c| *c == b'/').last().unwrap()) .unwrap() .to_string() }), ) } fn load_file(&mut self, f: impl Read, url: url::Url) { let start_time = std::time::Instant::now(); for (path, rest) in read_contents_file(f) { self.db.insert(path, rest.into()); } log::debug!("Read {} in {:?}", url, start_time.elapsed()); } } impl FileSearcher<'_> for RemoteContentsFileSearcher { fn search_files<'a>( &'a self, path: &'a Path, case_insensitive: bool, ) -> Box + 'a> { let path = if case_insensitive { PathBuf::from(path.to_str().unwrap().to_lowercase()) } else { path.to_owned() }; return Box::new(self.search_files_ex(move |p| { if case_insensitive { p.to_str().unwrap().to_lowercase() == path.to_str().unwrap() } else { p == path } })); } fn search_files_regex<'a>( &'a self, path: &'a str, case_insensitive: bool, ) -> Box + 'a> { let re = regex::RegexBuilder::new(path) .case_insensitive(case_insensitive) .build() .unwrap(); return Box::new(self.search_files_ex(move |p| { if case_insensitive { re.is_match(&p.to_str().unwrap().to_lowercase()) } else { re.is_match(p.to_str().unwrap()) } })); } } #[derive(Debug, Clone)] pub struct GeneratedFileSearcher { db: Vec<(PathBuf, String)>, } impl GeneratedFileSearcher { /// Create a new GeneratedFileSearcher. pub fn new(db: Vec<(PathBuf, String)>) -> GeneratedFileSearcher { Self { db } } /// Create an empty GeneratedFileSearcher. pub fn empty() -> GeneratedFileSearcher { Self::new(vec![]) } /// Create a new GeneratedFileSearcher from a file. /// /// # Arguments /// * `path` - The path to the file to load. pub fn from_path(path: &Path) -> GeneratedFileSearcher { let mut ret = Self::new(vec![]); ret.load_from_path(path); ret } /// Load the contents of a file into the database. /// /// # Arguments /// * `path` - The path to the file to load. pub fn load_from_path(&mut self, path: &Path) { let f = File::open(path).unwrap(); let f = BufReader::new(f); for line in f.lines() { let line = line.unwrap(); let (path, pkg) = line.split_once(' ').unwrap(); self.db.push((path.into(), pkg.to_owned())); } } fn search_files_ex<'a>( &'a self, mut matches: impl FnMut(&Path) -> bool + 'a, ) -> Box + 'a> { let x = self .db .iter() .filter(move |(p, _)| matches(p)) .map(|(_, pkg)| pkg.to_string()); Box::new(x) } } impl FileSearcher<'_> for GeneratedFileSearcher { fn search_files<'a>( &'a self, path: &'a Path, case_insensitive: bool, ) -> Box + 'a> { let path = if case_insensitive { PathBuf::from(path.to_str().unwrap().to_lowercase()) } else { path.to_owned() }; self.search_files_ex(move |p: &Path| { if case_insensitive { PathBuf::from(p.to_str().unwrap().to_lowercase()) == path } else { p == path } }) } fn search_files_regex<'a>( &'a self, path: &'a str, case_insensitive: bool, ) -> Box + 'a> { let re = regex::RegexBuilder::new(path) .case_insensitive(case_insensitive) .build() .unwrap(); return self.search_files_ex(move |p| re.is_match(p.to_str().unwrap())); } } // TODO(jelmer): read from a file lazy_static::lazy_static! { pub static ref GENERATED_FILE_SEARCHER: GeneratedFileSearcher = GeneratedFileSearcher::new(vec![ (PathBuf::from("/etc/locale.gen"), "locales".to_string()), // Alternative (PathBuf::from("/usr/bin/rst2html"), "python3-docutils".to_string()), // aclocal is a symlink to aclocal-1.XY (PathBuf::from("/usr/bin/aclocal"), "automake".to_string()), (PathBuf::from("/usr/bin/automake"), "automake".to_string()), // maven lives in /usr/share (PathBuf::from("/usr/bin/mvn"), "maven".to_string()), ]); } /// Get a list of packages that provide the given paths. /// /// # Arguments /// * `paths` - A list of paths to search for. /// * `searchers` - A list of searchers to use. /// * `regex` - Whether the paths are regular expressions. /// * `case_insensitive` - Whether the search should be case-insensitive. /// /// # Returns /// A list of packages that provide the given paths. pub fn get_packages_for_paths( paths: Vec<&str>, searchers: &[&dyn FileSearcher], regex: bool, case_insensitive: bool, ) -> Vec { let mut candidates = vec![]; // TODO(jelmer): Combine these, perhaps by creating one gigantic regex? for path in paths { for searcher in searchers { for pkg in if regex { searcher.search_files_regex(path, case_insensitive) } else { searcher.search_files(Path::new(path), case_insensitive) } { if !candidates.contains(&pkg) { candidates.push(pkg); } } } } candidates } pub struct MemoryAptSearcher(std::collections::HashMap); impl MemoryAptSearcher { pub fn new(db: std::collections::HashMap) -> MemoryAptSearcher { MemoryAptSearcher(db) } } impl FileSearcher<'_> for MemoryAptSearcher { fn search_files<'a>( &'a self, path: &'a Path, case_insensitive: bool, ) -> Box + 'a> { if case_insensitive { Box::new( self.0 .iter() .filter(move |(p, _)| { p.to_str().unwrap().to_lowercase() == path.to_str().unwrap() }) .map(|(_, pkg)| pkg.to_string()), ) } else { let hit = self.0.get(path); if let Some(hit) = hit { Box::new(std::iter::once(hit.clone())) } else { Box::new(std::iter::empty()) } } } fn search_files_regex<'a>( &'a self, path: &str, case_insensitive: bool, ) -> Box + 'a> { log::debug!("Searching for {} in {:?}", path, self.0.keys()); let re = regex::RegexBuilder::new(path) .case_insensitive(case_insensitive) .build() .unwrap(); Box::new( self.0 .iter() .filter(move |(p, _)| re.is_match(p.to_str().unwrap())) .map(|(_, pkg)| pkg.to_string()), ) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_uri_to_filename() { assert_eq!( uri_to_filename(&"http://example.com/foo/bar".parse().unwrap()), "example.com_foo_bar" ); } #[test] fn test_generated_file_searchers() { let searchers = &GENERATED_FILE_SEARCHER; assert_eq!( searchers .search_files(Path::new("/etc/locale.gen"), false) .collect::>(), vec!["locales"] ); assert_eq!( searchers .search_files(Path::new("/etc/LOCALE.GEN"), true) .collect::>(), vec!["locales"] ); assert_eq!( searchers .search_files(Path::new("/usr/bin/rst2html"), false) .collect::>(), vec!["python3-docutils"] ); } #[test] fn test_unwrap() { let data = b"hello world"; let f = std::io::Cursor::new(data); let f = unwrap(f, ""); let mut buf = Vec::new(); f.take(5).read_to_end(&mut buf).unwrap(); assert_eq!(buf, b"hello"); } } ognibuild-0.0.32/src/debian/fix_build.rs000064400000000000000000000474601046102023000162210ustar 00000000000000use crate::debian::build::BUILD_LOG_FILENAME; use crate::debian::build::{attempt_build, BuildOnceError, BuildOnceResult}; use crate::debian::context::Error; use crate::debian::context::Phase; pub use crate::fix_build::InterimError; use breezyshim::error::Error as BrzError; use breezyshim::workingtree::WorkingTree; use breezyshim::workspace::reset_tree; use buildlog_consultant::Match; use buildlog_consultant::Problem; use std::path::{Path, PathBuf}; /// Rescue a build log and store it in the users' cache directory pub fn rescue_build_log( output_directory: &Path, tree: Option<&WorkingTree>, ) -> Result<(), std::io::Error> { let xdg_cache_dir = match dirs::cache_dir() { Some(dir) => dir, None => { log::warn!("Unable to determine cache directory, not saving build log."); return Err(std::io::Error::new( std::io::ErrorKind::NotFound, "Unable to find cache directory", )); } }; let buildlogs_dir = xdg_cache_dir.join("ognibuild/buildlogs"); std::fs::create_dir_all(&buildlogs_dir)?; let target_log_file = buildlogs_dir.join(format!( "{}-{}.log", tree.map_or_else(|| PathBuf::from("build"), |t| t.basedir()) .display(), chrono::Local::now().format("%Y-%m-%d_%H%M%s"), )); std::fs::copy(output_directory.join("build.log"), &target_log_file)?; log::info!("Build log available in {}", target_log_file.display()); Ok(()) } /// A fixer is a struct that can resolve a specific type of problem. pub trait DebianBuildFixer: std::fmt::Debug + std::fmt::Display { /// Check if this fixer can potentially resolve the given problem. fn can_fix(&self, problem: &dyn Problem) -> bool; /// Attempt to resolve the given problem. fn fix(&self, problem: &dyn Problem, phase: &Phase) -> Result>; } pub fn resolve_error( problem: &dyn Problem, phase: &Phase, fixers: &[&dyn DebianBuildFixer], ) -> Result> { let relevant_fixers = fixers .iter() .filter(|fixer| fixer.can_fix(problem)) .collect::>(); if relevant_fixers.is_empty() { log::warn!("No fixer found for {:?}", problem); return Ok(false); } for fixer in relevant_fixers { log::info!("Attempting to use fixer {} to address {:?}", fixer, problem); let made_changes = fixer.fix(problem, phase)?; if made_changes { return Ok(true); } } Ok(false) } /// Error result from repeatedly running and attemptin to fix issues. #[derive(Debug)] pub enum IterateBuildError { /// The limit of fixing attempts was reached. FixerLimitReached(usize), /// A problem was detected that was recognized but could not be fixed. Persistent(Phase, Box), /// An error that we could not identify. Unidentified { retcode: i32, lines: Vec, secondary: Option>, phase: Option, }, MissingPhase, ResetTree(BrzError), /// Another error raised specifically by the callback function that is not fixable. Other(Error), } pub fn build_incrementally( local_tree: &WorkingTree, suffix: Option<&str>, build_suite: Option<&str>, output_directory: &Path, build_command: &str, fixers: &[&dyn DebianBuildFixer], build_changelog_entry: Option<&str>, max_iterations: Option, subpath: &Path, source_date_epoch: Option>, apt_repository: Option<&str>, apt_repository_key: Option<&str>, extra_repositories: Option>, run_gbp_dch: bool, ) -> Result { let mut fixed_errors: Vec<(Box, Phase)> = vec![]; log::info!("Using fixers: {:?}", fixers); loop { match attempt_build( local_tree, suffix, build_suite, output_directory, build_command, build_changelog_entry, subpath, source_date_epoch, run_gbp_dch, apt_repository, apt_repository_key, extra_repositories.as_ref(), ) { Ok(result) => { return Ok(result); } Err(BuildOnceError::Unidentified { stage: _, phase, retcode, command: _, description: _, }) => { log::warn!("Build failed with unidentified error. Giving up."); return Err(IterateBuildError::Unidentified { phase, retcode, lines: vec![], secondary: None, }); } Err(BuildOnceError::Detailed { phase, error, .. }) => { if phase.is_none() { log::info!("No relevant context, not making any changes."); return Err(IterateBuildError::MissingPhase); } let phase = phase.unwrap(); if fixed_errors.iter().any(|(e, p)| e == &error && p == &phase) { log::warn!("Error was still not fixed on second try. Giving up."); return Err(IterateBuildError::Persistent(phase, error)); } if max_iterations .map(|max| fixed_errors.len() >= max) .unwrap_or(false) { log::warn!("Max iterations reached. Giving up."); return Err(IterateBuildError::FixerLimitReached( max_iterations.unwrap(), )); } reset_tree(local_tree, None, Some(subpath)) .map_err(IterateBuildError::ResetTree)?; match resolve_error(error.as_ref(), &phase, fixers) { Ok(false) => { log::warn!("Failed to resolve error {:?}. Giving up.", error); return Err(IterateBuildError::Persistent(phase, error)); } Ok(true) => {} Err(InterimError::Other(e)) => { return Err(IterateBuildError::Other(e)); } Err(InterimError::Recognized(p)) => { if &error != &p { log::warn!("Detected problem while fixing {:?}: {:?}", error, p); } return Err(IterateBuildError::Persistent(phase, error)); } Err(InterimError::Unidentified { retcode, lines, secondary, }) => { log::warn!("Recognized error but unable to resolve: {:?}", lines); return Err(IterateBuildError::Unidentified { retcode, lines, phase: Some(phase), secondary, }); } } fixed_errors.push((error, phase)); crate::logs::rotate_logfile(&output_directory.join(BUILD_LOG_FILENAME)).unwrap(); } } } } #[cfg(test)] mod tests { use super::*; mod test_resolve_error { use super::*; use crate::debian::apt::AptManager; use crate::debian::context::DebianPackagingContext; use crate::debian::file_search::MemoryAptSearcher; use crate::session::plain::PlainSession; use breezyshim::commit::NullCommitReporter; use breezyshim::controldir::{create_standalone_workingtree, ControlDirFormat}; use breezyshim::tree::Tree; use buildlog_consultant::problems::common::*; use debian_control::lossless::Control; use std::collections::HashMap; use std::path::{Path, PathBuf}; use test_log::test; fn setup(path: &Path) -> WorkingTree { let tree = create_standalone_workingtree(&path, &ControlDirFormat::default()).unwrap(); std::fs::create_dir_all(path.join("debian")).unwrap(); std::fs::write( path.join("debian/control"), r#"Source: blah Build-Depends: libc6 Package: python-blah Depends: ${python3:Depends} Description: A python package Foo "#, ) .unwrap(); std::fs::write( path.join("debian/changelog"), r#"blah (0.1) UNRELEASED; urgency=medium * Initial release. (Closes: #XXXXXX) -- ognibuild Sat, 04 Apr 2020 14:12:13 +0000 "#, ) .unwrap(); tree.add(&[ Path::new("debian"), Path::new("debian/control"), Path::new("debian/changelog"), ]) .unwrap(); tree.build_commit() .message("Initial commit") .committer("ognibuild ") .commit() .unwrap(); tree } fn resolve( tree: &WorkingTree, error: &dyn Problem, phase: &Phase, apt_files: HashMap, ) -> bool { let session = PlainSession::new(); let apt = AptManager::new(&session, None); apt.set_searchers(vec![Box::new(MemoryAptSearcher::new(apt_files))]); let context = DebianPackagingContext::new( tree.clone(), Path::new(""), Some(("ognibuild".to_owned(), "ognibuild@jelmer.uk".to_owned())), true, Some(Box::new(NullCommitReporter::new())), ); let mut fixers: Vec> = crate::debian::fixers::versioned_package_fixers(&session, &context, &apt); fixers.extend(crate::debian::fixers::apt_fixers(&apt, &context)); resolve_error( error, phase, &fixers.iter().map(|f| f.as_ref()).collect::>(), ) .unwrap() } fn get_build_deps(tree: &dyn Tree) -> String { let content = tree.get_file_text(Path::new("debian/control")).unwrap(); let content = String::from_utf8(content).unwrap(); let control: Control = content.parse().unwrap(); control .source() .unwrap() .build_depends() .unwrap() .to_string() } #[test] fn test_missing_command_unknown() { let td = tempfile::tempdir().unwrap(); let tree = setup(td.path()); assert!(!resolve( &tree, &MissingCommand("acommandthatdoesnotexist".to_string()), &Phase::Build, HashMap::new() )); } #[test] fn test_missing_command_brz() { let env = breezyshim::testing::TestEnv::new(); let td = tempfile::tempdir().unwrap(); let tree = setup(td.path()); let apt_files = maplit::hashmap! { PathBuf::from("/usr/bin/b") => "bash".to_string(), PathBuf::from("/usr/bin/brz") => "brz".to_string(), PathBuf::from("/usr/bin/brzier") => "bash".to_string(), }; assert!(resolve( &tree, &MissingCommand("brz".to_string()), &Phase::Build, apt_files.clone() )); assert_eq!("libc6, brz", get_build_deps(&tree)); let rev = tree .branch() .repository() .get_revision(&tree.branch().last_revision()); assert_eq!( "Add missing build dependency on brz.\n", rev.unwrap().message ); // Now that the dependency is added, we should not try to add it again. assert!(!resolve( &tree, &MissingCommand("brz".to_owned()), &Phase::Build, apt_files )); assert_eq!("libc6, brz", get_build_deps(&tree)); std::mem::drop(env); } #[test] fn test_missing_command_ps() { let apt_files = maplit::hashmap! { PathBuf::from("/bin/ps") => "procps".to_string(), PathBuf::from("/usr/bin/pscal") => "xcal".to_string(), }; let td = tempfile::tempdir().unwrap(); let tree = setup(td.path()); assert!(resolve( &tree, &MissingCommand("ps".to_owned()), &Phase::Build, apt_files )); assert_eq!("libc6, procps", get_build_deps(&tree)); } #[test] fn test_missing_ruby_file() { let apt_files = maplit::hashmap! { PathBuf::from("/usr/lib/ruby/vendor_ruby/rake/testtask.rb") => "rake".to_string(), }; let td = tempfile::tempdir().unwrap(); let tree = setup(td.path()); assert!(resolve( &tree, &MissingRubyFile::new("rake/testtask".to_string()), &Phase::Build, apt_files )); assert_eq!("libc6, rake", get_build_deps(&tree)); } #[test] fn test_missing_ruby_file_from_gem() { let apt_files = maplit::hashmap! { PathBuf::from("/usr/share/rubygems-integration/all/gems/activesupport-5.2.3/lib/active_support/core_ext/string/strip.rb") => "ruby-activesupport".to_string(), }; let td = tempfile::tempdir().unwrap(); let tree = setup(td.path()); assert!(resolve( &tree, &MissingRubyFile::new("active_support/core_ext/string/strip".to_string()), &Phase::Build, apt_files )); assert_eq!("libc6, ruby-activesupport", get_build_deps(&tree)); } #[test] fn test_missing_ruby_gem() { let apt_files = maplit::hashmap! { PathBuf::from("/usr/share/rubygems-integration/all/specifications/bio-1.5.2.gemspec") => "ruby-bio".to_string(), PathBuf::from("/usr/share/rubygems-integration/all/specifications/bio-2.0.2.gemspec") => "ruby-bio".to_string(), }; let td = tempfile::tempdir().unwrap(); let tree = setup(td.path()); assert!(resolve( &tree, &MissingRubyGem::simple("bio".to_string()), &Phase::Build, apt_files.clone() )); assert_eq!("libc6, ruby-bio", get_build_deps(&tree)); assert!(resolve( &tree, &MissingRubyGem::new("bio".to_string(), Some("2.0.3".to_string())), &Phase::Build, apt_files )); assert_eq!("libc6, ruby-bio (>= 2.0.3)", get_build_deps(&tree)); } #[test] fn test_missing_perl_module() { let apt_files = maplit::hashmap! { PathBuf::from("/usr/share/perl5/App/cpanminus/fatscript.pm") => "cpanminus".to_string(), }; let td = tempfile::tempdir().unwrap(); let tree = setup(td.path()); assert!(resolve( &tree, &MissingPerlModule { filename: Some("App/cpanminus/fatscript.pm".to_string()), module: "App::cpanminus::fatscript".to_string(), minimum_version: None, inc: Some(vec![ "/<>/blib/lib".to_string(), "/<>/blib/arch".to_string(), "/etc/perl".to_string(), "/usr/local/lib/x86_64-linux-gnu/perl/5.30.0".to_string(), "/usr/local/share/perl/5.30.0".to_string(), "/usr/lib/x86_64-linux-gnu/perl5/5.30".to_string(), "/usr/share/perl5".to_string(), "/usr/lib/x86_64-linux-gnu/perl/5.30".to_string(), "/usr/share/perl/5.30".to_string(), "/usr/local/lib/site_perl".to_string(), "/usr/lib/x86_64-linux-gnu/perl-base".to_string(), ".".to_string(), ]), }, &Phase::Build, apt_files )); assert_eq!("libc6, cpanminus", get_build_deps(&tree)); } #[test] fn test_missing_pkg_config() { let apt_files = maplit::hashmap! { PathBuf::from("/usr/lib/x86_64-linux-gnu/pkgconfig/xcb-xfixes.pc") => "libxcb-xfixes0-dev".to_string(), }; let td = tempfile::tempdir().unwrap(); let tree = setup(td.path()); assert!(resolve( &tree, &MissingPkgConfig::simple("xcb-xfixes".to_string()), &Phase::Build, apt_files )); assert_eq!("libc6, libxcb-xfixes0-dev", get_build_deps(&tree)); } #[test] fn test_missing_pkg_config_versioned() { let apt_files = maplit::hashmap! { PathBuf::from("/usr/lib/x86_64-linux-gnu/pkgconfig/xcb-xfixes.pc") => "libxcb-xfixes0-dev".to_string(), }; let td = tempfile::tempdir().unwrap(); let tree = setup(td.path()); assert!(resolve( &tree, &MissingPkgConfig::new("xcb-xfixes".to_string(), Some("1.0".to_string())), &Phase::Build, apt_files )); assert_eq!("libc6, libxcb-xfixes0-dev (>= 1.0)", get_build_deps(&tree)); } #[test] fn test_missing_python_module() { let apt_files = maplit::hashmap! { PathBuf::from("/usr/lib/python3/dist-packages/m2r.py") => "python3-m2r".to_string(), }; let td = tempfile::tempdir().unwrap(); let tree = setup(td.path()); assert!(resolve( &tree, &MissingPythonModule::simple("m2r".to_string()), &Phase::Build, apt_files )); assert_eq!("libc6, python3-m2r", get_build_deps(&tree)); } #[test] fn test_missing_go_package() { let apt_files = maplit::hashmap! { PathBuf::from("/usr/share/gocode/src/github.com/chzyer/readline/utils_test.go") => "golang-github-chzyer-readline-dev".to_string(), }; let td = tempfile::tempdir().unwrap(); let tree = setup(td.path()); assert!(resolve( &tree, &MissingGoPackage { package: "github.com/chzyer/readline".to_string() }, &Phase::Build, apt_files )); assert_eq!( "libc6, golang-github-chzyer-readline-dev", get_build_deps(&tree) ); } #[test] fn test_missing_vala_package() { let apt_files = maplit::hashmap! { PathBuf::from("/usr/share/vala-0.48/vapi/posix.vapi") => "valac-0.48-vapi".to_string(), }; let td = tempfile::tempdir().unwrap(); let tree = setup(td.path()); assert!(resolve( &tree, &MissingValaPackage("posix".to_string()), &Phase::Build, apt_files )); assert_eq!("libc6, valac-0.48-vapi", get_build_deps(&tree)); } } } ognibuild-0.0.32/src/debian/fixers.rs000064400000000000000000000340531046102023000155460ustar 00000000000000use crate::debian::apt::AptManager; use crate::debian::context::{DebianPackagingContext, Error}; use crate::debian::fix_build::DebianBuildFixer; use crate::dependencies::debian::{DebianDependency, TieBreaker}; use crate::session::Session; use breezyshim::tree::Tree; use buildlog_consultant::problems::common::NeedPgBuildExtUpdateControl; use buildlog_consultant::sbuild::Phase; use buildlog_consultant::Problem; use debian_analyzer::editor::Editor; use std::path::Path; fn targeted_python_versions(tree: &dyn Tree, subpath: &Path) -> Vec { let f = tree.get_file(&subpath.join("debian/control")).unwrap(); let control = debian_control::Control::read(f).unwrap(); let source = control.source().unwrap(); let all = if let Some(build_depends) = source.build_depends() { build_depends } else { return vec![]; }; let targeted = vec![]; for entry in all.entries() { for relation in entry.relations() { let mut targeted = vec![]; if relation.name().starts_with("python3-") { targeted.push("python3".to_owned()); } if relation.name().starts_with("pypy") { targeted.push("pypy".to_owned()); } if relation.name().starts_with("python-") { targeted.push("python".to_owned()); } } } targeted } pub struct PythonTieBreaker { targeted: Vec, } impl PythonTieBreaker { fn from_tree(tree: &dyn Tree, subpath: &Path) -> Self { let targeted = targeted_python_versions(tree, subpath); Self { targeted } } } impl TieBreaker for PythonTieBreaker { fn break_tie<'a>(&self, reqs: &[&'a DebianDependency]) -> Option<&'a DebianDependency> { if self.targeted.is_empty() { return None; } fn same(pkg: &str, python_version: &str) -> bool { if pkg.starts_with(&format!("{}-", python_version)) { return true; } if pkg.starts_with(&format!("lib{}-", python_version)) { return true; } pkg == format!("lib{}-dev", python_version) } for python_version in &self.targeted { for req in reqs { if req .package_names() .iter() .any(|name| same(name, &python_version)) { log::info!( "Breaking tie between {:?} to {:?}, since package already has {} build-dependencies", reqs, req, python_version, ); return Some(req); } } } None } } fn retry_apt_failure( _error: &dyn Problem, _phase: &Phase, _context: &DebianPackagingContext, ) -> Result { Ok(true) } fn enable_dh_autoreconf(context: &DebianPackagingContext, phase: &Phase) -> Result { // Debhelper >= 10 depends on dh-autoreconf and enables autoreconf by default. let debhelper_compat_version = debian_analyzer::debhelper::get_debhelper_compat_level(&context.abspath(Path::new("."))) .unwrap(); if !debhelper_compat_version .map(|dcv| dcv < 10) .unwrap_or(false) { return Ok(false); } let mut modified = false; let rules = context.edit_rules()?; for rule in rules.rules_by_target("%") { for (i, line) in rule.recipes().enumerate() { if !line.starts_with("dh ") { continue; } let new_line = debian_analyzer::rules::dh_invoke_add_with(&line, "autoreconf"); if line != new_line { rule.replace_command(i, &new_line); modified = true; } } } if modified { context.add_dependency(phase, &DebianDependency::simple("dh-autoreconf")) } else { Ok(false) } } fn fix_missing_configure( _error: &dyn Problem, phase: &Phase, context: &DebianPackagingContext, ) -> Result { if !context.has_filename(Path::new("configure.ac")) && !context.has_filename(Path::new("configure.in")) { return Ok(false); } enable_dh_autoreconf(context, phase) } fn fix_missing_automake_input( _error: &dyn Problem, phase: &Phase, context: &DebianPackagingContext, ) -> Result { // TODO(jelmer): If it's ./NEWS, ./AUTHORS or ./README that's missing, then // try to set 'export AUTOMAKE = automake --foreign' in debian/rules. // https://salsa.debian.org/jelmer/debian-janitor/issues/88 enable_dh_autoreconf(context, phase) } fn fix_missing_config_status_input( _error: &dyn Problem, _phase: &Phase, context: &DebianPackagingContext, ) -> Result { let autogen_path = "autogen.sh"; if !context.has_filename(Path::new(autogen_path)) { return Ok(false); } let mut rules = context.edit_rules()?; let rule_exists = rules .rules_by_target("override_dh_autoreconf") .next() .is_some(); if rule_exists { return Ok(false); } let rule = rules.add_rule("override_dh_autoreconf"); rule.push_command("dh_autoreconf ./autogen.sh"); rules.commit()?; context.commit("Run autogen.sh during build.", None) } pub struct PackageDependencyFixer<'a, 'b, 'c> where 'c: 'a, { apt: &'a AptManager<'c>, context: &'b DebianPackagingContext, tie_breakers: Vec>, } impl<'a, 'b, 'c> std::fmt::Display for PackageDependencyFixer<'a, 'b, 'c> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "PackageDependencyFixer") } } impl<'a, 'b, 'c> std::fmt::Debug for PackageDependencyFixer<'a, 'b, 'c> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "PackageDependencyFixer") } } impl<'a, 'b, 'c> DebianBuildFixer for PackageDependencyFixer<'a, 'b, 'c> { fn can_fix(&self, problem: &dyn Problem) -> bool { crate::buildlog::problem_to_dependency(problem).is_some() } fn fix( &self, problem: &dyn Problem, phase: &Phase, ) -> Result> { let dep = crate::buildlog::problem_to_dependency(problem).unwrap(); let deb_dep = crate::debian::apt::dependency_to_deb_dependency( &self.apt, dep.as_ref(), self.tie_breakers.as_slice(), ) .unwrap(); let deb_dep = if let Some(deb_dep) = deb_dep { deb_dep } else { return Ok(false); }; Ok(self.context.add_dependency(phase, &deb_dep).unwrap()) } } pub struct PgBuildExtOutOfDateControlFixer<'a, 'b, 'c, 'd> where 'a: 'c, { session: &'a dyn Session, context: &'b DebianPackagingContext, apt: &'c AptManager<'d>, } impl<'a, 'b, 'c, 'd> std::fmt::Debug for PgBuildExtOutOfDateControlFixer<'a, 'b, 'c, 'd> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "PgBuildExtOutOfDateControlFixer") } } impl<'a, 'b, 'c, 'd> std::fmt::Display for PgBuildExtOutOfDateControlFixer<'a, 'b, 'c, 'd> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "PgBuildExtOutOfDateControlFixer") } } impl<'a, 'b, 'c, 'd> DebianBuildFixer for PgBuildExtOutOfDateControlFixer<'a, 'b, 'c, 'd> { fn can_fix(&self, problem: &dyn Problem) -> bool { problem .as_any() .downcast_ref::() .is_some() } fn fix( &self, error: &dyn Problem, _phase: &Phase, ) -> std::result::Result> { let error = error .as_any() .downcast_ref::() .unwrap(); log::info!("Running 'pg_buildext updatecontrol'"); self.apt .satisfy(vec![crate::debian::apt::SatisfyEntry::Required( "postgresql-common".to_string(), )]) .unwrap(); let project = self .session .project_from_directory(&self.context.tree.abspath(Path::new(".")).unwrap(), None) .unwrap(); self.session .command(vec!["pg_buildext", "updatecontrol"]) .cwd(&project.internal_path()) .check_call() .unwrap(); std::fs::copy( project.internal_path().join(&error.generated_path), self.context.abspath(Path::new(&error.generated_path)), ) .unwrap(); self.context .commit("Run 'pgbuildext updatecontrol'.", Some(false))?; Ok(true) } } fn fix_missing_makefile_pl( error: &buildlog_consultant::problems::common::MissingPerlFile, _phase: &Phase, context: &DebianPackagingContext, ) -> Result { if error.filename == "Makefile.PL" && !context.has_filename(Path::new("Makefile.PL")) && context.has_filename(Path::new("dist.ini")) { // TODO(jelmer): add dist-zilla add-on to debhelper unimplemented!() } return Ok(false); } fn debcargo_coerce_unacceptable_prerelease( _error: &dyn Problem, _phase: &Phase, context: &DebianPackagingContext, ) -> Result { let path = context.abspath(Path::new("debian/debcargo.toml")); let text = std::fs::read_to_string(&path)?; let mut doc: toml_edit::DocumentMut = text.parse().unwrap(); doc.as_table_mut()["allow_prerelease_deps"] = toml_edit::value(true); std::fs::write(&path, doc.to_string())?; context.commit("Enable allow_prerelease_deps.", None)?; Ok(true) } macro_rules! simple_build_fixer { ($name:ident, $problem_cls:ty, $fn:expr) => { pub struct $name<'a>(&'a DebianPackagingContext); impl<'a> std::fmt::Display for $name<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", stringify!($name)) } } impl<'a> std::fmt::Debug for $name<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", stringify!($name)) } } impl<'a> DebianBuildFixer for $name<'a> { fn can_fix(&self, problem: &dyn Problem) -> bool { problem.as_any().downcast_ref::<$problem_cls>().is_some() } fn fix( &self, error: &dyn Problem, phase: &Phase, ) -> std::result::Result< bool, crate::fix_build::InterimError, > { let error = error.as_any().downcast_ref::<$problem_cls>().unwrap(); $fn(error, phase, self.0).map_err(|e| crate::fix_build::InterimError::Other(e)) } } }; } simple_build_fixer!( MissingConfigureFixer, buildlog_consultant::problems::common::MissingConfigure, fix_missing_configure ); simple_build_fixer!( MissingAutomakeInputFixer, buildlog_consultant::problems::common::MissingAutomakeInput, fix_missing_automake_input ); simple_build_fixer!( MissingConfigStatusInputFixer, buildlog_consultant::problems::common::MissingConfigStatusInput, fix_missing_config_status_input ); simple_build_fixer!( MissingPerlFileFixer, buildlog_consultant::problems::common::MissingPerlFile, fix_missing_makefile_pl ); simple_build_fixer!( DebcargoUnacceptablePredicateFixer, buildlog_consultant::problems::debian::DebcargoUnacceptablePredicate, debcargo_coerce_unacceptable_prerelease ); simple_build_fixer!( DebcargoUnacceptableComparatorFixer, buildlog_consultant::problems::debian::DebcargoUnacceptableComparator, debcargo_coerce_unacceptable_prerelease ); simple_build_fixer!( RetryAptFetchFailure, buildlog_consultant::problems::debian::AptFetchFailure, retry_apt_failure ); pub fn versioned_package_fixers<'a, 'b, 'c, 'd, 'e>( session: &'c dyn Session, packaging_context: &'b DebianPackagingContext, apt: &'a AptManager<'e>, ) -> Vec> where 'a: 'd, 'b: 'd, 'c: 'd, 'c: 'a, { vec![ Box::new(PgBuildExtOutOfDateControlFixer { context: packaging_context, session, apt, }), Box::new(MissingConfigureFixer(packaging_context)), Box::new(MissingAutomakeInputFixer(packaging_context)), Box::new(MissingConfigStatusInputFixer(packaging_context)), Box::new(MissingPerlFileFixer(packaging_context)), Box::new(DebcargoUnacceptablePredicateFixer(packaging_context)), Box::new(DebcargoUnacceptableComparatorFixer(packaging_context)), ] } pub fn apt_fixers<'a, 'b, 'c, 'd>( apt: &'a AptManager<'d>, packaging_context: &'b DebianPackagingContext, ) -> Vec> where 'a: 'c, 'b: 'c, { let apt_tie_breakers: Vec> = vec![ Box::new(PythonTieBreaker::from_tree( &packaging_context.tree, &packaging_context.subpath, )), Box::new(crate::debian::build_deps::BuildDependencyTieBreaker::from_session(apt.session())), #[cfg(feature = "udd")] Box::new(crate::debian::udd::PopconTieBreaker), ]; vec![ Box::new(RetryAptFetchFailure(packaging_context)) as Box, Box::new(PackageDependencyFixer { context: packaging_context, apt, tie_breakers: apt_tie_breakers, }) as Box, ] } pub fn default_fixers<'a, 'b, 'c, 'd>( packaging_context: &'a DebianPackagingContext, apt: &'b AptManager<'d>, ) -> Vec> where 'a: 'c, 'b: 'c, { let mut ret = Vec::new(); ret.extend(versioned_package_fixers( apt.session(), packaging_context, apt, )); ret.extend(apt_fixers(apt, packaging_context)); ret } ognibuild-0.0.32/src/debian/mod.rs000064400000000000000000000024061046102023000150220ustar 00000000000000pub mod apt; pub mod build; pub mod build_deps; pub mod context; #[cfg(feature = "dep-server")] pub mod dep_server; pub mod file_search; pub mod fix_build; pub mod fixers; pub mod sources_list; #[cfg(feature = "udd")] pub mod udd; pub mod upstream_deps; use breezyshim::tree::{Path, Tree}; use crate::session::Session; pub fn satisfy_build_deps( session: &dyn Session, tree: &dyn Tree, debian_path: &Path, ) -> Result<(), apt::Error> { let path = debian_path.join("control"); let f = tree.get_file_text(&path).unwrap(); let control: debian_control::Control = String::from_utf8(f).unwrap().parse().unwrap(); let source = control.source().unwrap(); let mut deps = vec![]; for dep in source .build_depends() .iter() .chain(source.build_depends_indep().iter()) .chain(source.build_depends_arch().iter()) { deps.push(apt::SatisfyEntry::Required(dep.to_string())); } for dep in source .build_conflicts() .iter() .chain(source.build_conflicts_indep().iter()) .chain(source.build_conflicts_arch().iter()) { deps.push(apt::SatisfyEntry::Conflict(dep.to_string())); } let apt_mgr = apt::AptManager::new(session, None); apt_mgr.satisfy(deps) } ognibuild-0.0.32/src/debian/sources_list.rs000064400000000000000000000072521046102023000167650ustar 00000000000000use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::Path; #[derive(Debug, PartialEq, Eq)] pub enum SourcesEntry { Deb { uri: String, dist: String, comps: Vec, }, DebSrc { uri: String, dist: String, comps: Vec, }, } pub fn parse_sources_list_entry(line: &str) -> Option { let parts = line.split_whitespace().collect::>(); if parts.len() < 3 { return None; } let uri = parts[1]; let dist = parts[2]; let comps = parts[3..].iter().map(|x| x.to_string()).collect::>(); if parts[0] == "deb" { return Some(SourcesEntry::Deb { uri: uri.to_string(), dist: dist.to_string(), comps, }); } if parts[0] == "deb-src" { return Some(SourcesEntry::DebSrc { uri: uri.to_string(), dist: dist.to_string(), comps, }); } None } pub struct SourcesList { list: Vec, } impl SourcesList { pub fn empty() -> SourcesList { SourcesList { list: vec![] } } pub fn iter(&self) -> std::slice::Iter { self.list.iter() } pub fn load(&mut self, path: &Path) { let f = File::open(path).unwrap(); for line in BufReader::new(f).lines() { let line = line.unwrap(); if let Some(entry) = parse_sources_list_entry(&line) { self.list.push(entry); } } } pub fn from_apt_dir(apt_dir: &Path) -> SourcesList { let mut sl = SourcesList::empty(); sl.load(&apt_dir.join("sources.list")); for entry in apt_dir.read_dir().unwrap() { let entry = entry.unwrap(); if entry.file_type().unwrap().is_file() { let path = entry.path(); sl.load(&path); } } sl } } impl Default for SourcesList { fn default() -> Self { Self::from_apt_dir(Path::new("/etc/apt")) } } #[cfg(test)] mod tests { #[test] fn test_parse_sources_list_entry() { use super::parse_sources_list_entry; use super::SourcesEntry; assert_eq!( parse_sources_list_entry( "deb http://archive.ubuntu.com/ubuntu/ bionic main restricted" ), Some(SourcesEntry::Deb { uri: "http://archive.ubuntu.com/ubuntu/".to_string(), dist: "bionic".to_string(), comps: vec!["main".to_string(), "restricted".to_string()] }) ); assert_eq!( parse_sources_list_entry( "deb-src http://archive.ubuntu.com/ubuntu/ bionic main restricted" ), Some(SourcesEntry::DebSrc { uri: "http://archive.ubuntu.com/ubuntu/".to_string(), dist: "bionic".to_string(), comps: vec!["main".to_string(), "restricted".to_string()] }) ); } #[test] fn test_sources_list() { let td = tempfile::tempdir().unwrap(); let path = td.path().join("sources.list"); std::fs::write( &path, "deb http://archive.ubuntu.com/ubuntu/ bionic main restricted\n", ) .unwrap(); let mut sl = super::SourcesList::empty(); sl.load(&path); assert_eq!(sl.list.len(), 1); assert_eq!( sl.list[0], super::SourcesEntry::Deb { uri: "http://archive.ubuntu.com/ubuntu/".to_string(), dist: "bionic".to_string(), comps: vec!["main".to_string(), "restricted".to_string()] } ); } } ognibuild-0.0.32/src/debian/udd.rs000064400000000000000000000035511046102023000150210ustar 00000000000000use crate::dependencies::debian::DebianDependency; use crate::dependencies::debian::TieBreaker; use sqlx::{Error, PgPool}; use tokio::runtime::Runtime; pub struct UDD { pool: PgPool, } impl UDD { // Function to create a new instance of UDD with a database connection pub async fn connect() -> Result { let pool = PgPool::connect("postgres://udd-mirror:udd-mirror@udd-mirror.debian.net:5432/udd") .await .unwrap(); Ok(UDD { pool }) } } async fn get_most_popular(reqs: &[&DebianDependency]) -> Result, Error> { let udd = UDD::connect().await.unwrap(); let names = reqs .iter() .flat_map(|req| req.package_names()) .collect::>(); let (max_popcon_name,): (Option,) = sqlx::query_as( "SELECT package FROM popcon WHERE package IN $1 ORDER BY insts DESC LIMIT 1", ) .bind(names) .fetch_one(&udd.pool) .await .unwrap(); Ok(max_popcon_name) } pub struct PopconTieBreaker; impl TieBreaker for PopconTieBreaker { fn break_tie<'a>(&self, reqs: &[&'a DebianDependency]) -> Option<&'a DebianDependency> { // TODO(jelmer): Pick package based on what appears most commonly in // build-depends{-indep,-arch} let rt = Runtime::new().unwrap(); let package = rt.block_on(get_most_popular(reqs)).unwrap(); if package.is_none() { log::info!("No relevant popcon information found, not ranking by popcon"); return None; } let package = package.unwrap(); let winner = reqs .into_iter() .find(|req| req.package_names().contains(&package.to_string())); if winner.is_none() { log::info!("No relevant popcon information found, not ranking by popcon"); } winner.copied() } } ognibuild-0.0.32/src/debian/upstream_deps.rs000064400000000000000000000060371046102023000171220ustar 00000000000000use crate::buildsystem::{BuildSystem, DependencyCategory}; use crate::dependencies::debian::DebianDependency; use crate::installer::Error as InstallerError; use crate::session::Session; /// Get the project-wide dependencies for a project. /// /// This function will return a tuple of two vectors of `DebianDependency` objects. The first /// vector will contain the build dependencies, and the second vector will contain the test /// dependencies. /// /// # Arguments /// * `session` - The session to use for the operation. /// * `buildsystem` - The build system to use for the operation. pub fn get_project_wide_deps( session: &dyn Session, buildsystem: &dyn BuildSystem, ) -> (Vec, Vec) { let mut build_deps = vec![]; let mut test_deps = vec![]; let apt = crate::debian::apt::AptManager::new(session, None); let apt_installer = crate::debian::apt::AptInstaller::new(apt); let scope = crate::installer::InstallationScope::Global; let build_fixers = [ Box::new(crate::fixers::InstallFixer::new(&apt_installer, scope)) as Box>, ]; let apt = crate::debian::apt::AptManager::new(session, None); let tie_breakers = vec![ Box::new(crate::debian::build_deps::BuildDependencyTieBreaker::from_session(session)) as Box, #[cfg(feature = "udd")] { Box::new(crate::debian::udd::PopconTieBreaker) as Box }, ]; match buildsystem.get_declared_dependencies( session, Some( build_fixers .iter() .map(|x| x.as_ref()) .collect::>() .as_slice(), ), ) { Err(e) => { log::error!("Unable to obtain declared dependencies: {}", e); } Ok(upstream_deps) => { for (kind, dep) in upstream_deps { let apt_dep = crate::debian::apt::dependency_to_deb_dependency( &apt, dep.as_ref(), tie_breakers.as_slice(), ) .unwrap(); if apt_dep.is_none() { log::warn!( "Unable to map upstream requirement {:?} (kind {}) to a Debian package", dep, kind, ); continue; } let apt_dep = apt_dep.unwrap(); log::debug!("Mapped {:?} (kind: {}) to {:?}", dep, kind, apt_dep); if [DependencyCategory::Universal, DependencyCategory::Build].contains(&kind) { build_deps.push(apt_dep.clone()); } if [DependencyCategory::Universal, DependencyCategory::Test].contains(&kind) { test_deps.push(apt_dep.clone()); } } } } (build_deps, test_deps) } ognibuild-0.0.32/src/dependencies/autoconf.rs000064400000000000000000000056051046102023000172710ustar 00000000000000use crate::dependency::Dependency; use crate::session::Session; use serde::{Deserialize, Serialize}; use std::io::BufRead; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AutoconfMacroDependency { macro_name: String, } impl AutoconfMacroDependency { pub fn new(macro_name: &str) -> Self { Self { macro_name: macro_name.to_string(), } } } impl Dependency for AutoconfMacroDependency { fn family(&self) -> &'static str { "autoconf-macro" } fn present(&self, _session: &dyn Session) -> bool { todo!() } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } fn m4_macro_regex(r#macro: &str) -> String { let defun_prefix = regex::escape(format!("AC_DEFUN([{}],", r#macro).as_str()); let au_alias_prefix = regex::escape(format!("AU_ALIAS([{}],", r#macro).as_str()); let m4_copy = format!(r"m4_copy\(.*,\s*\[{}\]\)", regex::escape(r#macro)); [ "(", &defun_prefix, "|", &au_alias_prefix, "|", &m4_copy, ")", ] .concat() } #[cfg(feature = "debian")] fn find_local_m4_macro(r#macro: &str) -> Option { // TODO(jelmer): Query some external service that can search all binary packages? let p = regex::Regex::new(&m4_macro_regex(r#macro)).unwrap(); for entry in std::fs::read_dir("/usr/share/aclocal").unwrap() { let entry = entry.unwrap(); if !entry.metadata().unwrap().is_file() { continue; } let f = std::fs::File::open(entry.path()).unwrap(); let reader = std::io::BufReader::new(f); for line in reader.lines() { if p.find(line.unwrap().as_str()).is_some() { return Some(entry.path().to_str().unwrap().to_string()); } } } None } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for AutoconfMacroDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let path = find_local_m4_macro(&self.macro_name); if path.is_none() { log::info!("No local m4 file found defining {}", self.macro_name); return None; } Some( apt.get_packages_for_paths(vec![path.as_ref().unwrap()], false, false) .unwrap() .iter() .map(|p| crate::dependencies::debian::DebianDependency::simple(p.as_str())) .collect(), ) } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingAutoconfMacro { fn to_dependency(&self) -> Option> { Some(Box::new(AutoconfMacroDependency::new(&self.r#macro))) } } ognibuild-0.0.32/src/dependencies/debian.rs000064400000000000000000000307221046102023000166730ustar 00000000000000use crate::dependency::Dependency; use crate::session::Session; use debian_control::lossless::relations::{Entry, Relation, Relations}; use debian_control::relations::VersionConstraint; use debversion::Version; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::collections::HashSet; use std::hash::Hash; pub struct DebianDependency(Relations); impl std::fmt::Debug for DebianDependency { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_tuple("DebianDependency") .field(&self.0.to_string()) .finish() } } impl Clone for DebianDependency { fn clone(&self) -> Self { let rels = self.0.to_string().parse().unwrap(); DebianDependency(rels) } } impl Serialize for DebianDependency { fn serialize(&self, serializer: S) -> Result where S: Serializer, { self.0.to_string().serialize(serializer) } } impl<'a> Deserialize<'a> for DebianDependency { fn deserialize(deserializer: D) -> Result where D: Deserializer<'a>, { let s = String::deserialize(deserializer)?; Ok(DebianDependency(s.parse().unwrap())) } } impl PartialEq for DebianDependency { fn eq(&self, other: &Self) -> bool { self.0.to_string() == other.0.to_string() } } impl Eq for DebianDependency {} impl Hash for DebianDependency { fn hash(&self, state: &mut H) { self.0.to_string().hash(state); } } impl DebianDependency { /// Create a new dependency from a package name. pub fn new(name: &str) -> DebianDependency { DebianDependency( name.parse() .unwrap_or_else(|_| panic!("Failed to parse dependency: {}", name)), ) } pub fn iter(&self) -> impl Iterator + '_ { self.0.entries() } pub fn relation_string(&self) -> String { self.0.to_string() } pub fn simple(name: &str) -> DebianDependency { Self::new(name) } pub fn is_empty(&self) -> bool { self.0.is_empty() } pub fn new_with_min_version(name: &str, min_version: &Version) -> DebianDependency { DebianDependency( format!("{} (>= {})", name, min_version) .parse() .unwrap_or_else(|_| { panic!("Failed to parse dependency: {} (>= {})", name, min_version) }), ) } pub fn touches_package(&self, package: &str) -> bool { for entry in self.0.entries() { for relation in entry.relations() { if relation.name() == package { return true; } } } false } pub fn package_names(&self) -> HashSet { let mut names = HashSet::new(); for entry in self.0.entries() { for relation in entry.relations() { names.insert(relation.name()); } } names } pub fn satisfied_by( &self, versions: &std::collections::HashMap, ) -> bool { let relation_satisfied = |relation: Relation| -> bool { let name = relation.name(); let version = if let Some(version) = versions.get(&name) { version } else { return false; }; match relation.version() { Some((VersionConstraint::Equal, v)) => version.cmp(&v) == std::cmp::Ordering::Equal, Some((VersionConstraint::GreaterThanEqual, v)) => version >= &v, Some((VersionConstraint::GreaterThan, v)) => version > &v, Some((VersionConstraint::LessThanEqual, v)) => version <= &v, Some((VersionConstraint::LessThan, v)) => version < &v, None => true, } }; self.0 .entries() .all(|entry| entry.relations().any(relation_satisfied)) } } /// Get the version of a package installed on the system. /// /// Returns `None` if the package is not installed. fn get_package_version(session: &dyn Session, package: &str) -> Option { let argv = vec!["dpkg-query", "-W", "-f=${Version}\n", package]; let output = session .command(argv) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::null()) .output() .unwrap(); match output.status.code() { Some(0) => { let output = String::from_utf8(output.stdout).unwrap(); if output.trim().is_empty() { return None; } Some(output.trim().parse().unwrap()) } Some(1) => None, _ => panic!("Failed to run dpkg-query"), } } impl Dependency for DebianDependency { fn family(&self) -> &'static str { "debian" } fn present(&self, session: &dyn Session) -> bool { use std::collections::HashMap; let mut versions = HashMap::new(); for name in self.package_names() { if let Some(version) = get_package_version(session, &name) { versions.insert(name, version); } else { // Package not found return false; } } let result = self.satisfied_by(&versions); if !result { log::debug!("Dependency not satisfied: {:?}", self); } else { log::debug!("Dependency satisfied: {:?}", self); } result } fn project_present(&self, _session: &dyn Session) -> bool { false } fn as_any(&self) -> &dyn std::any::Any { self } } impl From for Relations { fn from(dep: DebianDependency) -> Self { dep.0 } } impl From for DebianDependency { fn from(rel: Relations) -> Self { DebianDependency(rel) } } pub trait TieBreaker { fn break_tie<'a>(&self, reqs: &[&'a DebianDependency]) -> Option<&'a DebianDependency>; } pub fn default_tie_breakers(session: &dyn Session) -> Vec> { let mut tie_breakers: Vec> = Vec::new(); use crate::debian::build_deps::BuildDependencyTieBreaker; tie_breakers.push(Box::new(BuildDependencyTieBreaker::from_session(session))); #[cfg(feature = "udd")] { use crate::debian::udd::PopconTieBreaker; tie_breakers.push(Box::new(PopconTieBreaker)); } tie_breakers } pub trait IntoDebianDependency: Dependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> Option>; } impl IntoDebianDependency for DebianDependency { fn try_into_debian_dependency( &self, _apt: &crate::debian::apt::AptManager, ) -> Option> { Some(vec![self.clone()]) } } pub trait FromDebianDependency { fn from_debian_dependency(dependency: &DebianDependency) -> Option>; } pub fn extract_upstream_dependency(dep: &DebianDependency) -> Option> { crate::dependencies::RubyGemDependency::from_debian_dependency(dep) .or_else(|| { crate::dependencies::python::PythonPackageDependency::from_debian_dependency(dep) }) .or_else(|| crate::dependencies::RubyGemDependency::from_debian_dependency(dep)) .or_else(|| crate::dependencies::CargoCrateDependency::from_debian_dependency(dep)) .or_else(|| crate::dependencies::python::PythonDependency::from_debian_dependency(dep)) } #[cfg(feature = "upstream")] impl crate::upstream::FindUpstream for DebianDependency { fn find_upstream(&self) -> Option { let upstream_dep = extract_upstream_dependency(self)?; crate::upstream::find_upstream(upstream_dep.as_ref()) } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::debian::UnsatisfiedAptDependencies { fn to_dependency(&self) -> Option> { Some(Box::new(DebianDependency::new(&self.0))) } } pub fn extract_simple_exact_version( dep: &DebianDependency, ) -> Option<(String, Option)> { // Extract the package name and exact version from a dependency. Return None // if there are non-1 entries in the dependency, or non-1 relations in the entry or if the // version constraint is not Equal. let mut entries = dep.0.entries(); let first_entry = entries.next()?; if entries.next().is_some() { return None; } let mut relations = first_entry.relations(); let first_relation = relations.next()?; if relations.next().is_some() { return None; } let name = first_relation.name(); let version = match first_relation.version() { Some((VersionConstraint::Equal, v)) => Some(v), None => None, _ => return None, }; Some((name.to_string(), version)) } pub fn extract_simple_min_version( dep: &DebianDependency, ) -> Option<(String, Option)> { // Extract the package name and minimum version from a dependency. Return None // if there are non-1 entries in the dependency, or non-1 relations in the entry or if the // version constraint is not GreaterThanEqual or absent. let mut entries = dep.0.entries(); let first_entry = entries.next()?; if entries.next().is_some() { return None; } let mut relations = first_entry.relations(); let first_relation = relations.next()?; if relations.next().is_some() { return None; } let name = first_relation.name(); let version = match first_relation.version() { Some((VersionConstraint::GreaterThanEqual, v)) => Some(v), None => None, _ => return None, }; Some((name.to_string(), version)) } pub fn valid_debian_package_name(name: &str) -> bool { lazy_regex::regex_is_match!("[a-z0-9][a-z0-9+-\\.]+", name) } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum DebianDependencyCategory { Runtime, Build, Install, Test(String), } #[cfg(test)] mod tests { use super::*; use maplit::hashset; #[test] fn test_valid_debian_package_name() { assert!(valid_debian_package_name("libssl-dev")); assert!(valid_debian_package_name("libssl1.1")); assert!(valid_debian_package_name("libssl1.1-dev")); assert!(valid_debian_package_name("libssl1.1-dev~foo")); } #[test] fn test_touches_package() { let dep = DebianDependency::new("libssl-dev"); assert!(dep.touches_package("libssl-dev")); assert!(!dep.touches_package("libssl1.1")); } #[test] fn test_package_names() { let dep = DebianDependency::new("libssl-dev"); assert_eq!(dep.package_names(), hashset! {"libssl-dev".to_string()}); } #[test] fn test_package_names_multiple() { let dep = DebianDependency::new("libssl-dev, libssl1.1"); assert_eq!( dep.package_names(), hashset! {"libssl-dev".to_string(), "libssl1.1".to_string()} ); } #[test] fn test_package_names_multiple_with_version() { let dep = DebianDependency::new("libssl-dev (>= 1.1), libssl1.1 (>= 1.1)"); assert_eq!( dep.package_names(), hashset! {"libssl-dev".to_string(), "libssl1.1".to_string()} ); } #[test] fn test_satisfied_by() { let dep = DebianDependency::new("libssl-dev (>= 1.1), libssl1.1 (>= 1.1)"); let mut versions = std::collections::HashMap::new(); versions.insert("libssl-dev".to_string(), "1.2".parse().unwrap()); versions.insert("libssl1.1".to_string(), "1.2".parse().unwrap()); assert!(dep.satisfied_by(&versions)); } #[test] fn test_satisfied_by_missing_package() { let dep = DebianDependency::new("libssl-dev (>= 1.1), libssl1.1 (>= 1.1)"); let mut versions = std::collections::HashMap::new(); versions.insert("libssl-dev".to_string(), "1.2".parse().unwrap()); assert!(!dep.satisfied_by(&versions)); } #[test] fn test_satisfied_by_missing_version() { let dep = DebianDependency::new("libssl-dev (>= 1.1), libssl1.1 (>= 1.1)"); let mut versions = std::collections::HashMap::new(); versions.insert("libssl-dev".to_string(), "1.2".parse().unwrap()); versions.insert("libssl1.1".to_string(), "1.0".parse().unwrap()); assert!(!dep.satisfied_by(&versions)); } } ognibuild-0.0.32/src/dependencies/go.rs000064400000000000000000000166511046102023000160630ustar 00000000000000use crate::dependency::Dependency; use crate::installer::{Error, Explanation, InstallationScope, Installer}; use crate::session::Session; use serde::{Deserialize, Serialize}; use std::path::Path; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GoPackageDependency { pub package: String, pub version: Option, } impl GoPackageDependency { pub fn new(package: &str, version: Option<&str>) -> Self { Self { package: package.to_string(), version: version.map(|s| s.to_string()), } } pub fn simple(package: &str) -> Self { Self { package: package.to_string(), version: None, } } } impl Dependency for GoPackageDependency { fn family(&self) -> &'static str { "go-package" } fn present(&self, _session: &dyn Session) -> bool { unimplemented!() } fn project_present(&self, session: &dyn Session) -> bool { let mut cmd = vec!["go".to_string(), "list".to_string(), "-f".to_string()]; if let Some(version) = &self.version { cmd.push(format!("{{.Version}} == {}", version)); } else { cmd.push("{{.Version}}".to_string()); } cmd.push(self.package.clone()); session .command(cmd.iter().map(|s| s.as_str()).collect()) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for GoPackageDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let names = apt .get_packages_for_paths( vec![Path::new("/usr/share/gocode/src") .join(regex::escape(&self.package)) .join(".*") .to_str() .unwrap()], true, false, ) .unwrap(); if names.is_empty() { return None; } Some( names .iter() .map(|name| crate::dependencies::debian::DebianDependency::new(name)) .collect(), ) } } #[cfg(feature = "debian")] impl crate::dependencies::debian::FromDebianDependency for GoPackageDependency { fn from_debian_dependency( dependency: &super::debian::DebianDependency, ) -> Option> { let (package, version) = crate::dependencies::debian::extract_simple_exact_version(&dependency)?; let (_, package) = lazy_regex::regex_captures!(r"golang-(.*)-dev", &package)?; let mut parts = package.split('-').collect::>(); if parts[0] == "github" { parts[1] = "github.com"; } if parts[0] == "gopkg" { parts[1] = "gopkg.in"; } Some(Box::new(GoPackageDependency::new( &parts.join("/"), version.map(|s| s.to_string()).as_deref(), ))) } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingGoPackage { fn to_dependency(&self) -> Option> { Some(Box::new(GoPackageDependency::simple(&self.package))) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GoDependency { pub version: Option, } impl GoDependency { pub fn new(version: Option<&str>) -> Self { Self { version: version.map(|s| s.to_string()), } } } impl Dependency for GoDependency { fn family(&self) -> &'static str { "go" } fn present(&self, session: &dyn Session) -> bool { let mut cmd = vec!["go".to_string(), "version".to_string()]; if let Some(version) = &self.version { cmd.push(format!(">={}", version)); } session .command(cmd.iter().map(|s| s.as_str()).collect()) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn project_present(&self, _session: &dyn Session) -> bool { unimplemented!() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "upstream")] impl crate::upstream::FindUpstream for GoPackageDependency { fn find_upstream(&self) -> Option { upstream_ontologist::providers::go::remote_go_metadata(&self.package).ok() } } pub struct GoResolver<'a> { session: &'a dyn Session, } impl<'a> GoResolver<'a> { pub fn new(session: &'a dyn Session) -> Self { Self { session } } fn cmd(&self, reqs: &[&GoPackageDependency]) -> Vec { let mut cmd = vec!["go".to_string(), "get".to_string()]; for req in reqs { cmd.push(req.package.clone()); } cmd } } impl<'a> Installer for GoResolver<'a> { fn explain( &self, requirement: &dyn Dependency, _scope: InstallationScope, ) -> Result { let req = requirement .as_any() .downcast_ref::() .ok_or(Error::UnknownDependencyFamily)?; Ok(Explanation { message: format!("Install go package {}", req.package), command: Some(self.cmd(&[&req])), }) } fn install(&self, requirement: &dyn Dependency, scope: InstallationScope) -> Result<(), Error> { let req = requirement .as_any() .downcast_ref::() .ok_or(Error::UnknownDependencyFamily)?; let cmd = self.cmd(&[&req]); let (env, user) = match scope { InstallationScope::User => (std::collections::HashMap::new(), None), InstallationScope::Global => { // TODO(jelmer): Isn't this Debian-specific? ( std::collections::HashMap::from([( "GOPATH".to_string(), "/usr/share/gocode".to_string(), )]), Some("root"), ) } InstallationScope::Vendor => { return Err(Error::UnsupportedScope(scope)); } }; let mut cmd = self .session .command(cmd.iter().map(|s| s.as_str()).collect()) .env(env); if let Some(user) = user { cmd = cmd.user(user); } cmd.run_detecting_problems()?; Ok(()) } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for GoDependency { fn try_into_debian_dependency( &self, _apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { if let Some(version) = &self.version { Some(vec![ crate::dependencies::debian::DebianDependency::new_with_min_version( "golang-go", &version.parse().unwrap(), ), ]) } else { Some(vec![crate::dependencies::debian::DebianDependency::new( "golang-go", )]) } } } ognibuild-0.0.32/src/dependencies/haskell.rs000064400000000000000000000132401046102023000170700ustar 00000000000000use crate::dependency::Dependency; use crate::installer::{Error, Explanation, InstallationScope, Installer}; use crate::session::Session; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct HaskellPackageDependency { package: String, specs: Option>, } impl HaskellPackageDependency { pub fn new(package: &str, specs: Option>) -> Self { Self { package: package.to_string(), specs: specs.map(|v| v.iter().map(|s| s.to_string()).collect()), } } pub fn simple(package: &str) -> Self { Self { package: package.to_string(), specs: None, } } } impl std::str::FromStr for HaskellPackageDependency { type Err = String; fn from_str(s: &str) -> Result { let mut parts = s.splitn(2, ' '); let package = parts.next().ok_or("missing package name")?.to_string(); let specs = parts.next().map(|s| s.split(' ').collect()); Ok(Self::new(&package, specs)) } } fn ghc_pkg_list(session: &dyn Session) -> Vec<(String, String)> { let output = session .command(vec!["ghc-pkg", "list"]) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::null()) .output() .unwrap(); let output = String::from_utf8(output.stdout).unwrap(); output .lines() .filter_map(|line| { if let Some((name, version)) = line.strip_prefix(" ").and_then(|s| s.rsplit_once('-')) { Some((name.to_string(), version.to_string())) } else { None } }) .collect() } impl Dependency for HaskellPackageDependency { fn family(&self) -> &'static str { "haskell-package" } fn present(&self, session: &dyn Session) -> bool { // TODO: Check version ghc_pkg_list(session) .iter() .any(|(name, _version)| name == &self.package) } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for HaskellPackageDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> Option> { let path = format!( "/var/lib/ghc/package\\.conf\\.d/{}\\-.*\\.conf", regex::escape(&self.package) ); let names = apt .get_packages_for_paths(vec![path.as_str()], true, false) .unwrap(); if names.is_empty() { None } else { Some( names .into_iter() .map(|name| super::debian::DebianDependency::new(&name)) .collect(), ) } } } pub struct HackageResolver<'a> { session: &'a dyn Session, } impl<'a> HackageResolver<'a> { pub fn new(session: &'a dyn Session) -> Self { Self { session } } fn cmd( &self, reqs: &[&HaskellPackageDependency], scope: InstallationScope, ) -> Result, Error> { let mut cmd = vec!["cabal".to_string(), "install".to_string()]; match scope { InstallationScope::User => { cmd.push("--user".to_string()); } InstallationScope::Global => {} InstallationScope::Vendor => { return Err(Error::UnsupportedScope(scope)); } } cmd.extend(reqs.iter().map(|req| req.package.clone())); Ok(cmd) } } impl<'a> Installer for HackageResolver<'a> { fn install(&self, requirement: &dyn Dependency, scope: InstallationScope) -> Result<(), Error> { let requirement = requirement .as_any() .downcast_ref::() .ok_or(Error::UnknownDependencyFamily)?; let user = if scope != InstallationScope::Global { None } else { Some("root") }; let cmd = self.cmd(&[requirement], scope)?; log::info!("Hackage: running {:?}", cmd); let mut cmd = self .session .command(cmd.iter().map(|x| x.as_str()).collect()); if let Some(user) = user { cmd = cmd.user(user); } cmd.run_detecting_problems()?; Ok(()) } fn explain( &self, requirement: &dyn Dependency, scope: InstallationScope, ) -> Result { if let Some(requirement) = requirement .as_any() .downcast_ref::() { let cmd = self.cmd(&[requirement], scope)?; Ok(Explanation { message: format!("Install Haskell package {}", requirement.package), command: Some(cmd), }) } else { Err(Error::UnknownDependencyFamily) } } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingHaskellDependencies { fn to_dependency(&self) -> Option> { let d: HaskellPackageDependency = self.0[0].parse().unwrap(); Some(Box::new(d)) } } #[cfg(feature = "upstream")] impl crate::upstream::FindUpstream for HaskellPackageDependency { fn find_upstream(&self) -> Option { let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(upstream_ontologist::providers::haskell::remote_hackage_data(&self.package)).ok() } } ognibuild-0.0.32/src/dependencies/java.rs000064400000000000000000000145351046102023000163760ustar 00000000000000use crate::dependency::Dependency; use crate::session::Session; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct JavaClassDependency { classname: String, } impl JavaClassDependency { pub fn new(classname: &str) -> Self { Self { classname: classname.to_string(), } } } impl Dependency for JavaClassDependency { fn family(&self) -> &'static str { "java-class" } fn present(&self, _session: &dyn Session) -> bool { todo!() } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for JavaClassDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { apt.satisfy(vec![crate::debian::apt::SatisfyEntry::Required( "java-propose-classpath".to_string(), )]) .unwrap(); let output = String::from_utf8( apt.session() .command(vec![ "java-propose-classpath", &format!("-c{}", &self.classname), ]) .check_output() .unwrap(), ) .unwrap(); let classpath = output .trim_matches(':') .trim() .split(':') .collect::>(); if classpath.is_empty() { None } else { Some( classpath .iter() .map(|path| crate::dependencies::debian::DebianDependency::new(path)) .collect(), ) } } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingJavaClass { fn to_dependency(&self) -> Option> { Some(Box::new(JavaClassDependency::new(&self.classname))) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct JDKDependency; impl Dependency for JDKDependency { fn family(&self) -> &'static str { "jdk" } fn present(&self, session: &dyn Session) -> bool { session .command(vec!["javac", "-version"]) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn project_present(&self, _session: &dyn Session) -> bool { false } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for JDKDependency { fn try_into_debian_dependency( &self, _apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { Some(vec![crate::dependencies::debian::DebianDependency::new( "default-jdk", )]) } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingJDK { fn to_dependency(&self) -> Option> { Some(Box::new(JDKDependency)) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct JREDependency; impl Dependency for JREDependency { fn family(&self) -> &'static str { "jre" } fn present(&self, session: &dyn Session) -> bool { session .command(vec!["java", "-version"]) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn project_present(&self, _session: &dyn Session) -> bool { false } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for JREDependency { fn try_into_debian_dependency( &self, _apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { Some(vec![crate::dependencies::debian::DebianDependency::new( "default-jre", )]) } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingJRE { fn to_dependency(&self) -> Option> { Some(Box::new(JREDependency)) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct JDKFileDependency { jdk_path: std::path::PathBuf, filename: String, } impl JDKFileDependency { pub fn new(jdk_path: &str, filename: &str) -> Self { Self { jdk_path: std::path::PathBuf::from(jdk_path.to_string()), filename: filename.to_string(), } } pub fn path(&self) -> std::path::PathBuf { self.jdk_path.join(&self.filename) } } impl Dependency for JDKFileDependency { fn family(&self) -> &'static str { "jdk-file" } fn present(&self, _session: &dyn Session) -> bool { self.path().exists() } fn project_present(&self, _session: &dyn Session) -> bool { false } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for JDKFileDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let path = regex::escape(self.jdk_path.to_str().unwrap()) + ".*/" + ®ex::escape(self.filename.as_str()); let names = apt .get_packages_for_paths(vec![path.as_str()], true, false) .unwrap(); if names.is_empty() { None } else { Some( names .iter() .map(|name| crate::dependencies::debian::DebianDependency::simple(name)) .collect(), ) } } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingJDKFile { fn to_dependency(&self) -> Option> { Some(Box::new(JDKFileDependency::new( &self.jdk_path, &self.filename, ))) } } ognibuild-0.0.32/src/dependencies/latex.rs000064400000000000000000000077271046102023000165770ustar 00000000000000use crate::analyze::AnalyzedError; use crate::dependency::Dependency; use crate::installer::{Error, Explanation, InstallationScope, Installer}; use crate::session::Session; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LatexPackageDependency { pub package: String, } impl LatexPackageDependency { pub fn new(package: &str) -> Self { Self { package: package.to_string(), } } } impl Dependency for LatexPackageDependency { fn family(&self) -> &'static str { "latex-package" } fn present(&self, _session: &dyn Session) -> bool { todo!() } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingLatexFile { fn to_dependency(&self) -> Option> { if let Some(filename) = self.0.strip_suffix(".sty") { Some(Box::new(LatexPackageDependency::new(filename))) } else { None } } } pub struct TlmgrResolver<'a> { session: &'a dyn Session, repository: String, } impl<'a> TlmgrResolver<'a> { pub fn new(session: &'a dyn Session, repository: &str) -> Self { Self { session, repository: repository.to_string(), } } fn cmd( &self, reqs: &[&LatexPackageDependency], scope: InstallationScope, ) -> Result, Error> { let mut ret = vec![ "tlmgr".to_string(), format!("--repository={}", self.repository), "install".to_string(), ]; match scope { InstallationScope::User => { ret.push("--usermode".to_string()); } InstallationScope::Global => {} InstallationScope::Vendor => { return Err(Error::UnsupportedScope(scope)); } } ret.extend(reqs.iter().map(|req| req.package.clone())); Ok(ret) } } impl<'a> Installer for TlmgrResolver<'a> { fn explain( &self, dep: &dyn Dependency, scope: InstallationScope, ) -> Result { let dep = dep .as_any() .downcast_ref::() .ok_or(Error::UnknownDependencyFamily)?; let cmd = self.cmd(&[dep], scope)?; Ok(Explanation { message: format!("Install the LaTeX package {}", dep.package), command: Some(cmd), }) } fn install(&self, dep: &dyn Dependency, scope: InstallationScope) -> Result<(), Error> { let dep = dep .as_any() .downcast_ref::() .ok_or(Error::UnknownDependencyFamily)?; let cmd = self.cmd(&[dep], scope)?; log::info!("tlmgr: running {:?}", cmd); match self .session .command(cmd.iter().map(|x| x.as_str()).collect()) .run_detecting_problems() { Ok(_) => Ok(()), Err(AnalyzedError::Unidentified { lines, retcode, secondary, }) => { if lines.contains( &"tlmgr: user mode not initialized, please read the documentation!".to_string(), ) { self.session .command(vec!["tlmgr", "init-usertree"]) .check_call()?; Ok(()) } else { Err(Error::AnalyzedError(AnalyzedError::Unidentified { retcode, lines, secondary, })) } } Err(e) => Err(e.into()), } } } pub fn ctan<'a>(session: &'a dyn Session) -> TlmgrResolver<'a> { TlmgrResolver::new(session, "ctan") } ognibuild-0.0.32/src/dependencies/mod.rs000064400000000000000000001620511046102023000162310ustar 00000000000000use crate::buildlog::ToDependency; use crate::dependency::Dependency; use crate::session::Session; use serde::{Deserialize, Serialize}; use std::collections::HashSet; use std::path::PathBuf; pub mod autoconf; #[cfg(feature = "debian")] pub mod debian; pub mod go; pub mod haskell; pub mod java; pub mod latex; pub mod node; pub mod octave; pub mod perl; pub mod php; pub mod pytest; pub mod python; pub mod r; pub mod vague; pub mod xml; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BinaryDependency { binary_name: String, } impl BinaryDependency { pub fn new(binary_name: &str) -> Self { Self { binary_name: binary_name.to_string(), } } } impl Dependency for BinaryDependency { fn family(&self) -> &'static str { "binary" } fn present(&self, session: &dyn Session) -> bool { session .command(vec!["which", &self.binary_name]) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } impl ToDependency for buildlog_consultant::problems::common::MissingCommand { fn to_dependency(&self) -> Option> { Some(Box::new(BinaryDependency::new(&self.0))) } } impl ToDependency for buildlog_consultant::problems::common::MissingCommandOrBuildFile { fn to_dependency(&self) -> Option> { Some(Box::new(BinaryDependency::new(&self.filename))) } } const BIN_PATHS: &[&str] = &["/usr/bin", "/bin"]; #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for BinaryDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let paths = if std::path::Path::new(&self.binary_name).is_absolute() { vec![self.binary_name.clone()] } else { BIN_PATHS .iter() .map(|p| format!("{}/{}", p, self.binary_name)) .collect() }; // TODO(jelmer): Check for binaries which use alternatives Some( apt.get_packages_for_paths(paths.iter().map(|x| x.as_str()).collect(), false, false) .unwrap() .iter() .map(|p| crate::dependencies::debian::DebianDependency::simple(p.as_str())) .collect(), ) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VcsControlDirectoryAccessDependency { pub vcs: Vec, } impl VcsControlDirectoryAccessDependency { pub fn new(vcs: Vec<&str>) -> Self { Self { vcs: vcs.iter().map(|s| s.to_string()).collect(), } } } impl Dependency for VcsControlDirectoryAccessDependency { fn family(&self) -> &'static str { "vcs-access" } fn project_present(&self, session: &dyn Session) -> bool { self.vcs.iter().all(|vcs| match vcs.as_str() { "git" => session .command(vec!["git", "rev-parse", "--is-inside-work-tree"]) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success(), _ => todo!(), }) } fn present(&self, _session: &dyn Session) -> bool { false } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for VcsControlDirectoryAccessDependency { fn try_into_debian_dependency( &self, _apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let pkgs = self .vcs .iter() .filter_map(|vcs| match vcs.as_str() { "git" => Some("git"), "hg" => Some("mercurial"), "svn" => Some("subversion"), "bzr" => Some("bzr"), _ => { log::warn!("Unknown VCS {}", vcs); None } }) .collect::>(); let rels: Vec = pkgs.iter().map(|p| p.parse().unwrap()).collect(); Some( rels.into_iter() .map(|p| crate::dependencies::debian::DebianDependency::from(p)) .collect(), ) } } impl ToDependency for buildlog_consultant::problems::common::VcsControlDirectoryNeeded { fn to_dependency(&self) -> Option> { Some(Box::new(VcsControlDirectoryAccessDependency::new( self.vcs.iter().map(|s| s.as_str()).collect(), ))) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LuaModuleDependency { module: String, } impl LuaModuleDependency { pub fn new(module: &str) -> Self { Self { module: module.to_string(), } } } impl Dependency for LuaModuleDependency { fn family(&self) -> &'static str { "lua-module" } fn present(&self, session: &dyn Session) -> bool { // lua -e 'package_name = "socket"; status, _ = pcall(require, package_name); if status then os.exit(0) else os.exit(1) end' session .command(vec![ "lua", "-e", &format!( r#"package_name = "{}"; status, _ = pcall(require, package_name); if status then os.exit(0) else os.exit(1) end"#, self.module ), ]) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } impl ToDependency for buildlog_consultant::problems::common::MissingLuaModule { fn to_dependency(&self) -> Option> { Some(Box::new(LuaModuleDependency::new(&self.0))) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CargoCrateDependency { pub name: String, pub features: Option>, pub api_version: Option, pub minimum_version: Option, } impl CargoCrateDependency { pub fn new(name: &str, features: Option>, api_version: Option<&str>) -> Self { Self { name: name.to_string(), features: features.map(|v| v.iter().map(|s| s.to_string()).collect()), api_version: api_version.map(|s| s.to_string()), minimum_version: None, } } pub fn simple(name: &str) -> Self { Self { name: name.to_string(), features: None, api_version: None, minimum_version: None, } } } impl Dependency for CargoCrateDependency { fn family(&self) -> &'static str { "cargo-crate" } fn present(&self, _session: &dyn Session) -> bool { todo!() } fn project_present(&self, session: &dyn Session) -> bool { let mut cmd = vec!["cargo".to_string(), "metadata".to_string()]; if let Some(api_version) = &self.api_version { cmd.push(format!("--version={}", api_version)); } let output = session .command(cmd.iter().map(|s| s.as_str()).collect()) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::null()) .output() .unwrap(); let metadata: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); let packages = metadata["packages"].as_array().unwrap(); packages.iter().any(|package| { package["name"] == self.name && (self.features.is_none() || package["features"].as_array().unwrap().iter().all(|f| { self.features .as_ref() .unwrap() .contains(&f.as_str().unwrap().to_string()) })) }) } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for CargoCrateDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let path = format!( "/usr/share/cargo/registry/{}\\-[0-9]+.*/Cargo\\.toml", self.name ); Some( apt.get_packages_for_paths(vec![&path], true, false) .unwrap() .iter() .map(|p| { if self.api_version.is_some() { crate::dependencies::debian::DebianDependency::new_with_min_version( p.as_str(), &self.api_version.as_ref().unwrap().parse().unwrap(), ) } else { crate::dependencies::debian::DebianDependency::simple(p.as_str()) } }) .collect(), ) } } impl ToDependency for buildlog_consultant::problems::common::MissingCargoCrate { fn to_dependency(&self) -> Option> { Some(Box::new(CargoCrateDependency::new( &self.crate_name, None, None, ))) } } #[cfg(feature = "debian")] impl crate::dependencies::debian::FromDebianDependency for CargoCrateDependency { fn from_debian_dependency( dependency: &crate::dependencies::debian::DebianDependency, ) -> Option> { let (name, min_version) = crate::dependencies::debian::extract_simple_min_version(dependency)?; let (_, name, api_version, features) = lazy_regex::regex_captures!(r"librust-(.*)-([^-+]+)(\+.*?)-dev", &name)?; let features = if features.is_empty() { HashSet::new() } else { features[1..].split("-").collect::>() }; Some(Box::new(Self { name: name.to_string(), api_version: Some(api_version.to_string()), features: Some(features.into_iter().map(|t| t.to_string()).collect()), minimum_version: min_version.map(|v| v.upstream_version), })) } } impl ToDependency for buildlog_consultant::problems::common::MissingRustCompiler { fn to_dependency(&self) -> Option> { Some(Box::new(BinaryDependency::new("rustc"))) } } #[cfg(feature = "upstream")] impl crate::upstream::FindUpstream for CargoCrateDependency { fn find_upstream(&self) -> Option { let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(upstream_ontologist::providers::rust::remote_crate_data(&self.name)).ok() } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PkgConfigDependency { module: String, minimum_version: Option, } impl PkgConfigDependency { pub fn new(module: &str, minimum_version: Option<&str>) -> Self { Self { module: module.to_string(), minimum_version: minimum_version.map(|s| s.to_string()), } } pub fn simple(module: &str) -> Self { Self { module: module.to_string(), minimum_version: None, } } } impl Dependency for PkgConfigDependency { fn family(&self) -> &'static str { "pkg-config" } fn present(&self, session: &dyn Session) -> bool { log::debug!("Checking for pkg-config module {}", self.module); let cmd = [ "pkg-config".to_string(), "--exists".to_string(), if let Some(minimum_version) = &self.minimum_version { format!("{} >= {}", self.module, minimum_version) } else { self.module.clone() }, ]; let result = session .command(cmd.iter().map(|s| s.as_str()).collect()) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success(); if !result { log::debug!("pkg-config module {} not found", self.module); } else { log::debug!("pkg-config module {} found", self.module); } result } fn project_present(&self, _session: &dyn Session) -> bool { false } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for PkgConfigDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let mut names = apt .get_packages_for_paths( [format!( "/usr/lib/.*/pkgconfig/{}\\.pc", regex::escape(&self.module) )] .iter() .map(|s| s.as_str()) .collect(), true, false, ) .unwrap(); if names.is_empty() { names = apt .get_packages_for_paths( [ format!("/usr/lib/pkgconfig/{}\\.pc", regex::escape(&self.module)), format!("/usr/share/pkgconfig/{}\\.pc", regex::escape(&self.module)), ] .iter() .map(|s| s.as_str()) .collect(), true, false, ) .unwrap(); } if names.is_empty() { return None; } Some(if let Some(minimum_version) = &self.minimum_version { let minimum_version: debversion::Version = minimum_version.parse().unwrap(); names .into_iter() .map(|name| { crate::dependencies::debian::DebianDependency::new_with_min_version( &name, &minimum_version, ) }) .collect() } else { names .into_iter() .map(|name| crate::dependencies::debian::DebianDependency::simple(&name)) .collect() }) } } impl ToDependency for buildlog_consultant::problems::common::MissingPkgConfig { fn to_dependency(&self) -> Option> { Some(Box::new(PkgConfigDependency::new( &self.module, self.minimum_version.as_ref().map(|s| s.as_str()), ))) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PathDependency { path: PathBuf, } impl From for PathDependency { fn from(path: PathBuf) -> Self { Self { path } } } impl PathDependency { pub fn new(path: &str) -> Self { Self { path: PathBuf::from(path), } } } impl Dependency for PathDependency { fn family(&self) -> &'static str { "path" } fn present(&self, _session: &dyn Session) -> bool { self.path.exists() } fn project_present(&self, _session: &dyn Session) -> bool { if self.path.is_absolute() { false } else { self.path.exists() } } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for PathDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { Some( apt.get_packages_for_paths(vec![self.path.to_str().unwrap()], false, false) .unwrap() .iter() .map(|p| crate::dependencies::debian::DebianDependency::simple(p.as_str())) .collect(), ) } } impl ToDependency for buildlog_consultant::problems::common::MissingFile { fn to_dependency(&self) -> Option> { Some(Box::new(PathDependency { path: PathBuf::from(&self.path), })) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CHeaderDependency { header: String, } impl CHeaderDependency { pub fn new(header: &str) -> Self { Self { header: header.to_string(), } } } impl Dependency for CHeaderDependency { fn family(&self) -> &'static str { "c-header" } fn present(&self, session: &dyn Session) -> bool { session .command(vec![ "sh", "-c", &format!("echo \"#include <{}>\" | cc -E -", self.header), ]) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for CHeaderDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let mut deps = apt .get_packages_for_paths( vec![std::path::Path::new("/usr/include") .join(&self.header) .to_str() .unwrap()], false, false, ) .unwrap(); if deps.is_empty() { deps = apt .get_packages_for_paths( vec![std::path::Path::new("/usr/include") .join(".*") .join(&self.header) .to_str() .unwrap()], true, false, ) .unwrap(); } if deps.is_empty() { return None; } Some( deps.into_iter() .map(|name| crate::dependencies::debian::DebianDependency::simple(&name)) .collect(), ) } } impl ToDependency for buildlog_consultant::problems::common::MissingCHeader { fn to_dependency(&self) -> Option> { Some(Box::new(CHeaderDependency::new(&self.header))) } } #[derive(Debug, Clone)] pub struct JavaScriptRuntimeDependency; impl Dependency for JavaScriptRuntimeDependency { fn family(&self) -> &'static str { "javascript-runtime" } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn present(&self, session: &dyn Session) -> bool { session .command(vec!["node", "-e", "process.exit(0)"]) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for JavaScriptRuntimeDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let paths = vec!["/usr/bin/node", "/usr/bin/duk"]; Some( apt.get_packages_for_paths(paths, false, false) .map(|p| { p.iter() .map(|p| crate::dependencies::debian::DebianDependency::simple(p.as_str())) .collect() }) .unwrap(), ) } } impl ToDependency for buildlog_consultant::problems::common::MissingJavaScriptRuntime { fn to_dependency(&self) -> Option> { Some(Box::new(JavaScriptRuntimeDependency)) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ValaPackageDependency { package: String, } impl ValaPackageDependency { pub fn new(package: &str) -> Self { Self { package: package.to_string(), } } } impl Dependency for ValaPackageDependency { fn family(&self) -> &'static str { "vala-package" } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn present(&self, session: &dyn Session) -> bool { session .command(vec!["pkg-config", "--exists", &self.package]) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for ValaPackageDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { Some( apt.get_packages_for_paths( vec![&format!( "/usr/share/vala-[.0-9]+/vapi/{}\\.vapi", regex::escape(&self.package) )], true, false, ) .unwrap() .iter() .map(|p| crate::dependencies::debian::DebianDependency::simple(p.as_str())) .collect(), ) } } impl ToDependency for buildlog_consultant::problems::common::MissingValaPackage { fn to_dependency(&self) -> Option> { Some(Box::new(ValaPackageDependency::new(&self.0))) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RubyGemDependency { gem: String, minimum_version: Option, } impl RubyGemDependency { pub fn new(gem: &str, minimum_version: Option<&str>) -> Self { Self { gem: gem.to_string(), minimum_version: minimum_version.map(|s| s.to_string()), } } pub fn simple(gem: &str) -> Self { Self { gem: gem.to_string(), minimum_version: None, } } } impl Dependency for RubyGemDependency { fn family(&self) -> &'static str { "ruby-gem" } fn project_present(&self, session: &dyn Session) -> bool { let mut cmd = vec!["bundle".to_string(), "list".to_string()]; if let Some(minimum_version) = &self.minimum_version { cmd.push(format!(">={}", minimum_version)); } cmd.push(self.gem.clone()); session .command(cmd.iter().map(|s| s.as_str()).collect()) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn present(&self, session: &dyn Session) -> bool { let mut cmd = vec!["gem".to_string(), "list".to_string(), "--local".to_string()]; if let Some(minimum_version) = &self.minimum_version { cmd.push(format!(">={}", minimum_version)); } session .command(cmd.iter().map(|s| s.as_str()).collect()) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for RubyGemDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let names = apt .get_packages_for_paths( vec![ std::path::Path::new("/usr/share/rubygems-integration/all/specifications/") .join(format!("{}-.*\\.gemspec", regex::escape(&self.gem)).as_str()) .to_str() .unwrap(), ], true, false, ) .unwrap(); if names.is_empty() { return None; } Some( names .into_iter() .map(|name| { if let Some(min_version) = self.minimum_version.as_ref() { crate::dependencies::debian::DebianDependency::new_with_min_version( &name, &min_version.parse().unwrap(), ) } else { crate::dependencies::debian::DebianDependency::simple(&name) } }) .collect(), ) } } #[cfg(feature = "debian")] impl crate::dependencies::debian::FromDebianDependency for RubyGemDependency { fn from_debian_dependency( dependency: &crate::dependencies::debian::DebianDependency, ) -> Option> { let (name, min_version) = crate::dependencies::debian::extract_simple_min_version(dependency)?; let (_, name) = lazy_regex::regex_captures!(r"ruby-(.*)", &name)?; Some(Box::new(Self { gem: name.to_string(), minimum_version: min_version.map(|v| v.upstream_version.to_string()), })) } } impl ToDependency for buildlog_consultant::problems::common::MissingRubyGem { fn to_dependency(&self) -> Option> { Some(Box::new(RubyGemDependency::new( &self.gem, self.version.as_ref().map(|s| s.as_str()), ))) } } #[cfg(feature = "upstream")] impl crate::upstream::FindUpstream for RubyGemDependency { fn find_upstream(&self) -> Option { let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(upstream_ontologist::providers::ruby::remote_rubygem_metadata(&self.gem)).ok() } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DhAddonDependency { addon: String, } impl DhAddonDependency { pub fn new(addon: &str) -> Self { Self { addon: addon.to_string(), } } } impl Dependency for DhAddonDependency { fn family(&self) -> &'static str { "dh-addon" } fn present(&self, _session: &dyn Session) -> bool { todo!() } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for DhAddonDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { Some( apt.get_packages_for_paths( vec![&format!( "/usr/share/perl5/Debian/Debhelper/Sequence/{}.pm", regex::escape(&self.addon) )], true, false, ) .unwrap() .iter() .map(|p| crate::dependencies::debian::DebianDependency::simple(p.as_str())) .collect(), ) } } impl ToDependency for buildlog_consultant::problems::common::DhAddonLoadFailure { fn to_dependency(&self) -> Option> { Some(Box::new(DhAddonDependency::new(&self.path))) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LibraryDependency { library: String, } impl LibraryDependency { pub fn new(library: &str) -> Self { Self { library: library.to_string(), } } } impl Dependency for LibraryDependency { fn family(&self) -> &'static str { "library" } fn present(&self, session: &dyn Session) -> bool { session .command(vec!["ld", "-l", &self.library]) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for LibraryDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let paths = vec![ format!("/usr/lib/lib{}.so", &self.library), format!("/usr/lib/.*/lib{}.so", regex::escape(&self.library)), format!("/usr/lib/lib{}.a", &self.library), format!("/usr/lib/.*/lib{}.a", regex::escape(&self.library)), ]; Some( apt.get_packages_for_paths(paths.iter().map(|x| x.as_str()).collect(), true, false) .unwrap() .iter() .map(|p| crate::dependencies::debian::DebianDependency::simple(p.as_str())) .collect(), ) } } impl ToDependency for buildlog_consultant::problems::common::MissingLibrary { fn to_dependency(&self) -> Option> { Some(Box::new(LibraryDependency::new(&self.0))) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct StaticLibraryDependency { library: String, filename: String, } impl StaticLibraryDependency { pub fn new(library: &str, filename: &str) -> Self { Self { library: library.to_string(), filename: filename.to_string(), } } } impl Dependency for StaticLibraryDependency { fn family(&self) -> &'static str { "static-library" } fn present(&self, _session: &dyn Session) -> bool { todo!() } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for StaticLibraryDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let paths = vec![ format!("/usr/lib/lib{}.a", &self.library), format!("/usr/lib/.*/lib{}.a", regex::escape(&self.library)), ]; Some( apt.get_packages_for_paths(paths.iter().map(|x| x.as_str()).collect(), true, false) .unwrap() .iter() .map(|p| crate::dependencies::debian::DebianDependency::simple(p.as_str())) .collect(), ) } } impl ToDependency for buildlog_consultant::problems::common::MissingStaticLibrary { fn to_dependency(&self) -> Option> { Some(Box::new(StaticLibraryDependency::new( &self.library, &self.filename, ))) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RubyFileDependency { filename: String, } impl RubyFileDependency { pub fn new(filename: &str) -> Self { Self { filename: filename.to_string(), } } } impl Dependency for RubyFileDependency { fn family(&self) -> &'static str { "ruby-file" } fn present(&self, session: &dyn Session) -> bool { session .command(vec!["ruby", "-e", &format!("require '{}'", self.filename)]) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for RubyFileDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let paths = vec![format!( "/usr/lib/ruby/vendor_ruby/{}.rb", regex::escape(&self.filename) )]; let mut names = apt .get_packages_for_paths(paths.iter().map(|x| x.as_str()).collect(), false, false) .unwrap(); if names.is_empty() { let paths = vec![format!( "/usr/share/rubygems\\-integration/all/gems/([^/]+)/lib/{}\\.rb", regex::escape(&self.filename) )]; names = apt .get_packages_for_paths(paths.iter().map(|x| x.as_str()).collect(), true, false) .unwrap(); } if names.is_empty() { return None; } Some( names .iter() .map(|p| crate::dependencies::debian::DebianDependency::simple(p.as_str())) .collect(), ) } } impl ToDependency for buildlog_consultant::problems::common::MissingRubyFile { fn to_dependency(&self) -> Option> { Some(Box::new(RubyFileDependency::new(&self.filename))) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SprocketsFileDependency { content_type: String, name: String, } impl SprocketsFileDependency { pub fn new(content_type: &str, name: &str) -> Self { Self { content_type: content_type.to_string(), name: name.to_string(), } } } impl Dependency for SprocketsFileDependency { fn family(&self) -> &'static str { "sprockets-file" } fn present(&self, session: &dyn Session) -> bool { session .command(vec!["sprockets", "--check", &self.name]) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for SprocketsFileDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let path = match self.content_type.as_str() { "application/javascript" => format!( "/usr/share/,*/app/assets/javascripts/{}\\.js", regex::escape(&self.name) ), _ => return None, }; Some( apt.get_packages_for_paths(vec![&path], true, false) .unwrap() .iter() .map(|p| crate::dependencies::debian::DebianDependency::simple(p.as_str())) .collect(), ) } } impl ToDependency for buildlog_consultant::problems::common::MissingSprocketsFile { fn to_dependency(&self) -> Option> { Some(Box::new(SprocketsFileDependency::new( &self.content_type, &self.name, ))) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CMakeFileDependency { filename: String, version: Option, } impl CMakeFileDependency { pub fn new(filename: &str, version: Option<&str>) -> Self { Self { filename: filename.to_string(), version: version.map(|s| s.to_string()), } } pub fn simple(filename: &str) -> Self { Self { filename: filename.to_string(), version: None, } } } impl Dependency for CMakeFileDependency { fn family(&self) -> &'static str { "cmakefile" } fn present(&self, _session: &dyn Session) -> bool { todo!() } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for CMakeFileDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let paths = vec![ format!("/usr/lib/.*/cmake/.*/{}", regex::escape(&self.filename)), format!("/usr/share/.*/cmake/{}", regex::escape(&self.filename)), ]; Some( apt.get_packages_for_paths(paths.iter().map(|x| x.as_str()).collect(), true, false) .unwrap() .iter() .map(|p| crate::dependencies::debian::DebianDependency::simple(p.as_str())) .collect(), ) } } impl ToDependency for buildlog_consultant::problems::common::CMakeFilesMissing { fn to_dependency(&self) -> Option> { Some(Box::new(CMakeFileDependency::new( &self.filenames[0], self.version.as_ref().map(|s| s.as_str()), ))) } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)] pub enum MavenArtifactKind { #[default] Jar, Pom, } impl std::fmt::Display for MavenArtifactKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { MavenArtifactKind::Jar => write!(f, "jar"), MavenArtifactKind::Pom => write!(f, "pom"), } } } impl std::str::FromStr for MavenArtifactKind { type Err = String; fn from_str(s: &str) -> Result { match s { "jar" => Ok(MavenArtifactKind::Jar), "pom" => Ok(MavenArtifactKind::Pom), _ => Err("Invalid Maven artifact kind".to_string()), } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MavenArtifactDependency { pub group_id: String, pub artifact_id: String, pub version: Option, pub kind: Option, } impl MavenArtifactDependency { pub fn new( group_id: &str, artifact_id: &str, version: Option<&str>, kind: Option<&str>, ) -> Self { Self { group_id: group_id.to_string(), artifact_id: artifact_id.to_string(), version: version.map(|s| s.to_string()), kind: kind.map(|s| s.parse().unwrap()), } } pub fn simple(group_id: &str, artifact_id: &str) -> Self { Self { group_id: group_id.to_string(), artifact_id: artifact_id.to_string(), version: None, kind: None, } } } impl From<(String, String)> for MavenArtifactDependency { fn from((group_id, artifact_id): (String, String)) -> Self { Self { group_id, artifact_id, version: None, kind: Some(MavenArtifactKind::Jar), } } } impl From<(String, String, String)> for MavenArtifactDependency { fn from((group_id, artifact_id, version): (String, String, String)) -> Self { Self { group_id, artifact_id, version: Some(version), kind: Some(MavenArtifactKind::Jar), } } } impl From<(String, String, String, String)> for MavenArtifactDependency { fn from((group_id, artifact_id, version, kind): (String, String, String, String)) -> Self { Self { group_id, artifact_id, version: Some(version), kind: Some(kind.parse().unwrap()), } } } impl Dependency for MavenArtifactDependency { fn family(&self) -> &'static str { "maven-artifact" } fn present(&self, _session: &dyn Session) -> bool { todo!() } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for MavenArtifactDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let group_id = self.group_id.replace(".", "/"); let kind = self.kind.clone().unwrap_or_default().to_string(); let (path, regex) = if let Some(version) = self.version.as_ref() { ( std::path::Path::new("/usr/share/maven-repo") .join(group_id) .join(&self.artifact_id) .join(version) .join(format!("{}-{}.{}", self.artifact_id, version, kind)), true, ) } else { ( std::path::Path::new("/usr/share/maven-repo") .join(regex::escape(&group_id)) .join(regex::escape(&self.artifact_id)) .join(".*") .join(format!( "{}-.*\\.{}", regex::escape(&self.artifact_id), kind )), false, ) }; let names = apt .get_packages_for_paths(vec![path.to_str().unwrap()], regex, false) .unwrap(); if names.is_empty() { return None; } Some( names .iter() .map(|p| crate::dependencies::debian::DebianDependency::simple(p.as_str())) .collect(), ) } } impl std::str::FromStr for MavenArtifactDependency { type Err = String; fn from_str(s: &str) -> Result { let parts: Vec<&str> = s.split(':').collect(); match parts.len() { 2 => Ok(Self::from((parts[0].to_string(), parts[1].to_string()))), 3 => Ok(Self::from(( parts[0].to_string(), parts[1].to_string(), parts[2].to_string(), ))), 4 => Ok(Self::from(( parts[0].to_string(), parts[1].to_string(), parts[2].to_string(), parts[3].to_string(), ))), _ => Err("Invalid Maven artifact dependency".to_string()), } } } impl ToDependency for buildlog_consultant::problems::common::MissingMavenArtifacts { fn to_dependency(&self) -> Option> { let text = self.0[0].as_str(); let d: MavenArtifactDependency = text.parse().unwrap(); Some(Box::new(d)) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GnomeCommonDependency; impl Dependency for GnomeCommonDependency { fn family(&self) -> &'static str { "gnome-common" } fn present(&self, session: &dyn Session) -> bool { session .command(vec!["gnome-autogen.sh", "--version"]) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn project_present(&self, _session: &dyn Session) -> bool { false } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for GnomeCommonDependency { fn try_into_debian_dependency( &self, _apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { Some(vec![crate::dependencies::debian::DebianDependency::new( "gnome-common", )]) } } impl ToDependency for buildlog_consultant::problems::common::MissingGnomeCommonDependency { fn to_dependency(&self) -> Option> { Some(Box::new(GnomeCommonDependency)) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct QtModuleDependency { module: String, } impl QtModuleDependency { pub fn new(module: &str) -> Self { Self { module: module.to_string(), } } } impl Dependency for QtModuleDependency { fn family(&self) -> &'static str { "qt-module" } fn present(&self, _session: &dyn Session) -> bool { todo!() } fn project_present(&self, _session: &dyn Session) -> bool { false } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for QtModuleDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let names = apt .get_packages_for_paths( vec![&format!( "/usr/lib/.*/qt5/mkspecs/modules/qt_lib_{}\\.pri", regex::escape(&self.module) )], true, false, ) .unwrap(); if names.is_empty() { return None; } Some( names .iter() .map(|p| crate::dependencies::debian::DebianDependency::simple(p.as_str())) .collect(), ) } } impl ToDependency for buildlog_consultant::problems::common::MissingQtModules { fn to_dependency(&self) -> Option> { Some(Box::new(QtModuleDependency::new(&self.0[0]))) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct QTDependency; impl Dependency for QTDependency { fn family(&self) -> &'static str { "qt" } fn present(&self, _session: &dyn Session) -> bool { todo!() } fn project_present(&self, _session: &dyn Session) -> bool { false } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for QTDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let names = apt .get_packages_for_paths(vec!["/usr/lib/.*/qt[0-9]+/bin/qmake"], true, false) .unwrap(); if names.is_empty() { return None; } Some( names .iter() .map(|p| crate::dependencies::debian::DebianDependency::simple(p.as_str())) .collect(), ) } } impl ToDependency for buildlog_consultant::problems::common::MissingQt { fn to_dependency(&self) -> Option> { Some(Box::new(QTDependency)) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct X11Dependency; impl Dependency for X11Dependency { fn family(&self) -> &'static str { "x11" } fn present(&self, session: &dyn Session) -> bool { // Does the X binary exist? crate::session::which(session, "X").is_some() } fn project_present(&self, _session: &dyn Session) -> bool { false } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for X11Dependency { fn try_into_debian_dependency( &self, _apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { Some(vec![crate::dependencies::debian::DebianDependency::new( "libx11-dev", )]) } } impl ToDependency for buildlog_consultant::problems::common::MissingX11 { fn to_dependency(&self) -> Option> { Some(Box::new(X11Dependency)) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CertificateAuthorityDependency { url: String, } impl CertificateAuthorityDependency { pub fn new(url: &str) -> Self { Self { url: url.to_string(), } } } impl Dependency for CertificateAuthorityDependency { fn family(&self) -> &'static str { "certificate-authority" } fn present(&self, _session: &dyn Session) -> bool { todo!() } fn project_present(&self, _session: &dyn Session) -> bool { false } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for CertificateAuthorityDependency { fn try_into_debian_dependency( &self, _apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { Some(vec![crate::dependencies::debian::DebianDependency::simple( "ca-certificates", )]) } } impl ToDependency for buildlog_consultant::problems::common::UnknownCertificateAuthority { fn to_dependency(&self) -> Option> { Some(Box::new(CertificateAuthorityDependency::new(&self.0))) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LibtoolDependency; impl Dependency for LibtoolDependency { fn family(&self) -> &'static str { "libtool" } fn present(&self, session: &dyn Session) -> bool { session .command(vec!["libtoolize", "--version"]) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn project_present(&self, _session: &dyn Session) -> bool { false } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for LibtoolDependency { fn try_into_debian_dependency( &self, _apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { Some(vec![crate::dependencies::debian::DebianDependency::new( "libtool", )]) } } impl ToDependency for buildlog_consultant::problems::common::MissingLibtool { fn to_dependency(&self) -> Option> { Some(Box::new(LibtoolDependency)) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BoostComponentDependency { name: String, } impl BoostComponentDependency { pub fn new(name: &str) -> Self { Self { name: name.to_string(), } } } impl Dependency for BoostComponentDependency { fn family(&self) -> &'static str { "boost-component" } fn present(&self, _session: &dyn Session) -> bool { todo!() } fn project_present(&self, _session: &dyn Session) -> bool { false } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for BoostComponentDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let names = apt .get_packages_for_paths( vec![&format!( "/usr/lib/.*/libboost_{}", regex::escape(&self.name) )], true, false, ) .unwrap(); if names.is_empty() { return None; } Some( names .iter() .map(|p| crate::dependencies::debian::DebianDependency::simple(p.as_str())) .collect(), ) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct KF5ComponentDependency { name: String, } impl KF5ComponentDependency { pub fn new(name: &str) -> Self { Self { name: name.to_string(), } } } impl Dependency for KF5ComponentDependency { fn family(&self) -> &'static str { "kf5-component" } fn present(&self, _session: &dyn Session) -> bool { todo!() } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for KF5ComponentDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let names = apt .get_packages_for_paths( vec![&format!( "/usr/lib/.*/cmake/KF5{}/KF5{}Config\\.cmake", regex::escape(&self.name), regex::escape(&self.name) )], true, false, ) .unwrap(); if names.is_empty() { return None; } Some( names .iter() .map(|p| crate::dependencies::debian::DebianDependency::simple(p.as_str())) .collect(), ) } } impl ToDependency for buildlog_consultant::problems::common::MissingCMakeComponents { fn to_dependency(&self) -> Option> { match self.name.as_str() { "Boost" => Some(Box::new(BoostComponentDependency::new(&self.components[0]))), "KF5" => Some(Box::new(KF5ComponentDependency::new(&self.components[0]))), n => { log::warn!("Unknown CMake component: {}", n); None } } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GnulibDirectoryDependency { directory: PathBuf, } impl GnulibDirectoryDependency { pub fn new(directory: &str) -> Self { Self { directory: PathBuf::from(directory), } } } impl Dependency for GnulibDirectoryDependency { fn family(&self) -> &'static str { "gnulib-directory" } fn present(&self, _session: &dyn Session) -> bool { todo!() } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } impl ToDependency for buildlog_consultant::problems::common::MissingGnulibDirectory { fn to_dependency(&self) -> Option> { Some(Box::new(GnulibDirectoryDependency { directory: PathBuf::from(self.0.clone()), })) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct IntrospectionTypelibDependency { library: String, } impl IntrospectionTypelibDependency { pub fn new(library: &str) -> Self { Self { library: library.to_string(), } } } impl Dependency for IntrospectionTypelibDependency { fn family(&self) -> &'static str { "introspection-type-lib" } fn present(&self, _session: &dyn Session) -> bool { todo!() } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } impl ToDependency for buildlog_consultant::problems::common::MissingIntrospectionTypelib { fn to_dependency(&self) -> Option> { Some(Box::new(IntrospectionTypelibDependency::new(&self.0))) } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for IntrospectionTypelibDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> Option> { let names = apt .get_packages_for_paths( vec![&format!( "/usr/lib/.*/girepository\\-.*/{}\\-.*.typelib", regex::escape(&self.library) )], true, false, ) .unwrap(); if names.is_empty() { return None; } Some( names .iter() .map(|p| crate::dependencies::debian::DebianDependency::simple(p.as_str())) .collect(), ) } } impl ToDependency for buildlog_consultant::problems::common::MissingCSharpCompiler { fn to_dependency(&self) -> Option> { Some(Box::new(BinaryDependency::new("mcs"))) } } impl ToDependency for buildlog_consultant::problems::common::MissingXfceDependency { fn to_dependency(&self) -> Option> { match self.package.as_str() { "gtk-doc" => Some(Box::new(BinaryDependency::new("gtkdocize"))), n => { log::warn!("Unknown XFCE dependency: {}", n); None } } } } ognibuild-0.0.32/src/dependencies/node.rs000064400000000000000000000206621046102023000164000ustar 00000000000000use crate::dependencies::BinaryDependency; use crate::dependency::Dependency; use crate::installer::{Error, Explanation, InstallationScope, Installer}; use crate::session::Session; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NodePackageDependency { package: String, } impl NodePackageDependency { pub fn new(package: &str) -> Self { Self { package: package.to_string(), } } } impl Dependency for NodePackageDependency { fn family(&self) -> &'static str { "npm-package" } fn present(&self, session: &dyn Session) -> bool { // npm list -g package-name --depth=0 >/dev/null 2>&1 session .command(vec!["npm", "list", "-g", &self.package, "--depth=0"]) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for NodePackageDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> Option> { let paths = vec![ format!( "/usr/share/nodejs/.*/node_modules/{}/package\\.json", regex::escape(&self.package) ), format!( "/usr/lib/nodejs/{}/package\\.json", regex::escape(&self.package) ), format!( "/usr/share/nodejs/{}/package\\.json", regex::escape(&self.package) ), ]; let names = apt .get_packages_for_paths(paths.iter().map(|p| p.as_str()).collect(), true, false) .unwrap(); if names.is_empty() { None } else { Some( names .into_iter() .map(|name| super::debian::DebianDependency::new(&name)) .collect(), ) } } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingNodePackage { fn to_dependency(&self) -> Option> { Some(Box::new(NodePackageDependency::new(&self.0))) } } #[cfg(feature = "upstream")] impl crate::upstream::FindUpstream for NodePackageDependency { fn find_upstream(&self) -> Option { let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(upstream_ontologist::providers::node::remote_npm_metadata(&self.package)).ok() } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct NodeModuleDependency { module: String, } impl NodeModuleDependency { pub fn new(module: &str) -> Self { Self { module: module.to_string(), } } } impl Dependency for NodeModuleDependency { fn family(&self) -> &'static str { "node-module" } fn present(&self, session: &dyn Session) -> bool { // node -e 'try { require.resolve("express"); process.exit(0); } catch(e) { process.exit(1); }' session .command(vec![ "node", "-e", &format!( r#"try {{ require.resolve("{}"); process.exit(0); }} catch(e) {{ process.exit(1); }}"#, self.module ), ]) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for NodeModuleDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> Option> { let paths = vec![ format!( "/usr/share/nodejs/.*/node_modules/{}/package\\.json", regex::escape(&self.module) ), format!( "/usr/lib/nodejs/{}/package\\.json", regex::escape(&self.module) ), format!( "/usr/share/nodejs/{}/package\\.json", regex::escape(&self.module) ), ]; let names = apt .get_packages_for_paths(paths.iter().map(|p| p.as_str()).collect(), true, false) .unwrap(); if names.is_empty() { None } else { Some( names .into_iter() .map(|name| super::debian::DebianDependency::new(&name)) .collect(), ) } } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingNodeModule { fn to_dependency(&self) -> Option> { Some(Box::new(NodeModuleDependency::new(&self.0))) } } fn command_package(command: &str) -> Option<&str> { match command { "del-cli" => Some("del-cli"), "husky" => Some("husky"), "cross-env" => Some("cross-env"), "xo" => Some("xo"), "standard" => Some("standard"), "jshint" => Some("jshint"), "if-node-version" => Some("if-node-version"), "babel-cli" => Some("babel"), "c8" => Some("c8"), "prettier-standard" => Some("prettier-standard"), _ => None, } } pub struct NpmResolver<'a> { session: &'a dyn Session, } impl<'a> NpmResolver<'a> { pub fn new(session: &'a dyn Session) -> Self { Self { session } } fn cmd( &self, reqs: &[&NodePackageDependency], scope: InstallationScope, ) -> Result, Error> { let mut cmd = vec!["npm".to_string(), "install".to_string()]; match scope { InstallationScope::Global => cmd.push("-g".to_string()), InstallationScope::User => {} InstallationScope::Vendor => { return Err(Error::UnsupportedScope(scope)); } } cmd.extend(reqs.iter().map(|req| req.package.clone())); Ok(cmd) } } impl From for NodePackageDependency { fn from(dep: NodeModuleDependency) -> Self { let parts: Vec<&str> = dep.module.split('/').collect(); Self { // TODO: Is this legit? package: if parts[0].starts_with('@') { parts[..2].join("/") } else { parts[0].to_string() }, } } } fn to_node_package_req(requirement: &dyn Dependency) -> Option { if let Some(requirement) = requirement.as_any().downcast_ref::() { Some(requirement.clone().into()) } else if let Some(requirement) = requirement.as_any().downcast_ref::() { Some(requirement.clone()) } else if let Some(requirement) = requirement.as_any().downcast_ref::() { command_package(&requirement.binary_name).map(NodePackageDependency::new) } else { None } } impl<'a> Installer for NpmResolver<'a> { fn explain( &self, requirement: &dyn Dependency, scope: InstallationScope, ) -> Result { let requirement = to_node_package_req(requirement).ok_or(Error::UnknownDependencyFamily)?; Ok(Explanation { message: format!("install node package {}", requirement.package), command: Some(self.cmd(&[&requirement], scope)?), }) } fn install(&self, requirement: &dyn Dependency, scope: InstallationScope) -> Result<(), Error> { let requirement = to_node_package_req(requirement).ok_or(Error::UnknownDependencyFamily)?; let args = &self.cmd(&[&requirement], scope)?; let mut cmd = self .session .command(args.iter().map(|s| s.as_str()).collect()); match scope { InstallationScope::Global => { cmd = cmd.user("root"); } InstallationScope::User => {} InstallationScope::Vendor => {} } cmd.run_detecting_problems()?; Ok(()) } } ognibuild-0.0.32/src/dependencies/octave.rs000064400000000000000000000107641046102023000167360ustar 00000000000000use crate::dependency::Dependency; use crate::installer::{Error, Explanation, InstallationScope, Installer}; use crate::session::Session; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct OctavePackageDependency { package: String, minimum_version: Option, } impl OctavePackageDependency { pub fn new(package: &str, minimum_version: Option<&str>) -> Self { Self { package: package.to_string(), minimum_version: minimum_version.map(|s| s.to_string()), } } pub fn simple(package: &str) -> Self { Self { package: package.to_string(), minimum_version: None, } } } impl std::str::FromStr for OctavePackageDependency { type Err = String; fn from_str(s: &str) -> Result { if let Some((_, name, min_version)) = lazy_regex::regex_captures!("(.*) \\(>= (.*)\\)", s) { Ok(Self::new(name, Some(min_version))) } else if !s.contains(" ") { Ok(Self::simple(s)) } else { Err(format!("Failed to parse Octave package dependency: {}", s)) } } } impl Dependency for OctavePackageDependency { fn family(&self) -> &'static str { "octave-package" } fn present(&self, session: &dyn Session) -> bool { session .command(vec![ "octave", "--eval", &format!("pkg load {}", self.package), ]) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } pub struct OctaveForgeResolver<'a> { session: &'a dyn Session, } impl<'a> OctaveForgeResolver<'a> { pub fn new(session: &'a dyn Session) -> Self { Self { session } } fn cmd( &self, dependency: &OctavePackageDependency, scope: InstallationScope, ) -> Result, Error> { match scope { InstallationScope::Global => Ok(vec![ "octave-cli".to_string(), "--eval".to_string(), format!("pkg install -forge -global {}", dependency.package), ]), InstallationScope::User => Ok(vec![ "octave-cli".to_string(), "--eval".to_string(), format!("pkg install -forge -local {}", dependency.package), ]), InstallationScope::Vendor => Err(Error::UnsupportedScope(scope)), } } } impl<'a> Installer for OctaveForgeResolver<'a> { fn explain( &self, dependency: &dyn Dependency, scope: InstallationScope, ) -> Result { let dependency = dependency .as_any() .downcast_ref::() .unwrap(); let cmd = self.cmd(dependency, scope)?; Ok(Explanation { command: Some(cmd), message: format!("Install Octave package {}", dependency.package), }) } fn install(&self, dependency: &dyn Dependency, scope: InstallationScope) -> Result<(), Error> { let dependency = dependency .as_any() .downcast_ref::() .ok_or(Error::UnknownDependencyFamily)?; let cmd = self.cmd(dependency, scope)?; log::info!("Octave: installing {}", dependency.package); self.session .command(cmd.iter().map(|x| x.as_str()).collect()) .run_detecting_problems()?; Ok(()) } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for OctavePackageDependency { fn try_into_debian_dependency( &self, _apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { if let Some(minimum_version) = &self.minimum_version { Some(vec![ crate::dependencies::debian::DebianDependency::new_with_min_version( &format!("octave-{}", &self.package), &minimum_version.parse().unwrap(), ), ]) } else { Some(vec![crate::dependencies::debian::DebianDependency::new( &format!("octave-{}", &self.package), )]) } } } ognibuild-0.0.32/src/dependencies/perl.rs000064400000000000000000000302571046102023000164160ustar 00000000000000use crate::dependency::Dependency; use crate::installer::{Error, Explanation, InstallationScope, Installer}; use crate::session::Session; use serde::{Deserialize, Serialize}; use std::path::Path; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PerlModuleDependency { pub module: String, pub filename: Option, pub inc: Option>, } impl PerlModuleDependency { pub fn new(module: &str, filename: Option<&str>, inc: Option>) -> Self { Self { module: module.to_string(), filename: filename.map(|s| s.to_string()), inc: inc.map(|v| v.iter().map(|s| s.to_string()).collect()), } } pub fn simple(module: &str) -> Self { Self { module: module.to_string(), filename: None, inc: None, } } } impl Dependency for PerlModuleDependency { fn family(&self) -> &'static str { "perl-module" } fn present(&self, session: &dyn Session) -> bool { let mut cmd = vec!["perl".to_string(), "-M".to_string(), self.module.clone()]; if let Some(filename) = &self.filename { cmd.push(filename.to_string()); } if let Some(inc) = &self.inc { cmd.push("-I".to_string()); cmd.push(inc.join(":")); } cmd.push("-e".to_string()); cmd.push("1".to_string()); session .command(cmd.iter().map(|s| s.as_str()).collect()) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "upstream")] impl crate::upstream::FindUpstream for PerlModuleDependency { fn find_upstream(&self) -> Option { let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(upstream_ontologist::providers::perl::remote_cpan_data(&self.module)).ok() } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PerlPreDeclaredDependency { name: String, } fn known_predeclared_module(name: &str) -> Option<&str> { // TODO(jelmer): Can we obtain this information elsewhere? match name { "auto_set_repository" => Some("Module::Install::Repository"), "author_tests" => Some("Module::Install::AuthorTests"), "recursive_author_tests" => Some("Module::Install::AuthorTests"), "author_requires" => Some("Module::Install::AuthorRequires"), "readme_from" => Some("Module::Install::ReadmeFromPod"), "catalyst" => Some("Module::Install::Catalyst"), "githubmeta" => Some("Module::Install::GithubMeta"), "use_ppport" => Some("Module::Install::XSUtil"), "pod_from" => Some("Module::Install::PodFromEuclid"), "write_doap_changes" => Some("Module::Install::DOAPChangeSets"), "use_test_base" => Some("Module::Install::TestBase"), "jsonmeta" => Some("Module::Install::JSONMETA"), "extra_tests" => Some("Module::Install::ExtraTests"), "auto_set_bugtracker" => Some("Module::Install::Bugtracker"), _ => None, } } impl PerlPreDeclaredDependency { pub fn new(name: &str) -> Self { Self { name: name.to_string(), } } } impl Dependency for PerlPreDeclaredDependency { fn family(&self) -> &'static str { "perl-predeclared" } fn present(&self, session: &dyn Session) -> bool { if let Some(module) = known_predeclared_module(&self.name) { PerlModuleDependency::simple(module).present(session) } else { todo!() } } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for PerlPreDeclaredDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { if let Some(module) = known_predeclared_module(&self.name) { PerlModuleDependency::simple(module).try_into_debian_dependency(apt) } else { None } } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingPerlPredeclared { fn to_dependency(&self) -> Option> { match known_predeclared_module(self.0.as_str()) { Some(_module) => Some(Box::new(PerlModuleDependency::simple(self.0.as_str()))), None => { log::warn!("Unknown predeclared function: {}", self.0); None } } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PerlFileDependency { filename: String, } impl PerlFileDependency { pub fn new(filename: &str) -> Self { Self { filename: filename.to_string(), } } } impl Dependency for PerlFileDependency { fn family(&self) -> &'static str { "perl-file" } fn present(&self, session: &dyn Session) -> bool { session .command(vec!["perl", "-e", &format!("require '{}'", self.filename)]) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn project_present(&self, _session: &dyn Session) -> bool { false } fn as_any(&self) -> &dyn std::any::Any { self } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingPerlFile { fn to_dependency(&self) -> Option> { Some(Box::new(PerlFileDependency { filename: self.filename.clone(), })) } } pub struct CPAN<'a> { session: &'a dyn Session, skip_tests: bool, } impl<'a> CPAN<'a> { pub fn new(session: &'a dyn Session, skip_tests: bool) -> Self { Self { session, skip_tests, } } fn cmd( &self, reqs: &[&PerlModuleDependency], _scope: InstallationScope, ) -> Result, Error> { let mut ret = vec!["cpan".to_string(), "-i".to_string()]; if self.skip_tests { ret.push("-T".to_string()); } ret.extend(reqs.iter().map(|req| req.module.clone())); Ok(ret) } } impl<'a> Installer for CPAN<'a> { fn explain( &self, dep: &dyn Dependency, scope: InstallationScope, ) -> Result { if let Some(dep) = dep.as_any().downcast_ref::() { let cmd = self.cmd(&[&dep], scope)?; let explanation = Explanation { message: "Install the following Perl modules".to_string(), command: Some(cmd), }; Ok(explanation) } else { Err(Error::UnknownDependencyFamily) } } fn install(&self, dep: &dyn Dependency, scope: InstallationScope) -> Result<(), Error> { let env = maplit::hashmap! { "PERL_MM_USE_DEFAULT".to_string() => "1".to_string(), "PERL_MM_OPT".to_string() => "".to_string(), "PERL_MB_OPT".to_string() => "".to_string(), }; let user = match scope { InstallationScope::User => None, InstallationScope::Global => Some("root"), InstallationScope::Vendor => { return Err(Error::UnsupportedScope(scope)); } }; let dep = dep .as_any() .downcast_ref::() .ok_or(Error::UnknownDependencyFamily)?; let cmd = self.cmd(&[dep], scope)?; log::info!("CPAN: running {:?}", cmd); let mut cmd = self .session .command(cmd.iter().map(|s| s.as_str()).collect()) .env(env); if let Some(user) = user { cmd = cmd.user(user); } cmd.run_detecting_problems()?; Ok(()) } fn explain_some( &self, deps: Vec>, scope: InstallationScope, ) -> Result<(Vec, Vec>), Error> { let mut explanations = Vec::new(); let mut failed = Vec::new(); for dep in deps { match self.explain(&*dep, scope) { Ok(explanation) => explanations.push(explanation), Err(Error::UnknownDependencyFamily) => failed.push(dep), Err(e) => { return Err(e); } } } Ok((explanations, failed)) } fn install_some( &self, deps: Vec>, scope: InstallationScope, ) -> Result<(Vec>, Vec>), Error> { let mut installed = Vec::new(); let mut failed = Vec::new(); for dep in deps { match self.install(&*dep, scope) { Ok(()) => installed.push(dep), Err(Error::UnknownDependencyFamily) => failed.push(dep), Err(e) => { return Err(e); } } } Ok((installed, failed)) } } pub const DEFAULT_PERL_PATHS: &[&str] = &[ "/usr/share/perl5", "/usr/lib/.*/perl5/.*", "/usr/lib/.*/perl-base", "/usr/lib/.*/perl/[^/]+", "/usr/share/perl/[^/]+", ]; #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for PerlModuleDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let (regex, paths) = if let (Some(inc), Some(filename)) = (self.inc.as_ref(), self.filename.as_ref()) { ( false, inc.iter().map(|s| Path::new(s).join(filename)).collect(), ) } else if let Some(filename) = &self.filename { if !Path::new(filename).is_absolute() { ( true, DEFAULT_PERL_PATHS .iter() .map(|s| Path::new(s).join(filename)) .collect(), ) } else { (false, vec![Path::new(filename).to_path_buf()]) } } else { ( true, DEFAULT_PERL_PATHS .iter() .map(|s| Path::new(s).join(format!("{}.pm", &self.module.replace("::", "/")))) .collect(), ) }; let packages = apt .get_packages_for_paths( paths .iter() .map(|s| s.to_str().unwrap()) .collect::>(), regex, false, ) .unwrap(); Some( packages .into_iter() .map(|p| crate::dependencies::debian::DebianDependency::simple(&p)) .collect(), ) } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for PerlFileDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let packages = apt .get_packages_for_paths(vec![&self.filename], false, false) .unwrap(); Some( packages .into_iter() .map(|p| crate::dependencies::debian::DebianDependency::simple(&p)) .collect(), ) } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingPerlModule { fn to_dependency(&self) -> Option> { Some(Box::new(PerlModuleDependency { module: self.module.clone(), filename: self.filename.clone(), inc: self.inc.clone(), })) } } ognibuild-0.0.32/src/dependencies/php.rs000064400000000000000000000141021046102023000162320ustar 00000000000000use crate::dependency::Dependency; use crate::session::Session; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PhpClassDependency { php_class: String, } impl PhpClassDependency { pub fn new(php_class: &str) -> Self { Self { php_class: php_class.to_string(), } } } impl Dependency for PhpClassDependency { fn family(&self) -> &'static str { "php-class" } fn present(&self, session: &dyn Session) -> bool { session .command(vec!["php", "-r", &format!("new {}", self.php_class)]) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for PhpClassDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let path = format!("/usr/share/php/{}", self.php_class.replace("\\", "/")); let names = apt .get_packages_for_paths(vec![&path], false, false) .unwrap(); Some( names .into_iter() .map(|name| crate::dependencies::debian::DebianDependency::new(&name)) .collect(), ) } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingPhpClass { fn to_dependency(&self) -> Option> { Some(Box::new(PhpClassDependency::new(&self.php_class))) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PhpPackageDependency { pub package: String, pub channel: Option, pub min_version: Option, pub max_version: Option, } impl PhpPackageDependency { pub fn new( package: &str, channel: Option<&str>, min_version: Option<&str>, max_version: Option<&str>, ) -> Self { Self { package: package.to_string(), channel: channel.map(|s| s.to_string()), min_version: min_version.map(|s| s.to_string()), max_version: max_version.map(|s| s.to_string()), } } pub fn simple(package: &str) -> Self { Self { package: package.to_string(), channel: None, min_version: None, max_version: None, } } } impl Dependency for PhpPackageDependency { fn family(&self) -> &'static str { "php-package" } fn present(&self, _session: &dyn Session) -> bool { todo!() } fn project_present(&self, session: &dyn Session) -> bool { // Run `composer show` and check the output let output = session .command(vec!["composer", "show", "--format=json"]) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::null()) .output() .unwrap(); let packages: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); let packages = packages["installed"].as_array().unwrap(); packages.iter().any(|package| { package["name"] == self.package && (self.min_version.is_none() || package["version"] .as_str() .unwrap() .parse::() .unwrap() >= self .min_version .as_ref() .unwrap() .parse::() .unwrap()) && (self.max_version.is_none() || package["version"] .as_str() .unwrap() .parse::() .unwrap() <= self .max_version .as_ref() .unwrap() .parse::() .unwrap()) }) } fn as_any(&self) -> &dyn std::any::Any { self } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PhpExtensionDependency { pub extension: String, } impl PhpExtensionDependency { pub fn new(extension: &str) -> Self { Self { extension: extension.to_string(), } } } impl Dependency for PhpExtensionDependency { fn family(&self) -> &'static str { "php-extension" } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn present(&self, session: &dyn Session) -> bool { // Grep the output of php -m let output = session .command(vec!["php", "-m"]) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::null()) .output() .unwrap() .stdout; String::from_utf8(output) .unwrap() .lines() .any(|line| line == self.extension) } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for PhpExtensionDependency { fn try_into_debian_dependency( &self, _apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { Some(vec![crate::dependencies::debian::DebianDependency::new( &format!("php-{}", &self.extension), )]) } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingPHPExtension { fn to_dependency(&self) -> Option> { Some(Box::new(PhpExtensionDependency::new(&self.0))) } } ognibuild-0.0.32/src/dependencies/pytest.rs000064400000000000000000000103131046102023000167730ustar 00000000000000use crate::dependencies::Dependency; use crate::session::Session; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PytestPluginDependency { pub plugin: String, } impl PytestPluginDependency { pub fn new(plugin: &str) -> Self { Self { plugin: plugin.to_string(), } } } fn map_pytest_arguments_to_plugin(args: &[&str]) -> Option<&'static str> { for arg in args { if arg.starts_with("--cov") { return Some("cov"); } } None } fn map_pytest_config_option_to_plugin(name: &str) -> Option<&'static str> { match name { "asyncio_mode" => Some("asyncio"), n => { log::warn!("Unknown pytest config option {}", n); None } } } // TODO(jelmer): populate this using an automated process fn pytest_fixture_to_plugin(fixture: &str) -> Option<&str> { match fixture { "aiohttp_client" => Some("aiohttp"), "aiohttp_client_cls" => Some("aiohttp"), "aiohttp_server" => Some("aiohttp"), "aiohttp_raw_server" => Some("aiohttp"), "mock" => Some("mock"), "benchmark" => Some("benchmark"), "event_loop" => Some("asyncio"), "unused_tcp_port" => Some("asyncio"), "unused_udp_port" => Some("asyncio"), "unused_tcp_port_factory" => Some("asyncio"), "unused_udp_port_factory" => Some("asyncio"), _ => None, } } fn pytest_plugins(session: &dyn Session) -> Option> { let output = session .command(vec!["pytest", "--version"]) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::null()) .output() .unwrap(); for line in String::from_utf8(output.stdout).unwrap().lines() { if let Some(rest) = line.strip_prefix("plugins: ") { return Some( rest.split(',') .map(|s| { let mut parts = s.splitn(2, '='); ( parts.next().unwrap().to_string(), parts.next().unwrap_or("").to_string(), ) }) .collect(), ); } } None } impl Dependency for PytestPluginDependency { fn family(&self) -> &'static str { "pytest-plugin" } fn present(&self, session: &dyn Session) -> bool { if let Some(plugins) = pytest_plugins(session) { plugins.iter().any(|(name, _)| name == &self.plugin) } else { false } } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for PytestPluginDependency { fn try_into_debian_dependency( &self, _apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { Some(vec![crate::dependencies::debian::DebianDependency::simple( &format!("python3-pytest-{}", self.plugin), )]) } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingPytestFixture { fn to_dependency(&self) -> Option> { pytest_fixture_to_plugin(&self.0) .map(|plugin| Box::new(PytestPluginDependency::new(plugin)) as Box) } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::UnsupportedPytestArguments { fn to_dependency(&self) -> Option> { let args = self.0.iter().map(|x| x.as_str()).collect::>(); map_pytest_arguments_to_plugin(args.as_slice()) .map(|plugin| Box::new(PytestPluginDependency::new(plugin)) as Box) } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::UnsupportedPytestConfigOption { fn to_dependency(&self) -> Option> { map_pytest_config_option_to_plugin(&self.0) .map(|plugin| Box::new(PytestPluginDependency::new(plugin)) as Box) } } ognibuild-0.0.32/src/dependencies/python.rs000064400000000000000000000723621046102023000170000ustar 00000000000000#[cfg(feature = "debian")] use crate::debian::apt::AptManager; #[cfg(feature = "debian")] use crate::dependencies::debian::DebianDependency; use crate::dependency::Dependency; use crate::installer::{Error, Explanation, InstallationScope, Installer}; use crate::session::Session; #[cfg(feature = "debian")] use debian_control::{ lossless::relations::{Relation, Relations}, relations::VersionConstraint, }; use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; use pep508_rs::pep440_rs as pep440_rs; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PythonPackageDependency(pep508_rs::Requirement); impl From for PythonPackageDependency { fn from(requirement: pep508_rs::Requirement) -> Self { Self(requirement) } } impl TryFrom for pep508_rs::Requirement { type Error = pep508_rs::Pep508Error; fn try_from(value: PythonPackageDependency) -> Result { Ok(value.0) } } impl TryFrom for PythonPackageDependency { type Error = pep508_rs::Pep508Error; fn try_from(value: String) -> Result { Self::try_from(value.as_str()) } } impl TryFrom<&str> for PythonPackageDependency { type Error = pep508_rs::Pep508Error; fn try_from(value: &str) -> Result { use std::str::FromStr; let req = pep508_rs::Requirement::from_str(value)?; Ok(PythonPackageDependency(req)) } } impl PythonPackageDependency { pub fn package(&self) -> String { self.0.name.to_string() } pub fn new_with_min_version(package: &str, min_version: &str) -> Self { Self(pep508_rs::Requirement { name: pep508_rs::PackageName::new(package.to_string()).unwrap(), version_or_url: Some(min_version_as_version_or_url(min_version)), extras: vec![], marker: pep508_rs::MarkerTree::TRUE, origin: None, }) } pub fn simple(package: &str) -> Self { Self(pep508_rs::Requirement { name: pep508_rs::PackageName::new(package.to_string()).unwrap(), version_or_url: None, extras: vec![], marker: pep508_rs::MarkerTree::TRUE, origin: None, }) } } fn min_version_as_version_or_url(min_version: &str) -> pep508_rs::VersionOrUrl { use std::str::FromStr; let version_specifiers = std::iter::once( pep440_rs::VersionSpecifier::from_pattern( pep440_rs::Operator::GreaterThanEqual, pep440_rs::VersionPattern::verbatim(pep440_rs::Version::from_str(min_version).unwrap()), ) .unwrap(), ) .collect(); pep508_rs::VersionOrUrl::VersionSpecifier(version_specifiers) } fn major_python_version_as_marker(major_version: u32) -> pep508_rs::MarkerTree { pep508_rs::MarkerTree::expression(pep508_rs::MarkerExpression::Version { key: pep508_rs::MarkerValueVersion::PythonVersion, specifier: pep440_rs::VersionSpecifier::equals_star_version(pep440_rs::Version::new([ major_version as u64, ])), }) } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingPythonDistribution { fn to_dependency(&self) -> Option> { let version_or_url = self .minimum_version .as_ref() .map(|min_version| min_version_as_version_or_url(min_version)); let marker = self.python_version.as_ref().map(|python_major_version| { major_python_version_as_marker(*python_major_version as u32) }).unwrap_or(pep508_rs::MarkerTree::TRUE); let requirement = pep508_rs::Requirement { name: pep508_rs::PackageName::new(self.distribution.clone()).unwrap(), version_or_url, extras: vec![], marker, origin: None, }; Some(Box::new(PythonPackageDependency::from(requirement))) } } #[cfg(feature = "debian")] impl crate::dependencies::debian::FromDebianDependency for PythonPackageDependency { fn from_debian_dependency(dependency: &DebianDependency) -> Option> { // TODO: handle other things than min version let (name, min_version) = crate::dependencies::debian::extract_simple_min_version(dependency)?; let (_, python_version, name) = lazy_regex::regex_captures!("python([0-9.]*)-(.*)", &name)?; let major_python_version = if python_version.is_empty() { None } else { Some(python_version.parse::().unwrap()) }; Some(Box::new(PythonPackageDependency::from( pep508_rs::Requirement { name: pep508_rs::PackageName::new(name.to_owned()).unwrap(), version_or_url: min_version .map(|x| min_version_as_version_or_url(&x.upstream_version)), marker: major_python_version.map(major_python_version_as_marker).unwrap_or(pep508_rs::MarkerTree::TRUE), extras: vec![], origin: None, }, ))) } } #[derive(Debug, Clone, Default, Copy, Serialize, Deserialize)] pub enum PythonVersion { CPython2, #[default] CPython3, PyPy, PyPy3, } impl PythonVersion { pub fn executable(&self) -> &'static str { match self { PythonVersion::CPython2 => "python2", PythonVersion::CPython3 => "python3", PythonVersion::PyPy => "pypy", PythonVersion::PyPy3 => "pypy3", } } } impl Dependency for PythonPackageDependency { fn family(&self) -> &'static str { "python-package" } fn present(&self, session: &dyn Session) -> bool { let python_version = find_python_version(self.0.marker.to_dnf()).unwrap_or_default(); let cmd = python_version.executable(); session .command(vec![ cmd, "-c", &format!( r#"import pkg_resources; pkg_resources.require("""{}""")"#, self.0 ), ]) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn project_present(&self, _session: &dyn Session) -> bool { // TODO: check in the virtualenv, if any todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "upstream")] impl crate::upstream::FindUpstream for PythonPackageDependency { fn find_upstream(&self) -> Option { let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(upstream_ontologist::providers::python::remote_pypi_metadata(&self.package())).ok() } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PythonModuleDependency { module: String, minimum_version: Option, python_version: Option, } impl PythonModuleDependency { pub fn new(module: &str, minimum_version: Option<&str>, python_version: Option) -> Self { Self { module: module.to_string(), minimum_version: minimum_version.map(|s| s.to_string()), python_version } } pub fn simple(module: &str) -> Self { Self { module: module.to_string(), minimum_version: None, python_version: None, } } fn python_executable(&self) -> &str { self.python_version.unwrap_or_default().executable() } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingPythonModule { fn to_dependency(&self) -> Option> { Some(Box::new(PythonModuleDependency::new( &self.module, self.minimum_version.as_deref(), match self.python_version { Some(2) => Some(PythonVersion::CPython2), Some(3) => Some(PythonVersion::CPython3), None => None, _ => unimplemented!(), }, ))) } } impl Dependency for PythonModuleDependency { fn family(&self) -> &'static str { "python-module" } fn present(&self, session: &dyn Session) -> bool { let cmd = [ self.python_executable().to_string(), "-c".to_string(), format!( r#"import pkgutil; exit(0 if pkgutil.find_loader("{}") else 1)"#, self.module ), ]; session .command(cmd.iter().map(|s| s.as_str()).collect()) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn project_present(&self, _session: &dyn Session) -> bool { false } fn as_any(&self) -> &dyn std::any::Any { self } } pub struct PypiResolver<'a> { session: &'a dyn Session, } impl<'a> PypiResolver<'a> { pub fn new(session: &'a dyn Session) -> Self { Self { session } } pub fn cmd( &self, reqs: Vec<&PythonPackageDependency>, scope: InstallationScope, ) -> Result, Error> { let mut cmd = vec!["pip".to_string(), "install".to_string()]; match scope { InstallationScope::User => cmd.push("--user".to_string()), InstallationScope::Global => {} InstallationScope::Vendor => { return Err(Error::UnsupportedScope(scope)); } } cmd.extend(reqs.iter().map(|req| req.package().to_string())); Ok(cmd) } } impl<'a> Installer for PypiResolver<'a> { fn install(&self, requirement: &dyn Dependency, scope: InstallationScope) -> Result<(), Error> { let req = requirement .as_any() .downcast_ref::() .ok_or_else(|| Error::UnknownDependencyFamily)?; let args = self.cmd(vec![req], scope)?; let mut cmd = self .session .command(args.iter().map(|x| x.as_str()).collect()); match scope { InstallationScope::Global => { cmd = cmd.user("root"); } InstallationScope::User => {} InstallationScope::Vendor => { return Err(Error::UnsupportedScope(scope)); } } cmd.run_detecting_problems()?; Ok(()) } fn explain( &self, requirement: &dyn Dependency, scope: InstallationScope, ) -> Result { let req = requirement .as_any() .downcast_ref::() .ok_or_else(|| Error::UnknownDependencyFamily)?; let cmd = self.cmd(vec![req], scope)?; Ok(Explanation { message: format!("Install pip {}", req.0.name), command: Some(cmd), }) } } #[cfg(feature = "debian")] pub fn python_version_specifiers_to_debian( pkg_name: &str, version_specifiers: Option<&pep440_rs::VersionSpecifiers>, ) -> Relations { // TODO(jelmer): Dealing with epoch, etc? let mut rels: Vec = vec![]; if let Some(version_specifiers) = version_specifiers { for vs in version_specifiers.iter() { let v = vs.version().to_string(); match vs.operator() { pep440_rs::Operator::TildeEqual => { // PEP 440: For a given release identifier V.N , the compatible // release clause is approximately equivalent to the pair of // comparison clauses: >= V.N, == V.* let mut parts = v.split('.').map(|s| s.to_string()).collect::>(); parts.pop(); let last: isize = parts.pop().unwrap().parse().unwrap(); parts.push((last + 1).to_string()); let next_maj_deb_version: debversion::Version = parts.join(".").parse().unwrap(); let deb_version: debversion::Version = v.parse().unwrap(); rels.push(Relation::new( pkg_name, Some((VersionConstraint::GreaterThanEqual, deb_version)), )); rels.push(Relation::new( pkg_name, Some((VersionConstraint::LessThan, next_maj_deb_version)), )); } pep440_rs::Operator::NotEqual => { let deb_version: debversion::Version = v.parse().unwrap(); rels.push(Relation::new( pkg_name, Some((VersionConstraint::GreaterThan, deb_version.clone())), )); rels.push(Relation::new( pkg_name, Some((VersionConstraint::LessThan, deb_version)), )); } pep440_rs::Operator::Equal if v.ends_with(".*") => { let mut parts = v.split('.').map(|s| s.to_string()).collect::>(); parts.pop(); let last: isize = parts.pop().unwrap().parse().unwrap(); parts.push((last + 1).to_string()); let deb_version: debversion::Version = v.parse().unwrap(); let next_maj_deb_version: debversion::Version = parts.join(".").parse().unwrap(); rels.push(Relation::new( pkg_name, Some((VersionConstraint::GreaterThanEqual, deb_version)), )); rels.push(Relation::new( pkg_name, Some((VersionConstraint::LessThan, next_maj_deb_version)), )); } o => { let vc = match o { pep440_rs::Operator::GreaterThanEqual => { VersionConstraint::GreaterThanEqual } pep440_rs::Operator::GreaterThan => VersionConstraint::GreaterThan, pep440_rs::Operator::LessThanEqual => VersionConstraint::LessThanEqual, pep440_rs::Operator::LessThan => VersionConstraint::LessThan, pep440_rs::Operator::Equal => VersionConstraint::Equal, _ => unimplemented!(), }; let v: debversion::Version = v.parse().unwrap(); rels.push(Relation::new(pkg_name, Some((vc, v)))); } } } Relations::from(rels.into_iter().map(|r| r.into()).collect::>()) } else { Relations::from(vec![Relation::new(pkg_name, None).into()]) } } fn find_python_version(marker: Vec>) -> Option { let mut major_version = None; let mut implementation = None; for expr in marker.iter().flat_map(|x| x.iter()) { match expr { pep508_rs::MarkerExpression::Version { key: pep508_rs::MarkerValueVersion::PythonVersion, specifier, } => { let version = specifier.version(); major_version = Some(version.release()[0] as u32); } pep508_rs::MarkerExpression::String { key: pep508_rs::MarkerValueString::PlatformPythonImplementation, operator: pep508_rs::MarkerOperator::Equal, value } => match value.as_str() { "PyPy" => { implementation = Some("PyPy"); }, _ => {} } _ => {} } } match (major_version, implementation) { (Some(2), None) => Some(PythonVersion::CPython2), (Some(3), None) | (None, None) => Some(PythonVersion::CPython3), (Some(3), Some("PyPy")) | (None, Some("PyPy")) => Some(PythonVersion::PyPy3), (Some(2), Some("PyPy")) => Some(PythonVersion::PyPy), _ => { log::warn!("Unknown python implementation / version: {:?} {:?}", major_version, implementation); None } } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for PythonPackageDependency { fn try_into_debian_dependency( &self, apt_mgr: &crate::debian::apt::AptManager, ) -> Option> { let names = get_package_for_python_package( apt_mgr, &self.package(), find_python_version(self.0.marker.to_dnf()), self.0.version_or_url.as_ref(), ); Some(names) } } #[cfg(feature = "debian")] fn get_package_for_python_package( apt_mgr: &AptManager, package: &str, python_version: Option, version_or_url: Option<&pep508_rs::VersionOrUrl>, ) -> Vec { let pypy_regex = format!( "/usr/lib/pypy/dist\\-packages/{}-.*\\.(dist|egg)\\-info", regex::escape(&package.replace('-', "_")) ); let cpython2_regex = format!( "/usr/lib/python2\\.[0-9]/dist\\-packages/{}-.*\\.(dist|egg)\\-info", regex::escape(&package.replace('-', "_")) ); let cpython3_regex = format!( "/usr/lib/python3/dist\\-packages/{}-.*\\.(dist|egg)\\-info", regex::escape(&package.replace('-', "_")) ); let paths = match python_version { Some(PythonVersion::PyPy) => vec![pypy_regex], Some(PythonVersion::CPython2) => vec![cpython2_regex], Some(PythonVersion::CPython3) => vec![cpython3_regex], None => vec![cpython3_regex, cpython2_regex, pypy_regex], _ => unimplemented!(), }; let names = apt_mgr .get_packages_for_paths(paths.iter().map(|x| x.as_str()).collect(), true, true) .unwrap(); names .iter() .map(|name| { DebianDependency::from(python_version_specifiers_to_debian( name, if let Some(pep508_rs::VersionOrUrl::VersionSpecifier(specs)) = version_or_url { Some(specs) } else { None }, )) }) .collect() } #[cfg(any(feature = "debian", test))] fn get_possible_python3_paths_for_python_object(mut object_path: &str) -> Vec { let mut cpython3_regexes = vec![]; loop { cpython3_regexes.extend([ Path::new("/usr/lib/python3/dist\\-packages") .join(regex::escape(&object_path.replace('.', "/"))) .join("__init__\\.py"), Path::new("/usr/lib/python3/dist\\-packages").join(format!( "{}\\.py", regex::escape(&object_path.replace('.', "/")) )), Path::new("/usr/lib/python3\\.[0-9]+/lib\\-dynload").join(format!( "{}\\.cpython\\-.*\\.so", regex::escape(&object_path.replace('.', "/")) )), Path::new("/usr/lib/python3\\.[0-9]+/").join(format!( "{}\\.py", regex::escape(&object_path.replace('.', "/")) )), Path::new("/usr/lib/python3\\.[0-9]+/") .join(regex::escape(&object_path.replace('.', "/"))) .join("__init__\\.py"), ]); object_path = match object_path.rsplit_once('.') { Some((o, _)) => o, None => break, }; } cpython3_regexes } #[cfg(feature = "debian")] fn get_possible_pypy_paths_for_python_object(mut object_path: &str) -> Vec { let mut pypy_regexes = vec![]; loop { pypy_regexes.extend([ Path::new("/usr/lib/pypy/dist\\-packages") .join(regex::escape(&object_path.replace('.', "/"))) .join("__init__\\.py"), Path::new("/usr/lib/pypy/dist\\-packages").join(format!( "{}\\.py", regex::escape(&object_path.replace('.', "/")) )), Path::new("/usr/lib/pypy/dist\\-packages").join(format!( "{}\\.pypy-.*\\.so", regex::escape(&object_path.replace('.', "/")) )), ]); object_path = match object_path.rsplit_once('.') { Some((o, _)) => o, None => break, }; } pypy_regexes } #[cfg(feature = "debian")] fn get_possible_python2_paths_for_python_object(mut object_path: &str) -> Vec { let mut cpython2_regexes = vec![]; loop { cpython2_regexes.extend([ Path::new("/usr/lib/python2\\.[0-9]/dist\\-packages") .join(regex::escape(&object_path.replace('.', "/"))) .join("__init__\\.py"), Path::new("/usr/lib/python2\\.[0-9]/dist\\-packages").join(format!( "{}\\.py", regex::escape(&object_path.replace('.', "/")) )), Path::new("/usr/lib/python2.\\.[0-9]/lib\\-dynload").join(format!( "{}\\.so", regex::escape(&object_path.replace('.', "/")) )), ]); object_path = match object_path.rsplit_once('.') { Some((o, _)) => o, None => break, }; } cpython2_regexes } #[cfg(feature = "debian")] fn get_package_for_python_object_path( apt_mgr: &AptManager, object_path: &str, python_version: Option, version_specifiers: Option<&pep440_rs::VersionSpecifiers>, ) -> Vec { // Try to find the most specific file let paths = match python_version { Some(PythonVersion::CPython3) => get_possible_python3_paths_for_python_object(object_path), Some(PythonVersion::CPython2) => get_possible_python2_paths_for_python_object(object_path), Some(PythonVersion::PyPy) => get_possible_pypy_paths_for_python_object(object_path), None => get_possible_python3_paths_for_python_object(object_path) .into_iter() .chain(get_possible_python2_paths_for_python_object(object_path)) .chain(get_possible_pypy_paths_for_python_object(object_path)) .collect(), _ => unimplemented!(), }; let names = apt_mgr .get_packages_for_paths( paths.iter().map(|x| x.to_str().unwrap()).collect(), true, false, ) .unwrap(); names .into_iter() .map(|name| { DebianDependency::from(python_version_specifiers_to_debian( &name, version_specifiers, )) }) .collect() } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for PythonModuleDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> Option> { use std::str::FromStr; let specs = self.minimum_version.as_ref().map(|min_version| { std::iter::once( pep440_rs::VersionSpecifier::from_pattern( pep440_rs::Operator::GreaterThanEqual, pep440_rs::VersionPattern::verbatim( pep440_rs::Version::from_str(min_version).unwrap(), ), ) .unwrap(), ) .collect() }); Some(get_package_for_python_object_path( apt, &self.module, self.python_version, specs.as_ref(), )) } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingSetupPyCommand { fn to_dependency(&self) -> Option> { match self.0.as_str() { "test" => Some(Box::new(PythonPackageDependency::simple("setuptools"))), _ => None, } } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PythonDependency { pub min_version: Option, } impl PythonDependency { pub fn new(min_version: Option<&str>) -> Self { Self { min_version: min_version.map(|s| s.to_string()), } } pub fn simple() -> Self { Self { min_version: None } } fn executable(&self) -> &str { match &self.min_version { Some(min_version) => { if min_version.starts_with("2") { "python" } else { "python3" } } None => "python3", } } } impl Dependency for PythonDependency { fn family(&self) -> &'static str { "python" } fn present(&self, session: &dyn Session) -> bool { let cmd = match self.min_version { Some(ref min_version) => vec![ self.executable().to_string(), "-c".to_string(), format!( "import sys; sys.exit(0 if sys.version_info >= ({}) else 1)", min_version.replace('.', ", ") ), ], None => vec![ PythonVersion::default().executable().to_string(), "-c".to_string(), "import sys; sys.exit(0 if sys.version_info >= (3, 0) else 1)".to_string(), ], }; session .command(cmd.iter().map(|s| s.as_str()).collect()) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn project_present(&self, session: &dyn Session) -> bool { // Check if a virtualenv is present session.exists(Path::new("bin/python")) } fn as_any(&self) -> &dyn std::any::Any { self } } impl From<&pep440_rs::VersionSpecifiers> for PythonDependency { fn from(specs: &pep440_rs::VersionSpecifiers) -> Self { for specifier in specs.iter() { if specifier.operator() == &pep440_rs::Operator::GreaterThanEqual { return Self { min_version: Some(specifier.version().to_string()), }; } } Self { min_version: None } } } #[cfg(feature = "debian")] impl crate::dependencies::debian::FromDebianDependency for PythonDependency { fn from_debian_dependency(dependency: &DebianDependency) -> Option> { let (name, min_version) = crate::dependencies::debian::extract_simple_min_version(dependency)?; if name == "python" || name == "python3" { Some(Box::new(PythonDependency { min_version: min_version.map(|x| x.upstream_version.clone()), })) } else { None } } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for PythonDependency { fn try_into_debian_dependency( &self, _apt: &crate::debian::apt::AptManager, ) -> Option> { let mut deps = vec![]; if let Some(min_version) = &self.min_version { if min_version.starts_with("2") { deps.push( crate::dependencies::debian::DebianDependency::new_with_min_version( "python", &min_version.parse::().unwrap(), ), ); } else { deps.push( crate::dependencies::debian::DebianDependency::new_with_min_version( "python3", &min_version.parse::().unwrap(), ), ); } } else { deps.push(crate::dependencies::debian::DebianDependency::simple( "python3", )); } Some(deps) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_paths() { assert_eq!( vec![ PathBuf::from("/usr/lib/python3/dist\\-packages/dulwich/__init__\\.py"), PathBuf::from("/usr/lib/python3/dist\\-packages/dulwich\\.py"), PathBuf::from( "/usr/lib/python3\\.[0-9]+/lib\\-dynload/dulwich\\.cpython\\-.*\\.so" ), PathBuf::from("/usr/lib/python3\\.[0-9]+/dulwich\\.py"), PathBuf::from("/usr/lib/python3\\.[0-9]+/dulwich/__init__\\.py"), ], get_possible_python3_paths_for_python_object("dulwich"), ); assert_eq!( vec![ PathBuf::from("/usr/lib/python3/dist\\-packages/cleo/foo/__init__\\.py"), PathBuf::from("/usr/lib/python3/dist\\-packages/cleo/foo\\.py"), PathBuf::from( "/usr/lib/python3\\.[0-9]+/lib\\-dynload/cleo/foo\\.cpython\\-.*\\.so" ), PathBuf::from("/usr/lib/python3\\.[0-9]+/cleo/foo\\.py"), PathBuf::from("/usr/lib/python3\\.[0-9]+/cleo/foo/__init__\\.py"), PathBuf::from("/usr/lib/python3/dist\\-packages/cleo/__init__\\.py"), PathBuf::from("/usr/lib/python3/dist\\-packages/cleo\\.py"), PathBuf::from("/usr/lib/python3\\.[0-9]+/lib\\-dynload/cleo\\.cpython\\-.*\\.so"), PathBuf::from("/usr/lib/python3\\.[0-9]+/cleo\\.py"), PathBuf::from("/usr/lib/python3\\.[0-9]+/cleo/__init__\\.py"), ], get_possible_python3_paths_for_python_object("cleo.foo"), ); } } ognibuild-0.0.32/src/dependencies/r.rs000064400000000000000000000126761046102023000157220ustar 00000000000000use crate::dependency::Dependency; use crate::installer::{Error, Explanation, InstallationScope, Installer}; use crate::session::Session; use serde::{Deserialize, Serialize}; use r_description::lossy::{Relation}; use r_description::VersionConstraint; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RPackageDependency(Relation); impl From for Relation { fn from(dep: RPackageDependency) -> Self { dep.0 } } impl From for RPackageDependency { fn from(rel: Relation) -> Self { Self(rel) } } impl From for RPackageDependency { fn from(rel: r_description::lossless::Relation) -> Self { Self(rel.into()) } } impl RPackageDependency { pub fn new(package: &str, minimum_version: Option<&str>) -> Self { if let Some(minimum_version) = minimum_version { Self(Relation { name: package.to_string(), version: Some((VersionConstraint::GreaterThanEqual, minimum_version.parse().unwrap())) }.into()) } else { Self(Relation { name: package.to_string(), version: None }.into()) } } pub fn simple(package: &str) -> Self { Self(Relation { name: package.to_string(), version: None }.into()) } pub fn from_str(s: &str) -> Self { if let Some((_, name, min_version)) = lazy_regex::regex_captures!("(.*) \\(>= (.*)\\)", s) { Self::new(name, Some(min_version)) } else if !s.contains(" ") { Self::simple(s) } else { panic!("Invalid R package dependency: {}", s); } } } impl Dependency for RPackageDependency { fn family(&self) -> &'static str { "r-package" } fn present(&self, _session: &dyn Session) -> bool { todo!() } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for RPackageDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> Option> { let names = apt .get_packages_for_paths( vec![std::path::Path::new("/usr/lib/R/site-library") .join(&self.0.name) .join("DESCRIPTION") .to_str() .unwrap()], false, false, ) .unwrap(); if names.is_empty() { return None; } Some( names .into_iter() .map(|name| super::debian::DebianDependency::new(&name)) .collect(), ) } } pub struct RResolver<'a> { session: &'a dyn Session, repos: String, } impl<'a> RResolver<'a> { pub fn new(session: &'a dyn Session, repos: &str) -> Self { Self { session, repos: repos.to_string(), } } fn cmd(&self, req: &RPackageDependency) -> Vec { // R will install into the first directory in .libPaths() that is writeable. // TODO: explicitly set the library path to either the user's home directory or a system // directory. vec![ "R".to_string(), "-e".to_string(), format!( "install.packages('{}', repos='{})'", req.0.name, self.repos ), ] } } impl<'a> Installer for RResolver<'a> { /// Install the dependency into the session. fn install(&self, dep: &dyn Dependency, scope: InstallationScope) -> Result<(), Error> { let req = dep .as_any() .downcast_ref::() .ok_or(Error::UnknownDependencyFamily)?; let args = self.cmd(req); log::info!("RResolver({:?}): running {:?}", self.repos, args); let mut cmd = self .session .command(args.iter().map(|x| x.as_str()).collect()); match scope { InstallationScope::User => {} InstallationScope::Global => { cmd = cmd.user("root"); } InstallationScope::Vendor => { return Err(Error::UnsupportedScope(scope)); } } cmd.run_detecting_problems()?; Ok(()) } /// Explain how to install the dependency. fn explain( &self, dep: &dyn Dependency, _scope: InstallationScope, ) -> Result { if let Some(req) = dep.as_any().downcast_ref::() { Ok(Explanation { message: format!("Install R package {}", req.0.name), command: Some(self.cmd(req)), }) } else { Err(Error::UnknownDependencyFamily) } } } pub fn bioconductor(session: &dyn Session) -> RResolver { RResolver::new(session, "https://hedgehog.fhcrc.org/bioconductor") } pub fn cran(session: &dyn Session) -> RResolver { RResolver::new(session, "https://cran.r-project.org") } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingRPackage { fn to_dependency(&self) -> Option> { Some(Box::new(RPackageDependency::simple(&self.package))) } } ognibuild-0.0.32/src/dependencies/vague.rs000064400000000000000000000163751046102023000165700ustar 00000000000000#[cfg(feature = "debian")] use crate::dependencies::debian::DebianDependency; #[cfg(feature = "debian")] use crate::dependencies::python::PythonPackageDependency; use crate::dependencies::BinaryDependency; use crate::dependencies::Dependency; use crate::dependencies::PkgConfigDependency; use crate::session::Session; use serde::{Deserialize, Serialize}; use std::path::Path; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VagueDependency { pub name: String, pub minimum_version: Option, } impl VagueDependency { pub fn new(name: &str, minimum_version: Option<&str>) -> Self { Self { name: name.to_string(), minimum_version: minimum_version.map(|s| s.trim().to_string()), } } pub fn simple(name: &str) -> Self { Self { name: name.to_string(), minimum_version: None, } } pub fn expand(&self) -> Vec> { let mut ret: Vec> = vec![]; let lcname = self.name.to_lowercase(); if !self.name.contains(' ') { ret.push(Box::new(BinaryDependency::new(&self.name)) as Box); ret.push(Box::new(BinaryDependency::new(&self.name)) as Box); ret.push(Box::new(PkgConfigDependency::new( &self.name.clone(), self.minimum_version.clone().as_deref(), )) as Box); if lcname != self.name { ret.push(Box::new(BinaryDependency::new(&lcname)) as Box); ret.push(Box::new(BinaryDependency::new(&lcname)) as Box); ret.push(Box::new(PkgConfigDependency::new( &lcname, self.minimum_version.clone().as_deref(), )) as Box); } #[cfg(feature = "debian")] { ret.push(Box::new( if let Some(minimum_version) = &self.minimum_version { DebianDependency::new_with_min_version( &self.name, &minimum_version.parse().unwrap(), ) } else { DebianDependency::new(&self.name) }, )); let devname = if lcname.starts_with("lib") { format!("{}-dev", lcname) } else { format!("lib{}-dev", lcname) }; ret.push(if let Some(minimum_version) = &self.minimum_version { Box::new(DebianDependency::new_with_min_version( &devname, &minimum_version.parse().unwrap(), )) } else { Box::new(DebianDependency::new(&devname)) }); } } ret } } impl Dependency for VagueDependency { fn family(&self) -> &'static str { "vague" } fn present(&self, session: &dyn Session) -> bool { self.expand().iter().any(|d| d.present(session)) } fn project_present(&self, session: &dyn Session) -> bool { self.expand().iter().any(|d| d.project_present(session)) } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "debian")] fn known_vague_dep_to_debian(name: &str) -> Option<&str> { match name { "the Gnu Scientific Library" => Some("libgsl-dev"), "the required FreeType library" => Some("libfreetype-dev"), "the Boost C++ libraries" => Some("libboost-dev"), "the sndfile library" => Some("libsndfile-dev"), // TODO(jelmer): Support resolving virtual packages "PythonLibs" => Some("libpython3-dev"), "PythonInterp" => Some("python3"), "ZLIB" => Some("libz3-dev"), "Osmium" => Some("libosmium2-dev"), "glib" => Some("libglib2.0-dev"), "OpenGL" => Some("libgl-dev"), // TODO(jelmer): For Python, check minimum_version and map to python 2 or python 3 "Python" => Some("libpython3-dev"), "Lua" => Some("liblua5.4-dev"), _ => None, } } #[cfg(feature = "debian")] fn resolve_vague_dep_req( apt_mgr: &crate::debian::apt::AptManager, req: VagueDependency, ) -> Vec { let name = req.name.as_str(); let mut options = vec![]; if name.contains(" or ") { for entry in name.split(" or ") { options.extend(resolve_vague_dep_req( apt_mgr, VagueDependency { name: entry.to_string(), minimum_version: req.minimum_version.clone(), }, )); } } if let Some(dep) = known_vague_dep_to_debian(name) { options.push( if let Some(minimum_version) = req.minimum_version.as_ref() { DebianDependency::new_with_min_version(dep, &minimum_version.parse().unwrap()) } else { DebianDependency::new(dep) }, ); } for x in req.expand() { options.extend(crate::debian::apt::dependency_to_possible_deb_dependencies( apt_mgr, x.as_ref(), )); } if let Some(rest) = name.strip_prefix("GNU ") { options.extend(resolve_vague_dep_req( apt_mgr, VagueDependency::simple(rest), )); } if name.starts_with("py") || name.ends_with("py") { // TODO(jelmer): Try harder to determine whether this is a python package let dep = if let Some(min_version) = req.minimum_version.as_ref() { PythonPackageDependency::new_with_min_version(name, min_version) } else { PythonPackageDependency::simple(name) }; options.extend(crate::debian::apt::dependency_to_possible_deb_dependencies( apt_mgr, &dep, )); } // Try even harder if options.is_empty() { let paths = [ Path::new("/usr/lib") .join(".*") .join("pkgconfig") .join(format!("{}-.*\\.pc", regex::escape(&req.name))), Path::new("/usr/lib/pkgconfig").join(format!("{}-.*\\.pc", regex::escape(&req.name))), ]; options.extend( apt_mgr .get_packages_for_paths( paths.iter().map(|x| x.to_str().unwrap()).collect(), true, true, ) .unwrap() .iter() .map(|x| DebianDependency::new(x)), ) } options } #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for VagueDependency { fn try_into_debian_dependency( &self, apt_mgr: &crate::debian::apt::AptManager, ) -> std::option::Option> { Some(resolve_vague_dep_req(apt_mgr, self.clone())) } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingVagueDependency { fn to_dependency(&self) -> Option> { Some(Box::new(VagueDependency::new( &self.name, self.minimum_version.as_deref(), ))) } } ognibuild-0.0.32/src/dependencies/xml.rs000064400000000000000000000041541046102023000162510ustar 00000000000000use crate::dependencies::Dependency; use crate::session::Session; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct XmlEntityDependency { url: String, } impl XmlEntityDependency { pub fn new(url: &str) -> Self { Self { url: url.to_string(), } } } impl Dependency for XmlEntityDependency { fn family(&self) -> &'static str { "xml-entity" } fn present(&self, session: &dyn Session) -> bool { // Check if the entity is defined in the local XML catalog session .command(vec!["xmlcatalog", "--noout", "--resolve", &self.url]) .stdout(std::process::Stdio::null()) .stderr(std::process::Stdio::null()) .run() .unwrap() .success() } fn project_present(&self, _session: &dyn Session) -> bool { todo!() } fn as_any(&self) -> &dyn std::any::Any { self } } pub const XML_ENTITY_URL_MAP: &[(&str, &str)] = &[( "http://www.oasis-open.org/docbook/xml/", "/usr/share/xml/docbook/schema/dtd/", )]; #[cfg(feature = "debian")] impl crate::dependencies::debian::IntoDebianDependency for XmlEntityDependency { fn try_into_debian_dependency( &self, apt: &crate::debian::apt::AptManager, ) -> std::option::Option> { let path = XML_ENTITY_URL_MAP.iter().find_map(|(url, path)| { self.url .strip_prefix(url) .map(|rest| format!("{}{}", path, rest)) }); path.as_ref()?; Some( apt.get_packages_for_paths(vec![path.as_ref().unwrap()], false, false) .unwrap() .iter() .map(|p| crate::dependencies::debian::DebianDependency::simple(p.as_str())) .collect(), ) } } impl crate::buildlog::ToDependency for buildlog_consultant::problems::common::MissingXmlEntity { fn to_dependency(&self) -> Option> { Some(Box::new(XmlEntityDependency::new(&self.url))) } } ognibuild-0.0.32/src/dependency.rs000064400000000000000000000007401046102023000151360ustar 00000000000000use crate::session::Session; /// A dependency is a component that is required by a project to build or run. pub trait Dependency: std::fmt::Debug { fn family(&self) -> &'static str; /// Check whether the dependency is present in the session. fn present(&self, session: &dyn Session) -> bool; /// Check whether the dependency is present in the project. fn project_present(&self, session: &dyn Session) -> bool; fn as_any(&self) -> &dyn std::any::Any; } ognibuild-0.0.32/src/dist.rs000064400000000000000000000122611046102023000137640ustar 00000000000000use crate::buildsystem::{detect_buildsystems, Error}; use crate::fix_build::{iterate_with_build_fixers, BuildFixer, InterimError}; use crate::fixers::*; use crate::installer::{ auto_installation_scope, auto_installer, Error as InstallerError, InstallationScope, }; use crate::logs::{wrap, LogManager}; use crate::session::Session; use std::ffi::OsString; use std::path::Path; pub fn dist( session: &mut dyn Session, export_directory: &Path, reldir: &Path, target_dir: &Path, log_manager: &mut dyn LogManager, version: Option<&str>, quiet: bool, ) -> Result { session.chdir(reldir)?; if let Some(version) = version { // TODO(jelmer): Shouldn't include backend-specific code here std::env::set_var("SETUPTOOLS_SCM_PRETEND_VERSION", version); } // TODO(jelmer): use scan_buildsystems to also look in subdirectories let buildsystems = detect_buildsystems(export_directory); let scope = auto_installation_scope(session); let installer = auto_installer(session, scope, None); let mut fixers: Vec>> = vec![ Box::new(UnexpandedAutoconfMacroFixer::new( session, installer.as_ref(), )), Box::new(GnulibDirectoryFixer::new(session)), Box::new(MinimumAutoconfFixer::new(session)), Box::new(MissingGoSumEntryFixer::new(session)), Box::new(InstallFixer::new( installer.as_ref(), InstallationScope::User, )), ]; if session.is_temporary() { // Only muck about with temporary sessions fixers.extend([ Box::new(GitIdentityFixer::new(session)) as Box>, Box::new(SecretGpgKeyFixer::new(session)) as Box>, ]); } // Some things want to write to the user's home directory, e.g. pip caches in ~/.cache session.create_home()?; for buildsystem in buildsystems { return Ok(iterate_with_build_fixers( fixers .iter() .map(|x| x.as_ref()) .collect::>() .as_slice(), || -> Result<_, InterimError> { Ok(wrap(log_manager, || -> Result<_, Error> { buildsystem.dist(session, installer.as_ref(), target_dir, quiet) })?) }, None, )?); } Err(Error::NoBuildSystemDetected) } #[cfg(feature = "breezy")] // This is the function used by debianize() /// Create a dist tarball for a tree. /// /// # Arguments /// * `session` - session to run it /// * `tree` - Tree object to work in /// * `target_dir` - Directory to write tarball into /// * `include_controldir` - Whether to include the version control directory /// * `temp_subdir` - name of subdirectory in which to check out the source code; /// defaults to "package" pub fn create_dist( session: &mut dyn Session, tree: &T, target_dir: &Path, include_controldir: Option, log_manager: &mut dyn LogManager, version: Option<&str>, subpath: &Path, temp_subdir: Option<&str>, ) -> Result { let temp_subdir = temp_subdir.unwrap_or("package"); let project = session.project_from_vcs(tree, include_controldir, Some(temp_subdir))?; dist( session, project.external_path(), project.internal_path(), target_dir, log_manager, version, false, ) } #[cfg(feature = "breezy")] #[cfg(target_os = "linux")] /// Create a dist tarball for a tree. /// /// # Arguments /// * `session` - session to run it /// * `tree` - Tree object to work in /// * `target_dir` - Directory to write tarball into /// * `include_controldir` - Whether to include the version control directory /// * `temp_subdir` - name of subdirectory in which to check out the source code; /// defaults to "package" pub fn create_dist_schroot( tree: &T, target_dir: &Path, chroot: &str, packaging_tree: Option<&dyn breezyshim::tree::Tree>, packaging_subpath: Option<&Path>, include_controldir: Option, subpath: &Path, log_manager: &mut dyn LogManager, version: Option<&str>, temp_subdir: Option<&str>, ) -> Result { // TODO(jelmer): pass in package name as part of session prefix let mut session = crate::session::schroot::SchrootSession::new(chroot, Some("ognibuild-dist"))?; #[cfg(feature = "debian")] if let (Some(packaging_tree), Some(packaging_subpath)) = (packaging_tree, packaging_subpath) { crate::debian::satisfy_build_deps(&session, packaging_tree, packaging_subpath) .map_err(|e| Error::Other(format!("Failed to satisfy build dependencies: {:?}", e)))?; } #[cfg(not(feature = "debian"))] if packaging_tree.is_some() || packaging_subpath.is_some() { log::warn!("Ignoring packaging tree and subpath as debian feature is not enabled"); } create_dist( &mut session, tree, target_dir, include_controldir, log_manager, version, subpath, temp_subdir, ) } ognibuild-0.0.32/src/dist_catcher.rs000064400000000000000000000105531046102023000154570ustar 00000000000000use std::collections::HashMap; use std::ffi::OsString; use std::path::{Path, PathBuf}; pub const SUPPORTED_DIST_EXTENSIONS: &[&str] = &[ ".tar.gz", ".tgz", ".tar.bz2", ".tar.xz", ".tar.lzma", ".tbz2", ".tar", ".zip", ]; pub fn supported_dist_file(file: &Path) -> bool { SUPPORTED_DIST_EXTENSIONS .iter() .any(|&ext| file.ends_with(ext)) } pub struct DistCatcher { existing_files: Option>>, directories: Vec, files: std::sync::Mutex>, start_time: std::time::SystemTime, } impl DistCatcher { pub fn new(directories: Vec) -> Self { Self { directories: directories .iter() .map(|d| d.canonicalize().unwrap()) .collect(), files: std::sync::Mutex::new(Vec::new()), start_time: std::time::SystemTime::now(), existing_files: None, } } pub fn default(directory: &Path) -> Self { Self::new(vec![ directory.join("dist"), directory.to_path_buf(), directory.join(".."), ]) } pub fn start(&mut self) { self.existing_files = Some( self.directories .iter() .map(|d| { let mut map = HashMap::new(); for entry in d.read_dir().unwrap() { let entry = entry.unwrap(); map.insert(entry.path(), entry); } (d.clone(), map) }) .collect(), ); } pub fn find_files(&self) -> Option { let existing_files = self.existing_files.as_ref().unwrap(); let mut files = self.files.lock().unwrap(); for directory in &self.directories { let old_files = existing_files.get(directory).unwrap(); let mut possible_new = Vec::new(); let mut possible_updated = Vec::new(); if !directory.is_dir() { continue; } for entry in directory.read_dir().unwrap() { let entry = entry.unwrap(); if !entry.file_type().unwrap().is_file() || !supported_dist_file(&entry.path()) { continue; } let old_entry = old_files.get(&entry.path()); if old_entry.is_none() { possible_new.push(entry); continue; } if entry.metadata().unwrap().modified().unwrap() > self.start_time { possible_updated.push(entry); continue; } } if possible_new.len() == 1 { let entry = possible_new[0].path(); log::info!("Found new tarball {:?} in {:?}", entry, directory); files.push(entry.clone()); return Some(entry); } else if possible_new.len() > 1 { log::warn!( "Found multiple tarballs {:?} in {:?}", possible_new.iter().map(|e| e.path()).collect::>(), directory ); files.extend(possible_new.iter().map(|e| e.path())); return Some(possible_new[0].path()); } if possible_updated.len() == 1 { let entry = possible_updated[0].path(); log::info!("Found updated tarball {:?} in {:?}", entry, directory); files.push(entry.clone()); return Some(entry); } } None } pub fn copy_single(&self, target_dir: &Path) -> Result, std::io::Error> { for path in self.files.lock().unwrap().iter() { match std::fs::copy(path, target_dir.join(path.file_name().unwrap())) { Ok(_) => return Ok(Some(path.file_name().unwrap().into())), Err(e) => { if e.kind() == std::io::ErrorKind::AlreadyExists { continue; } return Err(e); } } } log::info!("No tarball created :("); Err(std::io::Error::new( std::io::ErrorKind::NotFound, "No tarball found", )) } } ognibuild-0.0.32/src/fix_build.rs000064400000000000000000000176231046102023000147750ustar 00000000000000use buildlog_consultant::{Match, Problem}; use log::{info, warn}; use std::fmt::{Debug, Display}; /// A fixer is a struct that can resolve a specific type of problem. pub trait BuildFixer: std::fmt::Debug + std::fmt::Display { /// Check if this fixer can potentially resolve the given problem. fn can_fix(&self, problem: &dyn Problem) -> bool; /// Attempt to resolve the given problem. fn fix(&self, problem: &dyn Problem) -> Result>; } #[derive(Debug)] pub enum InterimError { /// A problem that was detected during the build, and that we can attempt to fix. Recognized(Box), /// An error that we could not identify. Unidentified { retcode: i32, lines: Vec, secondary: Option>, }, /// Another error raised specifically by the callback function that is not fixable. Other(O), } /// Error result from repeatedly running and attemptin to fix issues. #[derive(Debug)] pub enum IterateBuildError { /// The limit of fixing attempts was reached. FixerLimitReached(usize), /// A problem was detected that was recognized but could not be fixed. Persistent(Box), /// An error that we could not identify. Unidentified { retcode: i32, lines: Vec, secondary: Option>, }, /// Another error raised specifically by the callback function that is not fixable. Other(O), } impl Display for IterateBuildError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { IterateBuildError::FixerLimitReached(limit) => { write!(f, "Fixer limit reached: {}", limit) } IterateBuildError::Persistent(p) => { write!(f, "Persistent build problem: {}", p) } IterateBuildError::Unidentified { retcode, lines, secondary, } => write!( f, "Unidentified error: retcode: {}, lines: {:?}, secondary: {:?}", retcode, lines, secondary ), IterateBuildError::Other(e) => write!(f, "Other error: {}", e), } } } impl std::error::Error for IterateBuildError {} /// Call cb() until there are no more DetailedFailures we can fix. /// /// # Arguments /// * `fixers`: List of fixers to use to resolve issues /// * `cb`: Callable to run the build /// * `limit: Maximum number of fixing attempts before giving up pub fn iterate_with_build_fixers< T, // The error type that the fixers can return. I: std::error::Error, // The error type that the callback function can return, and the eventual return type. E: From + std::error::Error, >( fixers: &[&dyn BuildFixer], mut cb: impl FnMut() -> Result>, limit: Option, ) -> Result> { let mut attempts = 0; let mut fixed_errors: std::collections::HashSet> = std::collections::HashSet::new(); loop { let mut to_resolve: Vec> = vec![]; match cb() { Ok(v) => return Ok(v), Err(InterimError::Recognized(e)) => to_resolve.push(e), Err(InterimError::Unidentified { retcode, lines, secondary, }) => { return Err(IterateBuildError::Unidentified { retcode, lines, secondary, }); } Err(InterimError::Other(e)) => return Err(IterateBuildError::Other(e)), } while let Some(f) = to_resolve.pop() { info!("Identified error: {:?}", f); if fixed_errors.contains(&f) { warn!("Failed to resolve error {:?}, it persisted. Giving up.", f); return Err(IterateBuildError::Persistent(f)); } attempts += 1; if let Some(limit) = limit { if limit <= attempts { return Err(IterateBuildError::FixerLimitReached(limit)); } } match resolve_error(f.as_ref(), fixers) { Err(InterimError::Recognized(n)) => { info!("New error {:?} while resolving {:?}", &n, &f); if to_resolve.contains(&n) { return Err(IterateBuildError::Persistent(n)); } to_resolve.push(f); to_resolve.push(n); } Err(InterimError::Unidentified { retcode, lines, secondary, }) => { return Err(IterateBuildError::Unidentified { retcode, lines, secondary, }); } Err(InterimError::Other(e)) => return Err(IterateBuildError::Other(e.into())), Ok(resolved) if !resolved => { warn!("Failed to find resolution for error {:?}. Giving up.", f); return Err(IterateBuildError::Persistent(f)); } Ok(_) => { fixed_errors.insert(f); } } } } } pub fn resolve_error( problem: &dyn Problem, fixers: &[&dyn BuildFixer], ) -> Result> { let relevant_fixers = fixers .iter() .filter(|fixer| fixer.can_fix(problem)) .collect::>(); if relevant_fixers.is_empty() { warn!("No fixer found for {:?}", problem); return Ok(false); } for fixer in relevant_fixers { info!("Attempting to use fixer {} to address {:?}", fixer, problem); let made_changes = fixer.fix(problem)?; if made_changes { return Ok(true); } } Ok(false) } pub fn run_fixing_problems< // The error type that the fixers can return. I: std::error::Error, // The error type that the callback function can return. E: From + std::error::Error + From, >( fixers: &[&dyn BuildFixer], limit: Option, session: &dyn crate::session::Session, args: &[&str], quiet: bool, cwd: Option<&std::path::Path>, user: Option<&str>, env: Option<&std::collections::HashMap>, ) -> Result, IterateBuildError> { iterate_with_build_fixers::, I, E>( fixers, || { crate::analyze::run_detecting_problems( session, args.to_vec(), None, quiet, cwd, user, env, None, ) .map_err(|e| match e { crate::analyze::AnalyzedError::Detailed { retcode: _, error } => { InterimError::Recognized(error) } crate::analyze::AnalyzedError::Unidentified { retcode, lines, secondary, } => InterimError::Unidentified { retcode, lines, secondary, }, crate::analyze::AnalyzedError::MissingCommandError { command } => { InterimError::Recognized(Box::new( buildlog_consultant::problems::common::MissingCommand(command), )) } crate::analyze::AnalyzedError::IoError(e) => InterimError::Other(e.into()), }) }, limit, ) .map_err(|e| match e { IterateBuildError::Other(e) => IterateBuildError::Other(e.into()), e => e, }) } ognibuild-0.0.32/src/fixers.rs000064400000000000000000000237271046102023000143320ustar 00000000000000use crate::fix_build::{BuildFixer, InterimError}; use crate::installer::{Error as InstallerError, InstallationScope, Installer}; use crate::session::Session; use buildlog_consultant::problems::common::{ MinimumAutoconfTooOld, MissingAutoconfMacro, MissingGitIdentity, MissingGnulibDirectory, MissingGoSumEntry, MissingSecretGpgKey, }; use buildlog_consultant::Problem; use std::io::{Seek, Write}; pub struct GnulibDirectoryFixer<'a> { session: &'a dyn Session, } impl std::fmt::Debug for GnulibDirectoryFixer<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("GnulibDirectoryFixer").finish() } } impl std::fmt::Display for GnulibDirectoryFixer<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "GnulibDirectoryFixer") } } impl<'a> GnulibDirectoryFixer<'a> { pub fn new(session: &'a dyn Session) -> Self { Self { session } } } impl<'a> BuildFixer for GnulibDirectoryFixer<'a> { fn can_fix(&self, problem: &dyn Problem) -> bool { problem .as_any() .downcast_ref::() .is_some() } fn fix(&self, _problem: &dyn Problem) -> Result> { self.session .command(vec!["./gnulib.sh"]) .check_call() .unwrap(); Ok(true) } } pub struct GitIdentityFixer<'a> { session: &'a dyn Session, } impl std::fmt::Debug for GitIdentityFixer<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("GitIdentityFixer").finish() } } impl std::fmt::Display for GitIdentityFixer<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "GitIdentityFixer") } } impl<'a> GitIdentityFixer<'a> { pub fn new(session: &'a dyn Session) -> Self { Self { session } } } impl<'a> BuildFixer for GitIdentityFixer<'a> { fn can_fix(&self, problem: &dyn Problem) -> bool { problem .as_any() .downcast_ref::() .is_some() } fn fix(&self, _problem: &dyn Problem) -> Result> { for name in ["user.email", "user.name"] { let output = std::process::Command::new("git") .arg("config") .arg("--global") .arg(name) .output() .unwrap(); let value = String::from_utf8(output.stdout).unwrap(); self.session .command(vec!["git", "config", "--global", name, &value]) .check_call() .unwrap(); } Ok(true) } } pub struct SecretGpgKeyFixer<'a> { session: &'a dyn Session, } impl std::fmt::Debug for SecretGpgKeyFixer<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("SecretGpgKeyFixer").finish() } } impl std::fmt::Display for SecretGpgKeyFixer<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "SecretGpgKey") } } impl<'a> SecretGpgKeyFixer<'a> { pub fn new(session: &'a dyn Session) -> Self { Self { session } } } impl<'a> BuildFixer for SecretGpgKeyFixer<'a> { fn can_fix(&self, problem: &dyn Problem) -> bool { problem .as_any() .downcast_ref::() .is_some() } fn fix(&self, _problem: &dyn Problem) -> Result> { let mut td = tempfile::tempfile().unwrap(); let script = br#"""Key-Type: 1 Key-Length: 4096 Subkey-Type: 1 Subkey-Length: 4096 Name-Real: Dummy Key for ognibuild Name-Email: dummy@example.com Expire-Date: 0 Passphrase: "" """#; td.write_all(script).unwrap(); td.seek(std::io::SeekFrom::Start(0)).unwrap(); self.session .command(vec!["gpg", "--gen-key", "--batch", "/dev/stdin"]) .stdin(td.into()) .check_call() .unwrap(); Ok(true) } } pub struct MinimumAutoconfFixer<'a> { session: &'a dyn Session, } impl std::fmt::Debug for MinimumAutoconfFixer<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("MinimumAutoconfFixer").finish() } } impl std::fmt::Display for MinimumAutoconfFixer<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "MinimumAutoconfFixer") } } impl<'a> MinimumAutoconfFixer<'a> { pub fn new(session: &'a dyn Session) -> Self { Self { session } } } impl<'a> BuildFixer for MinimumAutoconfFixer<'a> { fn can_fix(&self, problem: &dyn Problem) -> bool { problem .as_any() .downcast_ref::() .is_some() } fn fix(&self, problem: &dyn Problem) -> Result> { let problem = problem .as_any() .downcast_ref::() .unwrap(); for name in ["configure.ac", "configure.in"] { let p = self.session.external_path(std::path::Path::new(name)); let f = std::fs::File::open(&p).unwrap(); let buf = std::io::BufReader::new(f); use std::io::BufRead; let mut lines = buf.lines().map(|l| l.unwrap()).collect::>(); let mut found = false; for line in lines.iter_mut() { let m = lazy_regex::regex_find!(r"AC_PREREQ\((.*)\)", &line); if m.is_none() { continue; } *line = format!("AC_PREREQ({})", problem.0); found = true; } if !found { lines.insert(0, format!("AC_PREREQ({})", problem.0)); } std::fs::write( self.session.external_path(std::path::Path::new(name)), lines.concat(), ) .unwrap(); return Ok(true); } Ok(false) } } pub struct MissingGoSumEntryFixer<'a> { session: &'a dyn Session, } impl std::fmt::Debug for MissingGoSumEntryFixer<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("MissingGoSumEntryFixer").finish() } } impl std::fmt::Display for MissingGoSumEntryFixer<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "MissingGoSumEntryFixer") } } impl<'a> MissingGoSumEntryFixer<'a> { pub fn new(session: &'a dyn Session) -> Self { Self { session } } } impl<'a> BuildFixer for MissingGoSumEntryFixer<'a> { fn can_fix(&self, problem: &dyn Problem) -> bool { problem .as_any() .downcast_ref::() .is_some() } fn fix(&self, problem: &dyn Problem) -> Result> { let problem = problem .as_any() .downcast_ref::() .unwrap(); self.session .command(vec!["go", "mod", "download", &problem.package]) .check_call() .unwrap(); Ok(true) } } pub struct UnexpandedAutoconfMacroFixer<'a> { session: &'a dyn Session, installer: &'a dyn Installer, } impl std::fmt::Debug for UnexpandedAutoconfMacroFixer<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("UnexpandedAutoconfMacroFixer").finish() } } impl std::fmt::Display for UnexpandedAutoconfMacroFixer<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "UnexpandedAutoconfMacroFixer") } } impl<'a> UnexpandedAutoconfMacroFixer<'a> { pub fn new(session: &'a dyn Session, installer: &'a dyn Installer) -> Self { Self { session, installer } } } impl<'a> BuildFixer for UnexpandedAutoconfMacroFixer<'a> { fn can_fix(&self, problem: &dyn Problem) -> bool { problem .as_any() .downcast_ref::() .is_some() } fn fix(&self, problem: &dyn Problem) -> Result> { let problem = problem .as_any() .downcast_ref::() .unwrap(); let dep = crate::dependencies::autoconf::AutoconfMacroDependency::new(&problem.r#macro); self.installer .install(&dep, InstallationScope::Global) .unwrap(); self.session .command(vec!["autoconf", "-f"]) .check_call() .unwrap(); Ok(true) } } pub struct InstallFixer<'a> { installer: &'a dyn crate::installer::Installer, scope: crate::installer::InstallationScope, } impl std::fmt::Debug for InstallFixer<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("InstallFixer").finish() } } impl std::fmt::Display for InstallFixer<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "upstream requirement fixer") } } impl<'a> InstallFixer<'a> { pub fn new( installer: &'a dyn crate::installer::Installer, scope: crate::installer::InstallationScope, ) -> Self { Self { installer, scope } } } impl<'a> BuildFixer for InstallFixer<'a> { fn can_fix(&self, error: &dyn Problem) -> bool { let req = crate::buildlog::problem_to_dependency(error); req.is_some() } fn fix(&self, error: &dyn Problem) -> Result> { let req = crate::buildlog::problem_to_dependency(error); if let Some(req) = req { self.installer.install(req.as_ref(), self.scope).unwrap(); Ok(true) } else { Ok(false) } } } ognibuild-0.0.32/src/installer.rs000064400000000000000000000323231046102023000150170ustar 00000000000000use crate::dependency::Dependency; use crate::session::Session; #[derive(Debug)] pub enum Error { UnknownDependencyFamily, UnsupportedScope(InstallationScope), UnsupportedScopes(Vec), AnalyzedError(crate::analyze::AnalyzedError), SessionError(crate::session::Error), Other(String), } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Error::UnknownDependencyFamily => write!(f, "Unknown dependency family"), Error::UnsupportedScope(scope) => write!(f, "Unsupported scope: {:?}", scope), Error::UnsupportedScopes(scopes) => write!(f, "Unsupported scopes: {:?}", scopes), Error::AnalyzedError(e) => write!(f, "{}", e), Error::SessionError(e) => write!(f, "{}", e), Error::Other(s) => write!(f, "{}", s), } } } impl std::error::Error for Error {} impl From for Error { fn from(e: crate::analyze::AnalyzedError) -> Self { Error::AnalyzedError(e) } } impl From for Error { fn from(e: crate::session::Error) -> Self { Error::SessionError(e) } } /// An explanation is a human-readable description of what to do to install a dependency. pub struct Explanation { pub message: String, pub command: Option>, } impl Explanation { pub fn new(message: String, command: Option>) -> Self { Explanation { message, command } } } impl std::fmt::Display for Explanation { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", self.message)?; if let Some(command) = &self.command { write!( f, "\n\nRun the following command to install the dependency:\n\n" )?; for arg in command { write!(f, "{} ", arg)?; } writeln!(f)?; } Ok(()) } } /// The scope of an installation. #[derive(Debug, Clone, PartialEq, Eq, Copy)] pub enum InstallationScope { /// Under /usr in the system Global, /// In the current users' home directory User, /// Vendored in the projects' source directory Vendor, } impl std::str::FromStr for InstallationScope { type Err = Error; fn from_str(s: &str) -> Result { match s { "global" => Ok(InstallationScope::Global), "user" => Ok(InstallationScope::User), "vendor" => Ok(InstallationScope::Vendor), _ => Err(Error::Other(format!("Unknown installation scope: {}", s))), } } } /// An installer can take a dependency and install it into the session. pub trait Installer { /// Install the dependency into the session. fn install(&self, dep: &dyn Dependency, scope: InstallationScope) -> Result<(), Error>; /// Explain how to install the dependency. fn explain(&self, dep: &dyn Dependency, scope: InstallationScope) -> Result; fn explain_some( &self, deps: Vec>, scope: InstallationScope, ) -> Result<(Vec, Vec>), Error> { let mut explanations = Vec::new(); let mut failed = Vec::new(); for dep in deps { match self.explain(&*dep, scope) { Ok(explanation) => explanations.push(explanation), Err(Error::UnknownDependencyFamily) => failed.push(dep), Err(e) => { return Err(e); } } } Ok((explanations, failed)) } fn install_some( &self, deps: Vec>, scope: InstallationScope, ) -> Result<(Vec>, Vec>), Error> { let mut installed = Vec::new(); let mut failed = Vec::new(); for dep in deps { match self.install(&*dep, scope) { Ok(()) => installed.push(dep), Err(Error::UnknownDependencyFamily) => failed.push(dep), Err(e) => { return Err(e); } } } Ok((installed, failed)) } } /// A null installer does nothing. pub struct NullInstaller; impl NullInstaller { pub fn new() -> Self { NullInstaller } } impl Default for NullInstaller { fn default() -> Self { NullInstaller::new() } } impl Installer for NullInstaller { fn install(&self, _dep: &dyn Dependency, _scope: InstallationScope) -> Result<(), Error> { Err(Error::UnknownDependencyFamily) } fn explain( &self, _dep: &dyn Dependency, _scope: InstallationScope, ) -> Result { Err(Error::UnknownDependencyFamily) } } pub struct StackedInstaller<'a>(pub Vec>); impl<'a> StackedInstaller<'a> { pub fn new(resolvers: Vec>) -> Self { Self(resolvers) } } impl<'a> Installer for StackedInstaller<'a> { fn install(&self, requirement: &dyn Dependency, scope: InstallationScope) -> Result<(), Error> { for sub in &self.0 { match sub.install(requirement, scope) { Ok(()) => { return Ok(()); } Err(Error::UnknownDependencyFamily) => {} Err(e) => { return Err(e); } } } Err(Error::UnknownDependencyFamily) } fn explain( &self, requirements: &dyn Dependency, scope: InstallationScope, ) -> Result { for sub in &self.0 { match sub.explain(requirements, scope) { Ok(e) => { return Ok(e); } Err(Error::UnknownDependencyFamily) => {} Err(e) => { return Err(e); } } } Err(Error::UnknownDependencyFamily) } } pub fn installer_by_name<'a>( session: &'a dyn crate::session::Session, name: &str, ) -> Option> { // TODO: Use more dynamic way to load installers match name { #[cfg(feature = "debian")] "apt" => Some( Box::new(crate::debian::apt::AptInstaller::from_session(session)) as Box, ), "cpan" => Some( Box::new(crate::dependencies::perl::CPAN::new(session, false)) as Box, ), "ctan" => Some(Box::new(crate::dependencies::latex::ctan(session)) as Box), "pypi" => Some( Box::new(crate::dependencies::python::PypiResolver::new(session)) as Box, ), "npm" => Some( Box::new(crate::dependencies::node::NpmResolver::new(session)) as Box, ), "go" => { Some(Box::new(crate::dependencies::go::GoResolver::new(session)) as Box) } "hackage" => Some( Box::new(crate::dependencies::haskell::HackageResolver::new(session)) as Box, ), "cran" => Some(Box::new(crate::dependencies::r::cran(session)) as Box), "bioconductor" => { Some(Box::new(crate::dependencies::r::bioconductor(session)) as Box) } "octave-forge" => Some( Box::new(crate::dependencies::octave::OctaveForgeResolver::new( session, )) as Box, ), "native" => { Some(Box::new(StackedInstaller::new(native_installers(session))) as Box) } _ => None, } } pub fn native_installers<'a>( session: &'a dyn crate::session::Session, ) -> Vec> { // TODO: Use more dynamic way to load installers [ "ctan", "pypi", "npm", "go", "hackage", "cran", "bioconductor", "octave-forge", ] .iter() .map(|name| installer_by_name(session, name).unwrap()) .collect() } #[cfg(feature = "debian")] fn apt_installer<'a>( session: &'a dyn crate::session::Session, #[allow(unused_variables)] dep_server_url: Option<&url::Url>, ) -> Box { #[cfg(feature = "dep-server")] if let Some(dep_server_url) = dep_server_url { Box::new( crate::debian::dep_server::DepServerAptInstaller::from_session(session, dep_server_url), ) as Box } else { Box::new(crate::debian::apt::AptInstaller::from_session(session)) } #[cfg(not(feature = "dep-server"))] { Box::new(crate::debian::apt::AptInstaller::from_session(session)) } } /// Select installers by name. pub fn select_installers<'a>( session: &'a dyn crate::session::Session, names: &[&str], dep_server_url: Option<&url::Url>, ) -> Result, String> { let mut installers = Vec::new(); for name in names.iter() { if name == &"apt" { #[cfg(feature = "debian")] installers.push(apt_installer(session, dep_server_url)); #[cfg(not(feature = "debian"))] return Err("Apt installer not available".to_string()); } else if let Some(installer) = installer_by_name(session, name) { installers.push(installer); } else { return Err(format!("Unknown installer: {}", name)); } } Ok(Box::new(StackedInstaller(installers))) } pub fn auto_installation_scope(session: &dyn crate::session::Session) -> InstallationScope { let user = crate::session::get_user(session); // TODO(jelmer): Check VIRTUAL_ENV, and prioritize PypiResolver if // present? if user == "root" { log::info!("Running as root, so using global installation scope"); InstallationScope::Global } else if session.is_temporary() { log::info!("Running in a temporary session, so using global installation scope"); InstallationScope::Global } else { log::info!("Running as user, so using user installation scope"); InstallationScope::User } } pub fn auto_installer<'a>( session: &'a dyn crate::session::Session, scope: InstallationScope, dep_server_url: Option<&url::Url>, ) -> Box { // if session is SchrootSession or if we're root, use apt let mut installers: Vec> = Vec::new(); #[cfg(feature = "debian")] if scope == InstallationScope::Global && crate::session::which(session, "apt-get").is_some() { log::info!( "Using global installation scope and apt-get is available, so using apt installer" ); installers.push(apt_installer(session, dep_server_url)); } installers.extend(native_installers(session)); Box::new(StackedInstaller::new(installers)) } /// Install missing dependencies. /// /// This function takes a list of dependencies and installs them if they are not already present. /// /// # Arguments /// * `session` - The session to install the dependencies into. /// * `installer` - The installer to use. pub fn install_missing_deps( session: &dyn Session, installer: &dyn Installer, scopes: &[InstallationScope], deps: &[&dyn Dependency], ) -> Result<(), Error> { if deps.is_empty() { return Ok(()); } let missing = deps .iter() .filter(|dep| !dep.present(session)) .collect::>(); if !missing.is_empty() { log::info!("Missing dependencies: {:?}", missing); for dep in missing.into_iter() { log::info!("Installing {:?}", dep); let mut installed = false; for scope in scopes { match installer.install(*dep, *scope) { Ok(()) => { log::info!("Installed {:?}", dep); installed = true; break; } Err(Error::UnsupportedScope(_)) => {} Err(e) => { return Err(e); } } } if !installed { return Err(Error::UnsupportedScopes(scopes.to_vec())); } } } Ok(()) } /// Explain missing dependencies. /// /// This function takes a list of dependencies and returns a list of explanations for how to /// install them. /// /// # Arguments /// * `session` - The session to install the dependencies into. /// * `installer` - The installer to use. pub fn explain_missing_deps( session: &dyn Session, installer: &dyn Installer, deps: &[&dyn Dependency], ) -> Result, Error> { if deps.is_empty() { return Ok(vec![]); } let mut missing = vec![]; for dep in deps.iter() { if !dep.present(session) { missing.push(*dep) } } if !missing.is_empty() { let mut explanations = vec![]; for dep in missing.into_iter() { log::info!("Explaining {:?}", dep); explanations.push(installer.explain(dep, InstallationScope::Global)?); } Ok(explanations) } else { Ok(vec![]) } } ognibuild-0.0.32/src/lib.rs000064400000000000000000000006451046102023000135720ustar 00000000000000pub mod actions; pub mod analyze; pub mod buildlog; pub mod buildsystem; pub mod buildsystems; #[cfg(feature = "debian")] pub mod debian; pub mod dependencies; pub mod dependency; pub mod dist; pub mod dist_catcher; pub mod fix_build; pub mod fixers; pub mod installer; pub mod logs; pub mod output; pub mod session; pub mod shebang; #[cfg(feature = "upstream")] pub mod upstream; #[cfg(feature = "breezy")] pub mod vcs; ognibuild-0.0.32/src/logs.rs000064400000000000000000000116641046102023000137730ustar 00000000000000use log::debug; use std::fs; use std::fs::File; use std::io::{self, Write}; use std::os::unix::io::{AsRawFd, RawFd}; use std::path::{Path, PathBuf}; use std::process::Command; struct RedirectOutput { old_stdout: RawFd, old_stderr: RawFd, } impl RedirectOutput { fn new(to_file: &File) -> io::Result { let stdout = io::stdout(); let stderr = io::stderr(); stdout.lock().flush()?; stderr.lock().flush()?; let old_stdout = unsafe { libc::dup(libc::STDOUT_FILENO) }; let old_stderr = unsafe { libc::dup(libc::STDERR_FILENO) }; if old_stdout == -1 || old_stderr == -1 { return Err(io::Error::last_os_error()); } unsafe { libc::dup2(to_file.as_raw_fd(), libc::STDOUT_FILENO); libc::dup2(to_file.as_raw_fd(), libc::STDERR_FILENO); } Ok(RedirectOutput { old_stdout, old_stderr, }) } } impl Drop for RedirectOutput { fn drop(&mut self) { let stdout = io::stdout(); let stderr = io::stderr(); stdout.lock().flush().unwrap(); stderr.lock().flush().unwrap(); unsafe { libc::dup2(self.old_stdout, libc::STDOUT_FILENO); libc::dup2(self.old_stderr, libc::STDERR_FILENO); libc::close(self.old_stdout); libc::close(self.old_stderr); } } } struct CopyOutput { old_stdout: RawFd, old_stderr: RawFd, new_fd: Option, } impl CopyOutput { fn new(output_log: &std::path::Path, tee: bool) -> io::Result { let old_stdout = unsafe { libc::dup(libc::STDOUT_FILENO) }; let old_stderr = unsafe { libc::dup(libc::STDERR_FILENO) }; let new_fd = if tee { let process = Command::new("tee") .arg(output_log) .stdin(std::process::Stdio::piped()) .spawn()?; process.stdin.unwrap().as_raw_fd() } else { File::create(output_log)?.as_raw_fd() }; unsafe { libc::dup2(new_fd, libc::STDOUT_FILENO); libc::dup2(new_fd, libc::STDERR_FILENO); } Ok(CopyOutput { old_stdout, old_stderr, new_fd: Some(new_fd), }) } } impl Drop for CopyOutput { fn drop(&mut self) { if let Some(fd) = self.new_fd.take() { unsafe { libc::fsync(fd); libc::close(fd); } } unsafe { libc::dup2(self.old_stdout, libc::STDOUT_FILENO); libc::dup2(self.old_stderr, libc::STDERR_FILENO); libc::close(self.old_stdout); libc::close(self.old_stderr); } } } pub fn rotate_logfile(source_path: &std::path::Path) -> std::io::Result<()> { if source_path.exists() { let directory_path = source_path.parent().unwrap_or_else(|| Path::new("")); let name = source_path.file_name().unwrap().to_str().unwrap(); let mut i = 1; while directory_path.join(format!("{}.{}", name, i)).exists() { i += 1; } let target_path: PathBuf = directory_path.join(format!("{}.{}", name, i)); fs::rename(source_path, &target_path)?; debug!("Storing previous build log at {}", target_path.display()); } Ok(()) } pub enum LogMode { Copy, Redirect, } pub trait LogManager { fn start(&mut self) -> std::io::Result<()>; fn stop(&mut self) {} } /// Run a function capturing its output to a log file. pub fn wrap(logs: &mut dyn LogManager, f: impl FnOnce() -> R) -> R { logs.start().unwrap(); let result = f(); std::io::stdout().flush().unwrap(); std::io::stderr().flush().unwrap(); logs.stop(); result } pub struct DirectoryLogManager { path: PathBuf, mode: LogMode, copy_output: Option, redirect_output: Option, } impl DirectoryLogManager { pub fn new(path: PathBuf, mode: LogMode) -> Self { Self { path, mode, copy_output: None, redirect_output: None, } } } impl LogManager for DirectoryLogManager { fn start(&mut self) -> std::io::Result<()> { rotate_logfile(&self.path)?; match self.mode { LogMode::Copy => { self.copy_output = Some(CopyOutput::new(&self.path, true)?); } LogMode::Redirect => { self.redirect_output = Some(RedirectOutput::new(&File::create(&self.path)?)?); } } Ok(()) } fn stop(&mut self) { self.copy_output = None; self.redirect_output = None; } } pub struct NoLogManager; impl NoLogManager { pub fn new() -> Self { Self {} } } impl Default for NoLogManager { fn default() -> Self { Self::new() } } impl LogManager for NoLogManager { fn start(&mut self) -> std::io::Result<()> { Ok(()) } } ognibuild-0.0.32/src/output.rs000064400000000000000000000025441046102023000143640ustar 00000000000000pub trait Output: std::fmt::Debug { fn family(&self) -> &'static str; fn get_declared_dependencies(&self) -> Vec; } #[derive(Debug)] pub struct BinaryOutput(pub String); impl BinaryOutput { pub fn new(name: &str) -> Self { BinaryOutput(name.to_string()) } } impl Output for BinaryOutput { fn family(&self) -> &'static str { "binary" } fn get_declared_dependencies(&self) -> Vec { vec![] } } #[derive(Debug)] pub struct PythonPackageOutput { pub name: String, pub version: Option, } impl PythonPackageOutput { pub fn new(name: &str, version: Option<&str>) -> Self { PythonPackageOutput { name: name.to_string(), version: version.map(|s| s.to_string()), } } } impl Output for PythonPackageOutput { fn family(&self) -> &'static str { "python-package" } fn get_declared_dependencies(&self) -> Vec { vec![] } } #[derive(Debug)] pub struct RPackageOutput { pub name: String, } impl RPackageOutput { pub fn new(name: &str) -> Self { RPackageOutput { name: name.to_string(), } } } impl Output for RPackageOutput { fn family(&self) -> &'static str { "r-package" } fn get_declared_dependencies(&self) -> Vec { vec![] } } ognibuild-0.0.32/src/session/mod.rs000064400000000000000000000370401046102023000152650ustar 00000000000000use std::collections::HashMap; use std::io::{BufRead, Write}; use std::process::ExitStatus; pub mod plain; #[cfg(target_os = "linux")] pub mod schroot; #[cfg(target_os = "linux")] pub mod unshare; #[derive(Debug)] pub enum Error { CalledProcessError(ExitStatus), IoError(std::io::Error), SetupFailure(String, String), } impl From for Error { fn from(e: std::io::Error) -> Self { Error::IoError(e) } } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Error::CalledProcessError(code) => write!(f, "CalledProcessError({})", code), Error::IoError(e) => write!(f, "IoError({})", e), Error::SetupFailure(msg, _long_description) => write!(f, "SetupFailure({})", msg), } } } impl std::error::Error for Error {} pub trait Session { /// Change the current working directory in the session. fn chdir(&mut self, path: &std::path::Path) -> Result<(), crate::session::Error>; fn pwd(&self) -> &std::path::Path; /// Return the external path for a path inside the session. fn external_path(&self, path: &std::path::Path) -> std::path::PathBuf; /// Return the location of the session. fn location(&self) -> std::path::PathBuf; fn check_output( &self, argv: Vec<&str>, cwd: Option<&std::path::Path>, user: Option<&str>, env: Option>, ) -> Result, Error>; /// Ensure that the current users' home directory exists. fn create_home(&self) -> Result<(), Error>; fn check_call( &self, argv: Vec<&str>, cwd: Option<&std::path::Path>, user: Option<&str>, env: Option>, ) -> Result<(), crate::session::Error>; /// Check if a file or directory exists. fn exists(&self, path: &std::path::Path) -> bool; /// Create a directory. fn mkdir(&self, path: &std::path::Path) -> Result<(), crate::session::Error>; /// Recursively remove a directory. fn rmtree(&self, path: &std::path::Path) -> Result<(), crate::session::Error>; /// Setup a project from an existing directory. /// /// # Arguments /// * `path` - The path to the directory to setup the session from. /// * `subdir` - The subdirectory to use as the session root. fn project_from_directory( &self, path: &std::path::Path, subdir: Option<&str>, ) -> Result; fn command<'a>(&'a self, argv: Vec<&'a str>) -> CommandBuilder<'a>; fn popen( &self, argv: Vec<&str>, cwd: Option<&std::path::Path>, user: Option<&str>, stdout: Option, stderr: Option, stdin: Option, env: Option<&std::collections::HashMap>, ) -> std::process::Child; /// Check if the session is temporary. fn is_temporary(&self) -> bool; #[cfg(feature = "breezy")] /// Setup a project from a VCS tree. /// /// # Arguments /// * `tree` - The VCS tree to setup the session from. /// * `include_controldir` - Whether to include the control directory. /// * `subdir` - The subdirectory to use as the session root. /// /// # Returns /// A tuple containing the path to the tree in the session and /// the external path. fn project_from_vcs( &self, tree: &dyn crate::vcs::DupableTree, include_controldir: Option, subdir: Option<&str>, ) -> Result; fn read_dir(&self, path: &std::path::Path) -> Result, Error>; } pub enum Project { /// A project that does not need to be cleaned up. Noop(std::path::PathBuf), /// A temporary project that needs to be cleaned up. Temporary { external_path: std::path::PathBuf, internal_path: std::path::PathBuf, td: std::path::PathBuf, }, } impl Drop for Project { fn drop(&mut self) { match self { Project::Noop(_) => {} Project::Temporary { external_path: _, internal_path: _, td, } => { log::info!("Removing temporary project {}", td.display()); std::fs::remove_dir_all(td).unwrap(); } } } } impl Project { pub fn internal_path(&self) -> &std::path::Path { match self { Project::Noop(path) => path, Project::Temporary { internal_path, .. } => internal_path, } } pub fn external_path(&self) -> &std::path::Path { match self { Project::Noop(path) => path, Project::Temporary { external_path, .. } => external_path, } } } impl From for Project { fn from(tempdir: tempfile::TempDir) -> Self { Project::Temporary { external_path: tempdir.path().to_path_buf(), internal_path: tempdir.path().to_path_buf(), td: tempdir.into_path(), } } } pub struct CommandBuilder<'a> { session: &'a dyn Session, argv: Vec<&'a str>, cwd: Option<&'a std::path::Path>, user: Option<&'a str>, env: Option>, stdin: Option, stdout: Option, stderr: Option, quiet: bool, } impl<'a> CommandBuilder<'a> { pub fn new(session: &'a dyn Session, argv: Vec<&'a str>) -> Self { CommandBuilder { session, argv, cwd: None, user: None, env: None, stdin: None, stdout: None, stderr: None, quiet: false, } } pub fn quiet(mut self, quiet: bool) -> Self { self.quiet = quiet; self } /// Set the current working directory for the command. pub fn cwd(mut self, cwd: &'a std::path::Path) -> Self { self.cwd = Some(cwd); self } /// Set the user to run the command as. pub fn user(mut self, user: &'a str) -> Self { self.user = Some(user); self } /// Set the environment for the command. pub fn env(mut self, env: std::collections::HashMap) -> Self { assert!(self.env.is_none()); self.env = Some(env); self } /// Add an environment variable to the command. pub fn setenv(mut self, key: String, value: String) -> Self { self.env = match self.env { Some(mut env) => { env.insert(key, value); Some(env) } None => Some(std::collections::HashMap::from([(key, value)])), }; self } pub fn stdin(mut self, stdin: std::process::Stdio) -> Self { self.stdin = Some(stdin); self } pub fn stdout(mut self, stdout: std::process::Stdio) -> Self { self.stdout = Some(stdout); self } pub fn stderr(mut self, stderr: std::process::Stdio) -> Self { self.stderr = Some(stderr); self } pub fn run_with_tee(self) -> Result<(ExitStatus, Vec), Error> { assert!(self.stdout.is_none()); assert!(self.stderr.is_none()); run_with_tee( self.session, self.argv, self.cwd, self.user, self.env.as_ref(), self.stdin, self.quiet, ) } pub fn run_detecting_problems(self) -> Result, crate::analyze::AnalyzedError> { assert!(self.stdout.is_none()); assert!(self.stderr.is_none()); crate::analyze::run_detecting_problems( self.session, self.argv, None, self.quiet, self.cwd, self.user, self.env.as_ref(), self.stdin, ) } pub fn run_fixing_problems< I: std::error::Error, E: From + std::error::Error + From, >( self, fixers: &[&dyn crate::fix_build::BuildFixer], ) -> Result, crate::fix_build::IterateBuildError> { assert!(self.stdin.is_none()); assert!(self.stdout.is_none()); assert!(self.stderr.is_none()); crate::fix_build::run_fixing_problems( fixers, None, self.session, self.argv.as_slice(), self.quiet, self.cwd, self.user, self.env.as_ref(), ) } pub fn child(self) -> std::process::Child { self.session.popen( self.argv, self.cwd, self.user, self.stdout, self.stderr, self.stdin, self.env.as_ref(), ) } pub fn run(self) -> Result { let mut p = self.child(); let status = p.wait()?; Ok(status) } pub fn output(self) -> Result { let p = self.child(); let output = p.wait_with_output()?; Ok(output) } pub fn check_call(self) -> Result<(), Error> { self.session .check_call(self.argv, self.cwd, self.user, self.env) } pub fn check_output(self) -> Result, Error> { self.session .check_output(self.argv, self.cwd, self.user, self.env) } } pub fn which(session: &dyn Session, name: &str) -> Option { let ret = match session.check_output( vec!["which", name], Some(std::path::Path::new("/")), None, None, ) { Ok(ret) => ret, Err(Error::CalledProcessError(status)) if status.code() == Some(1) => return None, Err(e) => panic!("Unexpected error: {:?}", e), }; if ret.is_empty() { None } else { Some(String::from_utf8(ret).unwrap().trim().to_string()) } } pub fn get_user(session: &dyn Session) -> String { String::from_utf8( session .check_output( vec!["sh", "-c", "echo $USER"], Some(std::path::Path::new("/")), None, None, ) .unwrap(), ) .unwrap() .trim() .to_string() } /// A function to capture and forward stdout and stderr of a child process. fn capture_output( mut child: std::process::Child, forward: bool, ) -> Result<(std::process::ExitStatus, Vec), std::io::Error> { use std::io::{BufRead, BufReader}; use std::sync::mpsc::{channel, Receiver, Sender}; use std::thread; let mut output_log = Vec::::new(); // Channels to handle communication from threads let (tx, rx): (Sender>, Receiver>) = channel(); // Function to handle the stdout of the child process let stdout_tx = tx.clone(); let stdout = child.stdout.take().expect("Failed to capture stdout"); let stdout_handle = thread::spawn(move || -> Result<(), std::io::Error> { let reader = BufReader::new(stdout); for line in reader.lines() { let line = line?; if forward { std::io::stdout().write_all(line.as_bytes())?; std::io::stdout().write_all(b"\n")?; } stdout_tx .send(Some(line)) .expect("Failed to send stdout through channel"); } stdout_tx .send(None) .expect("Failed to send None through channel"); Ok(()) }); // Function to handle the stderr of the child process let stderr_tx = tx.clone(); let stderr = child.stderr.take().expect("Failed to capture stderr"); let stderr_handle = thread::spawn(move || -> Result<(), std::io::Error> { let reader = BufReader::new(stderr); for line in reader.lines() { let line = line?; if forward { std::io::stderr().write_all(line.as_bytes())?; std::io::stderr().write_all(b"\n")?; } stderr_tx .send(Some(line)) .expect("Failed to send stderr through channel"); } stderr_tx .send(None) .expect("Failed to send None through channel"); Ok(()) }); // Wait for the child process to exit let status = child.wait().expect("Child process wasn't running"); stderr_handle .join() .expect("Failed to join stderr thread")?; stdout_handle .join() .expect("Failed to join stdout thread")?; let mut terminated = 0; // Collect all output from both stdout and stderr while let Ok(line) = rx.recv() { if let Some(line) = line { output_log.push(line); } else { terminated += 1; if terminated == 2 { break; } } } Ok((status, output_log)) } pub fn run_with_tee( session: &dyn Session, args: Vec<&str>, cwd: Option<&std::path::Path>, user: Option<&str>, env: Option<&std::collections::HashMap>, stdin: Option, quiet: bool, ) -> Result<(ExitStatus, Vec), Error> { if let (Some(cwd), Some(user)) = (cwd, user) { log::debug!("Running command: {:?} in {:?} as user {}", args, cwd, user); } else if let Some(cwd) = cwd { log::debug!("Running command: {:?} in {:?}", args, cwd); } else if let Some(user) = user { log::debug!("Running command: {:?} as user {}", args, user); } else { log::debug!("Running command: {:?}", args); } let p = session.popen( args, cwd, user, Some(std::process::Stdio::piped()), Some(std::process::Stdio::piped()), Some(stdin.unwrap_or(std::process::Stdio::null())), env, ); // While the process is running, read its output and write it to stdout // *and* to the contents variable. Ok(capture_output(p, !quiet)?) } pub fn create_home(session: &impl Session) -> Result<(), Error> { let cwd = std::path::Path::new("/"); let home = String::from_utf8(session.check_output( vec!["sh", "-c", "echo $HOME"], Some(cwd), None, None, )?) .unwrap() .trim_end_matches('\n') .to_string(); let user = String::from_utf8(session.check_output( vec!["sh", "-c", "echo $LOGNAME"], Some(cwd), None, None, )?) .unwrap() .trim_end_matches('\n') .to_string(); log::info!("Creating directory {} in schroot session.", home); session.check_call(vec!["mkdir", "-p", &home], Some(cwd), Some("root"), None)?; session.check_call(vec!["chown", &user, &home], Some(cwd), Some("root"), None)?; Ok(()) } #[cfg(test)] mod tests { #[test] fn test_get_user() { let session = super::plain::PlainSession::new(); let user = super::get_user(&session); assert!(!user.is_empty()); } #[test] fn test_which() { let session = super::plain::PlainSession::new(); let which = super::which(&session, "ls"); assert!(which.unwrap().ends_with("/ls")); } #[test] fn test_capture_and_forward_output() { let p = std::process::Command::new("echo") .arg("Hello, world!") .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .spawn() .unwrap(); let (status, output) = super::capture_output(p, false).unwrap(); assert!(status.success()); assert_eq!(output, vec!["Hello, world!"]); } } ognibuild-0.0.32/src/session/plain.rs000064400000000000000000000305021046102023000156050ustar 00000000000000use crate::session::{CommandBuilder, Error, Project, Session}; pub struct PlainSession(std::path::PathBuf); impl Default for PlainSession { fn default() -> Self { Self::new() } } impl PlainSession { pub fn new() -> Self { PlainSession(std::path::PathBuf::from("/")) } fn prepend_user<'a>(&'a self, user: Option<&'a str>, mut args: Vec<&'a str>) -> Vec<&'a str> { if let Some(user) = user { if user != whoami::username() { args = vec!["sudo", "-u", user].into_iter().chain(args).collect(); } } args } } impl Session for PlainSession { fn location(&self) -> std::path::PathBuf { std::path::PathBuf::from("/") } fn exists(&self, path: &std::path::Path) -> bool { self.0.join(path).exists() } fn mkdir(&self, path: &std::path::Path) -> Result<(), Error> { std::fs::create_dir_all(self.0.join(path)).map_err(Error::IoError) } fn chdir(&mut self, path: &std::path::Path) -> Result<(), Error> { self.0 = self.0.join(path).canonicalize().unwrap(); Ok(()) } fn pwd(&self) -> &std::path::Path { &self.0 } fn external_path(&self, path: &std::path::Path) -> std::path::PathBuf { self.0.join(path).canonicalize().unwrap() } fn check_output( &self, argv: Vec<&str>, cwd: Option<&std::path::Path>, user: Option<&str>, env: Option>, ) -> Result, Error> { let argv = self.prepend_user(user, argv); let mut binding = std::process::Command::new(argv[0]); let mut cmd = binding.args(&argv[1..]); cmd = cmd.current_dir(cwd.unwrap_or(self.0.as_path())); if let Some(env) = env { cmd = cmd.envs(env); } let output = cmd.output(); match output { Ok(output) => { if output.status.success() { Ok(output.stdout) } else { Err(Error::CalledProcessError(output.status)) } } Err(e) => Err(Error::IoError(e)), } } fn rmtree(&self, path: &std::path::Path) -> Result<(), Error> { std::fs::remove_dir_all(path).map_err(Error::IoError) } fn check_call( &self, argv: Vec<&str>, cwd: Option<&std::path::Path>, user: Option<&str>, env: Option>, ) -> Result<(), Error> { let argv = self.prepend_user(user, argv); let mut binding = std::process::Command::new(argv[0]); let mut cmd = binding.args(&argv[1..]); cmd = cmd.current_dir(cwd.unwrap_or(self.0.as_path())); if let Some(env) = env { cmd = cmd.envs(env); } let status = cmd.status(); match status { Ok(status) => { if status.success() { Ok(()) } else { Err(Error::CalledProcessError(status)) } } Err(e) => Err(Error::IoError(e)), } } fn create_home(&self) -> Result<(), Error> { Ok(()) } fn project_from_directory( &self, path: &std::path::Path, _subdir: Option<&str>, ) -> Result { Ok(Project::Noop(path.to_path_buf())) } fn popen( &self, argv: Vec<&str>, cwd: Option<&std::path::Path>, user: Option<&str>, stdout: Option, stderr: Option, stdin: Option, env: Option<&std::collections::HashMap>, ) -> std::process::Child { let argv = self.prepend_user(user, argv); let mut binding = std::process::Command::new(argv[0]); let mut cmd = binding .args(&argv[1..]) .stdin(stdin.unwrap_or(std::process::Stdio::inherit())) .stdout(stdout.unwrap_or(std::process::Stdio::inherit())) .stderr(stderr.unwrap_or(std::process::Stdio::inherit())); let cwd = cwd.map_or_else(|| self.0.clone(), |p| self.0.join(p)); cmd = cmd.current_dir(cwd); if let Some(env) = env { cmd = cmd.envs(env); } cmd.spawn().unwrap() } fn is_temporary(&self) -> bool { false } #[cfg(feature = "breezy")] fn project_from_vcs( &self, tree: &dyn crate::vcs::DupableTree, include_controldir: Option, subdir: Option<&str>, ) -> Result { use crate::vcs::{dupe_vcs_tree, export_vcs_tree}; if include_controldir.unwrap_or(true) && tree.basedir().is_some() { // Optimization: just use the directory as-is, don't copy anything Ok(Project::Noop(tree.basedir().unwrap())) } else if !include_controldir.unwrap_or(false) { let td = tempfile::tempdir().unwrap(); let p = if let Some(subdir) = subdir { td.path().join(subdir) } else { td.path().to_path_buf() }; export_vcs_tree(tree.as_tree(), &p, None).unwrap(); Ok(Project::Temporary { internal_path: p.clone(), external_path: p, td: td.into_path(), }) } else { let td = tempfile::tempdir().unwrap(); let p = if let Some(subdir) = subdir { td.path().join(subdir) } else { td.path().to_path_buf() }; dupe_vcs_tree(tree, &p).unwrap(); Ok(Project::Temporary { internal_path: p.clone(), external_path: p, td: td.into_path(), }) } } fn command<'a>(&'a self, argv: Vec<&'a str>) -> CommandBuilder<'a> { CommandBuilder::new(self, argv) } fn read_dir(&self, path: &std::path::Path) -> Result, Error> { std::fs::read_dir(path) .map_err(Error::IoError)? .collect::, _>>() .map_err(Error::IoError) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_prepend_user() { let session = PlainSession::new(); let args = vec!["ls"]; let args = session.prepend_user(Some("root"), args); assert_eq!(args, vec!["sudo", "-u", "root", "ls"]); } #[test] fn test_prepend_user_no_user() { let session = PlainSession::new(); let args = vec!["ls"]; let args = session.prepend_user(None, args); assert_eq!(args, vec!["ls"]); } #[test] fn test_prepend_user_current_user() { let session = PlainSession::new(); let args = vec!["ls"]; let username = whoami::username(); let args = session.prepend_user(Some(username.as_str()), args); assert_eq!(args, vec!["ls"]); } #[test] fn test_location() { let session = PlainSession::new(); assert_eq!(session.location(), std::path::PathBuf::from("/")); } #[test] fn test_is_temporary() { let session = PlainSession::new(); assert!(!session.is_temporary()); } #[test] fn test_exists() { let session = PlainSession::new(); assert!(session.exists(std::path::Path::new("/"))); let td = tempfile::tempdir().unwrap(); assert!(session.exists(td.path())); let path = td.path().join("test"); assert!(!session.exists(&path)); } #[test] fn test_mkdir() { let session = PlainSession::new(); let td = tempfile::tempdir().unwrap(); let path = td.path().join("test"); session.mkdir(&path).unwrap(); assert!(session.exists(&path)); session.rmtree(&path).unwrap(); assert!(!session.exists(&path)); } #[test] fn test_chdir() { let mut session = PlainSession::new(); let td = tempfile::tempdir().unwrap(); let path = td.path().join("test"); session.mkdir(&path).unwrap(); session.chdir(&path).unwrap(); let pwd_bytes = session.check_output(vec!["pwd"], None, None, None).unwrap(); let reported = std::str::from_utf8(pwd_bytes.as_slice().strip_suffix(b"\n").unwrap()).unwrap(); assert_eq!(reported, path.canonicalize().unwrap().to_str().unwrap()); } #[test] fn test_pwd() { let mut session = PlainSession::new(); let pwd = session.pwd(); assert_eq!(pwd, std::path::Path::new("/")); let td = tempfile::tempdir().unwrap(); session.chdir(td.path()).unwrap(); let pwd = session.pwd(); assert_eq!(pwd, td.path().canonicalize().unwrap()); } #[test] fn test_external_path() { let session = PlainSession::new(); let td = tempfile::tempdir().unwrap(); let path = td.path().join("test"); session.mkdir(&path).unwrap(); assert_eq!(session.external_path(&path), path.canonicalize().unwrap()); } #[test] fn test_check_output() { let session = PlainSession::new(); let output = session .check_output(vec!["echo", "hello"], None, None, None) .unwrap(); assert_eq!(output, b"hello\n"); } #[test] fn test_check_call() { let session = PlainSession::new(); session.check_call(vec!["true"], None, None, None).unwrap(); } #[test] fn test_create_home() { let session = PlainSession::new(); session.create_home().unwrap(); } #[test] fn test_project_from_directory() { let session = PlainSession::new(); let td = tempfile::tempdir().unwrap(); let path = td.path().join("test"); session.mkdir(&path).unwrap(); let project = session.project_from_directory(&path, None).unwrap(); assert_eq!(project.external_path(), path); assert_eq!(project.internal_path(), path); } #[test] fn test_popen() { let session = PlainSession::new(); let child = session.popen( vec!["echo", "hello"], None, None, Some(std::process::Stdio::piped()), Some(std::process::Stdio::piped()), Some(std::process::Stdio::piped()), None, ); let output = child.wait_with_output().unwrap(); assert_eq!(output.stdout, b"hello\n"); } #[cfg(feature = "breezy")] #[test] fn test_project_from_vcs() { use breezyshim::tree::MutableTree; let env = breezyshim::testing::TestEnv::new(); let session = PlainSession::new(); let td = tempfile::tempdir().unwrap(); let tree = breezyshim::controldir::create_standalone_workingtree( td.path(), &breezyshim::controldir::ControlDirFormat::default(), ) .unwrap(); let path = td.path(); tree.put_file_bytes_non_atomic(std::path::Path::new("test"), b"hello") .unwrap(); tree.add(&[std::path::Path::new("test")]).unwrap(); tree.build_commit().message("test").commit().unwrap(); let project = session.project_from_vcs(&tree, None, None).unwrap(); assert_eq!(project.external_path(), path.canonicalize().unwrap()); assert_eq!(project.internal_path(), path.canonicalize().unwrap()); assert!(project.external_path().join(".bzr").exists()); let project = session.project_from_vcs(&tree, Some(true), None).unwrap(); assert_eq!(project.external_path(), path.canonicalize().unwrap()); assert_eq!(project.internal_path(), path.canonicalize().unwrap()); assert!(project.external_path().join(".bzr").exists()); let project = session.project_from_vcs(&tree, Some(false), None).unwrap(); assert_ne!(project.external_path(), path.canonicalize().unwrap()); assert_ne!(project.internal_path(), path.canonicalize().unwrap()); assert!(!project.external_path().join(".bzr").exists()); std::mem::drop(env); } #[test] fn test_output() { let session = PlainSession::new(); let output = session .command(vec!["echo", "hello"]) .stdout(std::process::Stdio::piped()) .output() .unwrap() .stdout; assert_eq!(output, b"hello\n"); } } ognibuild-0.0.32/src/session/schroot.rs000064400000000000000000000303641046102023000161710ustar 00000000000000use crate::session::{CommandBuilder, Error, Project, Session}; use std::io::{BufRead, Read}; extern crate rand; use rand::Rng; use std::iter; pub fn sanitize_session_name(name: &str) -> String { name.chars() .filter(|&c| c.is_alphanumeric() || "_-.".contains(c)) .collect() } pub fn generate_session_id(prefix: &str) -> String { let suffix: String = String::from_utf8( iter::repeat(()) .map(|()| rand::thread_rng().sample(rand::distributions::Alphanumeric)) .take(8) .collect(), ) .unwrap(); format!("{}-{}", sanitize_session_name(prefix), suffix) } pub struct SchrootSession { cwd: std::path::PathBuf, session_id: String, location: std::path::PathBuf, } impl SchrootSession { pub fn new(chroot: &str, session_prefix: Option<&str>) -> Result { let mut stderr = tempfile::tempfile().unwrap(); let mut extra_args = vec![]; if let Some(session_prefix) = session_prefix { let sanitized_session_name = generate_session_id(session_prefix); extra_args.extend(["-n".to_string(), sanitized_session_name]); } let cmd = std::process::Command::new("schroot") .arg("-c") .arg(chroot) .arg("-b") .args(extra_args) .stderr(std::process::Stdio::from(stderr.try_clone().unwrap())) .output() .unwrap(); let session_id = match cmd.status.code() { Some(0) => String::from_utf8(cmd.stdout).unwrap(), Some(_) => { let mut errlines = String::new(); stderr.read_to_string(&mut errlines).unwrap(); if errlines.len() == 1 { return Err(Error::SetupFailure( errlines.lines().next().unwrap().to_string(), errlines, )); } else if errlines.is_empty() { return Err(Error::SetupFailure( "No output from schroot".to_string(), errlines, )); } else { return Err(Error::SetupFailure( errlines.lines().last().unwrap().to_string(), errlines, )); } } None => panic!("schroot exited by signal"), }; log::info!("Opened schroot session {} (from {})", session_id, chroot); let output = std::process::Command::new("schroot") .arg("-c") .arg(format!("session:{}", session_id)) .arg("--location") .output() .unwrap(); let location = std::path::PathBuf::from( String::from_utf8(output.stdout) .unwrap() .trim_end_matches('\n'), ); Ok(Self { cwd: std::path::PathBuf::from("/"), session_id, location, }) } fn run_argv( &self, argv: Vec<&str>, cwd: Option<&std::path::Path>, user: Option<&str>, env: Option<&std::collections::HashMap>, ) -> Vec { let mut argv = argv.iter().map(|x| x.to_string()).collect::>(); let mut base_argv = vec![ "schroot".to_string(), "-r".to_string(), "-c".to_string(), format!("session:{}", self.session_id), ]; let cwd = cwd.unwrap_or(self.pwd()); base_argv.extend([ "-d".to_string(), cwd.to_path_buf().to_string_lossy().to_string(), ]); if let Some(user) = user { base_argv.extend(["-u".to_string(), user.to_string()]); } if let Some(env) = env { argv = vec![ "sh".to_string(), "-c".to_string(), env.iter() .map(|(key, value)| format!("{}={} ", key, shlex::try_quote(value).unwrap())) .chain( argv.iter() .map(|x| shlex::try_quote(x).unwrap().to_string()), ) .collect::>() .join(" "), ]; } [base_argv, vec!["--".to_string()], argv].concat() } fn build_tempdir(&self) -> std::path::PathBuf { let build_dir = "/build"; String::from_utf8( self.check_output( vec!["mktemp", "-d", "-p", build_dir], Some(std::path::Path::new("/")), None, None, ) .unwrap(), ) .unwrap() .trim_end_matches('\n') .to_string() .into() } } impl Drop for SchrootSession { fn drop(&mut self) { let stderr = tempfile::tempfile().unwrap(); match std::process::Command::new("schroot") .arg("-c") .arg(format!("session:{}", self.session_id)) .arg("-e") .stderr(std::process::Stdio::from(stderr.try_clone().unwrap())) .output() { Err(_) => { for line in std::io::BufReader::new(&stderr).lines() { let line = line.unwrap(); if let Some(rest) = line.strip_prefix("E: ") { log::error!("{}", rest); } } log::error!( "Failed to close schroot session {}, leaving stray.", self.session_id ); } Ok(_) => { log::debug!("Closed schroot session {}", self.session_id); } } } } impl Session for SchrootSession { fn rmtree(&self, path: &std::path::Path) -> Result<(), Error> { let fullpath = self.external_path(path); std::fs::remove_dir_all(fullpath).map_err(Error::IoError) } fn external_path(&self, path: &std::path::Path) -> std::path::PathBuf { let path = path.to_string_lossy(); if let Some(rest) = path.strip_prefix('/') { return self.location().join(rest); } self.location() .join( self.cwd .to_string_lossy() .to_string() .trim_start_matches('/'), ) .join(path.as_ref()) } fn location(&self) -> std::path::PathBuf { self.location.clone() } fn exists(&self, path: &std::path::Path) -> bool { let fullpath = self.external_path(path); fullpath.exists() } fn chdir(&mut self, path: &std::path::Path) -> Result<(), Error> { self.cwd = self.cwd.join(path); Ok(()) } fn pwd(&self) -> &std::path::Path { &self.cwd } fn mkdir(&self, path: &std::path::Path) -> Result<(), Error> { let fullpath = self.external_path(path); std::fs::create_dir_all(fullpath).map_err(Error::IoError) } fn check_output( &self, argv: Vec<&str>, cwd: Option<&std::path::Path>, user: Option<&str>, env: Option>, ) -> Result, Error> { let argv = self.run_argv(argv, cwd, user, env.as_ref()); let output = std::process::Command::new(&argv[0]) .args(&argv[1..]) .stderr(std::process::Stdio::inherit()) .output(); match output { Ok(output) => { if output.status.success() { Ok(output.stdout) } else { Err(Error::CalledProcessError(output.status)) } } Err(e) => Err(Error::IoError(e)), } } fn check_call( &self, argv: Vec<&str>, cwd: Option<&std::path::Path>, user: Option<&str>, env: Option>, ) -> Result<(), Error> { let argv = self.run_argv(argv, cwd, user, env.as_ref()); let status = std::process::Command::new(&argv[0]) .args(&argv[1..]) .status(); match status { Ok(status) => { if status.success() { Ok(()) } else { Err(Error::CalledProcessError(status)) } } Err(e) => Err(Error::IoError(e)), } } fn create_home(&self) -> Result<(), Error> { crate::session::create_home(self) } fn project_from_directory( &self, path: &std::path::Path, subdir: Option<&str>, ) -> Result { let subdir = subdir.unwrap_or("package"); let reldir = self.build_tempdir(); let export_directory = self.external_path(&reldir).join(subdir); // Copy tree from path to export_directory let mut options = fs_extra::dir::CopyOptions::new(); options.copy_inside = true; // Copy contents inside the source directory options.content_only = false; // Copy the entire directory options.skip_exist = false; // Skip if file already exists in the destination options.overwrite = true; // Overwrite files if they already exist options.buffer_size = 64000; // Buffer size in bytes options.depth = 0; // Recursion depth (0 for unlimited depth) // Perform the copy operation fs_extra::dir::copy(path, &export_directory, &options).unwrap(); Ok(Project::Temporary { external_path: export_directory, internal_path: reldir.join(subdir), td: self.external_path(&reldir), }) } fn popen( &self, argv: Vec<&str>, cwd: Option<&std::path::Path>, user: Option<&str>, stdout: Option, stderr: Option, stdin: Option, env: Option<&std::collections::HashMap>, ) -> std::process::Child { let argv = self.run_argv(argv, cwd, user, env); std::process::Command::new(&argv[0]) .args(&argv[1..]) .stdin(stdin.unwrap_or(std::process::Stdio::inherit())) .stdout(stdout.unwrap_or(std::process::Stdio::inherit())) .stderr(stderr.unwrap_or(std::process::Stdio::inherit())) .spawn() .unwrap() } fn is_temporary(&self) -> bool { true } #[cfg(feature = "breezy")] fn project_from_vcs( &self, tree: &dyn crate::vcs::DupableTree, include_controldir: Option, subdir: Option<&str>, ) -> Result { let reldir = self.build_tempdir(); let subdir = subdir.unwrap_or("package"); let export_directory = self.external_path(&reldir).join(subdir); if !include_controldir.unwrap_or(false) { crate::vcs::export_vcs_tree(tree.as_tree(), &export_directory, None).unwrap(); } else { crate::vcs::dupe_vcs_tree(tree, &export_directory).unwrap(); } Ok(Project::Temporary { external_path: export_directory, internal_path: reldir.join(subdir), td: self.external_path(&reldir), }) } fn command<'a>(&'a self, argv: Vec<&'a str>) -> CommandBuilder<'a> { CommandBuilder::new(self, argv) } fn read_dir(&self, path: &std::path::Path) -> Result, Error> { std::fs::read_dir(self.external_path(path)) .map_err(Error::IoError)? .collect::, _>>() .map_err(Error::IoError) } } #[cfg(test)] mod tests { #[test] fn test_sanitize_session_name() { assert_eq!(super::sanitize_session_name("foo"), "foo"); assert_eq!(super::sanitize_session_name("foo-bar"), "foo-bar"); assert_eq!(super::sanitize_session_name("foo_bar"), "foo_bar"); assert_eq!(super::sanitize_session_name("foo.bar"), "foo.bar"); assert_eq!(super::sanitize_session_name("foo!bar"), "foobar"); assert_eq!(super::sanitize_session_name("foo@bar"), "foobar"); } #[test] fn test_generate_session_id() { let id = super::generate_session_id("foo"); assert_eq!(id.len(), 12); assert_eq!(&id[..4], "foo-"); } } ognibuild-0.0.32/src/session/unshare.rs000064400000000000000000000517411046102023000161570ustar 00000000000000use crate::session::{CommandBuilder, Error, Project, Session}; use std::path::{Path, PathBuf}; pub struct UnshareSession { root: PathBuf, _tempdir: Option, cwd: PathBuf, } fn compression_flag(path: &Path) -> Result, crate::session::Error> { match path.extension().unwrap().to_str().unwrap() { "tar" => Ok(None), "gz" => Ok(Some("-z")), "bz2" => Ok(Some("-j")), "xz" => Ok(Some("-J")), "zst" => Ok(Some("--zstd")), e => Err(crate::session::Error::SetupFailure( "unknown extension".to_string(), format!("unknown extension: {}", e), )), } } impl UnshareSession { pub fn from_tarball(path: &Path) -> Result { let td = tempfile::tempdir().map_err(|e| { crate::session::Error::SetupFailure("tempdir failed".to_string(), e.to_string()) })?; // Run tar within unshare to extract the tarball. This is necessary because // the tarball may contain files that are owned by a different user. // // However, the tar executable is not available within the unshare environment. // Therefore, we need to extract the tarball to a temporary directory and then // move it to the final location. let root = td.path(); let f = std::fs::File::open(path).map_err(|e| { crate::session::Error::SetupFailure("open failed".to_string(), e.to_string()) })?; let output = std::process::Command::new("unshare") .arg("--map-users=auto") .arg("--map-groups=auto") .arg("--fork") .arg("--pid") .arg("--mount-proc") .arg("--net") .arg("--uts") .arg("--ipc") .arg("--wd") .arg(root) .arg("--") .arg("tar") .arg("x") .arg(compression_flag(path)?.unwrap_or("--")) .stdin(std::process::Stdio::from(f)) .stderr(std::process::Stdio::piped()) .output()?; if !output.status.success() { let stderr = String::from_utf8(output.stderr).unwrap(); return Err(crate::session::Error::SetupFailure( "tar failed".to_string(), stderr, )); } let s = Self { root: root.to_path_buf(), _tempdir: Some(td), cwd: std::path::PathBuf::from("/"), }; s.ensure_current_user(); Ok(s) } pub fn save_to_tarball(&self, path: &Path) -> Result<(), crate::session::Error> { // Create the tarball from within the session, dumping it to stdout let mut child = self.popen( vec![ "tar", "c", "--absolute-names", "--exclude", "/dev/*", "--exclude", "/proc/*", "--exclude", "/sys/*", compression_flag(path)?.unwrap_or("--"), "/", ], Some(std::path::Path::new("/")), Some("root"), Some(std::process::Stdio::piped()), None, None, None, ); let f = std::fs::File::create(path).map_err(|e| { crate::session::Error::SetupFailure("create failed".to_string(), e.to_string()) })?; let mut writer = std::io::BufWriter::new(f); std::io::copy(child.stdout.as_mut().unwrap(), &mut writer).map_err(|e| { crate::session::Error::SetupFailure("copy failed".to_string(), e.to_string()) })?; if child.wait()?.success() { Ok(()) } else { Err(crate::session::Error::SetupFailure( "tar failed".to_string(), "tar failed".to_string(), )) } } pub fn bootstrap() -> Result { let td = tempfile::tempdir().map_err(|e| { crate::session::Error::SetupFailure("tempdir failed".to_string(), e.to_string()) })?; let root = td.path(); std::process::Command::new("mmdebstrap") .current_dir(root) .arg("--mode=unshare") .arg("--variant=minbase") .arg("--quiet") .arg("sid") .arg(root) .arg("http://deb.debian.org/debian/") .status() .map_err(|e| { crate::session::Error::SetupFailure("mmdebstrap failed".to_string(), e.to_string()) })?; let s = Self { root: root.to_path_buf(), _tempdir: Some(td), cwd: std::path::PathBuf::from("/"), }; s.ensure_current_user(); Ok(s) } pub fn ensure_current_user(&self) { // Ensure that the current user has an entry in /etc/passwd let user = whoami::username(); let uid = nix::unistd::getuid().to_string(); let gid = nix::unistd::getgid().to_string(); match self.check_call( vec![ "/usr/sbin/groupadd", "--force", "--non-unique", "--gid", &gid, user.as_str(), ], Some(std::path::Path::new("/")), Some("root"), None, ) { Ok(_) => {} Err(e) => panic!("Error: {:?}", e), } let child = self.popen( vec![ "/usr/sbin/useradd", "--uid", &uid, "--gid", &gid, user.as_str(), ], Some(std::path::Path::new("/")), Some("root"), None, Some(std::process::Stdio::piped()), None, None, ); match child.wait_with_output() { Ok(output) => { match output.status.code() { // User created Some(0) => {} // Ignore if user already exists Some(9) => {} Some(4) => {} _ => panic!( "Error: {:?}: {}", output.status, String::from_utf8(output.stdout).unwrap() ), } } Err(e) => panic!("Error: {:?}", e), } } pub fn run_argv<'a>( &'a self, argv: Vec<&'a str>, cwd: Option<&'a std::path::Path>, user: Option<&'a str>, ) -> std::vec::Vec<&'a str> { let mut ret = vec![ "unshare", "--map-users=auto", "--map-groups=auto", "--fork", "--pid", "--mount-proc", "--net", "--uts", "--ipc", "--root", self.root.to_str().unwrap(), "--wd", cwd.unwrap_or(&self.cwd).to_str().unwrap(), ]; if let Some(user) = user { if user == "root" { ret.push("--map-root-user") } else { ret.push("--map-user"); ret.push(user); } } else { ret.push("--map-current-user") } ret.push("--"); ret.extend(argv); ret } fn build_tempdir(&self, user: Option<&str>) -> std::path::PathBuf { let build_dir = "/build"; // Ensure that the build directory exists self.check_call(vec!["mkdir", "-p", build_dir], None, user, None) .unwrap(); String::from_utf8( self.check_output( vec!["mktemp", "-d", format!("--tmpdir={}", build_dir).as_str()], Some(std::path::Path::new("/")), user, None, ) .unwrap(), ) .unwrap() .trim_end_matches('\n') .to_string() .into() } } impl Session for UnshareSession { fn chdir(&mut self, path: &std::path::Path) -> Result<(), crate::session::Error> { self.cwd = self.cwd.join(path); Ok(()) } fn pwd(&self) -> &std::path::Path { &self.cwd } fn external_path(&self, path: &std::path::Path) -> std::path::PathBuf { if let Ok(rest) = path.strip_prefix("/") { return self.location().join(rest); } self.location() .join( self.cwd .to_string_lossy() .to_string() .trim_start_matches('/'), ) .join(path) } fn location(&self) -> std::path::PathBuf { self.root.clone() } fn check_output( &self, argv: Vec<&str>, cwd: Option<&std::path::Path>, user: Option<&str>, env: Option>, ) -> Result, super::Error> { let argv = self.run_argv(argv, cwd, user); let output = std::process::Command::new(argv[0]) .args(&argv[1..]) .stderr(std::process::Stdio::inherit()) .envs(env.unwrap_or_default()) .output(); match output { Ok(output) => { if output.status.success() { Ok(output.stdout) } else { Err(Error::CalledProcessError(output.status)) } } Err(e) => Err(Error::IoError(e)), } } fn create_home(&self) -> Result<(), super::Error> { crate::session::create_home(self) } fn check_call( &self, argv: Vec<&str>, cwd: Option<&std::path::Path>, user: Option<&str>, env: Option>, ) -> Result<(), crate::session::Error> { let argv = self.run_argv(argv, cwd, user); let status = std::process::Command::new(argv[0]) .args(&argv[1..]) .envs(env.unwrap_or_default()) .status(); match status { Ok(status) => { if status.success() { Ok(()) } else { Err(Error::CalledProcessError(status)) } } Err(e) => Err(Error::IoError(e)), } } fn exists(&self, path: &std::path::Path) -> bool { let args = vec!["test", "-e", path.to_str().unwrap()]; self.check_call(args, None, None, None).is_ok() } fn mkdir(&self, path: &std::path::Path) -> Result<(), crate::session::Error> { let args = vec!["mkdir", path.to_str().unwrap()]; self.check_call(args, None, None, None) } fn rmtree(&self, path: &std::path::Path) -> Result<(), crate::session::Error> { let args = vec!["rm", "-rf", path.to_str().unwrap()]; self.check_call(args, None, None, None) } fn project_from_directory( &self, path: &std::path::Path, subdir: Option<&str>, ) -> Result { let subdir = subdir.unwrap_or("package"); let reldir = self.build_tempdir(Some("root")); let export_directory = self.external_path(&reldir).join(subdir); // Copy tree from path to export_directory let mut options = fs_extra::dir::CopyOptions::new(); options.copy_inside = true; // Copy contents inside the source directory options.content_only = false; // Copy the entire directory options.skip_exist = false; // Skip if file already exists in the destination options.overwrite = true; // Overwrite files if they already exist options.buffer_size = 64000; // Buffer size in bytes options.depth = 0; // Recursion depth (0 for unlimited depth) // Perform the copy operation fs_extra::dir::copy(path, &export_directory, &options).unwrap(); Ok(Project::Temporary { external_path: export_directory, internal_path: reldir.join(subdir), td: self.external_path(&reldir), }) } fn popen( &self, argv: Vec<&str>, cwd: Option<&std::path::Path>, user: Option<&str>, stdout: Option, stderr: Option, stdin: Option, env: Option<&std::collections::HashMap>, ) -> std::process::Child { let argv = self.run_argv(argv, cwd, user); let mut binding = std::process::Command::new(argv[0]); let mut cmd = binding.args(&argv[1..]); if let Some(env) = env { cmd = cmd.envs(env); } if let Some(stdin) = stdin { cmd = cmd.stdin(stdin); } if let Some(stdout) = stdout { cmd = cmd.stdout(stdout); } if let Some(stderr) = stderr { cmd = cmd.stderr(stderr); } cmd.spawn().unwrap() } fn is_temporary(&self) -> bool { true } #[cfg(feature = "breezy")] fn project_from_vcs( &self, tree: &dyn crate::vcs::DupableTree, include_controldir: Option, subdir: Option<&str>, ) -> Result { let reldir = self.build_tempdir(None); let subdir = subdir.unwrap_or("package"); let export_directory = self.external_path(&reldir).join(subdir); if !include_controldir.unwrap_or(false) { crate::vcs::export_vcs_tree(tree.as_tree(), &export_directory, None).unwrap(); } else { crate::vcs::dupe_vcs_tree(tree, &export_directory).unwrap(); } Ok(Project::Temporary { external_path: export_directory, internal_path: reldir.join(subdir), td: self.external_path(&reldir), }) } fn command<'a>(&'a self, argv: Vec<&'a str>) -> CommandBuilder<'a> { CommandBuilder::new(self, argv) } fn read_dir(&self, path: &std::path::Path) -> Result, Error> { std::fs::read_dir(self.external_path(path)) .map_err(Error::IoError)? .collect::, _>>() .map_err(Error::IoError) } } #[cfg(test)] mod tests { use super::*; lazy_static::lazy_static! { static ref TEST_SESSION: std::sync::Mutex = std::sync::Mutex::new(UnshareSession::bootstrap().unwrap()); } fn test_session() -> Option> { // Don't run tests if we're in github actions // TODO: check for ability to run unshare instead if std::env::var("GITHUB_ACTIONS").is_ok() { return None; } Some(TEST_SESSION.lock().unwrap()) } #[test] fn test_is_temporary() { let session = if let Some(session) = test_session() { session } else { return; }; assert!(session.is_temporary()); } #[test] fn test_chdir() { let mut session = if let Some(session) = test_session() { session } else { return; }; session.chdir(std::path::Path::new("/")).unwrap(); } #[test] fn test_check_output() { let session = if let Some(session) = test_session() { session } else { return; }; let output = String::from_utf8( session .check_output(vec!["ls"], Some(std::path::Path::new("/")), None, None) .unwrap(), ) .unwrap(); let dirs = output.split_whitespace().collect::>(); assert!(dirs.contains(&"bin")); assert!(dirs.contains(&"dev")); assert!(dirs.contains(&"etc")); assert!(dirs.contains(&"home")); assert!(dirs.contains(&"lib")); assert!(dirs.contains(&"usr")); assert!(dirs.contains(&"proc")); assert_eq!( "root", String::from_utf8( session .check_output(vec!["whoami"], None, Some("root"), None) .unwrap() ) .unwrap() .trim_end() ); assert_eq!( // Get current process uid String::from_utf8( session .check_output(vec!["id", "-u"], None, None, None) .unwrap() ) .unwrap() .trim_end(), String::from_utf8( session .check_output(vec!["id", "-u"], None, None, None) .unwrap() ) .unwrap() .trim_end() ); assert_eq!( "nobody", String::from_utf8( session .check_output(vec!["whoami"], None, Some("nobody"), None) .unwrap() ) .unwrap() .trim_end() ); } #[test] fn test_check_call() { let session = if let Some(session) = test_session() { session } else { return; }; session .check_call(vec!["true"], Some(std::path::Path::new("/")), None, None) .unwrap(); } #[test] fn test_create_home() { let session = if let Some(session) = test_session() { session } else { return; }; session.create_home().unwrap(); } fn save_and_reuse(name: &str) { let session = if let Some(session) = test_session() { session } else { return; }; let tempdir = tempfile::tempdir().unwrap(); let path = tempdir.path().join(name); session.save_to_tarball(&path).unwrap(); std::mem::drop(session); let session = UnshareSession::from_tarball(&path).unwrap(); assert!(session.exists(std::path::Path::new("/bin"))); // Verify that the session works let output = String::from_utf8( session .check_output(vec!["ls"], Some(std::path::Path::new("/")), None, None) .unwrap(), ) .unwrap(); let dirs = output.split_whitespace().collect::>(); assert!(dirs.contains(&"bin")); assert!(dirs.contains(&"dev")); assert!(dirs.contains(&"etc")); assert!(dirs.contains(&"home")); assert!(dirs.contains(&"lib")); } #[test] fn test_save_and_reuse() { save_and_reuse("test.tar"); } #[test] fn test_save_and_reuse_gz() { save_and_reuse("test.tar.gz"); } #[test] fn test_mkdir_rmdir() { let session = if let Some(session) = test_session() { session } else { return; }; let path = std::path::Path::new("/tmp/test"); session.mkdir(path).unwrap(); assert!(session.exists(path)); session.rmtree(path).unwrap(); assert!(!session.exists(path)); } #[test] fn test_project_from_directory() { let session = if let Some(session) = test_session() { session } else { return; }; let tempdir = tempfile::tempdir().unwrap(); std::fs::write(tempdir.path().join("test"), "test").unwrap(); let project = session .project_from_directory(tempdir.path(), None) .unwrap(); assert!(project.external_path().exists()); assert!(session.exists(project.internal_path())); session.rmtree(project.internal_path()).unwrap(); assert!(!session.exists(project.internal_path())); assert!(!project.external_path().exists()); } #[test] fn test_popen() { let session = if let Some(session) = test_session() { session } else { return; }; let child = session.popen( vec!["ls"], Some(std::path::Path::new("/")), None, Some(std::process::Stdio::piped()), Some(std::process::Stdio::piped()), Some(std::process::Stdio::piped()), None, ); let output = String::from_utf8(child.wait_with_output().unwrap().stdout).unwrap(); let dirs = output.split_whitespace().collect::>(); assert!(dirs.contains(&"etc")); assert!(dirs.contains(&"home")); assert!(dirs.contains(&"lib")); assert!(dirs.contains(&"usr")); assert!(dirs.contains(&"proc")); } #[test] fn test_external_path() { let mut session = if let Some(session) = test_session() { session } else { return; }; // Test absolute path let path = std::path::Path::new("/tmp/test"); assert_eq!( session.external_path(path), session.location().join("tmp/test") ); // Test relative path session.chdir(std::path::Path::new("/tmp")).unwrap(); let path = std::path::Path::new("test"); assert_eq!( session.external_path(path), session.location().join("tmp/test") ); } } ognibuild-0.0.32/src/shebang.rs000064400000000000000000000044571046102023000144400ustar 00000000000000use std::io::BufRead; use std::os::unix::fs::PermissionsExt; /// Work out what binary is necessary to run a script based on shebang /// /// # Arguments /// * `path` - Path to the script /// /// # Returns /// * `Ok(Some(binary))` - The binary necessary to run the script pub fn shebang_binary(path: &std::path::Path) -> std::io::Result> { let file = std::fs::File::open(path)?; if file.metadata()?.permissions().mode() & 0o111 == 0 { return Ok(None); } let bufreader = std::io::BufReader::new(file); let firstline = bufreader.lines().next(); let firstline = match firstline { Some(line) => line?, None => return Ok(None), }; if !firstline.starts_with("#!") { return Ok(None); } let args: Vec<&str> = firstline[2..].split_whitespace().collect(); let binary = if args[0] == "/usr/bin/env" || args[0] == "env" { args[1] } else { args[0] }; Ok(Some( std::path::Path::new(binary) .file_name() .unwrap() .to_string_lossy() .to_string(), )) } #[cfg(test)] mod tests { use super::*; fn assert_shebang(content: &str, executable: bool, expected: Option<&str>) { let td = tempfile::tempdir().unwrap(); let path = td.path().join("test.sh"); std::fs::write(&path, content).unwrap(); if executable { let mut perms = std::fs::metadata(&path).unwrap().permissions(); perms.set_mode(0o755); std::fs::set_permissions(&path, perms).unwrap(); } let binary = super::shebang_binary(&path).unwrap(); assert_eq!(binary, expected.map(|s| s.to_string())); } #[test] fn test_empty() { assert_shebang("", true, None); } #[test] fn test_not_executable() { assert_shebang("#!/bin/sh\necho hello", false, None); } #[test] fn test_noshebang_line() { assert_shebang("echo hello", true, None); } #[test] fn test_env() { assert_shebang("#!/usr/bin/env sh\necho hello", true, Some("sh")); } #[test] fn test_plain() { assert_shebang("#!/bin/sh\necho hello", true, Some("sh")); } #[test] fn test_with_arg() { assert_shebang("#!/bin/sh -e\necho hello", true, Some("sh")); } } ognibuild-0.0.32/src/upstream.rs000064400000000000000000000035751046102023000146710ustar 00000000000000//! This module provides a trait for dependencies that can find their upstream metadata. use crate::dependency::Dependency; pub use upstream_ontologist::UpstreamMetadata; /// A trait for dependencies that can find their upstream metadata. pub trait FindUpstream: Dependency { /// Find the upstream metadata for this dependency. fn find_upstream(&self) -> Option; } pub fn find_upstream(dependency: &dyn Dependency) -> Option { #[cfg(feature = "debian")] if let Some(dep) = dependency .as_any() .downcast_ref::() { return dep.find_upstream(); } if let Some(dep) = dependency .as_any() .downcast_ref::() { return dep.find_upstream(); } if let Some(dep) = dependency .as_any() .downcast_ref::() { return dep.find_upstream(); } if let Some(dep) = dependency .as_any() .downcast_ref::() { return dep.find_upstream(); } if let Some(dep) = dependency .as_any() .downcast_ref::() { return dep.find_upstream(); } if let Some(dep) = dependency .as_any() .downcast_ref::() { return dep.find_upstream(); } if let Some(dep) = dependency .as_any() .downcast_ref::() { return dep.find_upstream(); } if let Some(dep) = dependency .as_any() .downcast_ref::() { return dep.find_upstream(); } None } ognibuild-0.0.32/src/vcs.rs000064400000000000000000000053301046102023000136130ustar 00000000000000//! VCS-related functions use breezyshim::error::Error as BrzError; use breezyshim::tree::Tree; use std::path::{Path, PathBuf}; use url::Url; /// Export a VCS tree to a new location. /// /// # Arguments /// * `tree` - The tree to export /// * `directory` - The directory to export the tree to /// * `subpath` - The subpath to export pub fn export_vcs_tree( tree: &dyn Tree, directory: &Path, subpath: Option<&Path>, ) -> Result<(), BrzError> { breezyshim::export::export(tree, directory, subpath) } /// A Breezy tree that can be duplicated. pub trait DupableTree { /// Get the basis tree of this tree. fn basis_tree(&self) -> breezyshim::tree::RevisionTree; /// Get the parent location of this tree. fn get_parent(&self) -> Option; /// Get the base directory of this tree, if it has one. fn basedir(&self) -> Option; /// Get this tree as a Tree. fn as_tree(&self) -> &dyn Tree; } impl DupableTree for breezyshim::workingtree::WorkingTree { fn basis_tree(&self) -> breezyshim::tree::RevisionTree { self.basis_tree().unwrap() } fn get_parent(&self) -> Option { self.branch().get_parent() } fn basedir(&self) -> Option { Some(self.basedir()) } fn as_tree(&self) -> &dyn Tree { self } } impl DupableTree for breezyshim::tree::RevisionTree { fn basis_tree(&self) -> breezyshim::tree::RevisionTree { self.repository() .revision_tree(&self.get_revision_id()) .unwrap() } fn get_parent(&self) -> Option { let branch = self.repository().controldir().open_branch(None).unwrap(); branch.get_parent() } fn basedir(&self) -> Option { None } fn as_tree(&self) -> &dyn Tree { self } } /// Duplicate a VCS tree to a new location, including all history. /// /// For a RevisionTree, this will duplicate the tree to a new location. /// For a WorkingTree, this will duplicate the basis tree to a new location. /// /// # Arguments /// * `orig_tree` - The tree to duplicate /// * `directory` - The directory to duplicate the tree to pub fn dupe_vcs_tree(orig_tree: &dyn DupableTree, directory: &Path) -> Result<(), BrzError> { let tree = orig_tree.basis_tree(); let result = tree.repository().controldir().sprout( Url::from_directory_path(directory).unwrap(), None, Some(true), None, Some(&tree.get_revision_id()), )?; assert!(result.has_workingtree()); // Copy parent location - some scripts need this if let Some(parent) = orig_tree.get_parent() { let mut branch = result.open_branch(None)?; branch.set_parent(&parent); } Ok(()) }