diff --git a/.dockerignore b/.dockerignore index e996593..840f8c9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,7 +2,7 @@ .vscode **/__pycache__ -# git subrepos (==submodules) +# git subrepos android_* *_kernel_* clang* diff --git a/.gitignore b/.gitignore index 4de04fe..ae7f031 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ **/__pycache__ .mypy_cache -# git subrepos (==submodules) +# git subrepos /android_* /*_kernel_* /clang* @@ -18,4 +18,4 @@ /source /bundle /localversion -/multi-build \ No newline at end of file +/multi-build diff --git a/Dockerfile b/Dockerfile index 45f1aac..3676f67 100755 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ FROM python:3.12-slim-bookworm AS base ARG WDIR=/zero_build ENV CONAN_UPLOAD_CUSTOM 0 -# place sources from host to container +# transfer sources from host to container COPY . ${WDIR} WORKDIR ${WDIR} @@ -16,6 +16,7 @@ RUN \ apt-get install -y \ neovim \ curl \ + wget \ git \ gcc \ g++ \ diff --git a/README.md b/README.md index 468fa5c..c0e2552 100755 --- a/README.md +++ b/README.md @@ -7,9 +7,10 @@ An Android kernel with Kali NetHunter functionality. - [zero\_kernel](#zero_kernel) - [Contents](#contents) - [**Disclaimer**](#disclaimer) + - [Description](#description) - [Kernel Features](#kernel-features) - [Supported ROMs](#supported-roms) - - [Important Note](#important-note) + - [ROM Artifacts in Releases](#rom-artifacts-in-releases) - [Usage](#usage) - [Prerequisites](#prerequisites) - [Kernel](#kernel) @@ -27,6 +28,14 @@ An Android kernel with Kali NetHunter functionality. **Anything you do with this kernel you do at your own risk. By using it, you take the responsibility upon yourself and in case of any issue you are not to blame me or other related contributors.** +## Description + +Technically speaking, the codebase of this project is an extensive wrapper automating the entire Android kernel build process, starting from kernel source collection and ending with artifact packaging. + +The key goal is to modify the kernel in such a way that enables unique features of [Kali NetHunter](https://www.kali.org/docs/nethunter) — a ROM layer designed to add extended functionality for penetration testing in a mobile form factor. + +The architecture of this wrapper is ~~trying to be~~ as modular as possible, making it a little easier to add support for new devices. + ## Kernel Features The kernel has the following features: @@ -38,7 +47,7 @@ The kernel has the following features: ## Supported ROMs -For OnePlus 5/T devices: +For **OnePlus 5/T** devices: - 4.4 Linux kernel version: - LineageOS; @@ -52,11 +61,11 @@ For OnePlus 5/T devices: \** -- this, **in theory**, is relevant to all 4.14-based ROMs for this device in existence. -## Important Note +## ROM Artifacts in Releases The contents of each release include ROM builds compatible with corresponding kernel builds. These ROM files are **unmodified and mirrored from official sources**. -This can be verified with the checksums, which should be identical to the ones presented on the ROM project's official web page. +This can be verified via the checksums, which should be identical to the ones presented on the ROM project's official web page. You can always download the same ROM file from official sources if you'd like. The mirroring in this repository is done due to the fact that some ROM projects remove their older builds once they become too outdated. @@ -97,11 +106,10 @@ To run this tool in a `local` environment, you will need: You will also need a few Python packages. To install them, use: ```sh +python3 -m pip install poetry python3 -m poetry install --no-root ``` -To install `poetry`, use `python3 -m pip install poetry`. - ### Kernel Kernel build process can be launched by using the `python3 wrapper kernel ` command. @@ -110,14 +118,14 @@ For more options you can refer to the help message below. ```help $ python3 wrapper kernel --help -usage: wrapper kernel [-h] --buildenv {local,docker,podman} --base +usage: wrapper kernel [-h] --build-env {local,docker,podman} --base {los,pa,x,aosp} --codename CODENAME --lkv LKV [-c] [--clean-image] [--log-level {normal,verbose,quiet}] [-o OUTLOG] [--ksu] options: -h, --help show this help message and exit - --buildenv {local,docker,podman} + --build-env {local,docker,podman} select build environment --base {los,pa,x,aosp} select a kernel base for the build @@ -131,6 +139,7 @@ options: -o OUTLOG, --output OUTLOG save logs to a file --ksu add KernelSU support + ``` ### Assets @@ -139,14 +148,14 @@ As mentioned, there is also an asset downloader, which can collect latest versio ```help $ python3 wrapper assets --help -usage: wrapper assets [-h] --buildenv {local,docker,podman} --base +usage: wrapper assets [-h] --build-env {local,docker,podman} --base {los,pa,x,aosp} --codename CODENAME --chroot {full,minimal} [--rom-only] [--clean-image] [--clean] [--log-level {normal,verbose,quiet}] [-o OUTLOG] [--ksu] options: -h, --help show this help message and exit - --buildenv {local,docker,podman} + --build-env {local,docker,podman} select build environment --base {los,pa,x,aosp} select a kernel base for the build @@ -184,7 +193,7 @@ An option named `slim` is a much lighter version of `full` packaging, as only th ```help $ python3 wrapper bundle --help -usage: wrapper bundle [-h] --buildenv {local,docker,podman} --base +usage: wrapper bundle [-h] --build-env {local,docker,podman} --base {los,pa,x,aosp} --codename CODENAME --lkv LKV --package-type {conan,slim,full} [--conan-upload] [--clean-image] [--log-level {normal,verbose,quiet}] @@ -192,7 +201,7 @@ usage: wrapper bundle [-h] --buildenv {local,docker,podman} --base options: -h, --help show this help message and exit - --buildenv {local,docker,podman} + --build-env {local,docker,podman} select build environment --base {los,pa,x,aosp} select a kernel base for the build @@ -214,12 +223,23 @@ options: Here are some examples of commands: -- **(Recommended)** Build kernel and collect ROM via Docker: - - `python3 wrapper bundle --buildenv=docker --base=los --codename=dumpling --lkv=4.4 --package-type=slim`; -- Build kernel locally: - - `python3 wrapper kernel --buildenv=local --base=los --codename=dumpling --lkv=4.4`; -- Collect all the assets locally: - - `python3 wrapper assets --buildenv=local --base=los --codename=dumpling --package-type=full`. +**(Recommended)** Build kernel and collect ROM via Docker: + +```sh +python3 wrapper bundle --build-env=docker --base=los --codename=dumpling --lkv=4.4 --package-type=slim +``` + +Build kernel locally: + +```sh +python3 wrapper kernel --build-env=local --base=los --codename=dumpling --lkv=4.4 +``` + +Collect all of the assets locally: + +```sh +python3 wrapper assets --build-env=local --base=los --codename=dumpling --package-type=full +``` ## Credits diff --git a/conanfile.py b/conanfile.py index f6aa741..0e7fe98 100644 --- a/conanfile.py +++ b/conanfile.py @@ -1,13 +1,13 @@ from conans import ConanFile -class ZeroConan(ConanFile): +class ZeroKernelConan(ConanFile): name = "zero" - version = "0.4.0" + version = "0.4.1" author = "seppzer0" url = "https://gitlab.com/api/v4/projects/40803264/packages/conan" description = "An Android kernel with Kali NetHunter functionality." - topics = ("zero_kernel", "kali-nethunter") + topics = ("zero_kernel", "kali-nethunter", "nethunter") settings = None options = { "base": ("los", "pa", "x", "aosp"), @@ -19,13 +19,15 @@ def export_sources(self): self.copy("*", src="source", dst=".") def build(self): - cmd = "python3 wrapper kernel local {0} {1} &&"\ - "python3 wrapper assets local {0} {1} {2} --clean"\ - .format( - self.options.rom, - self.options.codename, - self.options.chroot - ) + shared_args = "--build-env=local --base={} --codename={} --chroot={}"\ + .format( + self.options.base, + self.options.codename, + self.options.chroot + ) + cmd = "python3 wrapper kernel {0} &&"\ + "python3 wrapper assets {0} --clean"\ + .format(shared_args) print(f"[cmd] {cmd}") self.run(cmd) diff --git a/docs/FAQ.md b/docs/FAQ.md index 7f2b57b..fbb8511 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -4,7 +4,7 @@ This page contains answers to popular questions in relation to this kernel. ## Q: How to TURN ON monitor mode on internal Wi-Fi card? -There are two options to switch internal Wi-Fi card to monitor mode: +**A:** There are two options to switch internal Wi-Fi card to monitor mode: - in Kali chroot environment, launch `airmon-ng start wlan0`; - in NetHunter app, navigate to the `Custom Commands` menu and launch the `Start wlan0 in monitor mode`. @@ -13,14 +13,14 @@ Be aware that while in monitor mode, you won't be able to connect to a Wi-Fi net ## Q: How to TURN OFF monitor mode on internal Wi-Fi card? -Similarly, depending on which approach you chose to turn on the monitor mode, there are two options: +**A:** Similarly, depending on which approach you chose to turn on the monitor mode, there are two options: - in Kali chroot environment -> `airmon-ng stop wlan0`; - in NetHunter app -> `Custom Commands` -> `Stop wlan0 in monitor mode`. ## Q: Why is there an unused wlan1 interface? -**TL;DR**: Because it's a ~~bug~~ feature of Android 13. +**A:** Because it's a ~~bug~~ feature of Android 13. Initially, when launching `airmon-ng` in Kali chroot environment without any of the interfaces in monitor mode and no external adapters plugged in, you will see two wlan interfaces: `wlan0` and `wlan1`. @@ -30,4 +30,8 @@ Switching `wlan0` to monitor mode disables `wlan1` completely. However, when res ## Q: How to TURN ON and OFF monitor mode on external Wi-Fi card? -For an external card, you would have to use `airmon-ng start ` and `airmon-ng stop ` commands. +**A:** For an external card, you would have to use `airmon-ng start ` and `airmon-ng stop ` commands. + +## Q: How do I switch from standard partition ROM to retrofit dynamic partition ROM and vice versa? + +**A:** Refer to these [instructions](https://gist.github.com/nkeor/d71b7884ee951de669b0d4baeacc58ba). diff --git a/docs/FLASHING.md b/docs/FLASHING.md index ededd83..3db1f8f 100755 --- a/docs/FLASHING.md +++ b/docs/FLASHING.md @@ -23,7 +23,7 @@ Listed below files are required: - compiled kernel, obviously; - ROM; - Magisk or KernelSU; -- TWRP, the unofficial 3.7.0 version (supports operations with encrypted and dynamic partitions); +- TWRP, the special [build](https://sourceforge.net/projects/op5-5t/files/Android-12/TWRP/twrp-3.7.0_12-5-dyn-cheeseburger_dumpling.img/download) by faoliveira78 (supports operations with encrypted and dynamic partitions); - DM-Verity and Force Encrypt disabler; - Kali NetHunter + Kali NetHunter Terminal apps; - Kali NetHunter Chroot (you can do this later, but it would be easier to download this beforehand); @@ -50,8 +50,9 @@ Before doing anything, please ensure that you have: - wipe your phone via `Wipe -> Advanced Wipe` menu, check all the shown boxes; - wipe your device again via `Wipe -> Format Data` menu (this will remove any encryption that is present on your device); - reboot into TWRP via `Reboot -> Recovery`; -- flash ROM; -- flash kernel; +- if using a Retrofit Dynamic Partitions ROM such as ParanoidAndoid -> untoggle `Unmount System before installing a ZIP` in the Settings; +- flash the ROM; +- flash the kernel; - **if using Magisk** --> flash root manager (Magisk; you must change the `.apk` extension into `.zip` for this); - flash DM-Verity and Force Encrypt disabler zip; - reboot into system via `Reboot -> System` . diff --git a/docs/TODO.md b/docs/TODO.md index 45372e6..be0e0a7 100755 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -26,19 +26,15 @@ - [x] add proper OS detection for preventing local builds in unsupported systems; - [x] add static analysis for the wrapper; - [x] add a single-point manifest with main info on the tool; -- [ ] add release body when creating a release via CI/CD; -- [ ] add static analysis report to release body; - [x] improve documentation; - [x] apply OOP paradigm; - [ ] add tests (unit/integration/etc); - [x] switch to Poetry for dependency management; - [ ] create a commit-based lockfile system for reproducible kernel builds; -- [ ] implement generators; - [x] switch to `pathlib`; - [ ] switch to `raise` instead of `sys.exit`; - [ ] use `pydantic`; - [x] add a FAQ page; -- [ ] add wiki; - [x] refactor Docker/Podman command formation; - [ ] refactor logging mechanism; - [x] fix Podman usage (.dockerignore); @@ -55,4 +51,5 @@ - [x] add a new argument responsible for Linux kernel version selection; - [x] add 4.14 Linux kernel version builds; - [ ] decompose `run` methods into separate functions as much as possible; -- [ ] new device support: OP9. +- [ ] new device support: OP9; +- [ ] add type checks with pyright. diff --git a/poetry.lock b/poetry.lock index ac7e633..72b23f9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "argparse" @@ -13,24 +13,24 @@ files = [ [[package]] name = "bandit" -version = "1.7.5" +version = "1.7.7" description = "Security oriented static analyser for python code." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "bandit-1.7.5-py3-none-any.whl", hash = "sha256:75665181dc1e0096369112541a056c59d1c5f66f9bb74a8d686c3c362b83f549"}, - {file = "bandit-1.7.5.tar.gz", hash = "sha256:bdfc739baa03b880c2d15d0431b31c658ffc348e907fe197e54e0389dd59e11e"}, + {file = "bandit-1.7.7-py3-none-any.whl", hash = "sha256:17e60786a7ea3c9ec84569fd5aee09936d116cb0cb43151023258340dbffb7ed"}, + {file = "bandit-1.7.7.tar.gz", hash = "sha256:527906bec6088cb499aae31bc962864b4e77569e9d529ee51df3a93b4b8ab28a"}, ] [package.dependencies] colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} -GitPython = ">=1.0.1" PyYAML = ">=5.3.1" rich = "*" stevedore = ">=1.20.0" [package.extras] -test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)", "tomli (>=1.1.0)"] +baseline = ["GitPython (>=3.1.30)"] +test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)"] toml = ["tomli (>=1.1.0)"] yaml = ["PyYAML"] @@ -47,13 +47,13 @@ files = [ [[package]] name = "certifi" -version = "2023.7.22" +version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, - {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] [[package]] @@ -168,12 +168,12 @@ files = [ [[package]] name = "conan" -version = "1.62.0" +version = "1.63.0" description = "Conan C/C++ package manager" optional = false python-versions = ">=3.6" files = [ - {file = "conan-1.62.0.tar.gz", hash = "sha256:aa308d84e64bdad523e84631f0c0e5c7efba376bcbc3e18b8784e5cb4e7197dc"}, + {file = "conan-1.63.0.tar.gz", hash = "sha256:4e2b8da8a68d18bc85e3f7512f72c2326a4806c352c5bd8affae667119de3704"}, ] [package.dependencies] @@ -231,57 +231,26 @@ files = [ {file = "fasteners-0.19.tar.gz", hash = "sha256:b4f37c3ac52d8a445af3a66bce57b33b5e90b97c696b7b984f530cf8f0ded09c"}, ] -[[package]] -name = "gitdb" -version = "4.0.11" -description = "Git Object Database" -optional = false -python-versions = ">=3.7" -files = [ - {file = "gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4"}, - {file = "gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b"}, -] - -[package.dependencies] -smmap = ">=3.0.1,<6" - -[[package]] -name = "gitpython" -version = "3.1.40" -description = "GitPython is a Python library used to interact with Git repositories" -optional = false -python-versions = ">=3.7" -files = [ - {file = "GitPython-3.1.40-py3-none-any.whl", hash = "sha256:cf14627d5a8049ffbf49915732e5eddbe8134c3bdb9d476e6182b676fc573f8a"}, - {file = "GitPython-3.1.40.tar.gz", hash = "sha256:22b126e9ffb671fdd0c129796343a02bf67bf2994b35449ffc9321aa755e18a4"}, -] - -[package.dependencies] -gitdb = ">=4.0.1,<5" - -[package.extras] -test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest", "pytest-cov", "pytest-instafail", "pytest-subtests", "pytest-sugar"] - [[package]] name = "idna" -version = "3.4" +version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, - {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, ] [[package]] name = "jinja2" -version = "3.1.2" +version = "3.1.3" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, + {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, + {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, ] [package.dependencies] @@ -316,71 +285,71 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" -version = "2.1.3" +version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] [[package]] @@ -452,17 +421,18 @@ files = [ [[package]] name = "pygments" -version = "2.16.1" +version = "2.17.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.7" files = [ - {file = "Pygments-2.16.1-py3-none-any.whl", hash = "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692"}, - {file = "Pygments-2.16.1.tar.gz", hash = "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29"}, + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, ] [package.extras] plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyjwt" @@ -604,17 +574,6 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -[[package]] -name = "smmap" -version = "5.0.1" -description = "A pure Python implementation of a sliding window memory map manager" -optional = false -python-versions = ">=3.7" -files = [ - {file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"}, - {file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"}, -] - [[package]] name = "stevedore" version = "5.1.0" @@ -631,13 +590,13 @@ pbr = ">=2.0.0,<2.1.0 || >2.1.0" [[package]] name = "tqdm" -version = "4.66.1" +version = "4.66.2" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" files = [ - {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"}, - {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"}, + {file = "tqdm-4.66.2-py3-none-any.whl", hash = "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9"}, + {file = "tqdm-4.66.2.tar.gz", hash = "sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531"}, ] [package.dependencies] @@ -678,5 +637,5 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [metadata] lock-version = "2.0" -python-versions = "^3.10" -content-hash = "919843e189af29beba09faa3dd693c2d30849d7d2629c24c005659b8411da841" +python-versions = "^3.12" +content-hash = "6682fda4a761676f4996be95093998d5c463a5d02aaa0669dc5858eab7ee95e0" diff --git a/pyproject.toml b/pyproject.toml index 0587514..5181c92 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,13 @@ [tool.poetry] name = "zero-kernel-builder" -version = "0.4.0" +version = "0.4.1" description = "An Android kernel with Kali NetHunter functionality." authors = ["seppzer0"] readme = "README.md" packages = [{include = "wrapper"}] [tool.poetry.dependencies] -python = "^3.10" +python = "^3.12" conan = "~1" argparse = "*" requests = "*" diff --git a/scripts/get_version.sh b/scripts/get_version.sh index 98729e6..cdceeb3 100644 --- a/scripts/get_version.sh +++ b/scripts/get_version.sh @@ -1,3 +1,6 @@ #!/bin/sh +# +# This is an SH version of get_version.py, usually used for cases when Python interpreter is unavailable. +# cat $(dirname $(realpath "$0"))/../pyproject.toml | grep "version = " | cut -d'"' -f 2 diff --git a/scripts/multi_build.py b/scripts/multi_build.py index 7a0cebe..d6d1cef 100644 --- a/scripts/multi_build.py +++ b/scripts/multi_build.py @@ -92,7 +92,7 @@ def main(args: argparse.Namespace) -> None: os.mkdir(dir_shared) # define values individually module = argset["module"] - buildenv = f"--buildenv {args.env}" + benv = f"--build-env {args.env}" base = f'--base {argset["rom"]}' codename = f'--codename {argset["codename"]}' lkv = f'--lkv {argset["lkv"]}' if argset["module"] in ("kernel", "bundle") else "" @@ -102,7 +102,7 @@ def main(args: argparse.Namespace) -> None: # if the build is last, make it automatically remove the Docker/Podman image from runner clean = "--clean-image" if count == len(argsets) and args.env in ("docker", "podman") else "" # form and launch the command - cmd = f"python3 wrapper {module} {buildenv} {base} {codename} {lkv} {size} {ksu} {clean} {extra}" + cmd = f"python3 wrapper {module} {benv} {base} {codename} {lkv} {size} {ksu} {clean} {extra}" print(f"[CMD]: {cmd}") subprocess.run(cmd.strip(), shell=True, check=True) # copy artifacts into the shared directory diff --git a/wrapper/__main__.py b/wrapper/__main__.py index c3a7b52..defeef8 100644 --- a/wrapper/__main__.py +++ b/wrapper/__main__.py @@ -9,11 +9,12 @@ import tools.messages as msg import tools.commands as ccmd -from models.bundle import BundleCreator -from models.kernel import KernelBuilder -from models.assets import AssetCollector +from models.bundle_creator import BundleCreator +from models.kernel_builder import KernelBuilder +from models.assets_collector import AssetsCollector -from engines.container import ContainerEngine +from engines.docker_engine import DockerEngine +from engines.podman_engine import PodmanEngine from configs import Config as cfg @@ -38,10 +39,10 @@ def parse_args() -> argparse.Namespace: # common argument attributes for subparsers help_base = "select a kernel base for the build" help_codename = "select device codename" - help_buildenv = "select build environment" + help_benv = "select build environment" help_clean = "remove Docker/Podman image from the host machine after build" help_loglvl = "select log level" - choices_buildenv = ("local", "docker", "podman") + choices_benv = ("local", "docker", "podman") choices_loglvl = ("normal", "verbose", "quiet") choices_base = ("los", "pa", "x", "aosp") help_logfile = "save logs to a file" @@ -50,10 +51,11 @@ def parse_args() -> argparse.Namespace: default_loglvl = "normal" # kernel parser_kernel.add_argument( - "--buildenv", + "--build-env", + dest="benv", required=True, - choices=choices_buildenv, - help=help_buildenv, + choices=choices_benv, + help=help_benv, ) parser_kernel.add_argument( "--base", @@ -103,10 +105,11 @@ def parse_args() -> argparse.Namespace: ) # assets parser_assets.add_argument( - "--buildenv", + "--build-env", + dest="benv", required=True, - choices=choices_buildenv, - help=help_buildenv + choices=choices_benv, + help=help_benv ) parser_assets.add_argument( "--base", @@ -163,10 +166,11 @@ def parse_args() -> argparse.Namespace: ) # bundle parser_bundle.add_argument( - "--buildenv", + "--build-env", + dest="benv", required=True, - choices=choices_buildenv, - help=help_buildenv + choices=choices_benv, + help=help_benv ) parser_bundle.add_argument( "--base", @@ -230,7 +234,7 @@ def validate_settings(config: dict) -> None: :param config: A dictionary containing app arguments. """ # detect OS family - if config.get("buildenv") == "local": + if config.get("benv") == "local": if not platform.system() == "Linux": msg.error("Can't build kernel on a non-Linux machine.") else: @@ -265,7 +269,7 @@ def main(args: argparse.Namespace) -> None: arguments["build_module"] = args.command params = { "build_module", - "buildenv", + "benv", "codename", "base", "lkv", @@ -291,37 +295,39 @@ def main(args: argparse.Namespace) -> None: os.remove(args.outlog) os.environ["OSTREAM"] = args.outlog msg.outputstream() - # containerized build - if args.buildenv in ("docker", "podman"): - ContainerEngine(config=passed_params).run() - # local build - else: - match args.command: - case "kernel": - KernelBuilder( - codename = args.codename, - base = args.base, - lkv = args.lkv, - clean = args.clean_kernel, - ksu = args.ksu, - ).run() - case "assets": - AssetCollector( - codename = args.codename, - base = args.base, - chroot = args.chroot, - clean = args.clean_assets, - rom_only = args.rom_only, - ksu = args.ksu, - ).run() - case "bundle": - BundleCreator( - codename = args.codename, - base = args.base, - lkv = args.lkv, - package_type = args.package_type, - ksu = args.ksu, - ).run() + # determine the build + match args.benv: + case "docker": + DockerEngine(config=passed_params).run() + case "podman": + PodmanEngine(config=passed_params).run() + case "local": + match args.command: + case "kernel": + KernelBuilder( + codename = args.codename, + base = args.base, + lkv = args.lkv, + clean = args.clean_kernel, + ksu = args.ksu, + ).run() + case "assets": + AssetsCollector( + codename = args.codename, + base = args.base, + chroot = args.chroot, + clean = args.clean_assets, + rom_only = args.rom_only, + ksu = args.ksu, + ).run() + case "bundle": + BundleCreator( + codename = args.codename, + base = args.base, + lkv = args.lkv, + package_type = args.package_type, + ksu = args.ksu, + ).run() if __name__ == "__main__": diff --git a/wrapper/bridge.py b/wrapper/bridge.py index e5aac06..ee65719 100644 --- a/wrapper/bridge.py +++ b/wrapper/bridge.py @@ -3,9 +3,9 @@ import tools.messages as msg -from models.bundle import BundleCreator -from models.kernel import KernelBuilder -from models.assets import AssetCollector +from models.bundle_creator import BundleCreator +from models.kernel_builder import KernelBuilder +from models.assets_collector import AssetsCollector from utils import Resources @@ -93,7 +93,7 @@ def main(args: argparse.Namespace) -> None: ksu = args.ksu, ).run() case "assets": - AssetCollector( + AssetsCollector( codename = args.codename, base = args.base, chroot = args.chroot, @@ -110,10 +110,14 @@ def main(args: argparse.Namespace) -> None: ksu = args.ksu, ).run() case _: - # if no module was selected, then shared tools are installed - tconf = Resources() - tconf.path_gen() - tconf.download() + # if no module was selected, then shared tools are (supposed to be) installed + if args.tools: + tconf = Resources() + tconf.path_gen() + tconf.download() + else: + # technically this part of code cannot be reached and is just an extra precaution + msg.error("Invalid argument set specified, please review") if __name__ == "__main__": diff --git a/wrapper/clients/los.py b/wrapper/clients/los.py index e85bce3..64389ee 100644 --- a/wrapper/clients/los.py +++ b/wrapper/clients/los.py @@ -1,25 +1,13 @@ -import requests +from .template_rom_api import TemplateRomApi -import tools.messages as msg - -class LineageOsApi: +class LineageOsApi(TemplateRomApi): """Limited interaction with Lineage API.""" - _endpoint = "https://download.lineageos.org/api/v1/{}/nightly/ro.build.version.incremental" + endpoint: str = "https://download.lineageos.org/api/v1/{}/nightly/ro.build.version.incremental" + json_key: str = "response" + rom_name: str = "LOS" def __init__(self, codename: str, rom_only: bool) -> None: - self._codename = codename - self._rom_only = rom_only - self._endpoint = self._endpoint.format(codename) - - def run(self) -> str: - """Get the latest version of LineageOS ROM.""" - data = requests.get(self._endpoint) - try: - data = data.json()["response"][0]["url"] - except Exception: - exit_flag = False if self._rom_only else True - msg.error(f"Could not connect to LOS API, HTTP status code: {data.status_code}", - dont_exit=exit_flag) - return data + super().__init__(codename, rom_only) + self.endpoint = self.endpoint.format(codename) diff --git a/wrapper/clients/pa.py b/wrapper/clients/pa.py index 875c259..e163354 100644 --- a/wrapper/clients/pa.py +++ b/wrapper/clients/pa.py @@ -1,36 +1,24 @@ -import requests +from .template_rom_api import TemplateRomApi -import tools.messages as msg - -class ParanoidAndroidApi: +class ParanoidAndroidApi(TemplateRomApi): """Limited interaction with ParanoidAndroid API.""" - _endpoint = "https://api.paranoidandroid.co/updates/{}" + endpoint: str = "https://api.paranoidandroid.co/updates/{}" + json_key: str = "updates" + rom_name: str = "PA" def __init__(self, codename: str, rom_only: bool) -> None: - self._codename = codename - self._rom_only = rom_only - self._endpoint = self._endpoint.format(self._codename_pa) + super().__init__(codename, rom_only) + # PA has a custom codename/device naming + self.endpoint = self.endpoint.format(self._codename_pa) @property def _codename_pa(self) -> str: """Custom codename-to-device mapper for PA API.""" - name_dict = { + custom_names = { "dumpling": "oneplus5t", "cheeseburger": "oneplus5", } - return name_dict[self._codename] - - def run(self) -> str: - """Get the latest version of ParanoidAndroid ROM.""" - data = requests.get(self._endpoint) - try: - data = data.json()["updates"][0]["url"] - except Exception: - exit_flag = False if self._rom_only else True - msg.error( - f"Could not connect to PA API, HTTP status code: {data.status_code}", - dont_exit=exit_flag - ) - return data + res = self.codename if self.codename not in custom_names else custom_names[self.codename] + return res diff --git a/wrapper/clients/template_rom_api.py b/wrapper/clients/template_rom_api.py new file mode 100644 index 0000000..c74e476 --- /dev/null +++ b/wrapper/clients/template_rom_api.py @@ -0,0 +1,28 @@ +import requests + +import tools.messages as msg + + +class TemplateRomApi: + """A template class for interacting with ROMs' APIs.""" + + endpoint: str + json_key: str + rom_name: str + + def __init__(self, codename: str, rom_only: str) -> None: + self.codename = codename + self.rom_only = rom_only + + def run(self) -> str: + """Get the latest build of the ROM.""" + data = requests.get(self.endpoint) + try: + data = data.json()[self.json_key][0]["url"] + except Exception: + exit_flag = False if self.rom_only else True + msg.error( + f"Could not connect to {self.rom_name} API, HTTP status code: {data.status_code}", + dont_exit=exit_flag + ) + return data diff --git a/wrapper/engines/__init__.py b/wrapper/engines/__init__.py index 1c300c0..6caf1fe 100644 --- a/wrapper/engines/__init__.py +++ b/wrapper/engines/__init__.py @@ -1 +1,2 @@ -from .container import ContainerEngine +from .docker_engine import DockerEngine +from .podman_engine import PodmanEngine diff --git a/wrapper/engines/container.py b/wrapper/engines/container.py deleted file mode 100644 index 8380978..0000000 --- a/wrapper/engines/container.py +++ /dev/null @@ -1,187 +0,0 @@ -import os -import shutil -from pathlib import Path - -import tools.messages as msg -import tools.commands as ccmd - -from configs import Config as cfg - - -class ContainerEngine: - """Use containers (Docker/Podman) for the build.""" - - _name_image: str = "zero-kernel-image" - _name_container: str = "zero-kernel-container" - _dir_init: Path = Path.cwd() - _dir_kernel: Path = Path(cfg.DIR_KERNEL) - _dir_assets: Path = Path(cfg.DIR_ASSETS) - _dir_bundle: Path = Path(cfg.DIR_BUNDLE) - _wdir_docker: Path = Path("/", "zero_build") - _wdir_local: Path = cfg.DIR_ROOT - - def __init__(self, config: dict) -> None: - self._buildenv = config.get("buildenv") - self._build_module = config.get("build_module") - self._codename = config.get("codename") - self._base = config.get("base") - self._lkv = config.get("lkv") - self._chroot = config.get("chroot", None) - self._package_type = config.get("package_type", None) - self._clean_image = config.get("clean_image", False) - self._clean_kernel = config.get("clean_kernel", False) - self._clean_assets = config.get("clean_assets", False) - self._rom_only = config.get("rom_only", False) - self._conan_upload = config.get("conan_upload", False) - self._ksu = config.get("ksu", False) - - @property - def _dir_bundle_conan(self) -> Path: - """Determine path to Conan's local cache.""" - res = "" - if os.getenv("CONAN_USER_HOME"): - res = Path(os.getenv("CONAN_USER_HOME")) - else: - res = Path(os.getenv("HOME"), ".conan") - return res - - @property - def _wrapper_cmd(self) -> str: - """Return the launch command for the wrapper.""" - # prepare launch command - cmd = f"python3 {Path('wrapper', 'bridge.py')}" - arguments = { - "--build-module": self._build_module, - "--codename": self._codename, - "--base": self._base, - "--lkv": self._lkv, - "--chroot": self._chroot, - "--package-type": self._package_type, - "--rom-only": self._rom_only, - "--ksu": self._ksu, - "--clean-kernel": self._clean_kernel, - "--clean-assets": self._clean_assets, - } - # extend the command with given arguments - for arg, value in arguments.items(): - if value not in (None, False, True): - cmd += f" {arg}={value}" - elif value in (True,): - cmd += f" {arg}" - # extend the command with the selected packaging option - if self._build_module == "bundle": - if self._package_type in ("slim", "full"): - cmd += f" && chmod 777 -R {Path(self._wdir_docker, self._dir_bundle)}" - else: - cmd += " && chmod 777 -R /root/.conan" - return cmd - - @property - def _container_options(self) -> list[str]: - """Form the list of Docker options.""" - # declare the base - options = [ - "-i", - "--rm", - "-e KVERSION={}".format(os.getenv("KVERSION")), - "-e LOGLEVEL={}".format(os.getenv("LOGLEVEL")), - "-w {}".format(self._wdir_docker), - ] - # mount directories - match self._build_module: - case "kernel": - options.append( - '-v {}/{}:{}/{}'.format( - cfg.DIR_ROOT, - self._dir_kernel, - self._wdir_docker, - self._dir_kernel - ) - ) - case "assets": - options.append( - '-v {}/{}:{}/{}'.format( - cfg.DIR_ROOT, - self._dir_assets, - self._wdir_docker, - self._dir_assets - ) - ) - case "bundle": - match self._package_type: - case "slim" | "full": - options.append( - '-v {}/{}:{}/{}'.format( - cfg.DIR_ROOT, - self._dir_bundle, - self._wdir_docker, - self._dir_bundle - ) - ) - case "conan": - if self._conan_upload: - options.append('-e CONAN_UPLOAD_CUSTOM=1') - # determine the path to local Conan cache and check if it exists - if self._dir_bundle_conan.is_dir(): - options.append(f'-v {self._dir_bundle_conan}:/"/root/.conan"') - else: - msg.error("Could not find Conan local cache on the host machine.") - return options - - def _create_dirs(self) -> None: - """Create required directories for volume mounting.""" - match self._build_module: - case "kernel": - kdir = Path(cfg.DIR_KERNEL) - if not kdir.is_dir(): - os.mkdir(kdir) - case "assets": - assetsdir = Path(cfg.DIR_ASSETS) - if not assetsdir.is_dir(): - os.mkdir(assetsdir) - case "bundle": - if self._package_type in ("slim", "full"): - # mount directory with release artifacts - bdir = Path(cfg.DIR_BUNDLE) - shutil.rmtree(bdir, ignore_errors=True) - os.mkdir(bdir) - - def _build(self) -> None: - """Build the Docker/Podman image.""" - print("\n") - alias = self._buildenv.capitalize() - msg.note(f"Building the {alias} image..") - os.chdir(self._wdir_local) - # build only if it is not present in local cache - # NOTE: this will crash in GitLab CI/CD (Docker-in-Docker), requires a workaround - if self._name_image not in ccmd.launch(f'{self._buildenv} images --format {"{{.Repository}}"}', get_output=True): - # force enable Docker Buildkit - if self._buildenv == "docker": - os.environ["DOCKER_BUILDKIT"] = "1" - cmd = "{} build . -f {} -t {} --load".format( - self._buildenv, - self._wdir_local / 'Dockerfile', - self._name_image - ) - ccmd.launch(cmd) - msg.done("Done!") - else: - msg.note(f"{alias} image is already present, skipping the build.") - print("\n") - - def run(self) -> None: - self._build() - # form the final "docker/podman run" command - cmd = '{} run {} {} /bin/bash -c "{}"'.format( - self._buildenv, - " ".join(self._container_options), - self._name_image, - self._wrapper_cmd - ) - # prepare directories - self._create_dirs() - ccmd.launch(cmd) - # navigate to root directory and clean image from host machine - os.chdir(self._dir_init) - if self._clean_image: - ccmd.launch(f"{self._buildenv} rmi {self._name_image}") diff --git a/wrapper/engines/docker_engine.py b/wrapper/engines/docker_engine.py new file mode 100644 index 0000000..7e517fd --- /dev/null +++ b/wrapper/engines/docker_engine.py @@ -0,0 +1,54 @@ +import os +from typing import override + +import tools.messages as msg +import tools.commands as ccmd + +from .template_container_engine import TemplateContainerEngine + + +class DockerEngine(TemplateContainerEngine): + """Docker engine.""" + + benv: str = "docker" + + def __init__(self, config: dict) -> None: + super().__init__(config) + + @staticmethod + def _force_buildkit() -> None: + """Force enable Docker BuildKit.""" + os.environ["DOCKER_BUILDKIT"] = "1" + + def _check_cache(self) -> bool: + """Check local Docker cache for the specified image. + + For now, this is done for Docker exclusively. + """ + img_cache_cmd = f'{self.benv} images --format {"{{.Repository}}"}' + img_cache = ccmd.launch(img_cache_cmd, get_output=True) + check = True if self.name_image in img_cache else False + return check + + @override + def run(self) -> None: + self._force_buildkit() + # build image only if it is not present in local cache + if not self._check_cache(): + self.build() + else: + msg.note("\nDocker image already in local cache, skipping it's build..\n") + # form the final command to create container + cmd = '{} run {} {} /bin/bash -c "{}"'.format( + self.benv, + " ".join(self.container_options), + self.name_image, + self.wrapper_cmd + ) + # prepare directories + self.create_dirs() + ccmd.launch(cmd) + # navigate to root directory and clean image from host machine + os.chdir(self.dir_init) + if self.clean_image: + ccmd.launch(f"{self.benv} rmi {self.name_image}") diff --git a/wrapper/engines/podman_engine.py b/wrapper/engines/podman_engine.py new file mode 100644 index 0000000..dec04e5 --- /dev/null +++ b/wrapper/engines/podman_engine.py @@ -0,0 +1,10 @@ +from .template_container_engine import TemplateContainerEngine + + +class PodmanEngine(TemplateContainerEngine): + """Podman engine.""" + + benv: str = "podman" + + def __init__(self, config: dict) -> None: + super().__init__(config) diff --git a/wrapper/engines/template_container_engine.py b/wrapper/engines/template_container_engine.py new file mode 100644 index 0000000..6f80cbf --- /dev/null +++ b/wrapper/engines/template_container_engine.py @@ -0,0 +1,182 @@ +import os +import shutil +from pathlib import Path + +import tools.messages as msg +import tools.commands as ccmd + +from configs import Config as cfg + + +class TemplateContainerEngine: + """A template engine for containerized builds.""" + + benv: str + name_image: str = "zero-kernel-image" + name_container: str = "zero-kernel-container" + dir_init: Path = Path.cwd() + dir_kernel: Path = Path(cfg.DIR_KERNEL) + dir_assets: Path = Path(cfg.DIR_ASSETS) + dir_bundle: Path = Path(cfg.DIR_BUNDLE) + wdir_container: Path = Path("/", "zero_build") + wdir_local: Path = cfg.DIR_ROOT + + def __init__(self, config: dict) -> None: + self.build_module = config.get("build_module") + self.codename = config.get("codename") + self.base = config.get("base") + self.lkv = config.get("lkv") + self.chroot = config.get("chroot", None) + self.package_type = config.get("package_type", None) + self.clean_image = config.get("clean_image", False) + self.clean_kernel = config.get("clean_kernel", False) + self.clean_assets = config.get("clean_assets", False) + self.rom_only = config.get("rom_only", False) + self.conan_upload = config.get("conan_upload", False) + self.ksu = config.get("ksu", False) + + @property + def dir_bundle_conan(self) -> Path: + """Determine path to Conan's local cache.""" + res = "" + if os.getenv("CONAN_USER_HOME"): + res = Path(os.getenv("CONAN_USER_HOME")) + else: + res = Path(os.getenv("HOME"), ".conan") + return res + + @property + def wrapper_cmd(self) -> str: + """Return the launch command for the wrapper.""" + # prepare launch command + cmd = f"python3 {Path('wrapper', 'bridge.py')}" + arguments = { + "--build-module": self.build_module, + "--codename": self.codename, + "--base": self.base, + "--lkv": self.lkv, + "--chroot": self.chroot, + "--package-type": self.package_type, + "--rom-only": self.rom_only, + "--ksu": self.ksu, + "--clean-kernel": self.clean_kernel, + "--clean-assets": self.clean_assets, + } + # extend the command with given arguments + for arg, value in arguments.items(): + # arguments that have a string value + if value not in (None, False, True): + cmd += f" {arg}={value}" + # arguments that act like boolean switches + elif value: + cmd += f" {arg}" + # extend the command with the selected packaging option + if self.build_module == "bundle": + if self.package_type in ("slim", "full"): + cmd += f" && chmod 777 -R {Path(self.wdir_container, self.dir_bundle)}" + else: + cmd += " && chmod 777 -R /root/.conan" + return cmd + + @property + def container_options(self) -> list[str]: + """Form the arguments for container launch.""" + # declare the base + options = [ + "-i", + "--rm", + "-e KVERSION={}".format(os.getenv("KVERSION")), + "-e LOGLEVEL={}".format(os.getenv("LOGLEVEL")), + "-w {}".format(self.wdir_container), + ] + # mount directories + match self.build_module: + case "kernel": + options.append( + '-v {}/{}:{}/{}'.format( + cfg.DIR_ROOT, + self.dir_kernel, + self.wdir_container, + self.dir_kernel + ) + ) + case "assets": + options.append( + '-v {}/{}:{}/{}'.format( + cfg.DIR_ROOT, + self.dir_assets, + self.wdir_container, + self.dir_assets + ) + ) + case "bundle": + match self.package_type: + case "slim" | "full": + options.append( + '-v {}/{}:{}/{}'.format( + cfg.DIR_ROOT, + self.dir_bundle, + self.wdir_container, + self.dir_bundle + ) + ) + case "conan": + if self.conan_upload: + options.append('-e CONAN_UPLOAD_CUSTOM=1') + # determine the path to local Conan cache and check if it exists + if self.dir_bundle_conan.is_dir(): + options.append(f'-v {self.dir_bundle_conan}:/"/root/.conan"') + else: + msg.error("Could not find Conan local cache on the host machine.") + return options + + def create_dirs(self) -> None: + """Create required directories for volume mounting.""" + match self.build_module: + case "kernel": + kdir = Path(cfg.DIR_KERNEL) + if not kdir.is_dir(): + os.mkdir(kdir) + case "assets": + assetsdir = Path(cfg.DIR_ASSETS) + if not assetsdir.is_dir(): + os.mkdir(assetsdir) + case "bundle": + if self.package_type in ("slim", "full"): + # mount directory with release artifacts + bdir = Path(cfg.DIR_BUNDLE) + shutil.rmtree(bdir, ignore_errors=True) + os.mkdir(bdir) + + def build(self) -> None: + """Build the image.""" + print("\n") + alias = self.benv.capitalize() + msg.note(f"Building the {alias} image..") + os.chdir(self.wdir_local) + # NOTE: this will crash in GitLab CI/CD (Docker-in-Docker), requires a workaround + cmd = "{} build . -f {} -t {} --load".format( + self.benv, + self.wdir_local / 'Dockerfile', + self.name_image + ) + ccmd.launch(cmd) + msg.done("Done!") + print("\n") + + def run(self) -> None: + self.build() + # form the final command to create container + cmd = '{} run {} {} /bin/bash -c "{}"'.format( + self.benv, + " ".join(self.container_options), + self.name_image, + self.wrapper_cmd + ) + # prepare directories + self.create_dirs() + ccmd.launch(cmd) + # navigate to root directory and clean image from host machine + os.chdir(self.dir_init) + if self.clean_image: + ccmd.launch(f"{self.benv} rmi {self.name_image}") diff --git a/wrapper/manifests/devices.json b/wrapper/manifests/devices.json index 4c8a68f..1715d31 100644 --- a/wrapper/manifests/devices.json +++ b/wrapper/manifests/devices.json @@ -94,5 +94,27 @@ "commit": "" } } + }, + "lemonade": { + "5.4": { + "pa": { + "type": "git", + "path": "android_kernel_oneplus_sm8350", + "url": "https://github.com/AOSPA/android_kernel_oneplus_sm8350", + "branch": "topaz", + "commit": "" + } + } + }, + "lemonadep": { + "5.4": { + "pa": { + "type": "git", + "path": "android_kernel_oneplus_sm8350", + "url": "https://github.com/AOSPA/android_kernel_oneplus_sm8350", + "branch": "topaz", + "commit": "" + } + } } } diff --git a/wrapper/manifests/tools.json b/wrapper/manifests/tools.json index f1f66ae..ea35b46 100644 --- a/wrapper/manifests/tools.json +++ b/wrapper/manifests/tools.json @@ -29,7 +29,7 @@ "type": "git", "path": "KernelSU", "url": "https://github.com/tiann/KernelSU", - "branch": "v0.7.1", + "branch": "v0.7.6", "commit": "" } } diff --git a/wrapper/models/__init__.py b/wrapper/models/__init__.py index a9e0fd0..098f099 100644 --- a/wrapper/models/__init__.py +++ b/wrapper/models/__init__.py @@ -1,3 +1,3 @@ -from .kernel import KernelBuilder -from .assets import AssetCollector -from .bundle import BundleCreator +from .kernel_builder import KernelBuilder +from .bundle_creator import BundleCreator +from .assets_collector import AssetsCollector diff --git a/wrapper/models/assets.py b/wrapper/models/assets_collector.py similarity index 99% rename from wrapper/models/assets.py rename to wrapper/models/assets_collector.py index 41b2692..26f5b0f 100755 --- a/wrapper/models/assets.py +++ b/wrapper/models/assets_collector.py @@ -11,8 +11,8 @@ from configs import Config as cfg -class AssetCollector: - """Asset collector.""" +class AssetsCollector: + """Assets collector.""" _root: Path = cfg.DIR_ROOT _dir_assets: Path = Path(_root, cfg.DIR_ASSETS) diff --git a/wrapper/models/bundle.py b/wrapper/models/bundle_creator.py similarity index 95% rename from wrapper/models/bundle.py rename to wrapper/models/bundle_creator.py index 13bdffc..4885de4 100644 --- a/wrapper/models/bundle.py +++ b/wrapper/models/bundle_creator.py @@ -9,8 +9,8 @@ import tools.commands as ccmd import tools.fileoperations as fo -from models.kernel import KernelBuilder -from models.assets import AssetCollector +from models.kernel_builder import KernelBuilder +from models.assets_collector import AssetsCollector from configs import Config as cfg @@ -59,9 +59,10 @@ def run(self) -> None: self._root / kdir / kfn, self._root / bdir / kfn ) - # copy the assets + # move the assets for afn in os.listdir(adir): - shutil.copy( + # here, because of their size assets are moved and not copied + shutil.move( self._root / adir / afn, self._root / bdir / afn ) @@ -118,7 +119,7 @@ def _collect_assets(self, rom_name: str, chroot: str) -> None: :param rom_name: Name of the ROM. :param chroot: Type of chroot. """ - AssetCollector( + AssetsCollector( codename = self._codename, base = rom_name, chroot = chroot, diff --git a/wrapper/models/kernel.py b/wrapper/models/kernel_builder.py similarity index 98% rename from wrapper/models/kernel.py rename to wrapper/models/kernel_builder.py index d46cf56..c73850d 100755 --- a/wrapper/models/kernel.py +++ b/wrapper/models/kernel_builder.py @@ -433,8 +433,6 @@ def _patch_ksu(self) -> None: with open(defconfig, "a") as f: extra_configs = ( "CONFIG_KSU=y", - "CONFIG_KSU_DEBUG=y", - "CONFIG_OVERLAY_FS=y", "CONFIG_MODULES=y", "CONFIG_MODULE_UNLOAD=y", "CONFIG_MODVERSIONS=y", @@ -595,10 +593,9 @@ def _create_zip(self) -> None: # define kernel versions: Linux and internal ver_base = self._linux_kernel_version ver_int = os.getenv("KVERSION") - # form the final ZIP file - name_base = f"{os.getenv('KNAME', 'zero')}-{self._ucodename}-{self._base}" - name_midd = f"{name_base}-ksu" if self._ksu else name_base - name_full = f"{name_midd}-{ver_base}-{ver_int}" + # create the final ZIP file + name_suffix = "-ksu" if self._ksu else "" + name_full = f"{os.getenv('KNAME', 'zero')}-{ver_int}-{self._ucodename}-{self._base}-{ver_base}{name_suffix}" kdir = self._root / cfg.DIR_KERNEL if not kdir.is_dir(): os.mkdir(kdir) diff --git a/wrapper/modifications/lemonade/placeholder b/wrapper/modifications/lemonade/placeholder new file mode 100644 index 0000000..e69de29 diff --git a/wrapper/modifications/lemonadep/placeholder b/wrapper/modifications/lemonadep/placeholder new file mode 100644 index 0000000..e69de29 diff --git a/wrapper/tools/commands.py b/wrapper/tools/commands.py index 8a9716d..f41cad6 100755 --- a/wrapper/tools/commands.py +++ b/wrapper/tools/commands.py @@ -14,8 +14,8 @@ def launch( A custom subprocess wrapper to launch commands. :param cmd: A command to launch. - :param loglvl: Log level. :param get_output: A switch to get the piped output of the command. + :param loglvl: Log level. """ # determine stdout and check some of the cases cstdout = subprocess.DEVNULL if loglvl == "quiet" else os.getenv("OSTREAM", None) @@ -33,7 +33,7 @@ def launch( result = subprocess.run(cmd, shell=True, check=True, stdout=cstdout, stderr=subprocess.STDOUT) # return only output if required if get_output is True: - return result.stdout.decode('utf-8').splitlines()[0] + return result.stdout.decode('utf-8').splitlines() except Exception: msg.error(f"Error executing command: {cmd}") # if output stream is a file -- close it diff --git a/wrapper/tools/fileoperations.py b/wrapper/tools/fileoperations.py index a78faf0..15a840b 100644 --- a/wrapper/tools/fileoperations.py +++ b/wrapper/tools/fileoperations.py @@ -43,15 +43,12 @@ def download(url: str) -> None: msg.note(f"Downloading {fn} ..") print(f" URL: {url}") try: - # URL for TWRP is weird, have to adjust the query - if "twrp" in url.lower(): - with requests.get(url, stream=True, headers={"referer": url}) as r: - r.raise_for_status() - with open(fn, 'wb') as f: - for chunk in r.iter_content(chunk_size=8192): - f.write(chunk) + if "sourceforge" in url: + msg.note("Sorceforge URL detected, using wget..") + fn = url.split("/download")[0].split("/")[-1] + ccmd.launch(f"wget -O {fn} {url}") else: - with requests.get(url, stream=True) as r: + with requests.get(url, stream=True, headers={"referer": url}) as r: r.raise_for_status() with open(fn, 'wb') as f: for chunk in r.iter_content(chunk_size=8192): diff --git a/wrapper/utils/resources.py b/wrapper/utils/resources.py index 49ee82d..e2c0457 100644 --- a/wrapper/utils/resources.py +++ b/wrapper/utils/resources.py @@ -102,12 +102,9 @@ def download(self) -> None: msg.note(f"Found an existing path: {path}") case _: msg.error("Invalid resource type detected. Use only: generic, git.") - - def export_path(self) -> None: - """Add path to PATH. - :param path: A path that is being exported to PATH. - """ + def export_path(self) -> None: + """Add path to PATH.""" for elem in self.paths: p = self.paths[elem]["path"] pathenv = str(f"{p}/bin/")