Files
odfstudio/predict_rebuild.py
T
elioperdreau 831bb85a3a Add --enable-odfstudio configure option (disabled by default)
Change-Id: I9083a0f6a59506029bcbff050b74d93a65b0e401
2026-08-26 18:35:21 +02:00

212 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
[fr] Script pour prédire quels fichiers C++ de LibreOffice seront recompilés
après la modification d’un fichier source ou header.
Inclut un mode "estimation rapide".
[en] Script to predict which LibreOffice C++ source files will be recompiled
after a source or header file is modified.
Includes a "quick estimation" mode.
Usage:
./predict_rebuild.py path/to/modified_file
./predict_rebuild.py --quick path/to/modified_file
"""
import os
import sys
import re
from collections import defaultdict, deque
# [fr] Racine du dépôt LibreOffice (par défaut le répertoire courant)
# [en] Root of the LibreOffice source tree (default: current directory)
ROOT = "."
# [fr] Expression régulière pour extraire les directives #include
# [en] Regular expression to extract #include directives
include_re = re.compile(r'#\s*include\s*[<"]([^">]+)[">]')
def find_source_files():
"""
[fr] Recherche récursivement tous les fichiers sources pertinents
(.cxx, .hxx, .hpp, .h) dans l’arborescence.
[en] Recursively finds all relevant source files
(.cxx, .hxx, .hpp, .h) in the source tree.
"""
files = []
for root, _, filenames in os.walk(ROOT):
for name in filenames:
if name.endswith((".cxx", ".hxx", ".hpp", ".h")):
files.append(os.path.join(root, name))
return files
def parse_includes(filepath):
"""
[fr] Extrait toutes les directives #include d’un fichier source.
[en] Extracts all #include directives from a source file.
"""
includes = []
try:
with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
for line in f:
match = include_re.search(line)
if match:
includes.append(match.group(1))
except OSError:
pass
return includes
def build_dependency_graph(files):
"""
[fr] Construit un graphe de dépendances (forward + reverse).
[en] Builds a dependency graph (forward + reverse).
"""
forward = defaultdict(set)
reverse = defaultdict(set)
# [fr] Association "nom de fichier" -> "chemin complet"
# [en] Map "filename" -> "full path"
file_map = {}
for f in files:
file_map[os.path.basename(f)] = f
for f in files:
includes = parse_includes(f)
for inc in includes:
inc_name = os.path.basename(inc)
if inc_name in file_map:
target = file_map[inc_name]
forward[f].add(target)
reverse[target].add(f)
return forward, reverse
def compute_affected(modified_file, reverse_graph):
"""
[fr] Calcule tous les fichiers impactés via le graphe inverse.
[en] Computes all affected files using the reverse graph.
"""
affected = set()
queue = deque([modified_file])
while queue:
current = queue.popleft()
for parent in reverse_graph.get(current, []):
if parent not in affected:
affected.add(parent)
queue.append(parent)
return affected
def group_by_module(files):
"""
[fr] Regroupe les fichiers par module LibreOffice.
[en] Groups files by LibreOffice module.
"""
modules = defaultdict(list)
for f in files:
rel = os.path.relpath(f, ROOT)
module = rel.split(os.sep)[0]
modules[module].append(f)
return modules
def quick_estimation(modified_file, to_recompile, modules):
"""
[fr] Fournit une estimation rapide de l’impact du rebuild.
[en] Provides a quick estimation of rebuild impact.
Heuristique:
- FAIBLE: <=3 fichiers, 1 module
- MOYEN: 4–30 fichiers ou >=2 modules
- MASSIF: >30 fichiers ou fichier modifié dans 'include/'
"""
num_files = len(to_recompile)
num_modules = len(modules)
# [fr] Cas critique : modification dans include/
# [en] Critical case: modification in include/
if os.path.normpath(modified_file).startswith(os.path.join(ROOT, "include")):
return "🔴 MASSIF", "Header global (include/) modifié"
if num_files <= 3 and num_modules == 1:
return "🟢 FAIBLE", "Modification locale"
if num_files <= 30 and num_modules <= 3:
return "🟡 MOYEN", "Plusieurs fichiers ou modules impactés"
return "🔴 MASSIF", "Nombre élevé de fichiers ou modules"
def main():
"""
[fr] Point d’entrée principal du script.
[en] Main entry point of the script.
"""
quick_mode = False
if len(sys.argv) == 3 and sys.argv[1] == "--quick":
quick_mode = True
modified_arg = sys.argv[2]
elif len(sys.argv) == 2:
modified_arg = sys.argv[1]
else:
print("Usage: predict_rebuild.py [--quick] path/to/modified_file")
sys.exit(1)
modified = os.path.abspath(modified_arg)
if not os.path.exists(modified):
print(f"File not found: {modified}")
sys.exit(1)
print("🔍 Scanning source tree...")
files = find_source_files()
print("🧩 Building dependency graph...")
_, reverse = build_dependency_graph(files)
print(f"✏️ Modified file: {modified}")
affected = compute_affected(modified, reverse)
# [fr] Seuls les .cxx sont recompilés
# [en] Only .cxx files are recompiled
to_recompile = sorted(f for f in affected if f.endswith(".cxx"))
modules = group_by_module(to_recompile)
if quick_mode:
# [fr] Mode estimation rapide
# [en] Quick estimation mode
level, reason = quick_estimation(modified, to_recompile, modules)
print("\n⚡ Estimation rapide / Quick estimation:")
print(f" Niveau / Level : {level}")
print(f" Raison / Reason: {reason}")
print(f" Fichiers / Files : {len(to_recompile)}")
print(f" Modules : {len(modules)}")
return
# [fr] Mode détaillé (par défaut)
# [en] Detailed mode (default)
print("\n📦 Files likely to be recompiled:")
for f in to_recompile:
print(" ", f)
print("\n🗂 Modules affected:")
for m, fs in sorted(modules.items()):
print(f" {m}: {len(fs)} file(s)")
print(f"\n✅ Total: {len(to_recompile)} source file(s)")
if __name__ == "__main__":
main()