# ------------------------------- # Difficult Rocket # Copyright © 2020-2023 by shenjackyuanjie 3695888@qq.com # All rights reserved # ------------------------------- import platform import warnings from pathlib import Path from typing import List, Tuple, Optional, Union, Any from enum import Enum from lib_not_dr.types import Options, Version, VersionRequirement def ensure_cmd_readable(cmd: str) -> str: """ 保证 参数中 不含空格 :param cmd: 要格式化的命令行参数 :return: 格式化后的命令行参数 """ if ' ' in str(cmd): return f'"{cmd}"' return cmd def format_cmd(arg_name: Optional[str] = None, arg_value: Optional[Union[str, List[str]]] = None, write: Optional[Any] = True) -> List[str]: """ 用来格式化输出命令行参数 :param arg_name: 类似 --show-memory 之类的主项 :param arg_value: 类似 xxx 类的内容 :param write: 是否写入 :return: 直接拼接好的命令行参数 不带 = """ if not write: return [] if arg_name is None: return [] if arg_value is None: return [arg_name] if isinstance(arg_value, list): arg_value = ','.join([ensure_cmd_readable(value) for value in arg_value]) return [f'{arg_name}{arg_value}'] arg_value = ensure_cmd_readable(arg_value) return [f'{arg_name}{arg_value}'] class NuitkaSubConfig(Options): """ Nuitka 配置的子项 Nuitka configuration sub-items """ name = 'Nuitka Sub Configuration' def gen_cmd(self) -> List[str]: """ 生成命令行参数 :return: """ raise NotImplementedError class NuitkaPluginConfig(NuitkaSubConfig): """ 控制 nuitka 的 plugin 相关参数的部分 Control part of nuitka's plugin related parameters """ name = 'Nuitka Plugin Configuration' # --enable-plugin=PLUGIN_NAME enable_plugin: List[str] = [] # --disable-plugin=PLUGIN_NAME disable_plugin: List[str] = [] # --plugin-no-detection plugin_no_detection: bool = False # --user-plugin=PATH user_plugin: List[Path] = [] # --show-source-changes show_source_changes: bool = False # --include-plugin-directory=MODULE/PACKAGE include_plugin_dir: List[str] = [] # --include-plugin-files=PATTERN include_plugin_files: List[str] = [] def gen_cmd(self) -> List[str]: lst = [] lst += format_cmd('--enable-plugin=', self.enable_plugin, self.enable_plugin) lst += format_cmd('--disable-plugin=', self.disable_plugin, self.disable_plugin) lst += format_cmd('--plugin-no-detection' if self.plugin_no_detection else None) lst += format_cmd('--user-plugin=', [str(plugin.absolute()) for plugin in self.user_plugin], self.user_plugin) lst += format_cmd('--show-source-changes' if self.show_source_changes else None) lst += format_cmd('--include-plugin-directory=', self.include_plugin_dir, self.include_plugin_dir) lst += format_cmd('--include-plugin-files=', self.include_plugin_files, self.include_plugin_files) return lst class NuitkaIncludeConfig(NuitkaSubConfig): """ 控制 nuitka 的 include 和 数据 相关参数的部分 Control part of nuitka's include related parameters """ name = 'Nuitka Include Configuration' # --include-package=PACKAGE include_packages: List[str] = [] # --include-module=MODULE include_modules: List[str] = [] # --prefer-source-code # --no-prefer-source-code for --module prefer_source_code: bool = False # --follow-stdlib follow_stdlib: bool = False def gen_cmd(self) -> List[str]: lst = [] lst += format_cmd('--include-package=', self.include_packages, self.include_packages) lst += format_cmd('--include-module=', self.include_modules, self.include_modules) lst += format_cmd('--prefer-source-code' if self.prefer_source_code else None) lst += format_cmd('--no-prefer-source-code' if not self.prefer_source_code else None) lst += format_cmd('--follow-stdlib' if self.follow_stdlib else None) return lst class NuitkaDataConfig(NuitkaSubConfig): """ 控制 nuitka 的 数据 相关参数的部分 Control part of nuitka's data related parameters """ name = 'Nuitka Data Configuration' # --include-package-data=PACKAGE=PACKAGE_PATH include_package_data: List[Tuple[Path, Path]] = [] # --include-data-files=PATH=PATH include_data_files: List[Tuple[Path, Path]] = [] # --include-data-dir=DIRECTORY=PATH include_data_dir: List[Tuple[Path, Path]] = [] # --noinclude-data-files=PATH no_include_data_files: List[Path] = [] # --list-package-data=LIST_PACKAGE_DATA list_package_data: List[str] = [] # --list-package-dlls=LIST_PACKAGE_DLLS list_package_dlls: List[str] = [] # --include-distribution-metadata=DISTRIBUTION include_distribution_metadata: List[str] = [] class NuitkaBinaryInfo(Options): """ nuitka 构建的二进制文件的信息 nuitka build binary file information """ name = 'Nuitka Binary Info' # --company-name=COMPANY_NAME company_name: Optional[str] = None # --product-name=PRODUCT_NAME product_name: Optional[str] = None # --file-version=FILE_VERSION # --macos-app-version=MACOS_APP_VERSION file_version: Optional[Union[str, Version]] = None # --product-version=PRODUCT_VERSION product_version: Optional[Union[str, Version]] = None # --file-description=FILE_DESCRIPTION file_description: Optional[str] = None # --copyright=COPYRIGHT_TEXT copyright: Optional[str] = None # --trademarks=TRADEMARK_TEXT trademarks: Optional[str] = None # Icon # --linux-icon=ICON_PATH # --macos-app-icon=ICON_PATH # --windows-icon-from-ico=ICON_PATH # --windows-icon-from-exe=ICON_EXE_PATH # 注意: 只有 Windows 下 才可以提供多个 ICO 文件 # 其他平台 和 EXE 下只会使用第一个路径 icon: Optional[List[Path]] = None # Console # --enable-console # --disable-console console: bool = True # Windows UAC # --windows-uac-admin windows_uac_admin: bool = False # --windows-uac-uiaccess windows_uac_ui_access: bool = False class NuitkaOutputConfig(Options): """ nuitka 构建的选项 nuitka build output information """ name = 'Nuitka Output Config' # --output-dir=DIRECTORY output_dir: Optional[Path] = None # --output-filename=FILENAME output_filename: Optional[str] = None # --quiet quiet: bool = False # --no-progressbar no_progressbar: bool = False # --verbose verbose: bool = False # --verbose-output=PATH verbose_output: Optional[Path] = None # --show-progress show_progress: bool = False # --show-memory show_memory: bool = False # --show-scons show_scons: bool = False # --show-modules show_modules: bool = False # --show-modules-output=PATH show_modules_output: Optional[Path] = None # --xml=XML_FILENAME xml: Optional[Path] = None # --report=REPORT_FILENAME report: Optional[Path] = None # --report-diffable report_diffable: bool = False # --remove-output remove_output: bool = False # --no-pyo-file no_pyo_file: bool = False class NuitkaDebugConfig(Options): """ nuitka 构建的调试选项 nuikta build debug information """ name = 'Nuitka Debug Config' # --debug debug: bool = False # --unstripped strip: bool = True # --profile profile: bool = False # --internal-graph internal_graph: bool = False # --trace-execution trace_execution: bool = False # --recompile-c-only recompile_c_only: bool = False # --generate-c-only generate_c_only: bool = False # --deployment deployment: bool = False # --no-deployment-flag=FLAG deployment_flag: Optional[str] = None # --experimental=FLAG experimental: Optional[str] = None class NuitkaTarget(Enum): """ 用于指定 nuitka 构建的目标 Use to specify the target of nuitka build exe: 不带任何参数 module: --module standalone: --standalone one_file: --onefile """ exe = '' module = 'module' standalone = 'standalone' one_file = 'package' class NuitkaScriptGenerator(Options): """ 用于帮助生成 nuitka 构建脚本的类 Use to help generate nuitka build script :arg main 需要编译的文件 """ name = 'Nuitka Script Generator' # --main=PATH # 可以有多个 输入时需要包在列表里 main: List[Path] # --run run_after_build: bool = False # --debugger debugger: bool = False # --execute-with-pythonpath execute_with_python_path: bool = False # --assume-yes-for-downloads download_confirm: bool = True # standalone/one_file/module/exe target: NuitkaTarget = NuitkaTarget.exe # --python-debug python_debug: bool = False # --python-flag=FLAG python_flag: List[str] = [] # --python-for-scons=PATH python_for_scons: Optional[Path] = None class CompilerHelper(Options): """ 用于帮助生成 nuitka 构建脚本的类 Use to help generate nuitka build script """ name = 'Nuitka Compiler Helper' output_path: Path = Path('./build') src_file: Path python_cmd: str = 'python' compat_nuitka_version: VersionRequirement = VersionRequirement("~1.8.0") # STATIC VERSION # 以下为 nuitka 的参数 # nuitka options below use_lto: bool = False # --lto=yes (no is faster) use_clang: bool = True # --clang use_msvc: bool = True # --msvc=latest use_mingw: bool = False # --mingw64 onefile: bool = False # --onefile onefile_tempdir: Optional[str] = '' # --onefile-tempdir-spec= standalone: bool = True # --standalone use_ccache: bool = True # not --disable-ccache enable_console: bool = True # --enable-console / --disable-console show_progress: bool = True # --show-progress show_memory: bool = False # --show-memory remove_output: bool = True # --remove-output save_xml: bool = False # --xml xml_path: Path = Path('build/compile_data.xml') save_report: bool = False # --report report_path: Path = Path('build/compile_report.xml') download_confirm: bool = True # --assume-yes-for-download run_after_build: bool = False # --run company_name: Optional[str] = '' product_name: Optional[str] = '' file_version: Optional[Version] = None product_version: Optional[Version] = None file_description: Optional[str] = '' # --file-description copy_right: Optional[str] = '' # --copyright icon_path: Optional[Path] = None follow_import: List[str] = [] no_follow_import: List[str] = [] include_data_dir: List[Tuple[str, str]] = [] include_packages: List[str] = [] enable_plugin: List[str] = [] # --enable-plugin=xxx,xxx disable_plugin: List[str] = [] # --disable-plugin=xxx,xxx def init(self, **kwargs) -> None: if (compat_version := kwargs.get('compat_nuitka_version')) is not None: if not self.compat_nuitka_version.accept(compat_version): warnings.warn( f"Nuitka version may not compat with {compat_version}\n" "requirement: {self.compat_nuitka_version}" ) # 非 windows 平台不使用 msvc if platform.system() != 'Windows': self.use_msvc = False self.use_mingw = False else: self.use_mingw = self.use_mingw and not self.use_msvc # Windows 平台下使用 msvc 时不使用 mingw def __str__(self): return self.as_markdown() def as_markdown(self, longest: Optional[int] = None) -> str: """ 输出编译器帮助信息 Output compiler help information Args: longest (Optional[int], optional): 输出信息的最大长度限制 The maximum length of output information. Defaults to None. Returns: str: 以 markdown 格式输出的编译器帮助信息 Compile helper information in markdown format """ front = super().as_markdown(longest) gen_cmd = self.gen_subprocess_cmd() return f"{front}\n\n```bash\n{' '.join(gen_cmd)}\n```" def gen_subprocess_cmd(self) -> List[str]: """生成 nuitka 构建脚本 Generate nuitka build script Returns: List[str]: 生成的 nuitka 构建脚本 Generated nuitka build script """ cmd_list = [self.python_cmd, '-m', 'nuitka'] # macos 和 非 macos icon 参数不同 if platform.system() == 'Darwin': cmd_list += format_cmd('--macos-app-version=', self.product_version, self.product_version) cmd_list += format_cmd('--macos-app-icon=', self.icon_path.absolute(), self.icon_path) elif platform.system() == 'Windows': cmd_list += format_cmd('--windows-icon-from-ico=', self.icon_path.absolute(), self.icon_path) elif platform.system() == 'Linux': cmd_list += format_cmd('--linux-icon=', self.icon_path.absolute(), self.icon_path) cmd_list += format_cmd('--lto=', 'yes' if self.use_lto else 'no') cmd_list += format_cmd('--clang' if self.use_clang else None) cmd_list += format_cmd('--msvc=latest' if self.use_msvc else None) cmd_list += format_cmd('--mingw64' if self.use_mingw else None) cmd_list += format_cmd('--standalone' if self.standalone else None) cmd_list += format_cmd('--onefile' if self.onefile else None) cmd_list += format_cmd('--onefile-tempdir-spec=', self.onefile_tempdir, self.onefile_tempdir) cmd_list += format_cmd('--disable-ccache' if not self.use_ccache else None) cmd_list += format_cmd('--show-progress' if self.show_progress else None) cmd_list += format_cmd('--show-memory' if self.show_memory else None) cmd_list += format_cmd('--remove-output' if self.remove_output else None) cmd_list += format_cmd('--assume-yes-for-download' if self.download_confirm else None) cmd_list += format_cmd('--run' if self.run_after_build else None) cmd_list += format_cmd('--enable-console' if self.enable_console else '--disable-console') cmd_list += format_cmd('--xml=', str(self.xml_path.absolute()), self.save_xml) cmd_list += format_cmd('--report=', str(self.report_path.absolute()), self.save_report) cmd_list += format_cmd('--output-dir=', str(self.output_path.absolute()), self.output_path) cmd_list += format_cmd('--company-name=', self.company_name, self.company_name) cmd_list += format_cmd('--product-name=', self.product_name, self.product_name) cmd_list += format_cmd('--file-version=', str(self.file_version), self.file_version) cmd_list += format_cmd('--product-version=', str(self.product_version), self.product_version) cmd_list += format_cmd('--file-description=', self.file_description, self.file_description) cmd_list += format_cmd('--copyright=', self.copy_right, self.copy_right) cmd_list += format_cmd('--follow-import-to=', self.follow_import, self.follow_import) cmd_list += format_cmd('--nofollow-import-to=', self.no_follow_import, self.no_follow_import) cmd_list += format_cmd('--enable-plugin=', self.enable_plugin, self.enable_plugin) cmd_list += format_cmd('--disable-plugin=', self.disable_plugin, self.disable_plugin) if self.include_data_dir: cmd_list += [f"--include-data-dir={src}={dst}" for src, dst in self.include_data_dir] if self.include_packages: cmd_list += [f"--include-package={package}" for package in self.include_packages] cmd_list.append(f"--main={self.src_file}") return cmd_list