Created
October 19, 2026 03:41
-
-
Save nquangit/0de0720baef746d6b19c8c7ea079200c to your computer and use it in GitHub Desktop.
Tìm các phiên bản frida-tools tương thích với phiên bản frida được nhập, rồi cho phép bạn chọn phiên bản để cài.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """Find frida-tools releases whose declared frida requirement accepts a target version. | |
| Uses PyPI release JSON first; when Requires-Dist is absent, reads the release-tagged | |
| setup.py from the official frida/frida-tools GitHub repository. Results are cached. | |
| """ | |
| import argparse | |
| import ast | |
| import json | |
| import shlex | |
| import subprocess | |
| import sys | |
| import urllib.error | |
| import urllib.request | |
| from concurrent.futures import ThreadPoolExecutor | |
| from pathlib import Path | |
| try: | |
| from packaging.requirements import InvalidRequirement, Requirement | |
| from packaging.version import InvalidVersion, Version | |
| except ImportError: | |
| sys.exit("Missing dependency: run 'python3 -m pip install packaging' first") | |
| PYPI = "https://pypi.org/pypi/frida-tools" | |
| GITHUB_RAW = "https://raw.githubusercontent.com/frida/frida-tools/{}/setup.py" | |
| CACHE = Path.home() / ".cache" / "frida-tools-resolver" | |
| def get_text(url): | |
| request = urllib.request.Request(url, headers={"User-Agent": "frida-tools-resolver/1.0"}) | |
| with urllib.request.urlopen(request, timeout=12) as response: | |
| return response.read().decode("utf-8") | |
| def get_json(url): | |
| return json.loads(get_text(url)) | |
| def dependencies_from_setup(source): | |
| """Extract literal setup(install_requires=[...]) without executing setup.py.""" | |
| tree = ast.parse(source) | |
| for node in ast.walk(tree): | |
| if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == "setup": | |
| for keyword in node.keywords: | |
| if keyword.arg == "install_requires": | |
| deps = ast.literal_eval(keyword.value) | |
| if isinstance(deps, (list, tuple)): | |
| return deps | |
| raise ValueError("No literal setup(install_requires=...) found") | |
| def release_dependencies(version): | |
| CACHE.mkdir(parents=True, exist_ok=True) | |
| cached = CACHE / (str(version) + ".json") | |
| if cached.exists(): | |
| try: | |
| obj = json.loads(cached.read_text(encoding="utf-8")) | |
| return obj["dependencies"], obj["source"] | |
| except (ValueError, KeyError, OSError): | |
| pass | |
| metadata = get_json(f"{PYPI}/{version}/json")["info"] | |
| deps = metadata.get("requires_dist") | |
| source = "PyPI Requires-Dist" | |
| if deps is None: | |
| deps = dependencies_from_setup(get_text(GITHUB_RAW.format(version))) | |
| source = "GitHub setup.py" | |
| cached.write_text(json.dumps({"dependencies": deps, "source": source}), encoding="utf-8") | |
| return deps, source | |
| def check(version, target): | |
| try: | |
| deps, source = release_dependencies(version) | |
| frida_reqs = [] | |
| for raw in deps: | |
| req = Requirement(raw) | |
| if req.name.lower().replace("_", "-") == "frida": | |
| frida_reqs.append(req) | |
| if not frida_reqs: | |
| return version, None, "No declared frida requirement" | |
| matched = all(target in req.specifier for req in frida_reqs) | |
| constraints = ", ".join(str(req.specifier) or "(any)" for req in frida_reqs) | |
| return version, matched, f"frida{constraints} [{source}]" | |
| except (urllib.error.URLError, TimeoutError, ValueError, SyntaxError, TypeError, InvalidRequirement, OSError) as exc: | |
| return version, None, f"Cannot verify: {exc}" | |
| def main(): | |
| parser = argparse.ArgumentParser(description="Find frida-tools versions for a specific frida version") | |
| parser.add_argument("frida", help="Exact frida version, e.g. 16.6.6") | |
| parser.add_argument("--limit", type=int, default=5, help="Number of compatible releases to print (default: 5)") | |
| parser.add_argument("--all", action="store_true", help="Check all published stable releases (more network calls)") | |
| parser.add_argument("--select", type=int, default=1, help="Use the Nth compatible result for the suggested/install command (default: 1)") | |
| parser.add_argument("--install", action="store_true", help="Install frida and the selected compatible frida-tools") | |
| args = parser.parse_args() | |
| if args.limit < 1: | |
| parser.error("--limit must be positive") | |
| if args.select < 1: | |
| parser.error("--select must be positive") | |
| if not args.all and args.select > args.limit: | |
| parser.error("--select cannot exceed --limit unless --all is used") | |
| try: | |
| target = Version(args.frida) | |
| except InvalidVersion: | |
| parser.error("Invalid frida version") | |
| try: | |
| releases = get_json(f"{PYPI}/json")["releases"] | |
| except (urllib.error.URLError, TimeoutError, ValueError, KeyError) as exc: | |
| sys.exit(f"Cannot get PyPI version list: {exc}") | |
| versions = [] | |
| for name, files in releases.items(): | |
| try: | |
| version = Version(name) | |
| except InvalidVersion: | |
| continue | |
| if version.is_prerelease or version.is_devrelease or not files: | |
| continue | |
| if all(f.get("yanked", False) for f in files): | |
| continue | |
| versions.append(version) | |
| versions.sort(reverse=True) | |
| matches = [] | |
| unverifiable = [] | |
| # Iterate in descending version order, prefetching only a small batch at a time. | |
| with ThreadPoolExecutor(max_workers=8) as pool: | |
| for i in range(0, len(versions), 8): | |
| for version, match, detail in pool.map(lambda v: check(v, target), versions[i:i + 8]): | |
| if match is True: | |
| matches.append(version) | |
| if args.all or len(matches) <= args.limit: | |
| print(f"{str(version):<12} {detail}") | |
| elif match is None: | |
| unverifiable.append(str(version)) | |
| if not args.all and len(matches) >= args.limit: | |
| break | |
| if not args.all and len(matches) >= args.limit: | |
| break | |
| if unverifiable: | |
| print(f"\nCould not verify {len(unverifiable)} release(s): {', '.join(unverifiable[:10])}" + (" ..." if len(unverifiable) > 10 else ""), file=sys.stderr) | |
| if not matches: | |
| sys.exit("No verified compatible frida-tools release found.") | |
| if args.select > len(matches): | |
| sys.exit(f"Only {len(matches)} verified matches found; cannot select #{args.select}") | |
| chosen = str(matches[args.select - 1]) | |
| cmd = [sys.executable, "-m", "pip", "install", "--upgrade", f"frida=={target}", f"frida-tools=={chosen}"] | |
| display_cmd = subprocess.list2cmdline(cmd) if sys.platform == "win32" else shlex.join(cmd) | |
| print("\nSuggested command:\n" + display_cmd) | |
| print("Note: this checks frida-tools' declared Frida requirement, not Python/wheel/platform availability.") | |
| if args.install: | |
| raise SystemExit(subprocess.call(cmd)) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment