Skip to content

Instantly share code, notes, and snippets.

@nquangit
Created October 19, 2026 03:41
Show Gist options
  • Select an option

  • Save nquangit/0de0720baef746d6b19c8c7ea079200c to your computer and use it in GitHub Desktop.

Select an option

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.
#!/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