Files
Sikuwa/parser.py
so陈 13a1072c6f
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
Sikuwa first commit
2026-02-20 23:53:48 +08:00

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