From 536374a2638627b6622273e0f4ebe5fba39b33a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Loipf=C3=BChrer?= Date: Mon, 22 Dec 2025 23:23:08 +0100 Subject: [PATCH 1/8] refactor(cli): rewrite cli in rust --- .github/workflows/ci.yml | 80 +- .gitignore | 5 + Cargo.lock | 955 ++++++++++++++++++ Cargo.toml | 3 + packages/debmagic-common-v2/Cargo.toml | 6 + packages/debmagic-common-v2/src/lib.rs | 14 + packages/debmagic/Cargo.toml | 19 + packages/debmagic/pyproject.toml | 21 +- packages/debmagic/src/build.rs | 212 ++++ packages/debmagic/src/build/common.rs | 85 ++ packages/debmagic/src/build/config.rs | 10 + packages/debmagic/src/build/driver_bare.rs | 92 ++ packages/debmagic/src/build/driver_docker.rs | 219 ++++ packages/debmagic/src/cli.rs | 43 + packages/debmagic/src/config.rs | 52 + .../debmagic/src/debmagic/cli/__init__.py | 3 - .../debmagic/src/debmagic/cli/__main__.py | 4 - .../debmagic/cli/_build_driver/__init__.py | 0 .../src/debmagic/cli/_build_driver/build.py | 141 --- .../src/debmagic/cli/_build_driver/common.py | 102 -- .../src/debmagic/cli/_build_driver/config.py | 11 - .../debmagic/cli/_build_driver/driver_bare.py | 32 - .../cli/_build_driver/driver_docker.py | 167 --- .../debmagic/cli/_build_driver/driver_lxd.py | 29 - packages/debmagic/src/debmagic/cli/_config.py | 63 -- packages/debmagic/src/debmagic/cli/_main.py | 95 -- .../debmagic/src/debmagic/cli/_version.py | 1 - packages/debmagic/src/debmagic/cli/py.typed | 0 packages/debmagic/src/main.rs | 65 ++ packages/debmagic/tests/test_config.py | 18 +- pyproject.toml | 9 +- uv.lock | 170 +--- 32 files changed, 1889 insertions(+), 837 deletions(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 packages/debmagic-common-v2/Cargo.toml create mode 100644 packages/debmagic-common-v2/src/lib.rs create mode 100644 packages/debmagic/Cargo.toml create mode 100644 packages/debmagic/src/build.rs create mode 100644 packages/debmagic/src/build/common.rs create mode 100644 packages/debmagic/src/build/config.rs create mode 100644 packages/debmagic/src/build/driver_bare.rs create mode 100644 packages/debmagic/src/build/driver_docker.rs create mode 100644 packages/debmagic/src/cli.rs create mode 100644 packages/debmagic/src/config.rs delete mode 100644 packages/debmagic/src/debmagic/cli/__init__.py delete mode 100644 packages/debmagic/src/debmagic/cli/__main__.py delete mode 100644 packages/debmagic/src/debmagic/cli/_build_driver/__init__.py delete mode 100644 packages/debmagic/src/debmagic/cli/_build_driver/build.py delete mode 100644 packages/debmagic/src/debmagic/cli/_build_driver/common.py delete mode 100644 packages/debmagic/src/debmagic/cli/_build_driver/config.py delete mode 100644 packages/debmagic/src/debmagic/cli/_build_driver/driver_bare.py delete mode 100644 packages/debmagic/src/debmagic/cli/_build_driver/driver_docker.py delete mode 100644 packages/debmagic/src/debmagic/cli/_build_driver/driver_lxd.py delete mode 100644 packages/debmagic/src/debmagic/cli/_config.py delete mode 100644 packages/debmagic/src/debmagic/cli/_main.py delete mode 100644 packages/debmagic/src/debmagic/cli/_version.py delete mode 100644 packages/debmagic/src/debmagic/cli/py.typed create mode 100644 packages/debmagic/src/main.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4feb4bf..103c7b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,7 @@ jobs: run: uv sync --locked --all-extras --dev - name: Run Ruff Format Check run: uv run ruff format --check - + docs: runs-on: ubuntu-latest steps: @@ -100,6 +100,65 @@ jobs: run: uv sync --locked --all-extras --dev - name: Run Unittests run: uv run pytest --ignore tests/integration . + + # Cargo / Rust stuff + cargo-fmt: + name: "cargo fmt" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: "Install Rust toolchain" + run: rustup component add rustfmt + - run: cargo fmt --all --check + + cargo-clippy: + name: "cargo clippy" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: Swatinem/rust-cache@v2 + with: + save-if: ${{ github.ref == 'refs/heads/main' }} + - name: "Install Rust toolchain" + run: rustup component add clippy + - name: "Clippy" + run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings + + cargo-test-linux: + name: "cargo test (linux)" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - uses: Swatinem/rust-cache@v2 + with: + save-if: ${{ github.ref == 'refs/heads/main' }} + - name: "Install Rust toolchain" + run: rustup show + - name: "Run tests" + run: cargo test + + # integration / self packaging tests + package_ourself: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + - name: Install uv + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + - uses: Swatinem/rust-cache@v2 + with: + save-if: ${{ github.ref == 'refs/heads/main' }} + - name: Install the project + run: uv sync --locked --all-extras --dev + - name: Install debmagic (cli) + run: uv pip install packages/debmagic + - name: Run Debmagic build on ourself + run: uv run debmagic build --driver=docker # TODO: integration tests currently don't work in the CI since they require running apt source on debian trixie -> CI runs on ubuntu # integration-tests: @@ -122,21 +181,4 @@ jobs: # - name: Install the project # run: uv sync --locked --all-extras --dev # - name: Run Integrationtests - # run: uv run pytest tests/integration - - package_ourself: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: "3.12" - - name: Install uv - uses: astral-sh/setup-uv@v6 - with: - enable-cache: true - - name: Install the project - run: uv sync --locked --all-extras --dev - - name: Run Debmagic build on ourself - run: uv run debmagic build --driver=docker \ No newline at end of file + # run: uv run pytest tests/integration \ No newline at end of file diff --git a/.gitignore b/.gitignore index b039a2d..9c2fc2e 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,8 @@ todo.md todo.org + + +# Added by cargo + +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..166a8e4 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,955 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +dependencies = [ + "serde_core", +] + +[[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 = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clap" +version = "4.5.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "config" +version = "0.15.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b30fa8254caad766fc03cb0ccae691e14bf3bd72bfff27f72802ce729551b3d6" +dependencies = [ + "async-trait", + "convert_case", + "json5", + "pathdiff", + "ron", + "rust-ini", + "serde-untagged", + "serde_core", + "serde_json", + "toml", + "winnow", + "yaml-rust2", +] + +[[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 0.2.16", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "debmagic" +version = "0.0.1-alpha1" +dependencies = [ + "anyhow", + "clap", + "config", + "dirs", + "glob", + "libc", + "serde", + "serde_json", + "uuid", +] + +[[package]] +name = "debmagic-common-v2" +version = "0.1.0" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + +[[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 = "erased-serde" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[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 = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "libredox" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "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 = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "pest" +version = "2.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror", +] + +[[package]] +name = "ron" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32" +dependencies = [ + "bitflags", + "once_cell", + "serde", + "serde_derive", + "typeid", + "unicode-ident", +] + +[[package]] +name = "rust-ini" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.146" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217ca874ae0207aac254aa02c957ded05585a90892cc8d87f9e5fa49669dadd8" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "toml" +version = "0.9.10+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" +dependencies = [ + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ + "winnow", +] + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "yaml-rust2" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2462ea039c445496d8793d052e13787f2b90e750b833afee748e601c17621ed9" +dependencies = [ + "arraydeque", + "encoding_rs", + "hashlink", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..13e17df --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +resolver = "3" +members = ["packages/debmagic", "packages/debmagic-common-v2"] diff --git a/packages/debmagic-common-v2/Cargo.toml b/packages/debmagic-common-v2/Cargo.toml new file mode 100644 index 0000000..7813eed --- /dev/null +++ b/packages/debmagic-common-v2/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "debmagic-common-v2" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/packages/debmagic-common-v2/src/lib.rs b/packages/debmagic-common-v2/src/lib.rs new file mode 100644 index 0000000..b93cf3f --- /dev/null +++ b/packages/debmagic-common-v2/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/packages/debmagic/Cargo.toml b/packages/debmagic/Cargo.toml new file mode 100644 index 0000000..3afd01d --- /dev/null +++ b/packages/debmagic/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "debmagic" +version = "0.0.1-alpha1" +edition = "2024" +documentation = "https://debmagic.readthedocs.org" +homepage = "https://github.com/SFTtech/debmagic" +repository = "https://github.com/SFTtech/debmagic.git" + +[dependencies] +clap = { version = "4.5.38", features = ["derive"] } +anyhow = { version = "1.0.100" } +config = { version = "0.15.9", features = ["toml"] } +dirs = "5.0.0" +# thiserror = "2.0.17" # for future uses when we need clearer error handling +glob = "0.3.3" +libc = "0.2.178" +serde = { version = "1.0.171", features = ["derive"] } +serde_json = "1.0.143" +uuid = { version = "1.18.1", features = ["v4"] } diff --git a/packages/debmagic/pyproject.toml b/packages/debmagic/pyproject.toml index 1f95067..8b459fb 100644 --- a/packages/debmagic/pyproject.toml +++ b/packages/debmagic/pyproject.toml @@ -1,3 +1,7 @@ +[build-system] +requires = ['maturin>=1.9.4,<2'] +build-backend = 'maturin' + [project] name = "debmagic" version = "0.0.1-alpha.1" @@ -7,11 +11,6 @@ license = "GPL-2.0-or-later" # readme = "../../README.md" requires-python = ">=3.12" classifiers = ["Programming Language :: Python :: 3"] -dependencies = [ - "debmagic-common", - "pydantic>=2,<3", - "pydantic-settings>=2.12.0", -] [project.urls] homepage = "https://github.com/SFTtech/debmagic" @@ -19,9 +18,9 @@ source = "https://github.com/SFTtech/debmagic.git" issues = "https://github.com/SFTtech/debmagic/issues" releasenotes = "https://github.com/SFTtech/debmagic/-/blob/main/debian/changelog" -[build-system] -requires = ["setuptools>=77.0.0", "setuptools-scm"] -build-backend = "setuptools.build_meta" - -[project.scripts] -debmagic = "debmagic.cli:main" +[tool.maturin] +bindings = "bin" +manifest-path = "Cargo.toml" +module-name = "debmagic.cli" +strip = true +exclude = [] diff --git a/packages/debmagic/src/build.rs b/packages/debmagic/src/build.rs new file mode 100644 index 0000000..9149002 --- /dev/null +++ b/packages/debmagic/src/build.rs @@ -0,0 +1,212 @@ +use std::{ + fs, + path::{Path, PathBuf}, +}; + +use crate::{ + build::{ + common::{BuildConfig, BuildDriver, BuildDriverType, BuildMetadata, PackageDescription}, + config::DriverConfig, + driver_bare::DriverBare, + driver_docker::DriverDocker, + }, + config::Config, +}; +use anyhow::{Context, anyhow}; +use glob::glob; + +pub mod common; +pub mod config; +pub mod driver_bare; +pub mod driver_docker; + +fn create_driver( + driver_type: &BuildDriverType, + build_config: &BuildConfig, + config: &DriverConfig, +) -> anyhow::Result> { + match driver_type { + BuildDriverType::Docker => Ok(Box::new(DriverDocker::create( + build_config, + &config.docker, + )?)), + BuildDriverType::Bare => Ok(Box::new(DriverBare::create(build_config, &config.bare))), + // BuildDriverType::Lxd => ... + } +} + +fn create_driver_from_build_root( + config: &DriverConfig, + build_root: &Path, +) -> anyhow::Result> { + let build_metadata_path = build_root.join("build.json"); + if !build_metadata_path.is_file() { + return Err(anyhow!("No build.json found")); + } + + let content = fs::read_to_string(&build_metadata_path)?; + let metadata: BuildMetadata = serde_json::from_str(&content).with_context(|| { + format!( + "Failed to read build metadata from {} - invalid json", + build_metadata_path.display() + ) + })?; + + match metadata.driver { + BuildDriverType::Docker => Ok(Box::new(DriverDocker::from_build_metadata( + &metadata.config, + &config.docker, + &metadata, + ))), + BuildDriverType::Bare => Ok(Box::new(DriverBare::from_build_metadata( + &metadata.config, + &config.bare, + &metadata, + ))), + // BuildDriverType::Lxd => ... + } +} + +fn write_build_metadata(config: &BuildConfig, driver: &dyn BuildDriver) -> anyhow::Result<()> { + let metadata = BuildMetadata { + driver: driver.driver_type(), + config: config.clone(), + driver_metadata: driver.get_build_metadata(), + }; + let path = config.build_root_dir.join("build.json"); + let json = + serde_json::to_string_pretty(&metadata).context("Failed serialize build metadata")?; + fs::write(path, json)?; + Ok(()) +} + +fn copy_glob(src_dir: &Path, pattern: &str, dest_dir: &Path) -> anyhow::Result<()> { + let full_pattern = src_dir.join(pattern).to_string_lossy().into_owned(); + for entry in glob(&full_pattern)? { + let path = entry?; + if path.is_file() { + let filename = path.file_name().ok_or(anyhow!( + "Could not retrieve filename from {}", + path.display() + ))?; + fs::copy(&path, dest_dir.join(filename))?; + } + } + Ok(()) +} + +fn copy_dir_all(src: impl AsRef, dst: impl AsRef) -> std::io::Result<()> { + // TODO: properly handle gitignore / other ignore files when copying + // Use ignore crate + // Simple copy logic (for advanced gitignore support, look at the `ignore` crate) + fs::create_dir_all(&dst)?; + for entry in fs::read_dir(src)? { + let entry = entry?; + let file_type = entry.file_type()?; + if file_type.is_dir() { + copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?; + } else if file_type.is_file() { + fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?; + } + // handle hardlinks, symlinks and similar weird filetypes + } + Ok(()) +} + +fn get_build_root_and_identifier( + config: &Config, + package: &PackageDescription, +) -> (String, PathBuf) { + let package_identifier = format!("{}-{}", package.name, package.version); + let build_root = config.temp_build_dir.join(&package_identifier); + (package_identifier, build_root) +} + +fn prepare_build_env( + config: &Config, + package: &PackageDescription, + output_dir: &Path, +) -> anyhow::Result { + let (package_identifier, build_root) = get_build_root_and_identifier(config, package); + if build_root.exists() { + fs::remove_dir_all(&build_root)?; + } + + let build_config = BuildConfig { + package_identifier, + source_dir: package.source_dir.clone(), + output_dir: output_dir.to_path_buf(), + build_root_dir: build_root, + distro: "debian".to_string(), + distro_version: "trixie".to_string(), + dry_run: config.dry_run, + sign_package: false, + }; + + build_config.create_dirs()?; + + copy_dir_all(&build_config.source_dir, build_config.build_source_dir())?; + + Ok(build_config) +} + +pub fn get_shell_in_build(config: &Config, package: &PackageDescription) -> anyhow::Result<()> { + let (_package_identifier, build_root) = get_build_root_and_identifier(config, package); + let driver = create_driver_from_build_root(&config.driver, &build_root)?; + driver.drop_into_shell()?; + Ok(()) +} + +pub fn build_package( + config: &Config, + package: &PackageDescription, + driver_type: BuildDriverType, + output_dir: &Path, +) -> anyhow::Result<()> { + let build_config = prepare_build_env(config, package, output_dir)?; + + let driver = create_driver(&driver_type, &build_config, &config.driver)?; + + write_build_metadata(&build_config, &*driver)?; + + let result = (|| -> anyhow::Result<()> { + driver.run_command( + &["apt-get", "-y", "build-dep", "."], + &build_config.build_source_dir(), + true, + )?; + driver.run_command( + &["dpkg-buildpackage", "-us", "-uc", "-ui", "-nc", "-b"], + &build_config.build_source_dir(), + false, + )?; + + if build_config.sign_package { + // SIGN .changes and .dsc files + // changes = *.changes / *.dsc + // driver.run_command(&["debsign", opts, changes], &build_config.build_source_dir(), false)?; + // driver.run_command(&["debrsign", opts, username, changes], &build_config.build_source_dir(), false)?; + } + + let parent_dir = build_config.build_source_dir().join(".."); + copy_glob(&parent_dir, "*.deb", &build_config.output_dir)?; + copy_glob(&parent_dir, "*.changes", &build_config.output_dir)?; + copy_glob(&parent_dir, "*.buildinfo", &build_config.output_dir)?; + copy_glob(&parent_dir, "*.dsc", &build_config.output_dir)?; + + Ok(()) + })(); + + if let Err(e) = result { + eprintln!("Build failed: {e}. Dropping into shell..."); + let res = driver.drop_into_shell(); + if let Err(shell_error) = res { + eprintln!("Dropping into shell failed: {shell_error}"); + } + driver.cleanup(); + return Err(e); + } + + driver.cleanup(); + Ok(()) +} diff --git a/packages/debmagic/src/build/common.rs b/packages/debmagic/src/build/common.rs new file mode 100644 index 0000000..5c36658 --- /dev/null +++ b/packages/debmagic/src/build/common.rs @@ -0,0 +1,85 @@ +use std::{ + collections::HashMap, + fmt::Debug, + fs, + path::{Path, PathBuf}, +}; + +use clap::ValueEnum; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize)] +pub enum BuildDriverType { + Docker, + Bare, + // Lxd +} + +#[derive(Debug, Clone)] +pub struct PackageDescription { + pub name: String, + pub version: String, + pub source_dir: PathBuf, +} + +pub type DriverSpecificBuildMetadata = HashMap; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BuildMetadata { + pub driver: BuildDriverType, + pub config: BuildConfig, + pub driver_metadata: DriverSpecificBuildMetadata, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct BuildConfig { + pub package_identifier: String, + pub build_root_dir: PathBuf, + pub source_dir: PathBuf, + pub output_dir: PathBuf, + pub dry_run: bool, + pub distro_version: String, + pub distro: String, + pub sign_package: bool, +} + +impl BuildConfig { + pub fn build_identifier(&self) -> String { + format!( + "{}-{}-{}", + self.package_identifier, self.distro, self.distro_version + ) + } + + pub fn build_work_dir(&self) -> PathBuf { + self.build_root_dir.join("work") + } + + pub fn build_temp_dir(&self) -> PathBuf { + self.build_root_dir.join("temp") + } + + pub fn build_source_dir(&self) -> PathBuf { + self.build_work_dir().join(&self.package_identifier) + } + + pub fn create_dirs(&self) -> std::io::Result<()> { + fs::create_dir_all(&self.output_dir)?; + fs::create_dir_all(self.build_work_dir())?; + fs::create_dir_all(self.build_temp_dir())?; + fs::create_dir_all(self.build_source_dir())?; + Ok(()) + } +} + +pub trait BuildDriver { + fn get_build_metadata(&self) -> DriverSpecificBuildMetadata; + + fn run_command(&self, cmd: &[&str], cwd: &Path, requires_root: bool) -> std::io::Result<()>; + + fn cleanup(&self); + + fn drop_into_shell(&self) -> std::io::Result<()>; + + fn driver_type(&self) -> BuildDriverType; +} diff --git a/packages/debmagic/src/build/config.rs b/packages/debmagic/src/build/config.rs new file mode 100644 index 0000000..769ca41 --- /dev/null +++ b/packages/debmagic/src/build/config.rs @@ -0,0 +1,10 @@ +use serde::Deserialize; + +use crate::build::driver_bare::DriverBareConfig; +use crate::build::driver_docker::DriverDockerConfig; + +#[derive(Deserialize, Debug, Default)] +pub struct DriverConfig { + pub docker: DriverDockerConfig, + pub bare: DriverBareConfig, +} diff --git a/packages/debmagic/src/build/driver_bare.rs b/packages/debmagic/src/build/driver_bare.rs new file mode 100644 index 0000000..ce77dbe --- /dev/null +++ b/packages/debmagic/src/build/driver_bare.rs @@ -0,0 +1,92 @@ +use std::{path::Path, process::Command}; + +use serde::{Deserialize, Serialize}; + +use crate::build::common::{ + BuildConfig, BuildDriver, BuildDriverType, BuildMetadata, DriverSpecificBuildMetadata, +}; + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct DriverBareConfig {} + +pub struct DriverBare { + config: BuildConfig, + _driver_config: DriverBareConfig, +} + +impl DriverBare { + pub fn create(config: &BuildConfig, driver_config: &DriverBareConfig) -> Self { + Self { + config: config.clone(), + _driver_config: driver_config.clone(), + } + } + + pub fn from_build_metadata( + config: &BuildConfig, + driver_config: &DriverBareConfig, + _build_metadata: &BuildMetadata, + ) -> Self { + Self { + config: config.clone(), + _driver_config: driver_config.clone(), + } + } +} + +impl BuildDriver for DriverBare { + fn get_build_metadata(&self) -> DriverSpecificBuildMetadata { + DriverSpecificBuildMetadata::from([]) + } + + fn run_command(&self, cmd: &[&str], cwd: &Path, requires_root: bool) -> std::io::Result<()> { + let mut full_cmd: Vec = Vec::new(); + + // Handle sudo logic + let is_root = unsafe { libc::getuid() == 0 }; + if requires_root && !is_root { + full_cmd.push("sudo".to_string()); + } + + full_cmd.extend(cmd.iter().map(|s| s.to_string())); + + if self.config.dry_run { + println!("[dry-run] Would run: {full_cmd:?}"); + return Ok(()); + } + + let mut command = Command::new(&full_cmd[0]); + command.args(&full_cmd[1..]); + + command.current_dir(cwd); + + // Inherit stdout/stderr to match Python behavior + let status = command.status()?; + + if status.success() { + Ok(()) + } else { + Err(std::io::Error::other(format!( + "Command failed with exit code: {:?}", + status.code() + ))) + } + } + + fn cleanup(&self) { + // No-op for bare driver + } + + fn drop_into_shell(&self) -> std::io::Result<()> { + let mut shell = Command::new("/usr/bin/env"); + shell.arg("bash"); + + // Use status() to wait for the shell to exit + let _ = shell.status()?; + Ok(()) + } + + fn driver_type(&self) -> BuildDriverType { + BuildDriverType::Bare + } +} diff --git a/packages/debmagic/src/build/driver_docker.rs b/packages/debmagic/src/build/driver_docker.rs new file mode 100644 index 0000000..4598191 --- /dev/null +++ b/packages/debmagic/src/build/driver_docker.rs @@ -0,0 +1,219 @@ +use std::{ + fs, + path::{Path, PathBuf}, + process::Command, +}; + +use anyhow::anyhow; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +use crate::build::common::{ + BuildConfig, BuildDriver, BuildDriverType, BuildMetadata, DriverSpecificBuildMetadata, +}; + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct DriverDockerConfig { + pub base_image: Option, +} + +// Constants +const BUILD_DIR_IN_CONTAINER: &str = "/debmagic"; +const DOCKER_USER: &str = "user"; +const DOCKERFILE_TEMPLATE: &str = r#" +FROM {base_image} +ARG USERNAME={docker_user} +ARG USER_UID=1000 +ARG USER_GID=$USER_UID +RUN apt-get update && apt-get install -y sudo dpkg-dev python3 +RUN groupadd --gid $USER_GID $USERNAME \ + && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \ + && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ + && chmod 0440 /etc/sudoers.d/$USERNAME +RUN mkdir -p {build_dir} +RUN chown $USERNAME:$USERNAME {build_dir} +USER $USERNAME +ENTRYPOINT ["sleep", "infinity"] +"#; + +#[derive(Debug, Serialize, Deserialize)] +pub struct DockerDriverBuildMetadata { + pub container_name: String, +} + +pub struct DriverDocker { + config: BuildConfig, + _driver_config: DriverDockerConfig, + container_name: String, +} + +impl DriverDocker { + pub fn create( + config: &BuildConfig, + driver_config: &DriverDockerConfig, + ) -> anyhow::Result { + let base_image = driver_config + .base_image + .clone() + .unwrap_or_else(|| format!("docker.io/{}:{}", config.distro, config.distro_version)); + + let formatted_dockerfile = DOCKERFILE_TEMPLATE + .replace("{base_image}", &base_image) + .replace("{docker_user}", DOCKER_USER) + .replace("{build_dir}", BUILD_DIR_IN_CONTAINER); + + let dockerfile_path = config.build_temp_dir().join("Dockerfile"); + fs::write(&dockerfile_path, formatted_dockerfile).expect("Failed to write Dockerfile"); + + let docker_image_name = format!("debmagic-{}", config.build_identifier()); + let mut build_args = Vec::new(); + + let uid = unsafe { libc::getuid() }; + if uid != 0 { + build_args.extend(["--build-arg".to_string(), format!("USER_UID={uid}")]); + } + let gid = unsafe { libc::getgid() }; + if gid != 0 { + build_args.extend(["--build-arg".to_string(), format!("USER_GID={gid}")]); + } + + let mut build_cmd = Command::new("docker"); + build_cmd.args(["build"]).args(&build_args).args([ + "--tag", + &docker_image_name, + "-f", + &dockerfile_path.to_string_lossy(), + &config.build_temp_dir().to_string_lossy(), + ]); + + if !config.dry_run && !build_cmd.status().map(|s| s.success()).unwrap_or(false) { + return Err(anyhow!("Error creating docker image")); + } + + let container_uuid = Uuid::new_v4().to_string(); + let mut run_cmd = Command::new("docker"); + run_cmd.args([ + "run", + "--detach", + "--name", + &container_uuid, + "--mount", + &format!( + "type=bind,src={},dst={}", + config.build_root_dir.display(), + BUILD_DIR_IN_CONTAINER + ), + &docker_image_name, + ]); + + if !config.dry_run && !run_cmd.status().map(|s| s.success()).unwrap_or(false) { + return Err(anyhow!("Error starting docker container")); + } + + Ok(Self { + config: config.clone(), + _driver_config: driver_config.clone(), + container_name: container_uuid, + }) + } + + pub fn from_build_metadata( + config: &BuildConfig, + driver_config: &DriverDockerConfig, + build_metadata: &BuildMetadata, + ) -> Self { + let container_name = build_metadata + .driver_metadata + .get("container_name") + .cloned() + .expect("Missing container_name in metadata"); + + Self { + config: config.clone(), + _driver_config: driver_config.clone(), + container_name, + } + } + fn translate_path_in_container( + &self, + path_in_source: &Path, + ) -> Result { + path_in_source + .strip_prefix(&self.config.build_root_dir) + .map(|rel| Path::new(BUILD_DIR_IN_CONTAINER).join(rel)) + .map_err(|_| { + std::io::Error::new( + std::io::ErrorKind::NotFound, + "Path is not relative to build root".to_string(), + ) + }) + } +} + +impl BuildDriver for DriverDocker { + fn get_build_metadata(&self) -> DriverSpecificBuildMetadata { + let mut meta = DriverSpecificBuildMetadata::new(); + meta.insert("container_name".to_string(), self.container_name.clone()); + meta + } + + fn run_command(&self, cmd: &[&str], cwd: &Path, requires_root: bool) -> std::io::Result<()> { + let mut exec_args = vec!["exec".to_string()]; + + let container_path = self + .translate_path_in_container(cwd) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?; + exec_args.push("--workdir".to_string()); + exec_args.push(container_path.to_string_lossy().to_string()); + + if requires_root { + exec_args.push("--user".to_string()); + exec_args.push("root".to_string()); + } + + exec_args.push(self.container_name.clone()); + exec_args.extend(cmd.iter().map(|s| s.to_string())); + + if self.config.dry_run { + println!("[dry-run] docker {exec_args:?}"); + return Ok(()); + } + + let status = Command::new("docker").args(exec_args).status()?; + if !status.success() { + return Err(std::io::Error::other("Docker exec failed")); + } + Ok(()) + } + + fn cleanup(&self) { + let _ = Command::new("docker") + .args(["rm", "-f", &self.container_name]) + .status(); + } + + fn drop_into_shell(&self) -> std::io::Result<()> { + if self.config.dry_run { + return Ok(()); + } + + let workdir = self.translate_path_in_container(&self.config.build_root_dir)?; + let _ = Command::new("docker") + .args([ + "exec", + "-it", + "--workdir", + &workdir.to_string_lossy(), + &self.container_name, + "/usr/bin/env", + "bash", + ]) + .status()?; + + Ok(()) + } + + fn driver_type(&self) -> BuildDriverType { + BuildDriverType::Docker + } +} diff --git a/packages/debmagic/src/cli.rs b/packages/debmagic/src/cli.rs new file mode 100644 index 0000000..7185c95 --- /dev/null +++ b/packages/debmagic/src/cli.rs @@ -0,0 +1,43 @@ +use std::path::PathBuf; + +use crate::build::common::BuildDriverType; +use clap::{Args, Parser, Subcommand}; + +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +pub struct Cli { + #[arg(short, long)] + pub config: Option, + + #[command(subcommand)] + pub command: Commands, +} + +#[derive(Subcommand, Debug)] +pub enum Commands { + Build(BuildSubcommandArgs), + Shell(ShellSubcommandArgs), + Test {}, + Check {}, + Version {}, +} + +#[derive(Args, Debug)] +pub struct BuildSubcommandArgs { + #[arg(short, long)] + pub driver: BuildDriverType, + + #[arg(long)] + pub driver_docker_build_image: Option, + + #[arg(short, long)] + pub source_dir: Option, + #[arg(short, long)] + pub output_dir: Option, +} + +#[derive(Args, Debug)] +pub struct ShellSubcommandArgs { + #[arg(short, long)] + pub source_dir: Option, +} diff --git a/packages/debmagic/src/config.rs b/packages/debmagic/src/config.rs new file mode 100644 index 0000000..c129add --- /dev/null +++ b/packages/debmagic/src/config.rs @@ -0,0 +1,52 @@ +use std::path::PathBuf; + +use crate::{build::config::DriverConfig, cli::Cli}; +use anyhow::{Context, anyhow}; +use config::{Config as ConfigBuilder, File}; +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +#[serde(default)] +pub struct Config { + pub driver: DriverConfig, + pub temp_build_dir: PathBuf, + pub dry_run: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + driver: DriverConfig::default(), + temp_build_dir: PathBuf::from("/tmp/debmagic"), + dry_run: false, + } + } +} + +impl Config { + pub fn new(cli_args: &Cli) -> anyhow::Result { + let mut builder = ConfigBuilder::builder(); + + let xdg_config_file = dirs::config_dir() + .map(|p| p.join("debmagic").join("config.toml")) + .ok_or(anyhow!("Could not determine user config directory"))?; + if xdg_config_file.is_file() { + let xdg_config_file = xdg_config_file.to_str(); + if let Some(xdg_config_file) = xdg_config_file { + builder = builder.add_source(File::with_name(xdg_config_file)); + } + } + + if let Some(config_override) = cli_args.config.as_ref().and_then(|f| f.to_str()) { + builder = builder.add_source(File::with_name(config_override)); + } + // TODO: reimplement cli arg overwrites + let build = builder + .build() + .context("Failed to initialize config reader")?; + let config: anyhow::Result = build + .try_deserialize() + .map_err(|e| anyhow!("Failed to read config: {e}")); + config + } +} diff --git a/packages/debmagic/src/debmagic/cli/__init__.py b/packages/debmagic/src/debmagic/cli/__init__.py deleted file mode 100644 index 16d68a6..0000000 --- a/packages/debmagic/src/debmagic/cli/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from ._main import main - -__all__ = ["main"] diff --git a/packages/debmagic/src/debmagic/cli/__main__.py b/packages/debmagic/src/debmagic/cli/__main__.py deleted file mode 100644 index 3bd8ec7..0000000 --- a/packages/debmagic/src/debmagic/cli/__main__.py +++ /dev/null @@ -1,4 +0,0 @@ -from ._main import main - -if __name__ == "__main__": - main() diff --git a/packages/debmagic/src/debmagic/cli/_build_driver/__init__.py b/packages/debmagic/src/debmagic/cli/_build_driver/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/packages/debmagic/src/debmagic/cli/_build_driver/build.py b/packages/debmagic/src/debmagic/cli/_build_driver/build.py deleted file mode 100644 index 371e09d..0000000 --- a/packages/debmagic/src/debmagic/cli/_build_driver/build.py +++ /dev/null @@ -1,141 +0,0 @@ -import re -import shutil -from pathlib import Path - -import pydantic -from debmagic.common.utils import copy_file_if_exists - -from .._config import DebmagicConfig -from .common import BuildConfig, BuildDriver, BuildDriverType, BuildMetadata, PackageDescription -from .config import BuildDriverConfig -from .driver_bare import BuildDriverBare -from .driver_docker import BuildDriverDocker -from .driver_lxd import BuildDriverLxd - - -def _create_driver(build_driver: BuildDriverType, build_config: BuildConfig, config: BuildDriverConfig) -> BuildDriver: - match build_driver: - case "docker": - return BuildDriverDocker.create(config=build_config, driver_config=config.docker) - case "lxd": - return BuildDriverLxd.create(config=build_config, driver_config=config.lxd) - case "bare": - return BuildDriverBare.create(config=build_config, driver_config=config.bare) - - -def _driver_from_build_root(build_root: Path): - build_metadata_path = build_root / "build.json" - if not build_metadata_path.is_file(): - raise RuntimeError(f"{build_metadata_path} does not exist") - try: - metadata = BuildMetadata.model_validate_json(build_metadata_path.read_text()) - except pydantic.ValidationError as e: - raise RuntimeError(f"{build_metadata_path} is invalid: {e}") from None - - match metadata.driver: - case "docker": - return BuildDriverDocker.from_build_metadata(metadata) - case "lxd": - return BuildDriverLxd.from_build_metadata(metadata) - case "none": - return BuildDriverBare.from_build_metadata(metadata) - case _: - raise RuntimeError(f"Unknown build driver {metadata.driver}") - - -def _write_build_metadata(config: BuildConfig, driver: BuildDriver): - driver_metadata = driver.get_build_metadata() - build_metadata_path = config.build_root_dir / "build.json" - metadata = BuildMetadata( - build_root=config.build_root_dir, - source_dir=config.build_source_dir, - driver=driver.driver_type(), - driver_metadata=driver_metadata, - ) - build_metadata_path.write_text(metadata.model_dump_json()) - - -def _ignore_patterns_from_gitignore(gitignore_path: Path): - if not gitignore_path.is_file(): - return None - - contents = gitignore_path.read_text().strip().splitlines() - relevant_lines = filter(lambda line: not re.match(r"\s*#.*", line) and line.strip(), contents) - return shutil.ignore_patterns(*relevant_lines) - - -def _get_package_build_root_and_identifier(config: DebmagicConfig, package: PackageDescription) -> tuple[str, Path]: - package_identifier = f"{package.name}-{package.version}" - build_root = config.temp_build_dir / package_identifier - return package_identifier, build_root - - -def _prepare_build_env(config: DebmagicConfig, package: PackageDescription, output_dir: Path) -> BuildConfig: - package_identifier, build_root = _get_package_build_root_and_identifier(config, package) - if build_root.exists(): - shutil.rmtree(build_root) - - build_config = BuildConfig( - package_identifier=package_identifier, - source_dir=package.source_dir, - output_dir=output_dir, - build_root_dir=build_root, - distro="debian", - distro_version="trixie", - dry_run=config.dry_run, - sign_package=False, - ) - - # prepare build environment, create the build directory structure, copy the sources - build_config.create_dirs() - source_ignore_pattern = _ignore_patterns_from_gitignore(package.source_dir / ".gitignore") - shutil.copytree( - build_config.source_dir, build_config.build_source_dir, dirs_exist_ok=True, ignore=source_ignore_pattern - ) - - return build_config - - -def get_shell_in_build(config: DebmagicConfig, package: PackageDescription): - _, build_root = _get_package_build_root_and_identifier(config, package) - driver = _driver_from_build_root(build_root=build_root) - driver.drop_into_shell() - - -def build( - config: DebmagicConfig, - package: PackageDescription, - build_driver: BuildDriverType, - output_dir: Path, -): - build_config = _prepare_build_env(config=config, package=package, output_dir=output_dir) - - driver = _create_driver(build_driver, build_config, config.driver_config) - _write_build_metadata(build_config, driver) - try: - driver.run_command(["apt-get", "-y", "build-dep", "."], cwd=build_config.build_source_dir, requires_root=True) - driver.run_command(["dpkg-buildpackage", "-us", "-uc", "-ui", "-nc", "-b"], cwd=build_config.build_source_dir) - if build_config.sign_package: - pass - # SIGN .changes and .dsc files - # changes = *.changes / *.dsc - # driver.run_command(["debsign", opts, changes], cwd=config.source_dir) - # driver.run_command(["debrsign", opts, username, changes], cwd=config.source_dir) - - # TODO: copy packages to output directory - copy_file_if_exists(source=build_config.build_source_dir / "..", glob="*.deb", dest=build_config.output_dir) - copy_file_if_exists( - source=build_config.build_source_dir / "..", glob="*.buildinfo", dest=build_config.output_dir - ) - copy_file_if_exists(source=build_config.build_source_dir / "..", glob="*.changes", dest=build_config.output_dir) - copy_file_if_exists(source=build_config.build_source_dir / "..", glob="*.dsc", dest=build_config.output_dir) - except Exception as e: - print(e) - print( - "Something failed during building -" - " dropping into interactive shell in build environment for easier debugging" - ) - driver.drop_into_shell() - raise e - finally: - driver.cleanup() diff --git a/packages/debmagic/src/debmagic/cli/_build_driver/common.py b/packages/debmagic/src/debmagic/cli/_build_driver/common.py deleted file mode 100644 index 4b9f04a..0000000 --- a/packages/debmagic/src/debmagic/cli/_build_driver/common.py +++ /dev/null @@ -1,102 +0,0 @@ -import abc -from dataclasses import dataclass -from pathlib import Path -from typing import Literal, Self, Sequence, TypeVar - -from pydantic import BaseModel - -BuildDriverType = Literal["docker", "lxd", "bare"] -SUPPORTED_BUILD_DRIVERS: list[BuildDriverType] = ["docker", "bare"] - - -class BuildError(RuntimeError): - pass - - -@dataclass -class PackageDescription: - name: str - version: str - source_dir: Path - - -DriverSpecificBuildMetadata = dict[str, str] # expand as needed - - -class BuildMetadata(BaseModel): - driver: BuildDriverType - build_root: Path - source_dir: Path - driver_metadata: DriverSpecificBuildMetadata - - -@dataclass -class BuildConfig: - package_identifier: str - source_dir: Path - output_dir: Path - dry_run: bool - distro_version: str # e.g. trixie - distro: str # e.g. debian - sign_package: bool # TODO: figure out if this is the right place - - # build paths - build_root_dir: Path - - @property - def build_identifier(self) -> str: - # TODO: include architecture - return f"{self.package_identifier}-{self.distro}-{self.distro_version}" - - @property - def build_work_dir(self) -> Path: - return self.build_root_dir / "work" - - @property - def build_temp_dir(self) -> Path: - return self.build_root_dir / "temp" - - @property - def build_source_dir(self) -> Path: - return self.build_work_dir / self.package_identifier - - def create_dirs(self): - self.output_dir.mkdir(exist_ok=True, parents=True) - self.build_work_dir.mkdir(exist_ok=True, parents=True) - self.build_temp_dir.mkdir(exist_ok=True, parents=True) - self.build_source_dir.mkdir(exist_ok=True, parents=True) - - -ConfigT = TypeVar("ConfigT") - - -class BuildDriver[ConfigT]: - @classmethod - @abc.abstractmethod - def create(cls, config: BuildConfig, driver_config: ConfigT) -> Self: - pass - - @classmethod - @abc.abstractmethod - def from_build_metadata(cls, build_metadata: BuildMetadata) -> Self: - pass - - @abc.abstractmethod - def get_build_metadata(self) -> DriverSpecificBuildMetadata: - pass - - @abc.abstractmethod - def run_command(self, cmd: Sequence[str | Path], cwd: Path | None = None, requires_root: bool = False): - pass - - @abc.abstractmethod - def cleanup(self): - pass - - @abc.abstractmethod - def drop_into_shell(self): - pass - - @abc.abstractmethod - def driver_type(self) -> BuildDriverType: - pass diff --git a/packages/debmagic/src/debmagic/cli/_build_driver/config.py b/packages/debmagic/src/debmagic/cli/_build_driver/config.py deleted file mode 100644 index 23cf45f..0000000 --- a/packages/debmagic/src/debmagic/cli/_build_driver/config.py +++ /dev/null @@ -1,11 +0,0 @@ -from pydantic import BaseModel - -from .driver_bare import BareDriverConfig -from .driver_docker import DockerDriverConfig -from .driver_lxd import LxdDriverConfig - - -class BuildDriverConfig(BaseModel): - bare: BareDriverConfig = BareDriverConfig() - docker: DockerDriverConfig = DockerDriverConfig() - lxd: LxdDriverConfig = LxdDriverConfig() diff --git a/packages/debmagic/src/debmagic/cli/_build_driver/driver_bare.py b/packages/debmagic/src/debmagic/cli/_build_driver/driver_bare.py deleted file mode 100644 index 0761f26..0000000 --- a/packages/debmagic/src/debmagic/cli/_build_driver/driver_bare.py +++ /dev/null @@ -1,32 +0,0 @@ -import os -from pathlib import Path -from typing import Sequence - -from debmagic.common.utils import run_cmd, run_cmd_in_foreground -from pydantic import BaseModel - -from .common import BuildConfig, BuildDriver - - -class BareDriverConfig(BaseModel): - pass - - -class BuildDriverBare(BuildDriver[BareDriverConfig]): - def __init__(self, config: BuildConfig) -> None: - self._config = config - - @classmethod - def create(cls, config: BuildConfig, driver_config: BareDriverConfig): - return cls(config=config) - - def run_command(self, cmd: Sequence[str | Path], cwd: Path | None = None, requires_root: bool = False): - if requires_root and not os.getuid() == 0: - cmd = ["sudo", *cmd] - run_cmd(cmd=cmd, dry_run=self._config.dry_run, cwd=cwd) - - def cleanup(self): - pass - - def drop_into_shell(self): - run_cmd_in_foreground(["/usr/bin/env", "bash"]) diff --git a/packages/debmagic/src/debmagic/cli/_build_driver/driver_docker.py b/packages/debmagic/src/debmagic/cli/_build_driver/driver_docker.py deleted file mode 100644 index 2a45adc..0000000 --- a/packages/debmagic/src/debmagic/cli/_build_driver/driver_docker.py +++ /dev/null @@ -1,167 +0,0 @@ -import os -import uuid -from pathlib import Path -from typing import Self, Sequence - -from debmagic.common.utils import run_cmd, run_cmd_in_foreground -from pydantic import BaseModel, Field - -from .common import ( - BuildConfig, - BuildDriver, - BuildDriverType, - BuildError, - BuildMetadata, - DriverSpecificBuildMetadata, -) - -BUILD_DIR_IN_CONTAINER = Path("/debmagic") - -DOCKER_USER = "user" - -DOCKERFILE_TEMPLATE = f""" -FROM {{base_image}} - -ARG USERNAME={DOCKER_USER} -ARG USER_UID=1000 -ARG USER_GID=$USER_UID - -RUN apt-get update && apt-get install -y sudo dpkg-dev python3 - -RUN groupadd --gid $USER_GID $USERNAME \ - && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \ - && echo $USERNAME ALL=\\(root\\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ - && chmod 0440 /etc/sudoers.d/$USERNAME - -RUN mkdir -p {BUILD_DIR_IN_CONTAINER} -RUN chown $USERNAME:$USERNAME {BUILD_DIR_IN_CONTAINER} -USER $USERNAME -ENTRYPOINT ["sleep", "infinity"] -""" - - -class DockerDriverConfig(BaseModel): - base_image: str | None = Field( - default=None, - description="Can be used to override the base image which the docker driver " - "uses to create the build environment", - ) - - -class DockerDriverBuildMetadata(BaseModel): - container_name: str - - -class BuildDriverDocker(BuildDriver[DockerDriverConfig]): - def __init__(self, build_root: Path, dry_run: bool, container_name: str): - self._build_root = build_root - self._dry_run = dry_run - - self._container_name = container_name - - def _translate_path_in_container(self, path_in_source: Path) -> Path: - if not path_in_source.is_relative_to(self._build_root): - raise BuildError("Cannot run in a path not relative to the original source directory") - rel = path_in_source.relative_to(self._build_root) - return BUILD_DIR_IN_CONTAINER / rel - - @classmethod - def create(cls, config: BuildConfig, driver_config: DockerDriverConfig) -> Self: - base_image = driver_config.base_image or f"docker.io/{config.distro}:{config.distro_version}" - - formatted_dockerfile = DOCKERFILE_TEMPLATE.format(base_image=base_image) - - dockerfile_path = config.build_temp_dir / "Dockerfile" - dockerfile_path.write_text(formatted_dockerfile) - - docker_image_name = f"debmagic-{config.build_identifier}" - - additional_args = [] - if not os.getuid() == 0: - # to reduce potential permission problems with missing user remappings on some systems - # we simply create the build user inside the docker container with the same uid / gid as our host user - additional_args.extend(["--build-arg", f"USER_UID={os.getuid()}", "--build-arg", f"USER_GID={os.getgid()}"]) - - ret = run_cmd( - [ - "docker", - "build", - *additional_args, - "--tag", - docker_image_name, - "-f", - dockerfile_path, - config.build_temp_dir, - ], - dry_run=config.dry_run, - check=False, - ) - if ret.returncode != 0: - raise BuildError("Error creating docker image for build") - - docker_container_name = str(uuid.uuid4()) - ret = run_cmd( - [ - "docker", - "run", - "--detach", - "--name", - docker_container_name, - "--mount", - f"type=bind,src={config.build_root_dir},dst={BUILD_DIR_IN_CONTAINER}", - docker_image_name, - ], - dry_run=config.dry_run, - check=False, - ) - if ret.returncode != 0: - raise BuildError("Error creating docker image for build") - - instance = cls(dry_run=config.dry_run, build_root=config.build_root_dir, container_name=docker_container_name) - return instance - - @classmethod - def from_build_metadata(cls, build_metadata: BuildMetadata) -> Self: - assert build_metadata.driver == "docker" - meta = DockerDriverBuildMetadata.model_validate(build_metadata.driver_metadata) - return cls(dry_run=False, build_root=build_metadata.build_root, container_name=meta.container_name) - - def get_build_metadata(self) -> DriverSpecificBuildMetadata: - meta = DockerDriverBuildMetadata(container_name=self._container_name) - return meta.model_dump() - - def run_command(self, cmd: Sequence[str | Path], cwd: Path | None = None, requires_root: bool = False): - conditional_args: list[str | Path] = [] - - if cwd: - cwd = self._translate_path_in_container(cwd) - conditional_args.extend(["--workdir", cwd]) - - if requires_root: - conditional_args.extend(["--user", "root"]) - - ret = run_cmd(["docker", "exec", *conditional_args, self._container_name, *cmd], dry_run=self._dry_run) - if ret.returncode != 0: - raise BuildError("Error building package") - - def cleanup(self): - run_cmd(["docker", "rm", "-f", self._container_name], dry_run=self._dry_run) - - def drop_into_shell(self): - if not self._dry_run: - run_cmd_in_foreground( - [ - "docker", - "exec", - "--interactive", - "--workdir", - self._translate_path_in_container(self._build_root), - "--tty", - self._container_name, - "/usr/bin/env", - "bash", - ] - ) - - def driver_type(self) -> BuildDriverType: - return "docker" diff --git a/packages/debmagic/src/debmagic/cli/_build_driver/driver_lxd.py b/packages/debmagic/src/debmagic/cli/_build_driver/driver_lxd.py deleted file mode 100644 index 7b22228..0000000 --- a/packages/debmagic/src/debmagic/cli/_build_driver/driver_lxd.py +++ /dev/null @@ -1,29 +0,0 @@ -from pathlib import Path -from typing import Self, Sequence - -from pydantic import BaseModel - -from .common import BuildConfig, BuildDriver - - -class LxdDriverConfig(BaseModel): - pass - - -class BuildDriverLxd(BuildDriver[LxdDriverConfig]): - @classmethod - def create(cls, config: BuildConfig, driver_config: LxdDriverConfig) -> Self: - raise NotImplementedError() - - @classmethod - def from_build_root(cls, build_root: Path) -> Self: - raise NotImplementedError() - - def run_command(self, cmd: Sequence[str | Path], cwd: Path | None = None, requires_root: bool = False): - raise NotImplementedError() - - def cleanup(self): - raise NotImplementedError() - - def drop_into_shell(self): - raise NotImplementedError() diff --git a/packages/debmagic/src/debmagic/cli/_config.py b/packages/debmagic/src/debmagic/cli/_config.py deleted file mode 100644 index d8b6288..0000000 --- a/packages/debmagic/src/debmagic/cli/_config.py +++ /dev/null @@ -1,63 +0,0 @@ -import argparse -from pathlib import Path -from typing import Annotated, ClassVar - -from pydantic import AfterValidator, Field -from pydantic_settings import ( - BaseSettings, - CliApp, - CliSettingsSource, - PydanticBaseSettingsSource, - TomlConfigSettingsSource, -) - -from ._build_driver.config import BuildDriverConfig - - -def resolve_path(p: Path) -> Path: - return p.resolve() - - -class DebmagicConfig(BaseSettings, cli_kebab_case=True, cli_avoid_json=True): - _config_file_paths: ClassVar[list[Path]] = [] - - driver_config: BuildDriverConfig = BuildDriverConfig() - - temp_build_dir: Annotated[ - Path, - Field(description="Temporary directory on the local machine used as root directory for all package builds"), - AfterValidator(resolve_path), - ] = Path("/tmp/debmagic") - - dry_run: Annotated[bool, Field(description="don't actually run anything that changes the system/package state")] = ( - False - ) - - @classmethod - def settings_customise_sources( - cls, - settings_cls: type[BaseSettings], - init_settings: PydanticBaseSettingsSource, - env_settings: PydanticBaseSettingsSource, - dotenv_settings: PydanticBaseSettingsSource, - file_secret_settings: PydanticBaseSettingsSource, - ) -> tuple[PydanticBaseSettingsSource, ...]: - # FIXME: once pydantic properly supports dynamic runtime paths for these kinds of use cases - # use a more sane method of injecting additional paths to load - # see https://github.com/pydantic/pydantic-settings/issues/259 - return tuple(TomlConfigSettingsSource(settings_cls, p) for p in cls._config_file_paths) - - -def get_config_argparser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser(add_help=False) - CliSettingsSource(DebmagicConfig, root_parser=parser) - return parser - - -def load_config(args: argparse.Namespace, config_paths: list[Path]) -> DebmagicConfig: - DebmagicConfig._config_file_paths = config_paths - # parser = argparse.ArgumentParser(add_help=False) - source = CliSettingsSource(DebmagicConfig) - config = CliApp.run(DebmagicConfig, cli_args=args, cli_settings_source=source) - - return config diff --git a/packages/debmagic/src/debmagic/cli/_main.py b/packages/debmagic/src/debmagic/cli/_main.py deleted file mode 100644 index da9d07f..0000000 --- a/packages/debmagic/src/debmagic/cli/_main.py +++ /dev/null @@ -1,95 +0,0 @@ -import argparse -import os -from pathlib import Path -from typing import Sequence - -from ._build_driver.build import build as build_driver_build -from ._build_driver.build import get_shell_in_build -from ._build_driver.common import SUPPORTED_BUILD_DRIVERS, PackageDescription -from ._config import get_config_argparser, load_config -from ._version import VERSION - - -def arg_resolved_path(arg: str) -> Path: - return Path(arg).resolve() - - -def _create_parser() -> argparse.ArgumentParser: - cli = argparse.ArgumentParser(description="Debmagic") - sp = cli.add_subparsers(dest="operation") - - cli.add_argument("--version", action="version", version=f"%(prog)s {VERSION}") - sp.add_parser("help", help="Show this help page and exit") - sp.add_parser("version", help="Print the version information and exit") - - common_cli = get_config_argparser() - common_cli.add_argument("-c", "--config", type=arg_resolved_path, help="Path to a config file") - - build_cli = sp.add_parser( - "build", parents=[common_cli], help="Build a debian package with the selected containerization driver" - ) - build_cli.add_argument("--driver", choices=SUPPORTED_BUILD_DRIVERS, required=True) - build_cli.add_argument("-s", "--source-dir", type=arg_resolved_path, default=Path.cwd()) - build_cli.add_argument("-o", "--output-dir", type=arg_resolved_path, default=Path.cwd()) - - sp.add_parser("check", parents=[common_cli], help="Run linters (e.g. lintian)") - sp.add_parser("test", parents=[common_cli], help="Run package tests (autopkgtest)") - - shell_cli = sp.add_parser("shell", parents=[common_cli], help="Attach a shell to a running debmagic build") - shell_cli.add_argument("-s", "--source-dir", type=arg_resolved_path, default=Path.cwd()) - return cli - - -def main(passed_args: Sequence[str] | None = None): - cli = _create_parser() - args = cli.parse_args(passed_args) - - config_file_paths: list[Path] = [] - if args.config: - config_file_paths.append(args.config) - - if "source_dir" in args: - config_file_paths.append(args.source_dir / ".debmagic.toml") - config_file_paths.append(args.source_dir / "debmagic.toml") - else: - config_file_paths.append(Path.cwd() / ".debmagic.toml") - config_file_paths.append(Path.cwd() / "debmagic.toml") - - xdg_config_home = Path(os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config")) - xdg_config_file = xdg_config_home / "debmagic" / "config.toml" - config_file_paths.append(xdg_config_file) - - config = load_config(args, config_file_paths) - - match args.operation: - case "help": - cli.print_help() - cli.exit(0) - - case "version": - print(f"{cli.prog} {VERSION}") - cli.exit(0) - - case "build": - build_driver_build( - config=config, - package=PackageDescription(name="debmagic", version="0.1.0", source_dir=args.source_dir), - build_driver=args.driver, - output_dir=args.output_dir, - ) - cli.exit(0) - - case "check": - raise NotImplementedError() - - case "test": - raise NotImplementedError() - - case "shell": - get_shell_in_build( - config=config, package=PackageDescription(name="debmagic", version="0.1.0", source_dir=args.source_dir) - ) - cli.exit(0) - - case _: - cli.exit(1, f"unhandled operation {args.operation}") diff --git a/packages/debmagic/src/debmagic/cli/_version.py b/packages/debmagic/src/debmagic/cli/_version.py deleted file mode 100644 index 84ede60..0000000 --- a/packages/debmagic/src/debmagic/cli/_version.py +++ /dev/null @@ -1 +0,0 @@ -VERSION = "0.0.1-alpha1" diff --git a/packages/debmagic/src/debmagic/cli/py.typed b/packages/debmagic/src/debmagic/cli/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/packages/debmagic/src/main.rs b/packages/debmagic/src/main.rs new file mode 100644 index 0000000..5b1419a --- /dev/null +++ b/packages/debmagic/src/main.rs @@ -0,0 +1,65 @@ +use std::{env, fs}; + +use clap::{CommandFactory, Parser}; + +use crate::{ + build::{build_package, common::PackageDescription, get_shell_in_build}, + cli::{Cli, Commands}, + config::Config, +}; + +pub mod build; +pub mod cli; +pub mod config; + +fn cli() -> anyhow::Result<()> { + let cli = Cli::parse(); + let config = Config::new(&cli)?; + + let current_dir = env::current_dir()?; + match &cli.command { + Commands::Build(args) => { + let source_dir = args.source_dir.as_deref().unwrap_or(¤t_dir); + let package = PackageDescription { + name: "debmagic".to_string(), + version: "0.1.0".to_string(), + source_dir: fs::canonicalize(source_dir)?, + }; + let output_dir = args.output_dir.as_deref().unwrap_or(¤t_dir); + build_package( + &config, + &package, + args.driver, + &fs::canonicalize(output_dir)?, + )?; + } + Commands::Shell(args) => { + let source_dir = args.source_dir.as_deref().unwrap_or(¤t_dir); + let package = PackageDescription { + name: "debmagic".to_string(), + version: "0.1.0".to_string(), + source_dir: fs::canonicalize(source_dir)?, + }; + get_shell_in_build(&config, &package)?; + } + Commands::Test {} => { + println!("Test subcommand! - not implemented"); + } + Commands::Check {} => { + println!("Check subcommand! - not implemented"); + } + Commands::Version {} => { + let cmd = Cli::command(); + println!("{}", cmd.render_version()); + } + } + + Ok(()) +} + +fn main() { + let result = cli(); + if let Err(err) = result { + eprintln!("Error: {err}"); + } +} diff --git a/packages/debmagic/tests/test_config.py b/packages/debmagic/tests/test_config.py index 29394ab..6756204 100644 --- a/packages/debmagic/tests/test_config.py +++ b/packages/debmagic/tests/test_config.py @@ -1,13 +1,13 @@ -from pathlib import Path +# from pathlib import Path -from debmagic.cli._config import get_config_argparser, load_config +# from debmagic.cli._config import get_config_argparser, load_config -def test_config_load(): - config_file = Path(__file__).parent / "assets" / "config1.toml" - parser = get_config_argparser() - args = parser.parse_args(["--temp-build-dir", "/tmp/bla"]) - config = load_config(args, [config_file]) +# def test_config_load(): +# config_file = Path(__file__).parent / "assets" / "config1.toml" +# parser = get_config_argparser() +# args = parser.parse_args(["--temp-build-dir", "/tmp/bla"]) +# config = load_config(args, [config_file]) - assert config.dry_run - assert config.temp_build_dir == Path("/tmp/bla") +# assert config.dry_run +# assert config.temp_build_dir == Path("/tmp/bla") diff --git a/pyproject.toml b/pyproject.toml index 27c0bfd..6dd9dd9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,7 @@ [dependency-groups] dev = [ { include-group = "docs" }, + "maturin", "pytest>=9.0.1", "pytest-cov>=7.0.0", "ruff", @@ -21,10 +22,14 @@ package = false [tool.uv.sources] debmagic-common = { workspace = true } debmagic-pkg = { workspace = true } -debmagic = { workspace = true } [tool.uv.workspace] -members = ["packages/*"] +members = [ + "packages/debmagic-common", + "packages/debmagic-pkg", + # for now we don't want to always add the rust cli to the python workspace to avoid increasing uv sync times in CI + # "packages/debmagic", +] [tool.ruff] line-length = 120 diff --git a/uv.lock b/uv.lock index c6ef444..1d719c6 100644 --- a/uv.lock +++ b/uv.lock @@ -4,13 +4,13 @@ requires-python = ">=3.12" [manifest] members = [ - "debmagic", "debmagic-common", "debmagic-pkg", ] [manifest.dependency-groups] dev = [ + { name = "maturin" }, { name = "myst-parser", specifier = ">=4.0.1" }, { name = "pre-commit" }, { name = "pytest", specifier = ">=9.0.1" }, @@ -39,15 +39,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" }, ] -[[package]] -name = "annotated-types" -version = "0.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, -] - [[package]] name = "anyio" version = "4.12.0" @@ -240,23 +231,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8d/4c/1968f32fb9a2604645827e11ff84a31e59d532e01995f904723b4f5328b3/coverage-7.13.0-py3-none-any.whl", hash = "sha256:850d2998f380b1e266459ca5b47bc9e7daf9af1d070f66317972f382d46f1904", size = 210068, upload-time = "2025-12-08T13:14:36.236Z" }, ] -[[package]] -name = "debmagic" -version = "0.0.1a1" -source = { editable = "packages/debmagic" } -dependencies = [ - { name = "debmagic-common" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, -] - -[package.metadata] -requires-dist = [ - { name = "debmagic-common", editable = "packages/debmagic-common" }, - { name = "pydantic", specifier = ">=2,<3" }, - { name = "pydantic-settings", specifier = ">=2.12.0" }, -] - [[package]] name = "debmagic-common" version = "0.0.1a1" @@ -438,6 +412,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] +[[package]] +name = "maturin" +version = "1.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/44/c593afce7d418ae6016b955c978055232359ad28c707a9ac6643fc60512d/maturin-1.10.2.tar.gz", hash = "sha256:259292563da89850bf8f7d37aa4ddba22905214c1e180b1c8f55505dfd8c0e81", size = 217835, upload-time = "2025-11-19T11:53:17.348Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/74/7f7e93019bb71aa072a7cdf951cbe4c9a8d5870dd86c66ec67002153487f/maturin-1.10.2-py3-none-linux_armv6l.whl", hash = "sha256:11c73815f21a755d2129c410e6cb19dbfacbc0155bfc46c706b69930c2eb794b", size = 8763201, upload-time = "2025-11-19T11:52:42.98Z" }, + { url = "https://files.pythonhosted.org/packages/4a/85/1d1b64dbb6518ee633bfde8787e251ae59428818fea7a6bdacb8008a09bd/maturin-1.10.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7fbd997c5347649ee7987bd05a92bd5b8b07efa4ac3f8bcbf6196e07eb573d89", size = 17072583, upload-time = "2025-11-19T11:52:45.636Z" }, + { url = "https://files.pythonhosted.org/packages/7c/45/2418f0d6e1cbdf890205d1dc73ebea6778bb9ce80f92e866576c701ded72/maturin-1.10.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3ce9b2ad4fb9c341f450a6d32dc3edb409a2d582a81bc46ba55f6e3b6196b22", size = 8827021, upload-time = "2025-11-19T11:52:48.143Z" }, + { url = "https://files.pythonhosted.org/packages/7f/83/14c96ddc93b38745d8c3b85126f7d78a94f809a49dc9644bb22b0dc7b78c/maturin-1.10.2-py3-none-manylinux_2_12_i686.manylinux2010_i686.musllinux_1_1_i686.whl", hash = "sha256:f0d1b7b5f73c8d30a7e71cd2a2189a7f0126a3a3cd8b3d6843e7e1d4db50f759", size = 8751780, upload-time = "2025-11-19T11:52:51.613Z" }, + { url = "https://files.pythonhosted.org/packages/46/8d/753148c0d0472acd31a297f6d11c3263cd2668d38278ed29d523625f7290/maturin-1.10.2-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:efcd496a3202ffe0d0489df1f83d08b91399782fb2dd545d5a1e7bf6fd81af39", size = 9241884, upload-time = "2025-11-19T11:52:53.946Z" }, + { url = "https://files.pythonhosted.org/packages/b9/f9/f5ca9fe8cad70cac6f3b6008598cc708f8a74dd619baced99784a6253f23/maturin-1.10.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:a41ec70d99e27c05377be90f8e3c3def2a7bae4d0d9d5ea874aaf2d1da625d5c", size = 8671736, upload-time = "2025-11-19T11:52:57.133Z" }, + { url = "https://files.pythonhosted.org/packages/0a/76/f59cbcfcabef0259c3971f8b5754c85276a272028d8363386b03ec4e9947/maturin-1.10.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:07a82864352feeaf2167247c8206937ef6c6ae9533025d416b7004ade0ea601d", size = 8633475, upload-time = "2025-11-19T11:53:00.389Z" }, + { url = "https://files.pythonhosted.org/packages/53/40/96cd959ad1dda6c12301860a74afece200a3209d84b393beedd5d7d915c0/maturin-1.10.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:04df81ee295dcda37828bd025a4ac688ea856e3946e4cb300a8f44a448de0069", size = 11177118, upload-time = "2025-11-19T11:53:03.014Z" }, + { url = "https://files.pythonhosted.org/packages/e5/b6/144f180f36314be183f5237011528f0e39fe5fd2e74e65c3b44a5795971e/maturin-1.10.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96e1d391e4c1fa87edf2a37e4d53d5f2e5f39dd880b9d8306ac9f8eb212d23f8", size = 9320218, upload-time = "2025-11-19T11:53:05.39Z" }, + { url = "https://files.pythonhosted.org/packages/eb/2d/2c483c1b3118e2e10fd8219d5291843f5f7c12284113251bf506144a3ac1/maturin-1.10.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a217aa7c42aa332fb8e8377eb07314e1f02cf0fe036f614aca4575121952addd", size = 8985266, upload-time = "2025-11-19T11:53:07.618Z" }, + { url = "https://files.pythonhosted.org/packages/1d/98/1d0222521e112cd058b56e8d96c72cf9615f799e3b557adb4b16004f42aa/maturin-1.10.2-py3-none-win32.whl", hash = "sha256:da031771d9fb6ddb1d373638ec2556feee29e4507365cd5749a2d354bcadd818", size = 7667897, upload-time = "2025-11-19T11:53:10.14Z" }, + { url = "https://files.pythonhosted.org/packages/a0/ec/c6c973b1def0d04533620b439d5d7aebb257657ba66710885394514c8045/maturin-1.10.2-py3-none-win_amd64.whl", hash = "sha256:da777766fd584440dc9fecd30059a94f85e4983f58b09e438ae38ee4b494024c", size = 8908416, upload-time = "2025-11-19T11:53:12.862Z" }, + { url = "https://files.pythonhosted.org/packages/1b/01/7da60c9f7d5dc92dfa5e8888239fd0fb2613ee19e44e6db5c2ed5595fab3/maturin-1.10.2-py3-none-win_arm64.whl", hash = "sha256:a4c29a770ea2c76082e0afc6d4efd8ee94405588bfae00d10828f72e206c739b", size = 7506680, upload-time = "2025-11-19T11:53:15.403Z" }, +] + [[package]] name = "mdit-py-plugins" version = "0.5.0" @@ -528,106 +523,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5d/c4/b2d28e9d2edf4f1713eb3c29307f1a63f3d67cf09bdda29715a36a68921a/pre_commit-4.5.0-py2.py3-none-any.whl", hash = "sha256:25e2ce09595174d9c97860a95609f9f852c0614ba602de3561e267547f2335e1", size = 226429, upload-time = "2025-11-22T21:02:40.836Z" }, ] -[[package]] -name = "pydantic" -version = "2.12.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "annotated-types" }, - { name = "pydantic-core" }, - { name = "typing-extensions" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, -] - -[[package]] -name = "pydantic-core" -version = "2.41.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, - { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, - { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, - { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, - { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, - { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, - { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, - { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, - { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, - { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, - { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, - { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, - { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, - { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, - { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, - { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, - { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, - { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, - { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, - { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, - { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, - { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, - { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, - { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, - { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, - { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, - { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, - { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, - { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, - { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, - { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, - { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, - { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, - { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, - { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, - { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, - { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, - { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, - { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, - { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, -] - -[[package]] -name = "pydantic-settings" -version = "2.12.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "typing-inspection" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/43/4b/ac7e0aae12027748076d72a8764ff1c9d82ca75a7a52622e67ed3f765c54/pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0", size = 194184, upload-time = "2025-11-10T14:25:47.013Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" }, -] - [[package]] name = "pygments" version = "2.19.2" @@ -679,15 +574,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ba/15/e8096189b18dda72e4923622abc10b021ecff723b397e22eff29fb86637b/python_debian-1.0.1-py3-none-any.whl", hash = "sha256:8f137c230c1d9279c2ac892b35915068b2aca090c9fd3da5671ff87af32af12c", size = 137453, upload-time = "2025-03-11T12:27:25.014Z" }, ] -[[package]] -name = "python-dotenv" -version = "1.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, -] - [[package]] name = "pyyaml" version = "6.0.3" @@ -977,18 +863,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, ] -[[package]] -name = "typing-inspection" -version = "0.4.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, -] - [[package]] name = "urllib3" version = "2.6.2" From 34d4df3a756d412961bcd84da86722107ba2e5af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Loipf=C3=BChrer?= Date: Tue, 23 Dec 2025 15:55:13 +0100 Subject: [PATCH 2/8] refactor(cli): align rust requirements with those in debian trixie --- debian/control | 24 +++++++++++++++++------- debian/rules | 1 + packages/debmagic/Cargo.toml | 18 +++++++++--------- packages/debmagic/src/main.rs | 21 ++++++++------------- 4 files changed, 35 insertions(+), 29 deletions(-) diff --git a/debian/control b/debian/control index 1011db3..51f121d 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,17 @@ Build-Depends: pybuild-plugin-pyproject, python3-setuptools, python3-debian, - python3-pydantic + cargo, + python3-maturin, + librust-clap-dev (>=4.5.23), + librust-anyhow-dev (>=1.0.95), + librust-config-dev (>=0.15.9), + librust-dirs-dev (>=5.0.0), + librust-glob-dev (>=0.3.2), + librust-libc-dev (>=0.2.169), + librust-serde-dev (>=1.0.217), + librust-serde-json-dev (>=1.0.139), + librust-uuid-dev (>=1.10.0) Rules-Requires-Root: no X-Style: black Standards-Version: 4.7.2 @@ -42,13 +52,13 @@ Description: Debian build instructions written in Python. Explicit is better than implicit. Package: debmagic -Architecture: all +Architecture: any Depends: - python3-debmagic-common, - python3-pydantic, - python3-pydantic-settings, ${misc:Depends}, - ${python3:Depends} -Multi-Arch: foreign + ${python3:Depends}, + ${shlibs:Depends}, Description: Debian package building made easy. Holistic cli for the whole debian package building workflow. +Built-Using: ${cargo:Built-Using} +XB-X-Cargo-Built-Using: ${cargo:X-Cargo-Built-Using} +Static-Built-Using: ${cargo:Static-Built-Using} diff --git a/debian/rules b/debian/rules index 8e09069..dade10d 100755 --- a/debian/rules +++ b/debian/rules @@ -32,6 +32,7 @@ def dh_auto(build: Build, stage: str, use_destdir: bool = False): @dhp.override def dh_auto_configure(build: Build): + build.cmd("cargo prepare-debian debian/cargo_registry --link-from-system", cwd="packages/debmagic") dh_auto(build, "dh_auto_configure") diff --git a/packages/debmagic/Cargo.toml b/packages/debmagic/Cargo.toml index 3afd01d..3b61a31 100644 --- a/packages/debmagic/Cargo.toml +++ b/packages/debmagic/Cargo.toml @@ -7,13 +7,13 @@ homepage = "https://github.com/SFTtech/debmagic" repository = "https://github.com/SFTtech/debmagic.git" [dependencies] -clap = { version = "4.5.38", features = ["derive"] } -anyhow = { version = "1.0.100" } -config = { version = "0.15.9", features = ["toml"] } -dirs = "5.0.0" +clap = { version = ">=4.5.23", features = ["derive"] } +anyhow = { version = ">=1.0.95" } +config = { version = ">=0.15.9", features = ["toml"] } +dirs = ">=5.0.0" # thiserror = "2.0.17" # for future uses when we need clearer error handling -glob = "0.3.3" -libc = "0.2.178" -serde = { version = "1.0.171", features = ["derive"] } -serde_json = "1.0.143" -uuid = { version = "1.18.1", features = ["v4"] } +glob = ">=0.3.2" +libc = ">=0.2.169" +serde = { version = ">=1.0.217", features = ["derive"] } +serde_json = ">=1.0.139" +uuid = { version = ">=1.10.0", features = ["v4"] } diff --git a/packages/debmagic/src/main.rs b/packages/debmagic/src/main.rs index 5b1419a..4927975 100644 --- a/packages/debmagic/src/main.rs +++ b/packages/debmagic/src/main.rs @@ -1,5 +1,6 @@ -use std::{env, fs}; +use std::{env, path}; +use anyhow::Context; use clap::{CommandFactory, Parser}; use crate::{ @@ -12,7 +13,7 @@ pub mod build; pub mod cli; pub mod config; -fn cli() -> anyhow::Result<()> { +fn main() -> anyhow::Result<()> { let cli = Cli::parse(); let config = Config::new(&cli)?; @@ -23,22 +24,23 @@ fn cli() -> anyhow::Result<()> { let package = PackageDescription { name: "debmagic".to_string(), version: "0.1.0".to_string(), - source_dir: fs::canonicalize(source_dir)?, + source_dir: path::absolute(source_dir).context("resolving source dir failed")?, }; let output_dir = args.output_dir.as_deref().unwrap_or(¤t_dir); build_package( &config, &package, args.driver, - &fs::canonicalize(output_dir)?, - )?; + &path::absolute(output_dir).context("resolving output dir failed")?, + ) + .context("Building the package failed")?; } Commands::Shell(args) => { let source_dir = args.source_dir.as_deref().unwrap_or(¤t_dir); let package = PackageDescription { name: "debmagic".to_string(), version: "0.1.0".to_string(), - source_dir: fs::canonicalize(source_dir)?, + source_dir: path::absolute(source_dir).context("resolving source dir failed")?, }; get_shell_in_build(&config, &package)?; } @@ -56,10 +58,3 @@ fn cli() -> anyhow::Result<()> { Ok(()) } - -fn main() { - let result = cli(); - if let Err(err) = result { - eprintln!("Error: {err}"); - } -} From 8e18b43cdbb07ae0f709d9a628d53b29d971e8ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Loipf=C3=BChrer?= Date: Tue, 23 Dec 2025 16:25:45 +0100 Subject: [PATCH 3/8] feat: add rustc buildenv for rust self-packaging --- Cargo.lock | 92 +++---------------- debian/changelog | 2 +- debian/rules | 17 ++++ .../src/debmagic/v0/_dpkg/_rustc_build_env.py | 45 +++++++++ .../src/debmagic/v0/_dpkg/build_env.py | 5 + 5 files changed, 81 insertions(+), 80 deletions(-) create mode 100644 packages/debmagic-pkg/src/debmagic/v0/_dpkg/_rustc_build_env.py diff --git a/Cargo.lock b/Cargo.lock index 166a8e4..9c9fb6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,7 +38,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -49,7 +49,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -256,23 +256,23 @@ dependencies = [ [[package]] name = "dirs" -version = "5.0.1" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -537,9 +537,9 @@ checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "redox_users" -version = "0.4.6" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", @@ -676,18 +676,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.69" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.69" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -853,15 +853,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -871,63 +862,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "winnow" version = "0.7.14" diff --git a/debian/changelog b/debian/changelog index 5d2dfc9..a6a9ba8 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -debmagic (0.1.0) UNRELEASED; urgency=medium +debmagic (0.0.1-alpha1) UNRELEASED; urgency=medium * WIP: Initial release. diff --git a/debian/rules b/debian/rules index dade10d..9378157 100755 --- a/debian/rules +++ b/debian/rules @@ -1,4 +1,6 @@ #!/usr/bin/env python3 +import shutil +import os import sys from pathlib import Path @@ -19,12 +21,23 @@ pkg = package( preset=[dhp], ) +os.environ.update({ + "PATH": f"/usr/share/cargo/bin:{os.environ['PATH']}", + "CARGO": "/usr/share/cargo/bin/cargo", + "CARGO_HOME": f"{Path.cwd()}/debian/cargo_home", + "CARGO_REGISTRY": f"{Path.cwd()}/debian/cargo_registry", + "DEB_CARGO_CRATE": f"{pkg.build_env.DEB_SOURCE}_{pkg.build_env.DEB_VERSION_UPSTREAM}" +}) + packages = { "python3-debmagic-common": ("packages/debmagic-common", "debmagic-common"), "debmagic": ("packages/debmagic", "debmagic"), "debmagic-pkg": ("packages/debmagic-pkg", "debmagic-pkg"), } +debmagic_cargo_lock = Path("packages/debmagic/Cargo.lock") +debmagic_cargo_lock_saved = Path("packages/debmagic/Cargo.lock.saved") + def dh_auto(build: Build, stage: str, use_destdir: bool = False): for pkg_name, (path, python_pkg_name) in packages.items(): destdir = f" --destdir debian/{pkg_name} " if use_destdir else "" @@ -38,6 +51,8 @@ def dh_auto_configure(build: Build): @dhp.override def dh_auto_build(build: Build): + if debmagic_cargo_lock.is_file(): + shutil.move(debmagic_cargo_lock, debmagic_cargo_lock_saved) dh_auto(build, "dh_auto_build") @@ -53,6 +68,8 @@ def dh_auto_test(build: Build): @dhp.override def dh_auto_clean(build: Build): + if debmagic_cargo_lock_saved.is_file(): + shutil.move(debmagic_cargo_lock_saved, debmagic_cargo_lock) dh_auto(build, "dh_auto_clean") diff --git a/packages/debmagic-pkg/src/debmagic/v0/_dpkg/_rustc_build_env.py b/packages/debmagic-pkg/src/debmagic/v0/_dpkg/_rustc_build_env.py new file mode 100644 index 0000000..9956263 --- /dev/null +++ b/packages/debmagic-pkg/src/debmagic/v0/_dpkg/_rustc_build_env.py @@ -0,0 +1,45 @@ +""" +Implement the build environment logic provided by /usr/share/rustc/architecture.mk +""" + + +def _rust_cpu(cpu: str, arch: str) -> str: + # $(subst i586,i686,...) + cpu = cpu.replace("i586", "i686") + + if "-riscv64-" in f"-{arch}-": + return cpu.replace("riscv64", "riscv64gc") + elif "-armhf-" in f"-{arch}-": + return cpu.replace("arm", "armv7") + elif "-armel-" in f"-{arch}-": + return cpu.replace("arm", "armv5te") + + return cpu + + +def _rust_os(system: str, arch_os: str) -> str: + if "-hurd-" in f"-{arch_os}-": + return system.replace("gnu", "hurd-gnu") + return system + + +def _get_rust_type(prefix: str, env_vars: dict[str, str]) -> str: + cpu = env_vars.get(f"{prefix}_GNU_CPU", "") + arch = env_vars.get(f"{prefix}_ARCH", "") + system = env_vars.get(f"{prefix}_GNU_SYSTEM", "") + arch_os = env_vars.get(f"{prefix}_ARCH_OS", "") + + r_cpu = _rust_cpu(cpu, arch) + r_os = _rust_os(system, arch_os) + + return f"{r_cpu}-unknown-{r_os}" + + +def build_rustc_build_env(env: dict[str, str]): + for machine in ["BUILD", "HOST", "TARGET"]: + var_name = f"DEB_{machine}_RUST_TYPE" + env[var_name] = _get_rust_type(f"DEB_{machine}", env) + + # Fallback for older dpkg versions (ifeq check) + if env["DEB_TARGET_RUST_TYPE"] == "-unknown-": + env["DEB_TARGET_RUST_TYPE"] = env["DEB_HOST_RUST_TYPE"] diff --git a/packages/debmagic-pkg/src/debmagic/v0/_dpkg/build_env.py b/packages/debmagic-pkg/src/debmagic/v0/_dpkg/build_env.py index 1b1433a..ecd74e8 100644 --- a/packages/debmagic-pkg/src/debmagic/v0/_dpkg/build_env.py +++ b/packages/debmagic-pkg/src/debmagic/v0/_dpkg/build_env.py @@ -6,6 +6,8 @@ from debmagic.common.models.package_version import PackageVersion from debmagic.common.utils import run_cmd +from ._rustc_build_env import build_rustc_build_env + def _cmd(cmd: str, input_data: str | None = None, env: dict[str, str] | None = None, cwd: Path | None = None) -> str: return run_cmd(cmd, check=True, env=env, input=input_data, text=True, capture_output=True).stdout.strip() @@ -75,4 +77,7 @@ def get_pkg_env(package_dir: Path, maint_options: str | None = None) -> tuple[di result["ELF_PACKAGE_METADATA"] = json.dumps(elf_meta, separators=(",", ":")) + # rustc/architecture.mk + build_rustc_build_env(result) + return result, version From 6179e6c672280cc1c7234fe4d7a2f8b691977958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Loipf=C3=BChrer?= Date: Tue, 23 Dec 2025 16:34:12 +0100 Subject: [PATCH 4/8] feat(cli): cache build-deps in docker image --- packages/debmagic/src/build/driver_docker.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/debmagic/src/build/driver_docker.rs b/packages/debmagic/src/build/driver_docker.rs index 4598191..3707ec6 100644 --- a/packages/debmagic/src/build/driver_docker.rs +++ b/packages/debmagic/src/build/driver_docker.rs @@ -30,6 +30,8 @@ RUN groupadd --gid $USER_GID $USERNAME \ && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \ && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ && chmod 0440 /etc/sudoers.d/$USERNAME +RUN mkdir -p /build/package/debian +RUN --mount=type=bind,source=debian/control,target=/build/package/debian/control apt-get -y build-dep /build/package RUN mkdir -p {build_dir} RUN chown $USERNAME:$USERNAME {build_dir} USER $USERNAME @@ -60,11 +62,25 @@ impl DriverDocker { let formatted_dockerfile = DOCKERFILE_TEMPLATE .replace("{base_image}", &base_image) .replace("{docker_user}", DOCKER_USER) - .replace("{build_dir}", BUILD_DIR_IN_CONTAINER); + .replace("{build_dir}", BUILD_DIR_IN_CONTAINER) + .replace( + "{debian_control_file}", + &config + .build_source_dir() + .join("debian") + .join("control") + .to_string_lossy(), + ); let dockerfile_path = config.build_temp_dir().join("Dockerfile"); fs::write(&dockerfile_path, formatted_dockerfile).expect("Failed to write Dockerfile"); + fs::create_dir_all(config.build_temp_dir().join("debian"))?; + fs::copy( + config.build_source_dir().join("debian").join("control"), + config.build_temp_dir().join("debian").join("control"), + )?; + let docker_image_name = format!("debmagic-{}", config.build_identifier()); let mut build_args = Vec::new(); From 75ab1bb3c2597e145d50563c6e36d3760a26c376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Loipf=C3=BChrer?= Date: Tue, 23 Dec 2025 16:50:36 +0100 Subject: [PATCH 5/8] fix: get rust-based self packaging to fully work --- debian/rules | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/debian/rules b/debian/rules index 9378157..1a378a9 100755 --- a/debian/rules +++ b/debian/rules @@ -35,8 +35,8 @@ packages = { "debmagic-pkg": ("packages/debmagic-pkg", "debmagic-pkg"), } -debmagic_cargo_lock = Path("packages/debmagic/Cargo.lock") -debmagic_cargo_lock_saved = Path("packages/debmagic/Cargo.lock.saved") +cargo_lock = Path("Cargo.lock") +cargo_lock_saved = Path("Cargo.lock.saved") def dh_auto(build: Build, stage: str, use_destdir: bool = False): for pkg_name, (path, python_pkg_name) in packages.items(): @@ -51,8 +51,8 @@ def dh_auto_configure(build: Build): @dhp.override def dh_auto_build(build: Build): - if debmagic_cargo_lock.is_file(): - shutil.move(debmagic_cargo_lock, debmagic_cargo_lock_saved) + if cargo_lock.is_file(): + shutil.move(cargo_lock, cargo_lock_saved) dh_auto(build, "dh_auto_build") @@ -68,8 +68,8 @@ def dh_auto_test(build: Build): @dhp.override def dh_auto_clean(build: Build): - if debmagic_cargo_lock_saved.is_file(): - shutil.move(debmagic_cargo_lock_saved, debmagic_cargo_lock) + if cargo_lock_saved.is_file(): + shutil.move(cargo_lock_saved, cargo_lock) dh_auto(build, "dh_auto_clean") From a731c1611d4ef7d47bf4e11736f4ba215b77c5cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Loipf=C3=BChrer?= Date: Tue, 23 Dec 2025 16:53:03 +0100 Subject: [PATCH 6/8] refactor: remove common-v2 rust pkg until we need it --- Cargo.lock | 4 ---- Cargo.toml | 2 +- packages/debmagic-common-v2/Cargo.toml | 6 ------ packages/debmagic-common-v2/src/lib.rs | 14 -------------- 4 files changed, 1 insertion(+), 25 deletions(-) delete mode 100644 packages/debmagic-common-v2/Cargo.toml delete mode 100644 packages/debmagic-common-v2/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 9c9fb6f..91a0dd0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -240,10 +240,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "debmagic-common-v2" -version = "0.1.0" - [[package]] name = "digest" version = "0.10.7" diff --git a/Cargo.toml b/Cargo.toml index 13e17df..60e9d71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "3" -members = ["packages/debmagic", "packages/debmagic-common-v2"] +members = ["packages/debmagic"] diff --git a/packages/debmagic-common-v2/Cargo.toml b/packages/debmagic-common-v2/Cargo.toml deleted file mode 100644 index 7813eed..0000000 --- a/packages/debmagic-common-v2/Cargo.toml +++ /dev/null @@ -1,6 +0,0 @@ -[package] -name = "debmagic-common-v2" -version = "0.1.0" -edition = "2024" - -[dependencies] diff --git a/packages/debmagic-common-v2/src/lib.rs b/packages/debmagic-common-v2/src/lib.rs deleted file mode 100644 index b93cf3f..0000000 --- a/packages/debmagic-common-v2/src/lib.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); - } -} From 9dabf66bc429fe59bfa7cb94a9f96d9165ded49d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Loipf=C3=BChrer?= Date: Tue, 23 Dec 2025 17:16:36 +0100 Subject: [PATCH 7/8] test(cli): add basic config parse test --- packages/debmagic/src/config.rs | 39 ++++++++++++++++++-------- packages/debmagic/src/main.rs | 24 ++++++++++++++-- packages/debmagic/tests/test_config.py | 13 --------- 3 files changed, 49 insertions(+), 27 deletions(-) delete mode 100644 packages/debmagic/tests/test_config.py diff --git a/packages/debmagic/src/config.rs b/packages/debmagic/src/config.rs index c129add..a2f09e6 100644 --- a/packages/debmagic/src/config.rs +++ b/packages/debmagic/src/config.rs @@ -24,22 +24,13 @@ impl Default for Config { } impl Config { - pub fn new(cli_args: &Cli) -> anyhow::Result { + pub fn new(config_files: &Vec, _cli_args: &Cli) -> anyhow::Result { let mut builder = ConfigBuilder::builder(); - let xdg_config_file = dirs::config_dir() - .map(|p| p.join("debmagic").join("config.toml")) - .ok_or(anyhow!("Could not determine user config directory"))?; - if xdg_config_file.is_file() { - let xdg_config_file = xdg_config_file.to_str(); - if let Some(xdg_config_file) = xdg_config_file { - builder = builder.add_source(File::with_name(xdg_config_file)); - } + for file in config_files { + builder = builder.add_source(File::with_name(&file.to_string_lossy())); } - if let Some(config_override) = cli_args.config.as_ref().and_then(|f| f.to_str()) { - builder = builder.add_source(File::with_name(config_override)); - } // TODO: reimplement cli arg overwrites let build = builder .build() @@ -50,3 +41,27 @@ impl Config { config } } + +#[cfg(test)] +mod tests { + use crate::cli::Commands; + + use super::*; + + #[test] + fn it_loads_a_simple_config() -> Result<(), anyhow::Error> { + let test_asset_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("assets"); + let cfg = Config::new( + &vec![test_asset_dir.join("config1.toml")], + &Cli { + config: None, + command: Commands::Version {}, + }, + )?; + assert!(cfg.dry_run); + + Ok(()) + } +} diff --git a/packages/debmagic/src/main.rs b/packages/debmagic/src/main.rs index 4927975..bc921ff 100644 --- a/packages/debmagic/src/main.rs +++ b/packages/debmagic/src/main.rs @@ -1,4 +1,7 @@ -use std::{env, path}; +use std::{ + env, + path::{self, PathBuf}, +}; use anyhow::Context; use clap::{CommandFactory, Parser}; @@ -13,9 +16,26 @@ pub mod build; pub mod cli; pub mod config; +fn get_config_file_paths() -> Vec { + let mut config_file_paths = vec![]; + let xdg_config_file = dirs::config_dir().map(|p| p.join("debmagic").join("config.toml")); + if let Some(xdg_config_file) = xdg_config_file { + if xdg_config_file.is_file() { + config_file_paths.push(xdg_config_file); + } + } + + config_file_paths +} + fn main() -> anyhow::Result<()> { let cli = Cli::parse(); - let config = Config::new(&cli)?; + + let mut config_file_paths = get_config_file_paths(); + if let Some(config_override) = &cli.config { + config_file_paths.push(config_override.clone()); + } + let config = Config::new(&config_file_paths, &cli)?; let current_dir = env::current_dir()?; match &cli.command { diff --git a/packages/debmagic/tests/test_config.py b/packages/debmagic/tests/test_config.py deleted file mode 100644 index 6756204..0000000 --- a/packages/debmagic/tests/test_config.py +++ /dev/null @@ -1,13 +0,0 @@ -# from pathlib import Path - -# from debmagic.cli._config import get_config_argparser, load_config - - -# def test_config_load(): -# config_file = Path(__file__).parent / "assets" / "config1.toml" -# parser = get_config_argparser() -# args = parser.parse_args(["--temp-build-dir", "/tmp/bla"]) -# config = load_config(args, [config_file]) - -# assert config.dry_run -# assert config.temp_build_dir == Path("/tmp/bla") From 99dd681bfa1abf7b4bb1e87a5355fb1d12fad8b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Loipf=C3=BChrer?= Date: Thu, 25 Dec 2025 11:19:03 +0100 Subject: [PATCH 8/8] feat(cli): persistent shell attachments, lots of cleanups --- .gitignore | 9 +- Cargo.lock | 18 +- Cargo.toml | 4 + debian/changelog | 2 +- debian/rules | 5 +- packages/debmagic/Cargo.toml | 3 +- packages/debmagic/src/build.rs | 297 +++++++++++++++---- packages/debmagic/src/build/common.rs | 9 +- packages/debmagic/src/build/config.rs | 2 +- packages/debmagic/src/build/driver_bare.rs | 12 +- packages/debmagic/src/build/driver_docker.rs | 6 +- packages/debmagic/src/config.rs | 4 +- packages/debmagic/src/main.rs | 30 +- 13 files changed, 290 insertions(+), 111 deletions(-) diff --git a/.gitignore b/.gitignore index 9c2fc2e..be7ea40 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,8 @@ venv* MANIFEST dist/ src/*/*.lock +# Rust +/target # testing /*.conf @@ -51,10 +53,3 @@ Thumbs.db # local todo.md todo.org - - - - -# Added by cargo - -/target diff --git a/Cargo.lock b/Cargo.lock index 91a0dd0..15b59b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -572,12 +572,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "ryu" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" - [[package]] name = "serde" version = "1.0.228" @@ -622,15 +616,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.146" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "217ca874ae0207aac254aa02c957ded05585a90892cc8d87f9e5fa49669dadd8" +checksum = "6af14725505314343e673e9ecb7cd7e8a36aa9791eb936235a3567cc31447ae4" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -883,3 +877,9 @@ dependencies = [ "encoding_rs", "hashlink", ] + +[[package]] +name = "zmij" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dccf46b25b205e4bebe1d5258a991df1cc17801017a845cb5b3fe0269781aa" diff --git a/Cargo.toml b/Cargo.toml index 60e9d71..1bdec8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,7 @@ [workspace] resolver = "3" members = ["packages/debmagic"] + +[workspace.package] +edition = "2024" +rust-version = "1.89" diff --git a/debian/changelog b/debian/changelog index a6a9ba8..3cb6bc9 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -debmagic (0.0.1-alpha1) UNRELEASED; urgency=medium +debmagic (0.0.1-alpha1) forky; urgency=medium * WIP: Initial release. diff --git a/debian/rules b/debian/rules index 1a378a9..1876b00 100755 --- a/debian/rules +++ b/debian/rules @@ -21,11 +21,12 @@ pkg = package( preset=[dhp], ) +# TODO: add this section to our rust debmagic module to make setting this up less annoying os.environ.update({ "PATH": f"/usr/share/cargo/bin:{os.environ['PATH']}", "CARGO": "/usr/share/cargo/bin/cargo", - "CARGO_HOME": f"{Path.cwd()}/debian/cargo_home", - "CARGO_REGISTRY": f"{Path.cwd()}/debian/cargo_registry", + "CARGO_HOME": f"{pkg.base_dir}/debian/cargo_home", + "CARGO_REGISTRY": f"{pkg.base_dir}/debian/cargo_registry", "DEB_CARGO_CRATE": f"{pkg.build_env.DEB_SOURCE}_{pkg.build_env.DEB_VERSION_UPSTREAM}" }) diff --git a/packages/debmagic/Cargo.toml b/packages/debmagic/Cargo.toml index 3b61a31..316cea7 100644 --- a/packages/debmagic/Cargo.toml +++ b/packages/debmagic/Cargo.toml @@ -1,10 +1,11 @@ [package] name = "debmagic" version = "0.0.1-alpha1" -edition = "2024" documentation = "https://debmagic.readthedocs.org" homepage = "https://github.com/SFTtech/debmagic" repository = "https://github.com/SFTtech/debmagic.git" +rust-version.workspace = true +edition.workspace = true [dependencies] clap = { version = ">=4.5.23", features = ["derive"] } diff --git a/packages/debmagic/src/build.rs b/packages/debmagic/src/build.rs index 9149002..a407b4b 100644 --- a/packages/debmagic/src/build.rs +++ b/packages/debmagic/src/build.rs @@ -1,6 +1,10 @@ +use core::time; use std::{ + cmp::{self}, fs, + io::{self, BufReader, BufWriter, IsTerminal, Seek, stdout}, path::{Path, PathBuf}, + thread, }; use crate::{ @@ -20,64 +24,212 @@ pub mod config; pub mod driver_bare; pub mod driver_docker; -fn create_driver( - driver_type: &BuildDriverType, - build_config: &BuildConfig, - config: &DriverConfig, +struct Build { + config: BuildConfig, + pub driver: Box, +} + +fn get_build_driver( + config: &BuildConfig, + driver_config: &DriverConfig, ) -> anyhow::Result> { - match driver_type { + match config.driver { BuildDriverType::Docker => Ok(Box::new(DriverDocker::create( - build_config, - &config.docker, + config, + &driver_config.docker, )?)), - BuildDriverType::Bare => Ok(Box::new(DriverBare::create(build_config, &config.bare))), + BuildDriverType::Bare => Ok(Box::new(DriverBare::create(config, &driver_config.bare))), // BuildDriverType::Lxd => ... } } -fn create_driver_from_build_root( +fn create_driver_from_metadata( config: &DriverConfig, - build_root: &Path, + metadata: &BuildMetadata, ) -> anyhow::Result> { - let build_metadata_path = build_root.join("build.json"); - if !build_metadata_path.is_file() { - return Err(anyhow!("No build.json found")); - } - - let content = fs::read_to_string(&build_metadata_path)?; - let metadata: BuildMetadata = serde_json::from_str(&content).with_context(|| { - format!( - "Failed to read build metadata from {} - invalid json", - build_metadata_path.display() - ) - })?; - - match metadata.driver { + let driver: anyhow::Result> = match &metadata.config.driver { BuildDriverType::Docker => Ok(Box::new(DriverDocker::from_build_metadata( &metadata.config, &config.docker, - &metadata, + metadata, ))), BuildDriverType::Bare => Ok(Box::new(DriverBare::from_build_metadata( &metadata.config, &config.bare, - &metadata, + metadata, ))), // BuildDriverType::Lxd => ... - } + }; + driver } -fn write_build_metadata(config: &BuildConfig, driver: &dyn BuildDriver) -> anyhow::Result<()> { - let metadata = BuildMetadata { - driver: driver.driver_type(), - config: config.clone(), - driver_metadata: driver.get_build_metadata(), - }; - let path = config.build_root_dir.join("build.json"); - let json = - serde_json::to_string_pretty(&metadata).context("Failed serialize build metadata")?; - fs::write(path, json)?; - Ok(()) +impl Build { + pub fn create(config: &BuildConfig, driver_config: &DriverConfig) -> anyhow::Result { + let driver = get_build_driver(config, driver_config)?; + Ok(Self { + config: config.clone(), + driver, + }) + } + + pub fn from_build_root( + build_root: &Path, + driver_config: &DriverConfig, + ) -> anyhow::Result { + let build_metadata_path = build_root.join("build.json"); + if !build_metadata_path.is_file() { + return Err(anyhow!("No build.json found")); + } + + let mut file = fs::OpenOptions::new() + .read(true) + .write(true) + .open(&build_metadata_path)?; + file.lock()?; + + let metadata = || -> anyhow::Result { + let reader = BufReader::new(&file); + let metadata: BuildMetadata = serde_json::from_reader(reader).with_context(|| { + format!( + "Failed to read build metadata from {} - invalid json", + build_metadata_path.display() + ) + })?; + Ok(metadata) + }(); + + let metadata = match metadata { + Err(meta_err) => { + file.unlock()?; + return Err(meta_err); + } + Ok(metadata) => metadata, + }; + + let driver = create_driver_from_metadata(driver_config, &metadata); + + let driver = match driver { + Err(driver_err) => { + file.unlock()?; + return Err(driver_err); + } + Ok(driver) => driver, + }; + + let result = || -> anyhow::Result<()> { + file.seek(io::SeekFrom::Start(0))?; + let updated_metadata = BuildMetadata { + num_processes_attached: &metadata.num_processes_attached + 1, + ..metadata.clone() + }; + let writer = BufWriter::new(&file); + serde_json::to_writer_pretty(writer, &updated_metadata) + .context("Failed to serialize build metadata")?; + Ok(()) + }(); + + file.unlock()?; + + result?; + + Ok(Self { + config: metadata.config.clone(), + driver, + }) + } + + fn build_metadata_path(&self) -> anyhow::Result { + let build_metadata_path = self.config.build_root_dir.join("build.json"); + if !build_metadata_path.is_file() { + return Err(anyhow!("No build.json found")); + } + Ok(build_metadata_path) + } + + pub fn detach(&self) -> anyhow::Result<()> { + // TODO: refactor the whole file locking / read + write metadata thing to not be as duplicated and error prone + let build_metadata_path = self.build_metadata_path()?; + + let mut file = fs::OpenOptions::new() + .read(true) + .write(true) + .open(&build_metadata_path)?; + file.lock()?; + + let metadata = || -> anyhow::Result { + let reader = BufReader::new(&file); + let metadata: BuildMetadata = serde_json::from_reader(reader).with_context(|| { + format!( + "Failed to read build metadata from {} - invalid json", + build_metadata_path.display() + ) + })?; + Ok(metadata) + }(); + + let metadata = match metadata { + Err(meta_err) => { + file.unlock()?; + return Err(meta_err); + } + Ok(metadata) => metadata, + }; + + let result = || -> anyhow::Result<()> { + file.seek(io::SeekFrom::Start(0))?; + let updated_metadata = BuildMetadata { + num_processes_attached: cmp::max(0, &metadata.num_processes_attached - 1), + ..metadata.clone() + }; + let writer = BufWriter::new(&file); + serde_json::to_writer_pretty(writer, &updated_metadata) + .context("Failed to serialize build metadata")?; + Ok(()) + }(); + + file.unlock()?; + result + } + + pub fn get_number_of_attached_processes(&self) -> anyhow::Result { + // TODO: refactor the whole file locking / read + write metadata thing to not be as duplicated and error prone + let build_metadata_path = self.build_metadata_path()?; + + let file = fs::OpenOptions::new() + .read(true) + .open(&build_metadata_path)?; + file.lock()?; + + let metadata = || -> anyhow::Result { + let reader = BufReader::new(&file); + let metadata: BuildMetadata = serde_json::from_reader(reader).with_context(|| { + format!( + "Failed to read build metadata from {} - invalid json", + build_metadata_path.display() + ) + })?; + Ok(metadata) + }(); + file.unlock()?; + + match metadata { + Err(meta_err) => Err(meta_err), + Ok(metadata) => Ok(metadata.num_processes_attached), + } + } + + pub fn write_metadata(&self) -> anyhow::Result<()> { + let metadata = BuildMetadata { + config: self.config.clone(), + driver_metadata: self.driver.get_build_metadata(), + num_processes_attached: 0, + }; + let path = self.config.build_root_dir.join("build.json"); + let json = serde_json::to_string_pretty(&metadata) + .context("Failed to serialize build metadata")?; + fs::write(path, json)?; + Ok(()) + } } fn copy_glob(src_dir: &Path, pattern: &str, dest_dir: &Path) -> anyhow::Result<()> { @@ -125,20 +277,22 @@ fn get_build_root_and_identifier( fn prepare_build_env( config: &Config, package: &PackageDescription, + driver_type: BuildDriverType, output_dir: &Path, -) -> anyhow::Result { +) -> anyhow::Result { let (package_identifier, build_root) = get_build_root_and_identifier(config, package); if build_root.exists() { fs::remove_dir_all(&build_root)?; } let build_config = BuildConfig { + driver: driver_type, package_identifier, source_dir: package.source_dir.clone(), output_dir: output_dir.to_path_buf(), build_root_dir: build_root, distro: "debian".to_string(), - distro_version: "trixie".to_string(), + distro_version: "forky".to_string(), dry_run: config.dry_run, sign_package: false, }; @@ -147,13 +301,19 @@ fn prepare_build_env( copy_dir_all(&build_config.source_dir, build_config.build_source_dir())?; - Ok(build_config) + let build = Build::create(&build_config, &config.driver)?; + Ok(build) } pub fn get_shell_in_build(config: &Config, package: &PackageDescription) -> anyhow::Result<()> { let (_package_identifier, build_root) = get_build_root_and_identifier(config, package); - let driver = create_driver_from_build_root(&config.driver, &build_root)?; - driver.drop_into_shell()?; + let build = Build::from_build_root(&build_root, &config.driver)?; + let result = build.driver.interactive_shell(); + + // TODO: detach - decrement num_attached_processes + build.detach()?; + + result?; Ok(()) } @@ -163,50 +323,59 @@ pub fn build_package( driver_type: BuildDriverType, output_dir: &Path, ) -> anyhow::Result<()> { - let build_config = prepare_build_env(config, package, output_dir)?; - - let driver = create_driver(&driver_type, &build_config, &config.driver)?; - - write_build_metadata(&build_config, &*driver)?; + let build = prepare_build_env(config, package, driver_type, output_dir)?; + build.write_metadata()?; let result = (|| -> anyhow::Result<()> { - driver.run_command( + build.driver.run_command( &["apt-get", "-y", "build-dep", "."], - &build_config.build_source_dir(), + &build.config.build_source_dir(), true, )?; - driver.run_command( + build.driver.run_command( &["dpkg-buildpackage", "-us", "-uc", "-ui", "-nc", "-b"], - &build_config.build_source_dir(), + &build.config.build_source_dir(), false, )?; - if build_config.sign_package { + if build.config.sign_package { // SIGN .changes and .dsc files // changes = *.changes / *.dsc // driver.run_command(&["debsign", opts, changes], &build_config.build_source_dir(), false)?; // driver.run_command(&["debrsign", opts, username, changes], &build_config.build_source_dir(), false)?; } - let parent_dir = build_config.build_source_dir().join(".."); - copy_glob(&parent_dir, "*.deb", &build_config.output_dir)?; - copy_glob(&parent_dir, "*.changes", &build_config.output_dir)?; - copy_glob(&parent_dir, "*.buildinfo", &build_config.output_dir)?; - copy_glob(&parent_dir, "*.dsc", &build_config.output_dir)?; + let parent_dir = build.config.build_source_dir().join(".."); + copy_glob(&parent_dir, "*.deb", &build.config.output_dir)?; + copy_glob(&parent_dir, "*.changes", &build.config.output_dir)?; + copy_glob(&parent_dir, "*.buildinfo", &build.config.output_dir)?; + copy_glob(&parent_dir, "*.dsc", &build.config.output_dir)?; Ok(()) })(); if let Err(e) = result { - eprintln!("Build failed: {e}. Dropping into shell..."); - let res = driver.drop_into_shell(); - if let Err(shell_error) = res { - eprintln!("Dropping into shell failed: {shell_error}"); + if stdout().is_terminal() { + eprintln!("Build failed: {e}. Dropping into shell..."); + let res = build.driver.interactive_shell(); + if let Err(shell_error) = res { + eprintln!("Dropping into shell failed: {shell_error}"); + } + } else { + eprintln!("Build failed: {e}"); } - driver.cleanup(); + build.driver.cleanup(); return Err(e); } - driver.cleanup(); + // busy waiting until no pro + while let Ok(attached_processes) = build.get_number_of_attached_processes() + && attached_processes > 0 + { + println!("Waiting for last shell to detach ..."); + thread::sleep(time::Duration::from_millis(10)); + } + + build.driver.cleanup(); Ok(()) } diff --git a/packages/debmagic/src/build/common.rs b/packages/debmagic/src/build/common.rs index 5c36658..ddf075d 100644 --- a/packages/debmagic/src/build/common.rs +++ b/packages/debmagic/src/build/common.rs @@ -26,13 +26,17 @@ pub type DriverSpecificBuildMetadata = HashMap; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BuildMetadata { - pub driver: BuildDriverType, pub config: BuildConfig, pub driver_metadata: DriverSpecificBuildMetadata, + // number of parallel debmagic processes working on this instance of a build + // used to determine when a BuildDriver can be fully stopped and cleaned up + pub num_processes_attached: i64, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BuildConfig { + pub driver: BuildDriverType, + pub package_identifier: String, pub build_root_dir: PathBuf, pub source_dir: PathBuf, @@ -71,7 +75,6 @@ impl BuildConfig { Ok(()) } } - pub trait BuildDriver { fn get_build_metadata(&self) -> DriverSpecificBuildMetadata; @@ -79,7 +82,7 @@ pub trait BuildDriver { fn cleanup(&self); - fn drop_into_shell(&self) -> std::io::Result<()>; + fn interactive_shell(&self) -> std::io::Result<()>; fn driver_type(&self) -> BuildDriverType; } diff --git a/packages/debmagic/src/build/config.rs b/packages/debmagic/src/build/config.rs index 769ca41..7e874af 100644 --- a/packages/debmagic/src/build/config.rs +++ b/packages/debmagic/src/build/config.rs @@ -3,7 +3,7 @@ use serde::Deserialize; use crate::build::driver_bare::DriverBareConfig; use crate::build::driver_docker::DriverDockerConfig; -#[derive(Deserialize, Debug, Default)] +#[derive(Deserialize, Debug, Default, Clone)] pub struct DriverConfig { pub docker: DriverDockerConfig, pub bare: DriverBareConfig, diff --git a/packages/debmagic/src/build/driver_bare.rs b/packages/debmagic/src/build/driver_bare.rs index ce77dbe..d8e5014 100644 --- a/packages/debmagic/src/build/driver_bare.rs +++ b/packages/debmagic/src/build/driver_bare.rs @@ -1,4 +1,4 @@ -use std::{path::Path, process::Command}; +use std::{env, path::Path, process::Command}; use serde::{Deserialize, Serialize}; @@ -42,8 +42,7 @@ impl BuildDriver for DriverBare { fn run_command(&self, cmd: &[&str], cwd: &Path, requires_root: bool) -> std::io::Result<()> { let mut full_cmd: Vec = Vec::new(); - // Handle sudo logic - let is_root = unsafe { libc::getuid() == 0 }; + let is_root = unsafe { libc::geteuid() == 0 }; if requires_root && !is_root { full_cmd.push("sudo".to_string()); } @@ -60,7 +59,6 @@ impl BuildDriver for DriverBare { command.current_dir(cwd); - // Inherit stdout/stderr to match Python behavior let status = command.status()?; if status.success() { @@ -77,11 +75,11 @@ impl BuildDriver for DriverBare { // No-op for bare driver } - fn drop_into_shell(&self) -> std::io::Result<()> { + fn interactive_shell(&self) -> std::io::Result<()> { let mut shell = Command::new("/usr/bin/env"); - shell.arg("bash"); + let shell_type = env::var("SHELL").unwrap_or("bash".to_string()); + shell.arg(shell_type); - // Use status() to wait for the shell to exit let _ = shell.status()?; Ok(()) } diff --git a/packages/debmagic/src/build/driver_docker.rs b/packages/debmagic/src/build/driver_docker.rs index 3707ec6..8e14ff1 100644 --- a/packages/debmagic/src/build/driver_docker.rs +++ b/packages/debmagic/src/build/driver_docker.rs @@ -84,11 +84,11 @@ impl DriverDocker { let docker_image_name = format!("debmagic-{}", config.build_identifier()); let mut build_args = Vec::new(); - let uid = unsafe { libc::getuid() }; + let uid = unsafe { libc::geteuid() }; if uid != 0 { build_args.extend(["--build-arg".to_string(), format!("USER_UID={uid}")]); } - let gid = unsafe { libc::getgid() }; + let gid = unsafe { libc::getegid() }; if gid != 0 { build_args.extend(["--build-arg".to_string(), format!("USER_GID={gid}")]); } @@ -208,7 +208,7 @@ impl BuildDriver for DriverDocker { .status(); } - fn drop_into_shell(&self) -> std::io::Result<()> { + fn interactive_shell(&self) -> std::io::Result<()> { if self.config.dry_run { return Ok(()); } diff --git a/packages/debmagic/src/config.rs b/packages/debmagic/src/config.rs index a2f09e6..2fd6728 100644 --- a/packages/debmagic/src/config.rs +++ b/packages/debmagic/src/config.rs @@ -28,7 +28,9 @@ impl Config { let mut builder = ConfigBuilder::builder(); for file in config_files { - builder = builder.add_source(File::with_name(&file.to_string_lossy())); + if file.is_file() { + builder = builder.add_source(File::with_name(&file.to_string_lossy())); + } } // TODO: reimplement cli arg overwrites diff --git a/packages/debmagic/src/main.rs b/packages/debmagic/src/main.rs index bc921ff..a663c93 100644 --- a/packages/debmagic/src/main.rs +++ b/packages/debmagic/src/main.rs @@ -16,31 +16,36 @@ pub mod build; pub mod cli; pub mod config; -fn get_config_file_paths() -> Vec { +fn get_config(cli: &Cli, source_dir: &Option) -> anyhow::Result { let mut config_file_paths = vec![]; let xdg_config_file = dirs::config_dir().map(|p| p.join("debmagic").join("config.toml")); - if let Some(xdg_config_file) = xdg_config_file { - if xdg_config_file.is_file() { - config_file_paths.push(xdg_config_file); - } + if let Some(xdg_config_file) = xdg_config_file + && xdg_config_file.is_file() + { + config_file_paths.push(xdg_config_file); + } + + if let Some(source_dir) = &source_dir { + config_file_paths.push(source_dir.join(".debmagic.toml")); + config_file_paths.push(source_dir.join("debmagic.toml")); } - config_file_paths + if let Some(config_file_override) = &cli.config { + config_file_paths.push(config_file_override.clone()); + } + + let config = Config::new(&config_file_paths, cli)?; + Ok(config) } fn main() -> anyhow::Result<()> { let cli = Cli::parse(); - let mut config_file_paths = get_config_file_paths(); - if let Some(config_override) = &cli.config { - config_file_paths.push(config_override.clone()); - } - let config = Config::new(&config_file_paths, &cli)?; - let current_dir = env::current_dir()?; match &cli.command { Commands::Build(args) => { let source_dir = args.source_dir.as_deref().unwrap_or(¤t_dir); + let config = get_config(&cli, &Some(source_dir.to_path_buf()))?; let package = PackageDescription { name: "debmagic".to_string(), version: "0.1.0".to_string(), @@ -57,6 +62,7 @@ fn main() -> anyhow::Result<()> { } Commands::Shell(args) => { let source_dir = args.source_dir.as_deref().unwrap_or(¤t_dir); + let config = get_config(&cli, &Some(source_dir.to_path_buf()))?; let package = PackageDescription { name: "debmagic".to_string(), version: "0.1.0".to_string(),