This commit is contained in:
ms
2026-10-15 12:48:24 +02:00
parent 6019eb03d4
commit f584ff2f42
2 changed files with 167 additions and 0 deletions
Executable
+165
View File
@@ -0,0 +1,165 @@
import os
import sys
import shutil
import subprocess
import tempfile
import zipfile
from pathlib import Path
from setuptools import setup, Extension
from Cython.Build import cythonize
# Dateien oder Ordner, die nicht mit Cython kompiliert werden sollen
# (werden stattdessen direkt an PyArmor übergeben)
EXCLUDE_FROM_CYTHON = {
"manage.py",
"wsgi.py",
"asgi.py",
}
def is_django_migration(path: Path) -> bool:
"""Prüft, ob eine Datei eine Django-Migrationsdatei ist."""
return "migrations" in path.parts and path.name != "__init__.py"
def prepare_source(source_input: str) -> Path:
"""Entpackt eine Zip-Datei oder kopiert den Quellordner in ein Arbeitsverzeichnis."""
src_path = Path(source_input).resolve()
temp_dir = Path(tempfile.mkdtemp(prefix="build_src_"))
if src_path.is_file() and src_path.suffix.lower() == ".zip":
print(f"[*] Entpacke Zip-Archiv '{src_path.name}' nach temporärem Pfad...")
with zipfile.ZipFile(src_path, 'r') as zip_ref:
zip_ref.extractall(temp_dir)
# Falls das Zip einen einzelnen Hauptordner enthält
entries = [p for p in temp_dir.iterdir() if p.is_dir() and not p.name.startswith(".")]
if len(entries) == 1:
return entries[0]
return temp_dir
elif src_path.is_dir():
print(f"[*] Kopiere Quellverzeichnis (Read-Only-Schutz der Originaldaten)...")
work_dir = temp_dir / src_path.name
shutil.copytree(src_path, work_dir, symlinks=True, ignore=shutil.ignore_patterns("dist", ".git", ".venv*", "__pycache__"))
return work_dir
else:
raise ValueError(f"Ungültiger Quellpfad: {source_input}")
def run_build(project_dir: Path, output_dist: Path):
if output_dist.exists():
shutil.rmtree(output_dist)
output_dist.mkdir(parents=True, exist_ok=True)
py_files = []
cython_candidates = []
pyarmor_candidates = []
# 1. Alle Dateien scannen
for root, dirs, files in os.walk(project_dir):
# Cache- und Build-Ordner überspringen
dirs[:] = [d for d in dirs if d not in ("__pycache__", ".git", "dist", "build")]
for file in files:
file_path = Path(root) / file
rel_path = file_path.relative_to(project_dir)
if file.endswith(".py"):
# manage.py und Migrationen direkt zu PyArmor leiten
if file in EXCLUDE_FROM_CYTHON or is_django_migration(rel_path):
pyarmor_candidates.append(rel_path)
else:
cython_candidates.append(rel_path)
else:
# Nicht-Python-Dateien (HTML, CSS, JSON etc.) direkt nach dist kopieren
target_file = output_dist / rel_path
target_file.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(file_path, target_file)
print(f"[*] Gefundene Python-Dateien: {len(cython_candidates) + len(pyarmor_candidates)}")
print(f" - Cython-Kandidaten: {len(cython_candidates)}")
print(f" - Direkte PyArmor-Ziele: {len(pyarmor_candidates)}")
# 2. Cython-Kompilierung durchführen
compiled_success = []
failed_cython = []
build_temp = Path(tempfile.mkdtemp(prefix="cython_build_"))
original_cwd = os.getcwd()
try:
os.chdir(project_dir)
for rel_file in cython_candidates:
ext_name = str(rel_file.with_suffix("")).replace(os.sep, ".")
try:
ext = Extension(ext_name, [str(rel_file)])
ext_modules = cythonize(
[ext],
compiler_directives={
"language_level": "3",
"always_allow_keywords": True,
"annotation_typing": False, # Verhinder Typkonflikte
},
quiet=True,
)
setup(
name=ext_name,
ext_modules=ext_modules,
script_args=["build_ext", "--inplace", f"--build-temp={build_temp}"],
)
compiled_success.append(rel_file)
except Exception as err:
print(f"[!] Cython-Fehler bei '{rel_file}', fällt auf PyArmor zurück ({err})")
failed_cython.append(rel_file)
finally:
os.chdir(original_cwd)
if build_temp.exists():
shutil.rmtree(build_temp, ignore_errors=True)
# 3. Kompilierte Binärdateien (.so / .pyd) nach dist/ übertragen
for rel_file in compiled_success:
source_folder = project_dir / rel_file.parent
target_folder = output_dist / rel_file.parent
target_folder.mkdir(parents=True, exist_ok=True)
base_name = rel_file.stem
binaries = list(source_folder.glob(f"{base_name}.*.so")) + list(source_folder.glob(f"{base_name}.*.pyd"))
for b in binaries:
shutil.move(str(b), str(target_folder / b.name))
# 4. Verbleibende Dateien (Fehlgeschlagene + Ausnahmen) via PyArmor schützen
all_pyarmor_files = pyarmor_candidates + failed_cython
if all_pyarmor_files:
print(f"[*] Verschlüssele {len(all_pyarmor_files)} Dateien mit PyArmor...")
for rel_file in all_pyarmor_files:
src_file = project_dir / rel_file
target_file = output_dist / rel_file
target_file.parent.mkdir(parents=True, exist_ok=True)
# PyArmor gen Befehl
cmd = [
sys.executable, "-m", "pyarmor.cli", "gen",
"-O", str(target_file.parent),
str(src_file)
]
res = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True)
if res.returncode != 0:
# Falls PyArmor fehlschlägt, Quelltext als Fallback kopieren
print(f"[!] PyArmor Warnung für {rel_file}: {res.stderr.strip()[:100]} - Kopiere Originaldatei")
shutil.copy2(src_file, target_file)
print(f"\n[+] Fertig! Das geschützte Projekt befindet sich in: {output_dist.resolve()}")
def main():
if len(sys.argv) < 2:
print("Verwendung: python build_dist.py <Pfad-zum-Projekt-oder-Zip-Datei>")
sys.exit(1)
input_source = sys.argv[1]
work_dir = None
try:
work_dir = prepare_source(input_source)
dist_dir = Path(input_source).parent / "dist" if Path(input_source).is_file() else Path(input_source) / "dist"
run_build(work_dir, dist_dir)
finally:
# Temporäres Arbeitsverzeichnis bereinigen
if work_dir and work_dir.parent.name.startswith("build_src_"):
shutil.rmtree(work_dir.parent, ignore_errors=True)
if __name__ == "__main__":
main()
+2
View File
@@ -0,0 +1,2 @@
python build__dist.py
# python build_dist.py d.zip