feat(dev-script): added appimage cleaner script #27
| @@ -17,11 +17,11 @@ jobs: | ||||
|       - name: Install required dependencies | ||||
|         run: | | ||||
|             sudo apt update | ||||
|             sudo apt install -y binutils coreutils desktop-file-utils gtk-update-icon-cache fakeroot fuse libgdk-pixbuf2.0-dev patchelf python3-pip python3-dev python3-setuptools squashfs-tools strace util-linux zsync git | ||||
|             sudo apt install -y binutils coreutils desktop-file-utils gtk-update-icon-cache fakeroot fuse libgdk-pixbuf2.0-dev patchelf python3-pip python3-dev python3-setuptools squashfs-tools strace util-linux zsync git zstd | ||||
|  | ||||
|       - name: Install tools | ||||
|         run: | | ||||
|             pip3 install git+https://github.com/Frederic98/appimage-builder.git | ||||
|             pip3 install git+https://github.com/Boria138/appimage-builder.git | ||||
|             pip3 install uv | ||||
|  | ||||
|       - name: Build AppImage | ||||
|   | ||||
| @@ -23,11 +23,11 @@ jobs: | ||||
|       - name: Install required dependencies | ||||
|         run: | | ||||
|             sudo apt update | ||||
|             sudo apt install -y binutils coreutils desktop-file-utils gtk-update-icon-cache fakeroot fuse libgdk-pixbuf2.0-dev patchelf python3-pip python3-dev python3-setuptools squashfs-tools strace util-linux zsync git | ||||
|             sudo apt install -y binutils coreutils desktop-file-utils gtk-update-icon-cache fakeroot fuse libgdk-pixbuf2.0-dev patchelf python3-pip python3-dev python3-setuptools squashfs-tools strace util-linux zsync git zstd | ||||
|  | ||||
|       - name: Install tools | ||||
|         run: | | ||||
|             pip3 install git+https://github.com/Frederic98/appimage-builder.git | ||||
|             pip3 install git+https://github.com/Boria138/appimage-builder.git | ||||
|             pip3 install uv | ||||
|  | ||||
|       - name: Build AppImage | ||||
| @@ -159,6 +159,7 @@ jobs: | ||||
|           mkdir -p extracted | ||||
|           find release/ -name '*.zip' -exec unzip -o {} -d extracted/ \; | ||||
|           find extracted/ -type f -exec mv {} release/ \; | ||||
|           find release/ -name '*.zip' -delete | ||||
|           rm -rf extracted/ | ||||
|  | ||||
|       - name: Extract changelog for version | ||||
|   | ||||
| @@ -68,11 +68,11 @@ jobs: | ||||
|       - name: Install required dependencies | ||||
|         run: | | ||||
|             sudo apt update | ||||
|             sudo apt install -y binutils coreutils desktop-file-utils gtk-update-icon-cache fakeroot fuse libgdk-pixbuf2.0-dev patchelf python3-pip python3-dev python3-setuptools squashfs-tools strace util-linux zsync git | ||||
|             sudo apt install -y binutils coreutils desktop-file-utils gtk-update-icon-cache fakeroot fuse libgdk-pixbuf2.0-dev patchelf python3-pip python3-dev python3-setuptools squashfs-tools strace util-linux zsync zstd git | ||||
|  | ||||
|       - name: Install tools | ||||
|         run: | | ||||
|             pip3 install git+https://github.com/Frederic98/appimage-builder.git | ||||
|             pip3 install git+https://github.com/Boria138/appimage-builder.git | ||||
|             pip3 install uv | ||||
|  | ||||
|       - name: Build AppImage | ||||
|   | ||||
| @@ -13,9 +13,9 @@ script: | ||||
|   # 5) чистим от ненужных модулей и бинарников | ||||
|   - rm -rf AppDir/usr/local/lib/python3.10/dist-packages/PySide6/Qt/qml/ | ||||
|   - rm -f AppDir/usr/local/lib/python3.10/dist-packages/PySide6/{assistant,designer,linguist,lrelease,lupdate} | ||||
|   - rm -f AppDir/usr/local/lib/python3.10/dist-packages/PySide6/{Qt3D*,QtBluetooth*,QtCharts*,QtConcurrent*,QtDataVisualization*,QtDesigner*,QtHelp*,QtMultimedia*,QtNetwork*,QtOpenGL*,QtPositioning*,QtPrintSupport*,QtQml*,QtQuick*,QtRemoteObjects*,QtScxml*,QtSensors*,QtSerialPort*,QtSql*,QtStateMachine*,QtTest*,QtWeb*,QtXml*} | ||||
|   - rm -f AppDir/usr/local/lib/python3.10/dist-packages/PySide6/{Qt3DAnimation*,Qt3DCore*,Qt3DExtras*,Qt3DInput*,Qt3DLogic*,Qt3DRender*,QtBluetooth*,QtCharts*,QtConcurrent*,QtDataVisualization*,QtDesigner*,QtExampleIcons*,QtGraphs*,QtGraphsWidgets*,QtHelp*,QtHttpServer*,QtLocation*,QtMultimedia*,QtMultimediaWidgets*,QtNetwork*,QtNetworkAuth*,QtNfc*,QtOpenGL*,QtOpenGLWidgets*,QtPdf*,QtPdfWidgets*,QtPositioning*,QtPrintSupport*,QtQml*,QtQuick*,QtQuick3D*,QtQuickControls2*,QtQuickTest*,QtQuickWidgets*,QtRemoteObjects*,QtScxml*,QtSensors*,QtSerialBus*,QtSerialPort*,QtSpatialAudio*,QtSql*,QtStateMachine*,QtSvgWidgets*,QtTest*,QtTextToSpeech*,QtUiTools*,QtWebChannel*,QtWebEngineCore*,QtWebEngineQuick*,QtWebEngineWidgets*,QtWebSockets*,QtWebView*,QtXml*} | ||||
|   - shopt -s extglob | ||||
|   - rm -rf AppDir/usr/local/lib/python3.10/dist-packages/PySide6/Qt/lib/!(libQt6Core*|libQt6Gui*|libQt6Widgets*|libQt6OpenGL*|libQt6XcbQpa*|libQt6Wayland*|libQt6Egl*|libicudata*|libicuuc*|libicui18n*|libQt6DBus*|libQt6Svg*|libQt6Qml*|libQt6Network*) | ||||
|   - rm -rf AppDir/usr/local/lib/python3.10/dist-packages/PySide6/Qt/lib/!(libQt6Core*|libQt6DBus*|libQt6Egl*|libQt6Gui*|libQt6Svg*|libQt6Wayland*|libQt6Widgets*|libQt6XcbQpa*|libicudata*|libicui18n*|libicuuc*) | ||||
| AppDir: | ||||
|   path: ./AppDir | ||||
|   after_bundle: | ||||
| @@ -82,5 +82,4 @@ AppDir: | ||||
|       PERLLIB: '${APPDIR}/usr/share/perl5:${APPDIR}/usr/lib/x86_64-linux-gnu/perl/5.34:${APPDIR}/usr/share/perl/5.34' | ||||
| AppImage: | ||||
|   sign-key: None | ||||
|   comp: xz | ||||
|   arch: x86_64 | ||||
|   | ||||
							
								
								
									
										378
									
								
								dev-scripts/appimage_clean.py
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										378
									
								
								dev-scripts/appimage_clean.py
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,378 @@ | ||||
| #!/usr/bin/env python3 | ||||
| """ | ||||
| PySide6 Dependencies Analyzer with ldd support | ||||
| Анализирует зависимости PySide6 модулей используя ldd для определения | ||||
| реальных зависимостей скомпилированных библиотек. | ||||
| """ | ||||
|  | ||||
| import ast | ||||
| import os | ||||
| import sys | ||||
| import subprocess | ||||
| import re | ||||
| from pathlib import Path | ||||
| from typing import Set, Dict, List | ||||
| import argparse | ||||
| import json | ||||
|  | ||||
|  | ||||
| class PySide6DependencyAnalyzer: | ||||
|     def __init__(self): | ||||
|         # Системные библиотеки, которые нужно всегда оставлять | ||||
|         self.system_libs = { | ||||
|             'libQt6XcbQpa', 'libQt6Wayland', 'libQt6Egl', | ||||
|             'libicudata', 'libicuuc', 'libicui18n', 'libQt6DBus' | ||||
|         } | ||||
|  | ||||
|         self.real_dependencies = {} | ||||
|         self.used_modules_code = set() | ||||
|         self.used_modules_ldd = set() | ||||
|         self.all_required_modules = set() | ||||
|  | ||||
|     def find_python_files(self, directory: Path) -> List[Path]: | ||||
|         """Находит все Python файлы в директории""" | ||||
|         python_files = [] | ||||
|         for root, dirs, files in os.walk(directory): | ||||
|             dirs[:] = [d for d in dirs if d not in {'.venv', '__pycache__', '.git'}] | ||||
|  | ||||
|             for file in files: | ||||
|                 if file.endswith('.py'): | ||||
|                     python_files.append(Path(root) / file) | ||||
|         return python_files | ||||
|  | ||||
|     def find_pyside6_libs(self, base_path: Path) -> Dict[str, Path]: | ||||
|         """Находит все PySide6 библиотеки (.so файлы)""" | ||||
|         libs = {} | ||||
|  | ||||
|         # Поиск в единственной локации | ||||
|         search_path = Path("../.venv/lib/python3.10/site-packages/PySide6") | ||||
|         print(f"Поиск PySide6 библиотек в: {search_path}") | ||||
|  | ||||
|         if search_path.exists(): | ||||
|             # Ищем .so файлы модулей | ||||
|             for so_file in search_path.glob("Qt*.*.so"): | ||||
|                 module_name = so_file.stem.split('.')[0]  # QtCore.abi3.so -> QtCore | ||||
|                 if module_name.startswith('Qt'): | ||||
|                     libs[module_name] = so_file | ||||
|  | ||||
|             # Также ищем в подпапках | ||||
|             for subdir in search_path.iterdir(): | ||||
|                 if subdir.is_dir() and subdir.name.startswith('Qt'): | ||||
|                     for so_file in subdir.glob("*.so*"): | ||||
|                         if 'Qt' in so_file.name: | ||||
|                             libs[subdir.name] = so_file | ||||
|                             break | ||||
|  | ||||
|         return libs | ||||
|  | ||||
|     def analyze_ldd_dependencies(self, lib_path: Path) -> Set[str]: | ||||
|         """Анализирует зависимости библиотеки с помощью ldd""" | ||||
|         qt_deps = set() | ||||
|  | ||||
|         try: | ||||
|             result = subprocess.run(['ldd', str(lib_path)], | ||||
|                                   capture_output=True, text=True, check=True) | ||||
|  | ||||
|             # Парсим вывод ldd и ищем Qt библиотеки | ||||
|             for line in result.stdout.split('\n'): | ||||
|                 # Ищем строки вида: libQt6Core.so.6 => /path/to/lib | ||||
|                 match = re.search(r'libQt6(\w+)\.so', line) | ||||
|                 if match: | ||||
|                     qt_module = f"Qt{match.group(1)}" | ||||
|                     qt_deps.add(qt_module) | ||||
|  | ||||
|         except (subprocess.CalledProcessError, FileNotFoundError) as e: | ||||
|             print(f"Предупреждение: не удалось выполнить ldd для {lib_path}: {e}") | ||||
|  | ||||
|         return qt_deps | ||||
|  | ||||
|     def build_real_dependency_graph(self, pyside_libs: Dict[str, Path]) -> Dict[str, Set[str]]: | ||||
|         """Строит граф зависимостей на основе ldd анализа""" | ||||
|         dependencies = {} | ||||
|  | ||||
|         print("Анализ реальных зависимостей с помощью ldd...") | ||||
|         for module, lib_path in pyside_libs.items(): | ||||
|             print(f"  Анализируется {module}...") | ||||
|             deps = self.analyze_ldd_dependencies(lib_path) | ||||
|             dependencies[module] = deps | ||||
|  | ||||
|             if deps: | ||||
|                 print(f"    Зависимости: {', '.join(sorted(deps))}") | ||||
|  | ||||
|         return dependencies | ||||
|  | ||||
|     def analyze_file_imports(self, file_path: Path) -> Set[str]: | ||||
|         """Анализирует один Python файл и возвращает используемые PySide6 модули""" | ||||
|         modules = set() | ||||
|         try: | ||||
|             with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: | ||||
|                 content = f.read() | ||||
|  | ||||
|             tree = ast.parse(content) | ||||
|  | ||||
|             for node in ast.walk(tree): | ||||
|                 if isinstance(node, ast.Import): | ||||
|                     for alias in node.names: | ||||
|                         if alias.name.startswith('PySide6.'): | ||||
|                             module = alias.name.split('.', 2)[1] | ||||
|                             if module.startswith('Qt'): | ||||
|                                 modules.add(module) | ||||
|  | ||||
|                 elif isinstance(node, ast.ImportFrom): | ||||
|                     if node.module and node.module.startswith('PySide6.'): | ||||
|                         module = node.module.split('.', 2)[1] | ||||
|                         if module.startswith('Qt'): | ||||
|                             modules.add(module) | ||||
|  | ||||
|         except Exception as e: | ||||
|             print(f"Ошибка при анализе {file_path}: {e}") | ||||
|  | ||||
|         return modules | ||||
|  | ||||
|     def get_all_dependencies(self, modules: Set[str], dependency_graph: Dict[str, Set[str]]) -> Set[str]: | ||||
|         """Получает все зависимости для набора модулей, используя граф зависимостей из ldd""" | ||||
|         all_deps = set(modules) | ||||
|  | ||||
|         if not dependency_graph: | ||||
|             return all_deps | ||||
|  | ||||
|         # Повторяем до тех пор, пока не найдем все транзитивные зависимости | ||||
|         changed = True | ||||
|         iteration = 0 | ||||
|         while changed and iteration < 10:  # Защита от бесконечного цикла | ||||
|             changed = False | ||||
|             current_deps = set(all_deps) | ||||
|  | ||||
|             for module in current_deps: | ||||
|                 if module in dependency_graph: | ||||
|                     new_deps = dependency_graph[module] - all_deps | ||||
|                     if new_deps: | ||||
|                         all_deps.update(new_deps) | ||||
|                         changed = True | ||||
|  | ||||
|             iteration += 1 | ||||
|  | ||||
|         return all_deps | ||||
|  | ||||
|     def analyze_project(self, project_path: Path, appdir_path: Path = None) -> Dict: | ||||
|         """Анализирует весь проект""" | ||||
|         python_files = self.find_python_files(project_path) | ||||
|         print(f"Найдено {len(python_files)} Python файлов") | ||||
|  | ||||
|         # Анализ статических импортов | ||||
|         used_modules_code = set() | ||||
|         file_modules = {} | ||||
|  | ||||
|         for file_path in python_files: | ||||
|             modules = self.analyze_file_imports(file_path) | ||||
|             if modules: | ||||
|                 file_modules[str(file_path.relative_to(project_path))] = list(modules) | ||||
|                 used_modules_code.update(modules) | ||||
|  | ||||
|         print(f"Найдено {len(used_modules_code)} модулей в коде: {', '.join(sorted(used_modules_code))}") | ||||
|  | ||||
|         # Поиск PySide6 библиотек | ||||
|         search_base = appdir_path if appdir_path else project_path | ||||
|         pyside_libs = self.find_pyside6_libs(search_base) | ||||
|  | ||||
|         if not pyside_libs: | ||||
|             print("ОШИБКА: PySide6 библиотеки не найдены! Анализ невозможен.") | ||||
|             return { | ||||
|                 'error': 'PySide6 библиотеки не найдены', | ||||
|                 'analysis_method': 'failed', | ||||
|                 'found_libraries': 0, | ||||
|                 'directly_used_code': sorted(used_modules_code), | ||||
|                 'all_required': [], | ||||
|                 'removable': [], | ||||
|                 'available_modules': [], | ||||
|                 'file_usage': file_modules | ||||
|             } | ||||
|  | ||||
|         print(f"Найдено {len(pyside_libs)} PySide6 библиотек") | ||||
|  | ||||
|         # Анализ реальных зависимостей с ldd | ||||
|         real_dependencies = self.build_real_dependency_graph(pyside_libs) | ||||
|  | ||||
|         # Определяем модули, которые реально используются через ldd | ||||
|         used_modules_ldd = set() | ||||
|         for module in used_modules_code: | ||||
|             if module in real_dependencies: | ||||
|                 used_modules_ldd.update(real_dependencies[module]) | ||||
|                 used_modules_ldd.add(module) | ||||
|  | ||||
|         print(f"Реальные зависимости через ldd: {', '.join(sorted(used_modules_ldd))}") | ||||
|  | ||||
|         # Объединяем результаты анализа кода и ldd | ||||
|         all_used_modules = used_modules_code | used_modules_ldd | ||||
|  | ||||
|         # Получаем все необходимые модули включая зависимости | ||||
|         all_required = self.get_all_dependencies(all_used_modules, real_dependencies) | ||||
|  | ||||
|         # Все доступные PySide6 модули | ||||
|         available_modules = set(pyside_libs.keys()) | ||||
|  | ||||
|         # Модули, которые можно удалить | ||||
|         removable = available_modules - all_required | ||||
|  | ||||
|         return { | ||||
|             'analysis_method': 'ldd + static analysis', | ||||
|             'found_libraries': len(pyside_libs), | ||||
|             'directly_used_code': sorted(used_modules_code), | ||||
|             'directly_used_ldd': sorted(used_modules_ldd), | ||||
|             'all_required': sorted(all_required), | ||||
|             'removable': sorted(removable), | ||||
|             'available_modules': sorted(available_modules), | ||||
|             'file_usage': file_modules, | ||||
|             'real_dependencies': {k: sorted(v) for k, v in real_dependencies.items()}, | ||||
|             'library_paths': {k: str(v) for k, v in pyside_libs.items()}, | ||||
|             'analysis_summary': { | ||||
|                 'total_modules': len(available_modules), | ||||
|                 'required_modules': len(all_required), | ||||
|                 'removable_modules': len(removable), | ||||
|                 'space_saving_potential': f"{len(removable)/len(available_modules)*100:.1f}%" if available_modules else "0%" | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     def generate_appimage_recipe(self, removable_modules: List[str], template_path: Path) -> str: | ||||
|         """Генерирует обновленный AppImage рецепт с командами очистки""" | ||||
|  | ||||
|         # Читаем существующий рецепт | ||||
|         try: | ||||
|             with open(template_path, 'r', encoding='utf-8') as f: | ||||
|                 recipe_content = f.read() | ||||
|         except FileNotFoundError: | ||||
|             print(f"Шаблон рецепта не найден: {template_path}") | ||||
|             return "" | ||||
|  | ||||
|         # Генерируем новые команды очистки | ||||
|         cleanup_lines = [] | ||||
|  | ||||
|         # QML удаляем только если не используется | ||||
|         qml_modules = {'QtQml', 'QtQuick', 'QtQuickWidgets'} | ||||
|         if qml_modules.issubset(set(removable_modules)): | ||||
|             cleanup_lines.append("  - rm -rf AppDir/usr/local/lib/python3.10/dist-packages/PySide6/Qt/qml/") | ||||
|  | ||||
|         # Инструменты разработки (всегда удаляем) | ||||
|         cleanup_lines.append("  - rm -f AppDir/usr/local/lib/python3.10/dist-packages/PySide6/{assistant,designer,linguist,lrelease,lupdate}") | ||||
|  | ||||
|         # Модули для удаления | ||||
|         if removable_modules: | ||||
|             modules_list = ','.join([f"{mod}*" for mod in sorted(removable_modules)]) | ||||
|             cleanup_lines.append(f"  - rm -f AppDir/usr/local/lib/python3.10/dist-packages/PySide6/{{{modules_list}}}") | ||||
|  | ||||
|         # Генерируем команду для удаления нативных библиотек с сохранением нужных | ||||
|         required_libs = set() | ||||
|         for module in sorted(set(self.all_required_modules)): | ||||
|             required_libs.add(f"libQt6{module.replace('Qt', '')}*") | ||||
|  | ||||
|         # Добавляем системные библиотеки | ||||
|         for lib in self.system_libs: | ||||
|             required_libs.add(f"{lib}*") | ||||
|  | ||||
|         keep_pattern = '|'.join(sorted(required_libs)) | ||||
|  | ||||
|         cleanup_lines.extend([ | ||||
|             "  - shopt -s extglob", | ||||
|             f"  - rm -rf AppDir/usr/local/lib/python3.10/dist-packages/PySide6/Qt/lib/!({keep_pattern})" | ||||
|         ]) | ||||
|  | ||||
|         # Заменяем блок очистки в рецепте | ||||
|         import re | ||||
|  | ||||
|         # Ищем блок "# 5) чистим от ненужных модулей и бинарников" до следующего комментария или до AppDir: | ||||
|         pattern = r'(  # 5\) чистим от ненужных модулей и бинарников\n).*?(?=\nAppDir:|\n  # [0-9]+\)|$)' | ||||
|  | ||||
|         new_cleanup_block = "  # 5) чистим от ненужных модулей и бинарников\n" + '\n'.join(cleanup_lines) | ||||
|  | ||||
|         updated_recipe = re.sub(pattern, new_cleanup_block, recipe_content, flags=re.DOTALL) | ||||
|  | ||||
|         return updated_recipe | ||||
|  | ||||
|  | ||||
| def main(): | ||||
|     parser = argparse.ArgumentParser(description='Анализ зависимостей PySide6 модулей с использованием ldd') | ||||
|     parser.add_argument('project_path', help='Путь к проекту для анализа') | ||||
|     parser.add_argument('--appdir', help='Путь к AppDir для поиска PySide6 библиотек') | ||||
|     parser.add_argument('--output', '-o', help='Путь для сохранения результатов (JSON)') | ||||
|     parser.add_argument('--verbose', '-v', action='store_true', help='Подробный вывод') | ||||
|  | ||||
|     args = parser.parse_args() | ||||
|  | ||||
|     project_path = Path(args.project_path) | ||||
|     if not project_path.exists(): | ||||
|         print(f"Ошибка: путь {project_path} не существует") | ||||
|         sys.exit(1) | ||||
|  | ||||
|     appdir_path = Path(args.appdir) if args.appdir else None | ||||
|     if appdir_path and not appdir_path.exists(): | ||||
|         print(f"Предупреждение: AppDir путь {appdir_path} не существует") | ||||
|         appdir_path = None | ||||
|  | ||||
|     analyzer = PySide6DependencyAnalyzer() | ||||
|     results = analyzer.analyze_project(project_path, appdir_path) | ||||
|  | ||||
|     # Сохраняем в анализатор для генерации команд | ||||
|     analyzer.all_required_modules = set(results.get('all_required', [])) | ||||
|  | ||||
|     # Выводим результаты | ||||
|     print("\n" + "="*60) | ||||
|     print("АНАЛИЗ ЗАВИСИМОСТЕЙ PYSIDE6 (ldd analysis)") | ||||
|     print("="*60) | ||||
|  | ||||
|     if 'error' in results: | ||||
|         print(f"\nОШИБКА: {results['error']}") | ||||
|         sys.exit(1) | ||||
|  | ||||
|     print(f"\nМетод анализа: {results['analysis_method']}") | ||||
|     print(f"Найдено библиотек: {results['found_libraries']}") | ||||
|  | ||||
|     if results['directly_used_code']: | ||||
|         print(f"\nИспользуемые модули в коде ({len(results['directly_used_code'])}):") | ||||
|         for module in results['directly_used_code']: | ||||
|             print(f"  • {module}") | ||||
|  | ||||
|     if results['directly_used_ldd']: | ||||
|         print(f"\nРеальные зависимости через ldd ({len(results['directly_used_ldd'])}):") | ||||
|         for module in results['directly_used_ldd']: | ||||
|             print(f"  • {module}") | ||||
|  | ||||
|     print(f"\nВсе необходимые модули ({len(results['all_required'])}):") | ||||
|     for module in results['all_required']: | ||||
|         print(f"  • {module}") | ||||
|  | ||||
|     print(f"\nМодули, которые можно удалить ({len(results['removable'])}):") | ||||
|     for module in results['removable']: | ||||
|         print(f"  • {module}") | ||||
|  | ||||
|     print(f"\nПотенциальная экономия места: {results['analysis_summary']['space_saving_potential']}") | ||||
|  | ||||
|     if args.verbose and results['real_dependencies']: | ||||
|         Devlin(f"\nРеальные зависимости (ldd):") | ||||
|         for module, deps in results['real_dependencies'].items(): | ||||
|             if deps: | ||||
|                 print(f"  {module} → {', '.join(deps)}") | ||||
|  | ||||
|     # Обновляем AppImage рецепт | ||||
|     recipe_path = Path("../build-aux/AppImageBuilder.yml") | ||||
|     if recipe_path.exists(): | ||||
|         updated_recipe = analyzer.generate_appimage_recipe(results['removable'], recipe_path) | ||||
|         if updated_recipe: | ||||
|             with open(recipe_path, 'w', encoding='utf-8') as f: | ||||
|                 f.write(updated_recipe) | ||||
|             print(f"\nAppImage рецепт обновлен: {recipe_path}") | ||||
|         else: | ||||
|             print(f"\nОШИБКА: не удалось обновить рецепт") | ||||
|     else: | ||||
|         print(f"\nПредупреждение: рецепт AppImage не найден в {recipe_path}") | ||||
|  | ||||
|     # Сохраняем результаты в JSON | ||||
|     if args.output: | ||||
|         with open(args.output, 'w', encoding='utf-8') as f: | ||||
|             json.dump(results, f, ensure_ascii=False, indent=2) | ||||
|         print(f"Результаты сохранены в: {args.output}") | ||||
|  | ||||
|     print("\n" + "="*60) | ||||
|  | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     main() | ||||
		Reference in New Issue
	
	Block a user