mirror of
https://github.com/okhsunrog/vpnhide.git
synced 2026-04-28 06:31:27 +00:00
Merge pull request #105 from okhsunrog/ci/speed-up-gradle-jobs
ci: speed up gradle jobs + add ruff for python scripts
This commit is contained in:
commit
7fd63f5e22
9 changed files with 120 additions and 50 deletions
78
.github/workflows/ci.yml
vendored
78
.github/workflows/ci.yml
vendored
|
|
@ -38,6 +38,39 @@ jobs:
|
|||
- name: Mark workspace safe
|
||||
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
|
||||
# Python lint + format (ruff). Fast (~100 ms on 1800 LoC) so it
|
||||
# runs first — fails before the slow Rust/Gradle steps.
|
||||
- name: ruff format
|
||||
uses: astral-sh/ruff-action@v4.0.0
|
||||
with:
|
||||
args: format --check
|
||||
- name: ruff check
|
||||
uses: astral-sh/ruff-action@v4.0.0
|
||||
with:
|
||||
args: check
|
||||
|
||||
# Cache cargo deps + target dirs for clippy/test. Same key shape as
|
||||
# zygisk + lsposed jobs — when those run on the same Cargo.lock the
|
||||
# restore-keys fallback shares warm artifacts across jobs.
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
/usr/local/cargo/registry
|
||||
/usr/local/cargo/git
|
||||
zygisk/target
|
||||
lsposed/native/target
|
||||
key: cargo-${{ runner.os }}-lint-${{ hashFiles('zygisk/Cargo.lock', 'lsposed/native/Cargo.lock') }}
|
||||
restore-keys: cargo-${{ runner.os }}-lint-
|
||||
|
||||
# Gradle cache (deps + configuration cache + wrapper). cache-read-only
|
||||
# on PRs so only main pushes write — keeps the cache from churning on
|
||||
# every PR's branch-scoped key.
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
with:
|
||||
cache-read-only: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Codegen
|
||||
- name: Verify generated iface lists are up to date
|
||||
run: |
|
||||
|
|
@ -74,17 +107,12 @@ jobs:
|
|||
# Kotlin
|
||||
- name: ktlint
|
||||
run: ktlint "lsposed/**/*.kt"
|
||||
# Gobley's cargo plugin reads ANDROID_NDK_ROOT (not _HOME) to find the
|
||||
# NDK at gradle configure time. The CI image only sets _HOME; export
|
||||
# _ROOT here until the next image rebuild bakes it in.
|
||||
- name: Android lint
|
||||
run: |
|
||||
export ANDROID_NDK_ROOT="$ANDROID_NDK_HOME"
|
||||
cd lsposed && ./gradlew --no-daemon :app:lint
|
||||
- name: Kotlin unit tests
|
||||
run: |
|
||||
export ANDROID_NDK_ROOT="$ANDROID_NDK_HOME"
|
||||
cd lsposed && ./gradlew --no-daemon :app:testDebugUnitTest
|
||||
# Single Gradle invocation: lint + tests share one configuration
|
||||
# phase + warm daemon. Configures Gobley's cargo plugin once instead
|
||||
# of twice. ANDROID_NDK_ROOT is baked into the CI image
|
||||
# (Dockerfile ENV), no manual export needed.
|
||||
- name: Android lint + Kotlin unit tests
|
||||
run: cd lsposed && ./gradlew :app:lint :app:testDebugUnitTest
|
||||
|
||||
kmod:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
@ -191,6 +219,13 @@ jobs:
|
|||
key: cargo-${{ runner.os }}-lsposed-${{ hashFiles('lsposed/native/Cargo.lock') }}
|
||||
restore-keys: cargo-${{ runner.os }}-lsposed-
|
||||
|
||||
# Gradle cache (deps + configuration cache + wrapper). cache-read-only
|
||||
# on PRs so only main pushes write the cache.
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
with:
|
||||
cache-read-only: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
- name: Set up keystore
|
||||
env:
|
||||
KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
|
||||
|
|
@ -218,14 +253,23 @@ jobs:
|
|||
storeFile=$KEYSTORE_PATH
|
||||
EOF
|
||||
|
||||
# Gobley's cargo plugin reads ANDROID_NDK_ROOT (not _HOME) to find the
|
||||
# NDK at gradle configure time. The CI image only sets _HOME; export
|
||||
# _ROOT here until the next image rebuild bakes it in.
|
||||
# Release tags get the full assembleRelease (R8/ProGuard, signed APK
|
||||
# ready for the GitHub release). PRs and main pushes get assembleDebug
|
||||
# — same code paths exercised, no R8 step (~1.5–2 min faster).
|
||||
# `case` instead of `[[`: container jobs default to /bin/sh (POSIX).
|
||||
- name: Build APK
|
||||
run: |
|
||||
export ANDROID_NDK_ROOT="$ANDROID_NDK_HOME"
|
||||
cd "$GITHUB_WORKSPACE/lsposed" && ./gradlew --no-daemon assembleRelease
|
||||
cp app/build/outputs/apk/release/app-release.apk "$GITHUB_WORKSPACE/vpnhide.apk"
|
||||
cd "$GITHUB_WORKSPACE/lsposed"
|
||||
case "$GITHUB_REF" in
|
||||
refs/tags/v*)
|
||||
./gradlew assembleRelease
|
||||
cp app/build/outputs/apk/release/app-release.apk "$GITHUB_WORKSPACE/vpnhide.apk"
|
||||
;;
|
||||
*)
|
||||
./gradlew assembleDebug
|
||||
cp app/build/outputs/apk/debug/app-debug.apk "$GITHUB_WORKSPACE/vpnhide.apk"
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
|
|
|
|||
|
|
@ -103,6 +103,10 @@ CI runs the same checks. See [.github/workflows/ci.yml](../.github/workflows/ci.
|
|||
python3 scripts/codegen-interfaces.py
|
||||
git diff --quiet # must be clean
|
||||
|
||||
# Python (ruff, config in pyproject.toml). uvx runs without installing anything global.
|
||||
uvx ruff format --check
|
||||
uvx ruff check
|
||||
|
||||
# Rust
|
||||
cd zygisk && cargo fmt --check && cargo ndk -t arm64-v8a clippy -- -D warnings
|
||||
cd ../lsposed/native && cargo fmt --check && cargo ndk -t arm64-v8a clippy -- -D warnings
|
||||
|
|
@ -116,8 +120,7 @@ gcc -O2 -Wall -Werror -o /tmp/test_iface_lists kmod/test_iface_lists.c && /tmp/t
|
|||
|
||||
# Kotlin
|
||||
ktlint "lsposed/**/*.kt"
|
||||
cd lsposed && ./gradlew --no-daemon :app:lint
|
||||
cd lsposed && ./gradlew --no-daemon :app:testDebugUnitTest
|
||||
cd lsposed && ./gradlew :app:lint :app:testDebugUnitTest
|
||||
```
|
||||
|
||||
## Build versions
|
||||
|
|
|
|||
|
|
@ -45,8 +45,11 @@ import sys
|
|||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
|
||||
from build_lib import get_build_version, make_zip, version_sort_key # type: ignore[import-not-found]
|
||||
|
||||
from build_lib import ( # type: ignore[import-not-found]
|
||||
get_build_version,
|
||||
make_zip,
|
||||
version_sort_key,
|
||||
)
|
||||
|
||||
# Module file name on disk after `make`.
|
||||
KMOD_KO = "vpnhide_kmod.ko"
|
||||
|
|
@ -128,13 +131,9 @@ def native_build_one(
|
|||
|
||||
module_prop = staging / "module.prop"
|
||||
content = module_prop.read_text(encoding="utf-8")
|
||||
content = re.sub(
|
||||
r"^version=.*", f"version=v{build_version}", content, flags=re.MULTILINE
|
||||
)
|
||||
content = re.sub(r"^version=.*", f"version=v{build_version}", content, flags=re.MULTILINE)
|
||||
if re.search(r"^gkiVariant=", content, flags=re.MULTILINE):
|
||||
content = re.sub(
|
||||
r"^gkiVariant=.*", f"gkiVariant={kmi}", content, flags=re.MULTILINE
|
||||
)
|
||||
content = re.sub(r"^gkiVariant=.*", f"gkiVariant={kmi}", content, flags=re.MULTILINE)
|
||||
else:
|
||||
content = content.rstrip() + f"\ngkiVariant={kmi}\n"
|
||||
update_json_url = (
|
||||
|
|
@ -219,9 +218,7 @@ def find_runtime() -> tuple[str, bool]:
|
|||
sys.exit(1)
|
||||
|
||||
|
||||
def container_build_one(
|
||||
runtime: str, is_podman: bool, repo_root: Path, kmi: str
|
||||
) -> None:
|
||||
def container_build_one(runtime: str, is_podman: bool, repo_root: Path, kmi: str) -> None:
|
||||
image = f"ghcr.io/ylarod/ddk-min:{kmi}-{DDK_IMAGE_TAG}"
|
||||
mount_spec = f"{repo_root}:/work"
|
||||
cmd = [runtime, "run", "--rm"]
|
||||
|
|
@ -314,10 +311,7 @@ def main() -> int:
|
|||
parser.add_argument(
|
||||
"--kdir",
|
||||
type=str,
|
||||
help=(
|
||||
"Kernel source directory (overrides KDIR/KERNEL_SRC). Implies "
|
||||
"native mode."
|
||||
),
|
||||
help=("Kernel source directory (overrides KDIR/KERNEL_SRC). Implies native mode."),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--clang-dir",
|
||||
|
|
|
|||
21
pyproject.toml
Normal file
21
pyproject.toml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Ruff config — applies to every .py in the repo.
|
||||
# All our scripts are stdlib-only by policy (see scripts/build_lib.py
|
||||
# header), so ruff is a dev/CI tool here, never a runtime dependency.
|
||||
|
||||
[tool.ruff]
|
||||
target-version = "py312" # CI image (Ubuntu 24.04) ships Python 3.12
|
||||
line-length = 100
|
||||
extend-exclude = [
|
||||
# third-party / vendored — not our code, don't lint
|
||||
"zygisk/third_party",
|
||||
# cargo build outputs that may contain stray .py files
|
||||
"**/target",
|
||||
# local-only worktrees / agent state, untracked
|
||||
".claude",
|
||||
]
|
||||
|
||||
[tool.ruff.lint]
|
||||
# Conservative rule set: pycodestyle (E/W) + pyflakes (F) + isort (I)
|
||||
# + bugbear foot-guns (B) + pyupgrade (UP) + simplifications (SIM).
|
||||
# No pylint-style noise, no mypy.
|
||||
select = ["E", "F", "W", "I", "B", "UP", "SIM"]
|
||||
|
|
@ -117,8 +117,8 @@ def parse_fragment(path: Path) -> dict:
|
|||
if date_match.start() > en_match.start():
|
||||
raise ValueError(f"{path.name}: date line must appear before the language sections")
|
||||
|
||||
en_body = text[en_match.end():ru_match.start()].strip()
|
||||
ru_body = text[ru_match.end():].strip()
|
||||
en_body = text[en_match.end() : ru_match.start()].strip()
|
||||
ru_body = text[ru_match.end() :].strip()
|
||||
if not en_body:
|
||||
raise ValueError(f"{path.name}: empty English section")
|
||||
if not ru_body:
|
||||
|
|
|
|||
|
|
@ -464,7 +464,7 @@ def emit_rust(rules: list[Rule], tests: list[TestVector]) -> str:
|
|||
expected = "true" if t.is_vpn else "false"
|
||||
lines.append(
|
||||
f" assert_eq!(matches_vpn({rust_byte_lit(t.name)}), {expected}, "
|
||||
f"\"matches_vpn({t.name!r})\");"
|
||||
f'"matches_vpn({t.name!r})");'
|
||||
)
|
||||
lines.append(" }")
|
||||
lines.append("}")
|
||||
|
|
|
|||
|
|
@ -114,8 +114,7 @@ def main() -> int:
|
|||
for past in data.get("history", []):
|
||||
if past.get("version") == version:
|
||||
console.print(
|
||||
f"[red]error:[/red] v{version} already exists in history[]. "
|
||||
"Pick a new version.",
|
||||
f"[red]error:[/red] v{version} already exists in history[]. Pick a new version.",
|
||||
)
|
||||
return 1
|
||||
|
||||
|
|
@ -169,11 +168,14 @@ def main() -> int:
|
|||
|
||||
console.print()
|
||||
console.print("[bold]Next steps:[/bold]")
|
||||
console.print(f" git commit -am \"chore: release v{version}\"")
|
||||
console.print(f' git commit -am "chore: release v{version}"')
|
||||
console.print(f" git tag v{version} && git push && git push origin v{version}")
|
||||
console.print(" # CI builds artifacts and creates a DRAFT release — review on the Releases page, click Publish")
|
||||
console.print(
|
||||
" # CI builds artifacts and creates a DRAFT release — "
|
||||
"review on the Releases page, click Publish"
|
||||
)
|
||||
console.print(" ./scripts/update-json.sh")
|
||||
console.print(f" git commit -am \"chore: update-json for v{version}\"")
|
||||
console.print(f' git commit -am "chore: update-json for v{version}"')
|
||||
console.print(" git push")
|
||||
return 0
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,7 @@ def github_token() -> str | None:
|
|||
if token := os.environ.get("GITHUB_TOKEN"):
|
||||
return token
|
||||
try:
|
||||
out = subprocess.run(
|
||||
["gh", "auth", "token"], capture_output=True, text=True, check=True
|
||||
)
|
||||
out = subprocess.run(["gh", "auth", "token"], capture_output=True, text=True, check=True)
|
||||
return out.stdout.strip() or None
|
||||
except (FileNotFoundError, subprocess.CalledProcessError):
|
||||
return None
|
||||
|
|
@ -34,9 +32,7 @@ headers = {"Accept": "application/vnd.github+json"}
|
|||
if token := github_token():
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
|
||||
resp = httpx.get(
|
||||
"https://api.github.com/repos/okhsunrog/vpnhide/releases", headers=headers
|
||||
)
|
||||
resp = httpx.get("https://api.github.com/repos/okhsunrog/vpnhide/releases", headers=headers)
|
||||
resp.raise_for_status()
|
||||
releases = resp.json()
|
||||
|
||||
|
|
@ -53,7 +49,13 @@ for release in releases:
|
|||
total = sum(a["download_count"] for a in assets)
|
||||
grand_total += total
|
||||
|
||||
table = Table(title=f"{release['tag_name']} ({total} downloads)", title_style="bold", show_header=False, box=None, padding=(0, 1))
|
||||
table = Table(
|
||||
title=f"{release['tag_name']} ({total} downloads)",
|
||||
title_style="bold",
|
||||
show_header=False,
|
||||
box=None,
|
||||
padding=(0, 1),
|
||||
)
|
||||
table.add_column("Asset", style="cyan")
|
||||
table.add_column("Count", justify="right", style="yellow")
|
||||
table.add_column("Bar", style="green", no_wrap=True)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,11 @@ import sys
|
|||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
|
||||
from build_lib import get_build_version, make_zip, version_sort_key # type: ignore[import-not-found]
|
||||
from build_lib import ( # type: ignore[import-not-found]
|
||||
get_build_version,
|
||||
make_zip,
|
||||
version_sort_key,
|
||||
)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue