Analyse une liste de projets
import json import os import subprocess import sys import xml.etree.ElementTree as ET from datetime import date, datetime import time
from boltons.iterutils import remap
'''
jq . projet_240928_182342.json
jq .[].nom projet_240928_182342.json
jq .[].nom,.[].modules[].name projet_240928_182342.json
jq "[ .[] | {nom_module: .modules[].name} ]" projet_240928_182342.json
jq "[ .[] | . as $parent | {nom_module: .modules[].name, projet: $parent.nom} ]" projet_240928_182342.json
Pour lister le nom des produits : jq "[ .[] | . as $parent | {nom_module: .modules[].name, projet: $parent.nom} ]" projet_240929_131613.json
Pour lister la version d'angular : jq "[ .[] | . as $parent | {nom_module: .modules[].name, projet: $parent.nom, angular: .modules[].packageJson.dependencies."@angular/core"} ]" projet_240929_131613.json
Pour lister la version de java : jq "[ .[] | . as $parent | {nom_module: .modules[].name, projet: $parent.nom, java: .modules[].pom.properties."java.version"} ]" projet_240929_131613.json
Pour lister la version du projet : jq "[ .[] | . as $parent | {nom_module: .modules[].name, projet: $parent.nom, version_projet: .modules[].pom.effectivePom.version.version} ]" projet_240929_131613.json
Pour lister le groupId, l'artifactId et la version des projets : jq "[ .[] | . as $parent | {nom_module: .modules[].name, projet: $parent.nom, version_projet: ( .modules[].pom.effectivePom.version.groupId + ":" + .modules[].pom.effectivePom.version.artifactId + ":" + .modules[].pom.effectivePom.version.version ) } ]" projet_240929_131613.json
'''
class Parametres:
def __init__(self):
self.repertoires = []
self.repertoires_ignore = []
self.debug = False
class ErreurRunMvnException(Exception): pass
class ErreurRunGitException(Exception): pass
def is_git_repo(path): """Vérifie si un répertoire est un repo git.""" return os.path.isdir(os.path.join(path, ".git"))
def parse_pom(pom_path): tree = ET.parse(pom_path) root = tree.getroot()
# Espaces de noms (pour éviter les erreurs lors de la recherche d'éléments dans le fichier XML)
namespaces = {'maven': 'http://maven.apache.org/POM/4.0.0'}
# Récupérer les informations du parent s'il existe
parent = root.find('maven:parent', namespaces)
parent_info = {}
if parent is not None:
if parent.find('maven:groupId', namespaces) is not None:
parent_info['groupId'] = parent.find('maven:groupId', namespaces).text
if parent.find('maven:artifactId', namespaces) is not None:
parent_info['artifactId'] = parent.find('maven:artifactId', namespaces).text
if parent.find('maven:version', namespaces) is not None:
parent_info['version'] = parent.find('maven:version', namespaces).text
version_projet = {}
root.find('maven:parent', namespaces)
if root.find('maven:groupId', namespaces) is not None:
version_projet['groupId'] = root.find('maven:groupId', namespaces).text
if root.find('maven:artifactId', namespaces) is not None:
version_projet['artifactId'] = root.find('maven:artifactId', namespaces).text
if root.find('maven:version', namespaces) is not None:
version_projet['version'] = root.find('maven:version', namespaces).text
# Récupérer les propriétés s'il existe
properties = root.find('maven:properties', namespaces)
props = {}
if properties is not None:
for prop in properties:
props[prop.tag.split('}')[-1]] = prop.text
return parent_info, props, version_projet
def parse_pom_xml(pom_path, debug): """Parse le fichier pom.xml et extrait les informations du parent et des propriétés."""
parent_info, props, version_projet = parse_pom(pom_path)
# les dépendances
dependances = None
effectivePom = None
try:
currentDir = os.getcwd()
outpoutFile = f'{currentDir}/data/dependency.json'
command = f"mvn dependency:3.8.0:tree -DoutputType=json -DoutputFile={currentDir}/data/dependency.json -DoutputEncoding=utf8"
repo_path = os.path.dirname(pom_path)
result = subprocess.run(command, cwd=repo_path, shell=True, capture_output=True, text=True)
if debug or result.returncode != 0:
print("dependancy tree output:" + result.stdout)
print("dependancy tree error:" + result.stderr)
if result.returncode != 0:
raise ErreurRunMvnException(f"Erreur lors de l'exécution de la commande {command}: {result.stderr}")
if os.path.isfile(outpoutFile):
with open(outpoutFile, 'r', encoding='utf-8') as file:
data = json.load(file)
if data is not None:
bad_keys = {'classifier', 'type', 'classifier', 'optional'}
drop_keys = lambda path, key, value: key not in bad_keys
clean = remap(data, visit=drop_keys)
if clean is not None:
dependances = clean
# analyse effective pom
outpoutFile = f'{currentDir}/data/effectivePom.xml'
command = f"mvn help:effective-pom -Doutput={outpoutFile}"
repo_path = os.path.dirname(pom_path)
result = subprocess.run(command, cwd=repo_path, shell=True, capture_output=True, text=True)
if debug or result.returncode != 0:
print("effective-pom output:" + result.stdout)
print("effective-pom error:" + result.stderr)
if result.returncode != 0:
raise ErreurRunMvnException(f"Erreur lors de l'exécution de la commande {command}: {result.stderr}")
if os.path.isfile(outpoutFile):
parent_info_effective, props_effective, version_projet_effective = parse_pom(outpoutFile)
if parent_info_effective is not None or props_effective is not None or version_projet_effective is not None:
effectivePom = {}
if parent_info_effective is not None:
effectivePom['parent_info'] = parent_info_effective
if version_projet_effective is not None:
effectivePom['version'] = version_projet_effective
if props_effective is not None:
effectivePom['properties'] = props_effective
except ErreurRunMvnException as e:
details = e.args[0]
print(f"Erreur pour analyser le pom {pom_path} : {details}")
return parent_info, props, version_projet, dependances, effectivePom
def parse_package_json(package_path): """Parse le fichier package.json et extrait la version d'Angular/CLI s'il existe.""" mapVersion = {} with open(package_path, 'r', encoding='utf-8') as file: data = json.load(file) dependencies = data.get('dependencies', {}) devDependencies = data.get('devDependencies', {}) if dependencies is not None: mapVersion['dependencies'] = dependencies if devDependencies is not None: mapVersion['devDependencies'] = devDependencies name = data.get('name', {}) if name is not None: mapVersion['name'] = name version = data.get('version', {}) if version is not None: mapVersion['version'] = version scripts = data.get('scripts', {}) if scripts is not None: mapVersion['scripts'] = scripts
return mapVersion
def run_git_command(command, repo_path): """Exécute une commande git et retourne la sortie.""" result = subprocess.run(command, cwd=repo_path, shell=True, capture_output=True, text=True) if result.returncode != 0: raise ErreurRunGitException(f"Erreur lors de l'exécution de la commande {command}: {result.stderr}") return result.stdout
def get_branch_info(branch): """Extrait les informations d'une branche (nom, date, hash court, message de commit).""" parts = branch.split(maxsplit=5) short_commit = parts[0] date_str = parts[1] + " " + parts[2] + " " + parts[3] if len(parts) > 3 else "" # print(f"{short_commit}: {date_str}!{parts}") date = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S %z") if len(date_str) > 0 else None name = parts[4] if len(parts) > 3 else "" message = parts[5] if len(parts) > 4 else "" return short_commit, date, message, name
def has_uncommitted_changes(repo_path): """Vérifie s'il y a des fichiers non commités dans le dépôt.""" status_output = run_git_command("git status --porcelain", repo_path) if status_output.strip(): # Si la sortie n'est pas vide, il y a des changements non commités return True return False
def get_local_branche(repo_path) -> str: """Vérifie s'il y a des fichiers non commités dans le dépôt.""" status_output = run_git_command("git rev-parse --abbrev-ref HEAD", repo_path) res = status_output.strip() if res: # Si la sortie n'est pas vide, il y a des changements non commités return res return None
def fetch_and_list_branches(repo_path): # Étape 1: Faire un git fetch try: run_git_command("git fetch --all", repo_path) except ErreurRunGitException as e: details = e.args[0] print(f"Erreur pour executer git {repo_path} : {details}") return None, None, None, None
localBranche = get_local_branche(repo_path)
# Étape 3: Obtenir les 5 dernières branches distantes
remote_branches_output = run_git_command(
"git for-each-ref --sort=-committerdate --format=\"%(objectname:short) %(committerdate:iso8601) %(refname:short) %(contents:subject)\" refs/remotes --count=5",
repo_path
)
remote_branches = []
# list_remote_branche=[]
for line in remote_branches_output.strip().split("\n"):
if line:
short_commit, date, message, nameBranche2 = get_branch_info(line)
liste = line.split()
if len(liste) >= 2:
branch_name = nameBranche2 # liste[2]
remote_branches.append({
"name": branch_name,
"commit": short_commit,
"date": date.isoformat(),
"message": message
})
# list_remote_branche.append(branch_name)
# Étape 2: Obtenir les 5 dernières branches locales
local_branches_output = run_git_command(
"git for-each-ref --sort=-committerdate --format=\"%(objectname:short) %(committerdate:iso8601) %(refname:short) %(contents:subject)\" refs/heads --count=5",
repo_path
)
# print(local_branches_output)
local_branches = []
for line in local_branches_output.strip().split("\n"):
if line:
# print(f'line: {line}')
short_commit, date, message, nameBranche = get_branch_info(line)
liste = line.split()
if len(liste) >= 2:
branch_name = nameBranche # liste[2]
branche_module = {
"name": branch_name,
"commit": short_commit,
"date": date.isoformat(),
"message": message
}
local_branches.append(branche_module)
if localBranche is not None and localBranche == branch_name:
branche_module["current"] = True
liste_remote = [item for item in remote_branches if item["name"] == "origin/" + branch_name]
if liste_remote:
branche_module["remote_exists"] = True
branche_module["remote_eq"] = liste_remote[0]['commit'] == short_commit
else:
branche_module["remote_exists"] = False
fichierNonCommite = has_uncommitted_changes(repo_path)
# Étape 4: Retourner un objet structuré avec les branches locales et distantes
return local_branches, remote_branches, fichierNonCommite, localBranche
def find_repos_and_parse_files(parametre: Parametres): """Cherche dans les sous-répertoires pour identifier les repos git ou ceux contenant pom.xml ou package.json."""
# start_dir_list, debug, ignoreProjet
listeGlobal = []
# nomFichierGlobal=f"{start_dir}/.git"
today = datetime.today()
filename = today.strftime("projet_%y%m%d_%H%M%S.json")
for start_dir in parametre.repertoires:
for root, dirs, files in os.walk(start_dir):
if is_git_repo(root) or "pom.xml" in files or "package.json" in files:
# print(f"\nDépôt Git trouvé dans : {root}")
pathFile = os.path.basename(root)
dirs[:] = []
if parametre.repertoires_ignore is not None and pathFile in parametre.repertoires_ignore:
print(f"projet ignore {pathFile} : {root}")
continue
print(f"projet {pathFile} : {root}")
projet = {
'nom': pathFile,
'rep': root,
'modules': []
}
listeGlobal.append(projet)
root0 = root
for root2, dirs2, files2 in os.walk(root0):
depotGit = is_git_repo(root2)
dirs2[:] = [d for d in dirs2 if d not in {'node', 'node_modules', 'target', '.venv', '.git'}]
if "pom.xml" in files2 or "package.json" in files2:
# print(f"\nProjet : {root}")
liste = []
name = '.'
if root2 != root0:
name = os.path.basename(root2)
module = {
'name': name,
'path': root2
}
projet['modules'].append(module)
if "pom.xml" in files2:
pom_path = os.path.join(root2, "pom.xml")
parent_info, properties, version_projet, dependances, effectivePom = parse_pom_xml(pom_path,
parametre.debug)
pom = {
}
if parent_info is not None:
pom['parent_info'] = parent_info
if version_projet is not None:
pom['version'] = version_projet
if properties is not None:
pom['properties'] = properties
if dependances is not None:
pom['dependances'] = dependances
if effectivePom is not None:
pom['effectivePom'] = effectivePom
module['pom'] = pom
if "package.json" in files2:
package_path = os.path.join(root2, "package.json")
map = parse_package_json(package_path)
if map is not None:
module['packageJson'] = map
if depotGit:
local_branches, remote_branches, fichierNonCommite, localBranche = fetch_and_list_branches(
root2)
gitModule = {
'localBranches': local_branches,
'remoteBranches': remote_branches,
'fichierNonCommite': fichierNonCommite,
'localBranche': localBranche
}
module['git'] = gitModule
with open(f"data/{filename}", "w") as outfile:
json.dump(listeGlobal, outfile)
print("date de debut : " + datetime.now().strftime("%Y-%m-%d %H:%M:%S")) start = datetime.now()
param = sys.argv
parametres = Parametres()
debut_parametre = '--parametre=' debut_ignore = '--ignore=' debut_debug = '--debug'
for p in param[1:]: if p.startswith(debut_parametre): s = p[len(debut_parametre):] parametres.repertoires = s.split(',') elif p.startswith(debut_ignore): s = p[len(debut_ignore):] parametres.repertoires_ignore = s.split(',') elif p == debut_debug: parametres.debug = True else: raise Exception("Parametre invalide : " + p)
if not parametres.repertoires: raise Exception("Liste des répertoires absente")
find_repos_and_parse_files(parametres)
print("date de fin : " + datetime.now().strftime("%Y-%m-%d %H:%M:%S")) delta = datetime.now() - start ms = delta.total_seconds() * 1000 milli = int(ms % 1000) seconds = (ms / 1000) % 60 seconds = int(seconds) minutes = (ms / (1000 * 60)) % 60 minutes = int(minutes) hours = int(ms / (1000 * 60 * 60)) % 24 print(f"duree d'execution : {hours:02d}:{minutes:02d}:{seconds:02d}.{milli:03d} ({ms} ms)")