Some checks are pending
CI / Test (Python 3.10 on macos-latest) (push) Waiting to run
CI / Test (Python 3.11 on macos-latest) (push) Waiting to run
CI / Test (Python 3.12 on macos-latest) (push) Waiting to run
CI / Test (Python 3.8 on macos-latest) (push) Waiting to run
CI / Test (Python 3.9 on macos-latest) (push) Waiting to run
CI / Test (Python 3.10 on ubuntu-latest) (push) Waiting to run
CI / Test (Python 3.11 on ubuntu-latest) (push) Waiting to run
CI / Test (Python 3.12 on ubuntu-latest) (push) Waiting to run
CI / Test (Python 3.8 on ubuntu-latest) (push) Waiting to run
CI / Test (Python 3.9 on ubuntu-latest) (push) Waiting to run
CI / Test (Python 3.10 on windows-latest) (push) Waiting to run
CI / Test (Python 3.11 on windows-latest) (push) Waiting to run
CI / Test (Python 3.12 on windows-latest) (push) Waiting to run
CI / Test (Python 3.8 on windows-latest) (push) Waiting to run
CI / Test (Python 3.9 on windows-latest) (push) Waiting to run
CI / Lint (push) Waiting to run
CI / Release (push) Blocked by required conditions
Documentation / Build Documentation (push) Waiting to run
318 lines
10 KiB
Python
318 lines
10 KiB
Python
# sikuwa/parser.py
|
|
"""
|
|
Sikuwa 配置解析器
|
|
"""
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional, Any
|
|
from dataclasses import dataclass, field
|
|
|
|
|
|
@dataclass
|
|
class NuitkaConfig:
|
|
"""Nuitka 专属配置"""
|
|
standalone: bool = True
|
|
follow_imports: bool = True
|
|
remove_output: bool = True
|
|
show_progress: bool = True
|
|
include_packages: List[str] = field(default_factory=list)
|
|
include_modules: List[str] = field(default_factory=list)
|
|
exclude_modules: List[str] = field(default_factory=list)
|
|
windows_icon: Optional[str] = None
|
|
windows_company: Optional[str] = None
|
|
windows_product: Optional[str] = None
|
|
macos_app_name: Optional[str] = None
|
|
|
|
|
|
@dataclass
|
|
class BuildConfig:
|
|
"""构建配置(完整版)"""
|
|
# 基础配置
|
|
project_name: str
|
|
main_script: str
|
|
version: str = "1.0.0"
|
|
|
|
# 目录配置
|
|
src_dir: str = "."
|
|
output_dir: str = "dist"
|
|
build_dir: str = "build"
|
|
|
|
# 平台配置
|
|
platforms: List[str] = field(default_factory=lambda: ["windows", "linux"])
|
|
|
|
# Nuitka 配置
|
|
nuitka: NuitkaConfig = field(default_factory=NuitkaConfig)
|
|
|
|
# 资源配置
|
|
resources: List[Dict[str, str]] = field(default_factory=list)
|
|
|
|
# 构建钩子
|
|
pre_build_commands: List[str] = field(default_factory=list)
|
|
post_build_commands: List[str] = field(default_factory=list)
|
|
|
|
# 向后兼容属性
|
|
@property
|
|
def name(self) -> str:
|
|
return self.project_name
|
|
|
|
@property
|
|
def entry(self) -> str:
|
|
return self.main_script
|
|
|
|
# 兼容旧版属性
|
|
icon: Optional[str] = None
|
|
console: bool = False
|
|
onefile: bool = True
|
|
strip: bool = True
|
|
upx: bool = False
|
|
follow_imports: bool = True
|
|
standalone: bool = True
|
|
include_data: List[tuple] = field(default_factory=list)
|
|
include_modules: List[str] = field(default_factory=list)
|
|
exclude_modules: List[str] = field(default_factory=list)
|
|
product_name: Optional[str] = None
|
|
product_version: str = "1.0.0"
|
|
company_name: Optional[str] = None
|
|
file_description: Optional[str] = None
|
|
extra_args: List[str] = field(default_factory=list)
|
|
|
|
|
|
class ConfigParser:
|
|
"""配置文件解析器"""
|
|
|
|
def __init__(self, config_path: Path):
|
|
self.config_path = config_path
|
|
self.config: Dict[str, Any] = {}
|
|
|
|
def parse(self) -> BuildConfig:
|
|
"""解析配置文件"""
|
|
if not self.config_path.exists():
|
|
raise FileNotFoundError(f"配置文件不存在: {self.config_path}")
|
|
|
|
file_content = self.config_path.read_text(encoding='utf-8')
|
|
|
|
if self.config_path.suffix == '.toml':
|
|
self.config = self._parse_toml(file_content)
|
|
elif self.config_path.suffix in ['.yaml', '.yml']:
|
|
self.config = self._parse_yaml(file_content)
|
|
elif self.config_path.suffix == '.json':
|
|
self.config = self._parse_json(file_content)
|
|
else:
|
|
raise ValueError(f"不支持的配置文件格式: {self.config_path.suffix}")
|
|
|
|
return self._to_build_config()
|
|
|
|
def _parse_toml(self, file_content: str) -> Dict:
|
|
"""解析 TOML 配置"""
|
|
try:
|
|
import tomllib
|
|
except ImportError:
|
|
try:
|
|
import tomli as tomllib
|
|
except ImportError:
|
|
raise ImportError("需要安装 tomli: pip install tomli")
|
|
|
|
return tomllib.loads(file_content)
|
|
|
|
def _parse_yaml(self, file_content: str) -> Dict:
|
|
"""解析 YAML 配置"""
|
|
try:
|
|
import yaml
|
|
return yaml.safe_load(file_content)
|
|
except ImportError:
|
|
raise ImportError("需要安装 PyYAML: pip install pyyaml")
|
|
|
|
def _parse_json(self, file_content: str) -> Dict:
|
|
"""解析 JSON 配置"""
|
|
import json
|
|
return json.loads(file_content)
|
|
|
|
def _to_build_config(self) -> BuildConfig:
|
|
"""转换为 BuildConfig 对象"""
|
|
build_section = self.config.get('build', {})
|
|
|
|
config_name = build_section.get('name')
|
|
config_entry = build_section.get('entry')
|
|
|
|
if not config_name:
|
|
raise ValueError("配置文件缺少必需字段: build.name")
|
|
if not config_entry:
|
|
raise ValueError("配置文件缺少必需字段: build.entry")
|
|
|
|
# 创建 Nuitka 配置
|
|
nuitka_section = build_section.get('nuitka', {})
|
|
nuitka_config = NuitkaConfig(
|
|
standalone=nuitka_section.get('standalone', True),
|
|
follow_imports=nuitka_section.get('follow_imports', True),
|
|
remove_output=nuitka_section.get('remove_output', True),
|
|
show_progress=nuitka_section.get('show_progress', True),
|
|
include_packages=nuitka_section.get('include_packages', []),
|
|
include_modules=nuitka_section.get('include_modules', []),
|
|
exclude_modules=nuitka_section.get('exclude_modules', []),
|
|
windows_icon=build_section.get('icon'),
|
|
windows_company=build_section.get('company_name'),
|
|
windows_product=build_section.get('product_name', config_name),
|
|
macos_app_name=nuitka_section.get('macos_app_name')
|
|
)
|
|
|
|
# 处理资源文件
|
|
resources = []
|
|
data_files = nuitka_section.get('include_data', [])
|
|
if data_files:
|
|
for item in data_files:
|
|
if isinstance(item, dict):
|
|
resources.append({
|
|
'src': item.get('src', ''),
|
|
'dest': item.get('dst', item.get('dest', ''))
|
|
})
|
|
|
|
# 获取产品信息
|
|
product_section = self.config.get('product', {})
|
|
|
|
# 创建完整配置
|
|
result = BuildConfig(
|
|
project_name=config_name,
|
|
main_script=config_entry,
|
|
version=product_section.get('version', '1.0.0'),
|
|
src_dir=build_section.get('src_dir', '.'),
|
|
output_dir=build_section.get('output_dir', 'dist'),
|
|
build_dir=build_section.get('build_dir', 'build'),
|
|
platforms=build_section.get('platforms', ['windows', 'linux']),
|
|
nuitka=nuitka_config,
|
|
resources=resources,
|
|
pre_build_commands=build_section.get('pre_build_commands', []),
|
|
post_build_commands=build_section.get('post_build_commands', []),
|
|
|
|
# 兼容旧版属性
|
|
icon=build_section.get('icon'),
|
|
console=build_section.get('console', False),
|
|
onefile=build_section.get('onefile', True),
|
|
strip=build_section.get('strip', True),
|
|
upx=build_section.get('upx', False),
|
|
follow_imports=nuitka_section.get('follow_imports', True),
|
|
standalone=nuitka_section.get('standalone', True),
|
|
include_modules=nuitka_section.get('include_modules', []),
|
|
exclude_modules=nuitka_section.get('exclude_modules', []),
|
|
product_name=product_section.get('name', config_name),
|
|
product_version=product_section.get('version', '1.0.0'),
|
|
company_name=product_section.get('company'),
|
|
file_description=product_section.get('description'),
|
|
extra_args=build_section.get('extra_args', [])
|
|
)
|
|
|
|
# 处理 include_data 兼容性
|
|
if data_files:
|
|
result.include_data = [
|
|
(item['src'], item.get('dst', item.get('dest', '')))
|
|
for item in data_files if isinstance(item, dict)
|
|
]
|
|
|
|
return result
|
|
|
|
|
|
def parse_config(config_path: Path) -> BuildConfig:
|
|
"""快速解析配置文件"""
|
|
parser = ConfigParser(config_path)
|
|
return parser.parse()
|
|
|
|
|
|
def create_default_config(output_path: Path, format_type: str = 'toml'):
|
|
"""创建默认配置文件"""
|
|
if format_type == 'toml':
|
|
template_content = '''[build]
|
|
name = "myapp"
|
|
entry = "main.py"
|
|
src_dir = "."
|
|
output_dir = "dist"
|
|
build_dir = "build"
|
|
platforms = ["windows", "linux"]
|
|
console = false
|
|
onefile = true
|
|
[build.nuitka]
|
|
standalone = true
|
|
follow_imports = true
|
|
remove_output = true
|
|
show_progress = true
|
|
include_packages = []
|
|
include_modules = []
|
|
exclude_modules = []
|
|
[product]
|
|
name = "My Application"
|
|
version = "1.0.0"
|
|
company = "Your Company"
|
|
description = "Application description"
|
|
'''
|
|
elif format_type == 'yaml':
|
|
template_content = '''build:
|
|
name: myapp
|
|
entry: main.py
|
|
src_dir: "."
|
|
output_dir: dist
|
|
build_dir: build
|
|
platforms: [windows, linux]
|
|
console: false
|
|
onefile: true
|
|
|
|
nuitka:
|
|
standalone: true
|
|
follow_imports: true
|
|
remove_output: true
|
|
show_progress: true
|
|
product:
|
|
name: My Application
|
|
version: 1.0.0
|
|
company: Your Company
|
|
description: Application description
|
|
'''
|
|
elif format_type == 'json':
|
|
template_content = '''{
|
|
"build": {
|
|
"name": "myapp",
|
|
"entry": "main.py",
|
|
"src_dir": ".",
|
|
"output_dir": "dist",
|
|
"build_dir": "build",
|
|
"platforms": ["windows", "linux"],
|
|
"console": false,
|
|
"onefile": true,
|
|
"nuitka": {
|
|
"standalone": true,
|
|
"follow_imports": true,
|
|
"remove_output": true,
|
|
"show_progress": true
|
|
}
|
|
},
|
|
"product": {
|
|
"name": "My Application",
|
|
"version": "1.0.0",
|
|
"company": "Your Company",
|
|
"description": "Application description"
|
|
}
|
|
}'''
|
|
else:
|
|
raise ValueError(f"不支持的格式: {format_type}")
|
|
|
|
output_path.write_text(template_content.strip(), encoding='utf-8')
|
|
|
|
|
|
def validate_config(config: BuildConfig) -> List[str]:
|
|
"""验证配置有效性"""
|
|
errors = []
|
|
|
|
# 检查源码目录
|
|
src_dir = Path(config.src_dir)
|
|
if not src_dir.exists():
|
|
errors.append(f"源码目录不存在: {config.src_dir}")
|
|
|
|
# 检查入口文件
|
|
entry_file = src_dir / config.main_script
|
|
if not entry_file.exists():
|
|
errors.append(f"入口文件不存在: {entry_file}")
|
|
|
|
# 检查图标文件
|
|
if config.icon:
|
|
icon_file = Path(config.icon)
|
|
if not icon_file.exists():
|
|
errors.append(f"图标文件不存在: {config.icon}")
|
|
|
|
return errors
|