Sikuwa first commit
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

This commit is contained in:
so陈
2026-02-20 23:53:48 +08:00
commit 13a1072c6f
57 changed files with 13519 additions and 0 deletions

829
cli.py Normal file
View File

@@ -0,0 +1,829 @@
# sikuwa/cli.py
"""
Sikuwa 命令行界面
"""
import click
import sys
from pathlib import Path
from typing import Optional
from sikuwa.config import ConfigManager, BuildConfig, create_config
from sikuwa.builder import SikuwaBuilder, build_project, clean_project, sync_project
from sikuwa.log import get_logger, LogLevel
from sikuwa.i18n import _
# 全局日志器
logger = get_logger("sikuwa.cli")
@click.group()
@click.version_option(version="1.2.0", prog_name="sikuwa")
def cli():
"""
Sikuwa - Python 项目打包工具
基于 Nuitka 的跨平台 Python 应用构建工具
"""
pass
@cli.command()
@click.option(
'-c', '--config',
type=click.Path(exists=True),
help='配置文件路径 (默认: sikuwa.toml)'
)
@click.option(
'-p', '--platform',
type=click.Choice(['windows', 'linux', 'macos'], case_sensitive=False),
help='目标平台 (默认: 构建所有平台)'
)
@click.option(
'-m', '--mode',
type=click.Choice(['nuitka', 'native'], case_sensitive=False),
help='编译模式: nuitka (默认) | native (Python→C/C++→GCC→dll/so+exe)'
)
@click.option(
'-v', '--verbose',
is_flag=True,
help=_('详细输出模式')
)
@click.option(
'-f', '--force',
is_flag=True,
help=_('强制重新构建')
)
@click.option(
'--keep-c-source',
is_flag=True,
help='保留生成的 C/C++ 源码 (仅 native 模式)'
)
def build(config: Optional[str], platform: Optional[str], mode: Optional[str],
verbose: bool, force: bool, keep_c_source: bool):
"""
构建项目
支持两种编译模式:
\b
1. nuitka (默认): 使用 Nuitka 编译器
2. native: Python → C/C++ → GCC/G++ → dll/so + exe
生成通用动态链接库,不使用 Python 专用格式 (.pyd)
示例:
sikuwa build # 使用 Nuitka 构建
sikuwa build -m native # 使用原生编译器构建
sikuwa build -m native -v # 原生编译 + 详细输出
sikuwa build -p windows # 只构建 Windows 平台
sikuwa build -c my_config.toml # 使用指定配置文件
"""
try:
# 加载配置
logger.info_operation("加载构建配置...")
build_config = ConfigManager.load_config(config)
# 如果命令行指定了编译模式,覆盖配置文件中的设置
if mode:
build_config.compiler_mode = mode
if mode == 'native' and keep_c_source:
build_config.native_options.keep_c_source = True
# 验证配置
logger.info_operation("验证配置...")
build_config.validate()
# 执行构建
logger.info_operation(f"开始构建项目: {build_config.project_name}")
logger.info_operation(f"编译模式: {build_config.compiler_mode.upper()}")
success = build_project(
config=build_config,
platform=platform,
verbose=verbose,
force=force
)
if success:
click.echo("\n[OK] 构建成功完成!", err=False)
sys.exit(0)
else:
click.echo("\n[FAIL] 构建失败!", err=True)
sys.exit(1)
except FileNotFoundError as e:
click.echo(f"[FAIL] {e}", err=True)
click.echo("\n提示: 使用 'sikuwa init' 创建配置文件", err=True)
sys.exit(1)
except Exception as e:
click.echo(f"[FAIL] 构建失败: {e}", err=True)
if verbose:
import traceback
click.echo(traceback.format_exc(), err=True)
sys.exit(1)
@cli.command()
@click.option(
'-c', '--config',
type=click.Path(exists=True),
help='配置文件路径 (默认: sikuwa.toml)'
)
@click.option(
'-v', '--verbose',
is_flag=True,
help=_('详细输出模式')
)
def clean(config: Optional[str], verbose: bool):
"""
清理构建文件
删除构建过程中生成的所有文件和目录
示例:
sikuwa clean # 清理构建文件
sikuwa clean -v # 详细输出
"""
try:
# 加载配置
logger.info_operation("加载配置...")
build_config = ConfigManager.load_config(config)
# 执行清理
logger.info_operation("开始清理构建文件...")
success = clean_project(
config=build_config,
verbose=verbose
)
if success:
click.echo("\n[OK] 清理完成!", err=False)
sys.exit(0)
else:
click.echo("\n[FAIL] 清理失败!", err=True)
sys.exit(1)
except FileNotFoundError as e:
click.echo(f"[FAIL] {e}", err=True)
sys.exit(1)
except Exception as e:
click.echo(f"[FAIL] 清理失败: {e}", err=True)
if verbose:
import traceback
click.echo(traceback.format_exc(), err=True)
sys.exit(1)
@cli.command()
@click.option(
'-o', '--output',
default='sikuwa.toml',
help='输出配置文件名 (默认: sikuwa.toml)'
)
@click.option(
'--force',
is_flag=True,
help=_('覆盖已存在的文件')
)
def init(output: str, force: bool):
"""
初始化项目配置
创建默认的 sikuwa.toml 配置文件
示例:
sikuwa init # 创建 sikuwa.toml
sikuwa init -o custom.toml # 创建自定义配置文件
sikuwa init --force # 强制覆盖已存在的文件
"""
try:
output_path = Path(output)
# 检查文件是否已存在
if output_path.exists() and not force:
click.echo(f"[WARN] 配置文件已存在: {output}", err=True)
click.echo("使用 --force 选项强制覆盖", err=True)
sys.exit(1)
# 创建配置文件
logger.info_operation(f"创建配置文件: {output}")
create_config(output)
click.echo(f"\n[OK] 配置文件已创建: {output}")
click.echo("\n下一步:")
click.echo(" 1. 编辑 sikuwa.toml配置项目信息")
click.echo(" 2. 运行 'sikuwa build' 开始构建")
sys.exit(0)
except Exception as e:
click.echo(f"[FAIL] 创建配置文件失败: {e}", err=True)
sys.exit(1)
@cli.command()
@click.option(
'-c', '--config',
type=click.Path(exists=True),
help='配置文件路径 (默认: sikuwa.toml)'
)
def info(config: Optional[str]):
"""
显示项目信息
显示当前项目的配置信息
示例:
sikuwa info # 显示项目信息
sikuwa info -c custom.toml # 显示指定配置文件的信息
"""
try:
# 加载配置
build_config = ConfigManager.load_config(config)
# 显示项目信息
click.echo("\n" + "=" * 70)
click.echo(f"项目信息: {build_config.project_name}")
click.echo("=" * 70)
click.echo(f"\n基础信息:")
click.echo(f" 项目名称: {build_config.project_name}")
click.echo(f" 版本: {build_config.version}")
if build_config.description:
click.echo(f" 描述: {build_config.description}")
if build_config.author:
click.echo(f" 作者: {build_config.author}")
click.echo(f"\n构建配置:")
click.echo(f" 入口文件: {build_config.main_script}")
click.echo(f" 源代码目录: {build_config.src_dir}")
click.echo(f" 输出目录: {build_config.output_dir}")
click.echo(f" 构建目录: {build_config.build_dir}")
click.echo(f" 目标平台: {', '.join(build_config.platforms)}")
click.echo(f"\nNuitka 选项:")
click.echo(f" Standalone: {build_config.nuitka_options.standalone}")
click.echo(f" OneFile: {build_config.nuitka_options.onefile}")
click.echo(f" Follow Imports: {build_config.nuitka_options.follow_imports}")
click.echo(f" Show Progress: {build_config.nuitka_options.show_progress}")
click.echo(f" Enable Console: {build_config.nuitka_options.enable_console}")
if build_config.nuitka_options.include_packages:
click.echo(f"\n包含的包:")
for pkg in build_config.nuitka_options.include_packages:
click.echo(f" - {pkg}")
if build_config.resources:
click.echo(f"\n资源文件:")
for resource in build_config.resources:
click.echo(f" - {resource}")
click.echo("\n" + "=" * 70 + "\n")
except FileNotFoundError as e:
click.echo(f"[FAIL] {e}", err=True)
sys.exit(1)
except Exception as e:
click.echo(f"[FAIL] 读取配置失败: {e}", err=True)
sys.exit(1)
@cli.command()
def version():
"""
显示版本信息
"""
click.echo("\nSikuwa v1.2.0")
click.echo("Python 项目打包工具")
click.echo("基于 Nuitka 的跨平台构建系统")
click.echo("\nGitHub: https://github.com/FORGE24/Sikuwa/")
click.echo("文档: https://www.sanrol-cloud.top\n")
@cli.command()
@click.option(
'-c', '--config',
type=click.Path(exists=True),
help='配置文件路径 (默认: sikuwa.toml)'
)
def validate(config: Optional[str]):
"""
验证配置文件
检查配置文件是否正确
示例:
sikuwa validate # 验证默认配置文件
sikuwa validate -c custom.toml # 验证指定配置文件
"""
try:
# 加载配置
logger.info_operation("加载配置文件...")
build_config = ConfigManager.load_config(config)
# 验证配置
logger.info_operation("验证配置...")
build_config.validate()
click.echo("\n[OK] 配置文件有效!")
# 显示摘要信息
click.echo(f"\n项目: {build_config.project_name}")
click.echo(f"版本: {build_config.version}")
click.echo(f"入口: {build_config.main_script}")
click.echo(f"平台: {', '.join(build_config.platforms)}\n")
sys.exit(0)
except FileNotFoundError as e:
click.echo(f"[FAIL] {e}", err=True)
sys.exit(1)
except ValueError as e:
click.echo(f"[FAIL] 配置验证失败: {e}", err=True)
sys.exit(1)
except Exception as e:
click.echo(f"[FAIL] 验证失败: {e}", err=True)
sys.exit(1)
@cli.command()
def doctor():
"""
检查构建环境
检查系统环境和依赖项是否满足构建要求
"""
import subprocess
import platform
click.echo("\n" + "=" * 70)
click.echo("Sikuwa 环境诊断")
click.echo("=" * 70)
# 检查 Python 版本
click.echo("\n[1] Python 环境")
python_version = sys.version_info
click.echo(f" 版本: {python_version.major}.{python_version.minor}.{python_version.micro}")
click.echo(f" 路径: {sys.executable}")
if python_version.major < 3 or (python_version.major == 3 and python_version.minor < 7):
click.echo(" [FAIL] Python 版本过低,需要 3.7+", err=True)
else:
click.echo(" [OK] Python 版本满足要求")
# 检查操作系统
click.echo("\n[2] 操作系统")
os_name = platform.system()
os_version = platform.version()
click.echo(f" 系统: {os_name}")
click.echo(f" 版本: {os_version}")
click.echo(f" 架构: {platform.machine()}")
# 检查 Nuitka
click.echo("\n[3] Nuitka")
try:
result = subprocess.run(
["nuitka3", "--version"],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0:
version = result.stdout.strip().split('\n')[0]
click.echo(f" [OK] 已安装: {version}")
else:
click.echo(" [FAIL] Nuitka 未正确安装", err=True)
except FileNotFoundError:
click.echo(" [FAIL] Nuitka 未安装", err=True)
click.echo(" 安装命令: pip install nuitka")
except Exception as e:
click.echo(f" [WARN] 检查 Nuitka 时出错: {e}", err=True)
# 检查编译器 (Windows)
if os_name == "Windows":
click.echo("\n[4] C 编译器 (Windows)")
# 检查 MinGW
try:
result = subprocess.run(
["gcc", "--version"],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0:
version = result.stdout.strip().split('\n')[0]
click.echo(f" [OK] GCC 已安装: {version}")
else:
click.echo(" [WARN] GCC 未找到", err=True)
except FileNotFoundError:
click.echo(" [WARN] GCC 未安装", err=True)
click.echo(" 推荐安装 MinGW-w64 或 MSVC")
except Exception as e:
click.echo(f" [WARN] 检查 GCC 时出错: {e}", err=True)
# 检查 MSVC
try:
result = subprocess.run(
["cl"],
capture_output=True,
text=True,
timeout=5
)
if "Microsoft" in result.stderr or "Microsoft" in result.stdout:
click.echo(" [OK] MSVC 已安装")
else:
click.echo(" [INFO] MSVC 未找到")
except FileNotFoundError:
click.echo(" [INFO] MSVC 未安装")
except Exception as e:
click.echo(f" [INFO] 检查 MSVC 时出错: {e}")
# 检查编译器 (Linux/macOS)
elif os_name in ["Linux", "Darwin"]:
click.echo(f"\n[4] C 编译器 ({os_name})")
try:
result = subprocess.run(
["gcc", "--version"],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0:
version = result.stdout.strip().split('\n')[0]
click.echo(f" [OK] GCC 已安装: {version}")
else:
click.echo(" [FAIL] GCC 未找到", err=True)
except FileNotFoundError:
click.echo(" [FAIL] GCC 未安装", err=True)
if os_name == "Linux":
click.echo(" 安装命令: sudo apt install gcc # Debian/Ubuntu")
click.echo(" sudo yum install gcc # RedHat/CentOS")
elif os_name == "Darwin":
click.echo(" 安装命令: xcode-select --install")
except Exception as e:
click.echo(f" [WARN] 检查 GCC 时出错: {e}", err=True)
# 检查必需的 Python 包
click.echo("\n[5] Python 依赖包")
required_packages = {
'click': 'CLI 框架',
'tomli': 'TOML 解析器',
'tomli_w': 'TOML 写入器',
'nuitka': 'Python 编译器'
}
for package, description in required_packages.items():
try:
__import__(package)
click.echo(f" [OK] {package:15s} - {description}")
except ImportError:
click.echo(f" [FAIL] {package:15s} - {description} (未安装)", err=True)
# 检查可选包
click.echo("\n[6] 可选依赖包")
optional_packages = {
'ordered_set': _('有序集合支持'),
'zstandard': 'Zstandard 压缩',
}
for package, description in optional_packages.items():
try:
__import__(package)
click.echo(f" [OK] {package:15s} - {description}")
except ImportError:
click.echo(f" [INFO] {package:15s} - {description} (未安装)")
# 总结
click.echo("\n" + "=" * 70)
click.echo(_("诊断完成"))
click.echo("=" * 70)
click.echo("\n如果有 [FAIL] 项,请先解决这些问题后再进行构建。\n")
@cli.command()
@click.argument('query', required=False)
def help_cmd(query: Optional[str]):
"""
显示帮助信息
示例:
sikuwa help # 显示总体帮助
sikuwa help build # 显示 build 命令帮助
sikuwa help config # 显示配置文件帮助
"""
if not query:
# 显示总体帮助
click.echo("\n" + "=" * 70)
click.echo("Sikuwa - Python 项目打包工具")
click.echo("=" * 70)
click.echo("\n常用命令:")
click.echo(" sikuwa init 创建配置文件")
click.echo(" sikuwa build 构建项目")
click.echo(" sikuwa clean 清理构建文件")
click.echo(" sikuwa sync 同步项目依赖")
click.echo(" sikuwa info 显示项目信息")
click.echo(" sikuwa doctor 检查构建环境")
click.echo("\n获取更多帮助:")
click.echo(" sikuwa --help 显示所有命令")
click.echo(" sikuwa <command> --help 显示命令详细帮助")
click.echo(" sikuwa help config 配置文件帮助")
click.echo("\n快速开始:")
click.echo(" 1. sikuwa init # 创建配置文件")
click.echo(" 2. 编辑 sikuwa.toml # 配置项目")
click.echo(" 3. sikuwa sync # 同步项目依赖")
click.echo(" 4. sikuwa build # 构建项目")
click.echo("\n文档: https://www.sanrol-cloud.top")
click.echo("=" * 70 + "\n")
elif query.lower() == "config":
# 配置文件帮助
click.echo("\n" + "=" * 70)
click.echo("Sikuwa 配置文件说明")
click.echo("=" * 70)
click.echo("\n配置文件示例 (sikuwa.toml):\n")
click.echo("""[sikuwa]
project_name = "my_app"
version = "1.0.0"
description = "My Application"
author = "Your Name"
main_script = "main.py"
src_dir = "."
output_dir = "dist"
build_dir = "build"
platforms = ["windows", "linux"]
resources = ["config.json", "data/"]
[sikuwa.nuitka]
standalone = true
onefile = false
follow_imports = true
show_progress = true
enable_console = true
optimize = true
include_packages = ["requests", "numpy"]
include_modules = []
include_data_files = []
include_data_dirs = []
windows_icon = "icon.ico"
windows_company_name = "My Company"
windows_product_name = "My Product"
""")
click.echo("\n主要配置项:")
click.echo(" project_name 项目名称")
click.echo(" main_script 入口文件")
click.echo(" platforms 目标平台 (windows/linux/macos)")
click.echo(" standalone 独立模式")
click.echo(" onefile 单文件模式")
click.echo(" include_packages 包含的 Python 包")
click.echo("\n详细文档: https://www.sanrol-cloud.top")
click.echo("=" * 70 + "\n")
else:
# 显示特定命令的帮助
ctx = click.Context(cli)
cmd = cli.get_command(ctx, query)
if cmd:
click.echo(cmd.get_help(ctx))
else:
click.echo(f"[FAIL] 未知命令: {query}", err=True)
click.echo("使用 'sikuwa --help' 查看所有可用命令", err=True)
@cli.command()
@click.option(
'-c', '--config',
type=click.Path(exists=True),
help='配置文件路径 (默认: sikuwa.toml)'
)
@click.option(
'--format',
type=click.Choice(['text', 'json'], case_sensitive=False),
default='text',
help='输出格式 (默认: text)'
)
def show_config(config: Optional[str], format: str):
"""
显示完整配置
以易读的格式显示所有配置选项
示例:
sikuwa show-config # 显示配置 (文本格式)
sikuwa show-config --format json # 显示配置 (JSON 格式)
"""
try:
# 加载配置
build_config = ConfigManager.load_config(config)
if format == 'json':
# JSON 格式输出
import json
config_dict = build_config.to_dict()
click.echo(json.dumps(config_dict, indent=2, ensure_ascii=False))
else:
# 文本格式输出
config_dict = build_config.to_dict()
click.echo("\n" + "=" * 70)
click.echo(_("完整配置"))
click.echo("=" * 70)
def print_dict(d, indent=0):
for key, value in d.items():
if isinstance(value, dict):
click.echo(" " * indent + f"{key}:")
print_dict(value, indent + 1)
elif isinstance(value, list):
click.echo(" " * indent + f"{key}:")
for item in value:
click.echo(" " * (indent + 1) + f"- {item}")
else:
click.echo(" " * indent + f"{key}: {value}")
print_dict(config_dict)
click.echo("=" * 70 + "\n")
sys.exit(0)
except FileNotFoundError as e:
click.echo(f"[FAIL] {e}", err=True)
sys.exit(1)
except Exception as e:
click.echo(f"[FAIL] 读取配置失败: {e}", err=True)
sys.exit(1)
@cli.command()
@click.option(
'-c', '--config',
type=click.Path(exists=True),
help='配置文件路径 (默认: sikuwa.toml)'
)
@click.option(
'-v', '--verbose',
is_flag=True,
help=_('详细输出模式')
)
def sync(config: Optional[str], verbose: bool):
"""
同步项目依赖
自动创建或进入虚拟环境,并安装配置文件中指定的依赖包
示例:
sikuwa sync # 同步依赖
sikuwa sync -v # 详细输出
sikuwa sync -c my_config.toml # 使用指定配置文件
"""
try:
from sikuwa.builder import sync_project
# 加载配置
logger.info_operation("加载构建配置...")
build_config = ConfigManager.load_config(config)
# 验证配置
logger.info_operation("验证配置...")
build_config.validate()
# 执行同步
logger.info_operation(f"开始同步项目依赖: {build_config.project_name}")
success = sync_project(
config=build_config,
verbose=verbose
)
if success:
click.echo("\n[OK] 依赖同步成功完成!", err=False)
sys.exit(0)
else:
click.echo("\n[FAIL] 依赖同步失败!", err=True)
sys.exit(1)
except FileNotFoundError as e:
click.echo(f"[FAIL] {e}", err=True)
click.echo("\n提示: 使用 'sikuwa init' 创建配置文件", err=True)
sys.exit(1)
except Exception as e:
click.echo(f"[FAIL] 同步失败: {e}", err=True)
if verbose:
import traceback
click.echo(traceback.format_exc(), err=True)
sys.exit(1)
@cli.command()
@click.option(
'-c', '--config',
type=click.Path(exists=True),
help='配置文件路径 (默认: sikuwa.toml)'
)
@click.option(
'-v', '--verbose',
is_flag=True,
help='详细输出模式'
)
def build_sequence(config: Optional[str], verbose: bool):
"""
执行编译序列构建
支持按配置文件中定义的项目依赖关系进行拓扑排序,并可选择并行构建
示例:
sikuwa build-sequence # 执行编译序列构建
sikuwa build-sequence -v # 详细输出
sikuwa build-sequence -c my_config.toml # 使用指定配置文件
"""
try:
from sikuwa.builder import build_sequence
# 加载配置
logger.info_operation("加载构建配置...")
build_config = ConfigManager.load_config(config)
# 验证配置
logger.info_operation("验证配置...")
build_config.validate()
# 执行编译序列构建
success = build_sequence(
config=build_config,
verbose=verbose
)
if success:
click.echo("\n[OK] 编译序列构建成功完成!", err=False)
sys.exit(0)
else:
click.echo("\n[FAIL] 编译序列构建失败!", err=True)
sys.exit(1)
except FileNotFoundError as e:
click.echo(f"[FAIL] {e}", err=True)
click.echo("\n提示: 使用 'sikuwa init' 创建配置文件", err=True)
sys.exit(1)
except Exception as e:
click.echo(f"[FAIL] 构建失败: {e}", err=True)
if verbose:
import traceback
click.echo(traceback.format_exc(), err=True)
sys.exit(1)
def main():
"""主入口函数"""
try:
cli()
except KeyboardInterrupt:
click.echo("\n\n[WARN] 用户中断操作", err=True)
sys.exit(130)
except Exception as e:
click.echo(f"\n[FAIL] 未预期的错误: {e}", err=True)
import traceback
click.echo(traceback.format_exc(), err=True)
sys.exit(1)
if __name__ == '__main__':
main()