import os from pathlib import Path from PyInstaller.utils.hooks import collect_all, copy_metadata # Spec CLI frozen — EXE de PRODUCTION (anonymisation fichier unique sans GUI). # Même moteur / mêmes datas que anonymisation_onefile.spec, mais : # - entrypoint = scripts/anonymize_cli.py (CLI production, pas launcher.py) # Contrat : Anonymisation-CLI.exe # Modèle CamemBERT-bio ONNX OBLIGATOIRE (fail-closed, code 3 si absent). # - console=True (CLI), pas de Splash # - name = Anonymisation-CLI -> ne remplace pas dist/Anonymisation.exe # (Le harnais perf D-19 reste scripts/anonymize_batch_cli.py, non buildé ici.) block_cipher = None project_dir = Path(globals().get("SPECPATH", os.getcwd())).resolve() def _data_entry(relative_path: str, target_dir: str | None = None): src = project_dir / relative_path if not src.exists(): return None return (str(src), target_dir or relative_path) binaries = [] datas = [] for relative_path, target_dir in [ ("config", "config"), ("data/bdpm", "data/bdpm"), ("data/edsnlp", "data/edsnlp"), ("data/finess", "data/finess"), ("data/insee", "data/insee"), ("models/camembert-bio-deid/onnx", "models/camembert-bio-deid/onnx"), ("detectors", "detectors"), ("scripts", "scripts"), ("assets", "assets"), ]: entry = _data_entry(relative_path, target_dir) if entry is not None: datas.append(entry) for relative_path in [ "data/stopwords_manuels.txt", "data/villes_blacklist.txt", "data/dpi_labels_blacklist.txt", "data/companion_blacklist.txt", ]: entry = _data_entry(relative_path, "data") if entry is not None: datas.append(entry) onnxtr_cache_dir = Path(os.environ.get("ONNXTR_CACHE_DIR", Path.home() / ".cache" / "onnxtr")) required_onnxtr_weights = [ "db_resnet50-69ba0015.onnx", "crnn_vgg16_bn-743599aa.onnx", ] missing_onnxtr_weights = [] for filename in required_onnxtr_weights: src = onnxtr_cache_dir / "models" / filename if src.exists(): datas.append((str(src), "models/onnxtr/models")) else: missing_onnxtr_weights.append(str(src)) if missing_onnxtr_weights: raise FileNotFoundError( "Poids OCR OnnxTR manquants pour le build frozen : " + ", ".join(missing_onnxtr_weights) + ". Précharger OnnxTR (lancer une OCR une fois) ou définir ONNXTR_CACHE_DIR avant PyInstaller." ) hiddenimports = [ "anonymizer_core_refactored_onnx", "admin_rules", "config_defaults", "profile_defaults", "gui_batch_paths", "manual_masking", "pdf_mask_designer", "format_converter", "ner_manager_onnx", "camembert_ner_manager", "eds_pseudo_manager", "gliner_manager", "vlm_manager", "build_info", # OCR OnnxTR (ONNX Runtime, remplace docTR — sans torch ni doctr) "onnxtr", "onnxtr.io", "onnxtr.models", "onnxtr.models.detection", "onnxtr.models.recognition", "onnxtr.utils", "onnxtr.utils.data", # Dépendances transitives OnnxTR (hiddenimports défensifs vs omission PyInstaller) "pyclipper", "scipy.cluster.hierarchy", "scipy.special", "cv2", "edsnlp", "edsnlp.pipes", "edsnlp.pipes.ner", "edsnlp.pipes.ner.pseudo", "spacy", "spacy.lang.fr", "gliner", "onnxruntime", "transformers", "tokenizers", "pdfplumber", "fitz", "PIL", "yaml", "loguru", "regex", ] def _collect_optional_package(package_name: str): try: package_datas, package_binaries, package_hiddenimports = collect_all(package_name) datas.extend(package_datas) binaries.extend(package_binaries) hiddenimports.extend(package_hiddenimports) try: datas.extend(copy_metadata(package_name, recursive=True)) except Exception: pass except Exception: pass for _package_name in [ "edsnlp", "spacy", "thinc", "blis", "srsly", "catalogue", "confection", "cymem", "preshed", "murmurhash", "gliner", "loguru", ]: _collect_optional_package(_package_name) # P0-3 (Plan 3) : exclusion dure de la pile torch. Le core fait un # `import torch` lazy (try/except no-op) dans _configure_torch_threads que # l'analyse statique suivrait ; en frozen l'ImportError est attendue et gérée. EXCLUDED_TORCH_STACK = [ "torch", "torchvision", "optimum", "doctr", ] a = Analysis( [str(project_dir / "scripts" / "anonymize_cli.py")], pathex=[str(project_dir)], binaries=binaries, datas=datas, hiddenimports=hiddenimports, excludes=EXCLUDED_TORCH_STACK, cipher=block_cipher, noarchive=False, ) pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) exe = EXE( pyz, a.scripts, a.binaries, a.zipfiles, a.datas, [], name="Anonymisation-CLI", debug=False, strip=False, upx=False, console=True, )