PyInstallerEx 0.1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- PyInstallerEx/PyInstallerEx.py +0 -0
- PyInstallerEx/__init__.py +13 -0
- PyInstallerEx/__main__.py +144 -0
- PyInstallerEx/core/__init__.py +9 -0
- PyInstallerEx/core/config.py +91 -0
- PyInstallerEx/core/packager.py +961 -0
- PyInstallerEx/utils/__init__.py +14 -0
- PyInstallerEx/utils/compression.py +69 -0
- PyInstallerEx/utils/file_utils.py +50 -0
- PyInstallerEx/utils/icon_helper.py +78 -0
- PyInstallerEx/utils/logging.py +154 -0
- PyInstallerEx/utils/system_utils.py +59 -0
- pyinstallerex-0.1.6.data/data/bin/launcher_windows.exe +0 -0
- pyinstallerex-0.1.6.dist-info/METADATA +528 -0
- pyinstallerex-0.1.6.dist-info/RECORD +19 -0
- pyinstallerex-0.1.6.dist-info/WHEEL +5 -0
- pyinstallerex-0.1.6.dist-info/entry_points.txt +3 -0
- pyinstallerex-0.1.6.dist-info/licenses/LICENSE +21 -0
- pyinstallerex-0.1.6.dist-info/top_level.txt +2 -0
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
PyInstallerEx - Enhanced PyInstaller packaging tool
|
|
4
|
+
扩展PyInstaller使其拥有单文件安装功能
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.6"
|
|
8
|
+
__author__ = "iiixxxiii"
|
|
9
|
+
|
|
10
|
+
from .core.packager import PackageBuilder
|
|
11
|
+
from .core.config import PackageConfig
|
|
12
|
+
|
|
13
|
+
__all__ = ["PackageBuilder", "PackageConfig"]
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
PyInstallerEx命令行入口点
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import print_function
|
|
8
|
+
import argparse
|
|
9
|
+
import sys
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
from .core.packager import PackageBuilder
|
|
13
|
+
from .core.config import PackageConfig
|
|
14
|
+
from .utils.logging import setup_logging
|
|
15
|
+
from .utils.file_utils import FileUtils
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main():
|
|
19
|
+
"""主函数 - 命令行接口"""
|
|
20
|
+
parser = argparse.ArgumentParser(
|
|
21
|
+
description="PyInstallerEx - Enhanced PyInstaller packaging tool",
|
|
22
|
+
epilog="Example: python -m PyInstallerEx main.py --cfg config.json"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
parser.add_argument("script", nargs="?", default=None,
|
|
26
|
+
help="Python script to package (mutually exclusive with --folder)")
|
|
27
|
+
parser.add_argument("--cfg", "--config", dest="config",
|
|
28
|
+
help="Custom configuration file path")
|
|
29
|
+
parser.add_argument("--output", "-o", help="Output directory")
|
|
30
|
+
parser.add_argument("--verbose", "-v", action="store_true",
|
|
31
|
+
help="Verbose output")
|
|
32
|
+
parser.add_argument("--name", "-n", help="Output filename (default: script name)")
|
|
33
|
+
parser.add_argument("--extract-mode", dest="extract_mode", choices=["temp", "local"],
|
|
34
|
+
default="temp", help="Extraction mode: 'temp' (system temp dir, default) or 'local' (beside exe)")
|
|
35
|
+
parser.add_argument("--engine-mode", dest="engine_mode", choices=["pyinstaller", "nuitka", "pyoxidizer"],
|
|
36
|
+
default="pyinstaller", help="Packaging engine: 'pyinstaller' (default), 'nuitka', or 'pyoxidizer'")
|
|
37
|
+
parser.add_argument("--folder", dest="folder",
|
|
38
|
+
help="Package a local folder directly (skip engine build, mutually exclusive with script)")
|
|
39
|
+
parser.add_argument("--run", dest="run",
|
|
40
|
+
help="Main entry file or relative path to run after extraction (used with --folder)")
|
|
41
|
+
|
|
42
|
+
# 计算默认图标路径(包内的logo.ico)
|
|
43
|
+
_default_icon = os.path.join(os.path.dirname(os.path.abspath(__file__)), "logo.ico")
|
|
44
|
+
parser.add_argument("--icon", default=_default_icon,
|
|
45
|
+
help="Icon file path for the executable (default: package logo.ico)")
|
|
46
|
+
|
|
47
|
+
args = parser.parse_args()
|
|
48
|
+
|
|
49
|
+
# 设置日志
|
|
50
|
+
setup_logging(args.verbose)
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
# 参数互斥校验:script 和 --folder 不能同时使用
|
|
54
|
+
if args.script and args.folder:
|
|
55
|
+
print("[FAIL] Error: 'script' and '--folder' are mutually exclusive. Please specify only one.", file=sys.stderr)
|
|
56
|
+
sys.exit(1)
|
|
57
|
+
|
|
58
|
+
# 必须提供 script 或 --folder 之一
|
|
59
|
+
if not args.script and not args.folder:
|
|
60
|
+
print("[FAIL] Error: either 'script' or '--folder' must be specified.", file=sys.stderr)
|
|
61
|
+
sys.exit(1)
|
|
62
|
+
|
|
63
|
+
# --folder 模式校验:目录必须存在
|
|
64
|
+
if args.folder:
|
|
65
|
+
if not os.path.isdir(args.folder):
|
|
66
|
+
print("[FAIL] Error: --folder path does not exist or is not a directory: {0}".format(args.folder), file=sys.stderr)
|
|
67
|
+
sys.exit(1)
|
|
68
|
+
|
|
69
|
+
# 加载配置
|
|
70
|
+
if args.config:
|
|
71
|
+
config = PackageConfig.from_file(args.config)
|
|
72
|
+
else:
|
|
73
|
+
# 使用默认配置
|
|
74
|
+
config = PackageConfig()
|
|
75
|
+
|
|
76
|
+
# 设置解压模式、引擎模式和图标
|
|
77
|
+
config.extract_mode = args.extract_mode
|
|
78
|
+
config.engine_mode = args.engine_mode
|
|
79
|
+
|
|
80
|
+
# 设置 --run 参数(main 字段)
|
|
81
|
+
if args.run:
|
|
82
|
+
config.main = args.run
|
|
83
|
+
|
|
84
|
+
# 验证图标参数
|
|
85
|
+
icon_path = args.icon
|
|
86
|
+
if icon_path and icon_path != _default_icon: # 用户指定了自定义图标
|
|
87
|
+
# 检查文件是否存在
|
|
88
|
+
if not os.path.exists(icon_path):
|
|
89
|
+
print("[WARNING] Icon file not found: {0}".format(icon_path), file=sys.stderr)
|
|
90
|
+
config.icon = None
|
|
91
|
+
# 检查文件格式是否为.ico
|
|
92
|
+
elif not icon_path.lower().endswith('.ico'):
|
|
93
|
+
print("[WARNING] Icon file must be in .ico format, got: {0}".format(icon_path), file=sys.stderr)
|
|
94
|
+
print("[WARNING] Please convert your image to .ico format before using --icon parameter", file=sys.stderr)
|
|
95
|
+
config.icon = None
|
|
96
|
+
else:
|
|
97
|
+
config.icon = icon_path
|
|
98
|
+
else:
|
|
99
|
+
# 使用默认图标(如果存在)
|
|
100
|
+
if os.path.exists(_default_icon):
|
|
101
|
+
config.icon = _default_icon
|
|
102
|
+
else:
|
|
103
|
+
config.icon = None
|
|
104
|
+
|
|
105
|
+
# 创建构建器
|
|
106
|
+
builder = PackageBuilder(config, verbose=args.verbose)
|
|
107
|
+
|
|
108
|
+
output_dir = args.output if args.output else None
|
|
109
|
+
|
|
110
|
+
# --folder 模式:直接打包文件夹
|
|
111
|
+
if args.folder:
|
|
112
|
+
folder_path = os.path.abspath(args.folder)
|
|
113
|
+
|
|
114
|
+
# 确定输出文件名:优先用 -n,否则用文件夹名
|
|
115
|
+
if args.name:
|
|
116
|
+
config.filename = args.name
|
|
117
|
+
elif config.filename == "{filename}":
|
|
118
|
+
config.filename = os.path.basename(folder_path)
|
|
119
|
+
|
|
120
|
+
result_path = builder.build_folder(folder_path, output_dir)
|
|
121
|
+
else:
|
|
122
|
+
# 常规脚本打包模式
|
|
123
|
+
# 如果通过-n指定了名称,则覆盖配置中的filename
|
|
124
|
+
if args.name:
|
|
125
|
+
config.filename = args.name
|
|
126
|
+
# 如果配置中filename仍然是默认值"{filename}",则使用脚本名
|
|
127
|
+
elif config.filename == "{filename}":
|
|
128
|
+
script_name = FileUtils.get_script_name(args.script)
|
|
129
|
+
config.filename = script_name
|
|
130
|
+
|
|
131
|
+
# 执行打包
|
|
132
|
+
script_path = args.script
|
|
133
|
+
result_path = builder.build(script_path, output_dir)
|
|
134
|
+
|
|
135
|
+
print("\n[OK] Packaging completed successfully!")
|
|
136
|
+
print("[Output] File: {0}".format(result_path))
|
|
137
|
+
|
|
138
|
+
except Exception as e:
|
|
139
|
+
print("\n[FAIL] Packaging failed: {0}".format(e), file=sys.stderr)
|
|
140
|
+
sys.exit(1)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
main()
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
配置管理模块
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import print_function
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import hashlib
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PackageConfig(object):
|
|
13
|
+
"""打包配置数据类"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, filename="{filename}", version="1.0.0", installer=None,
|
|
16
|
+
tmp_dir="ex_{filename}_{md5}", description="Application packaged with PyInstallerEx",
|
|
17
|
+
author="Unknown", extract_mode="temp", engine_mode="pyinstaller",
|
|
18
|
+
icon=None, main=None):
|
|
19
|
+
self.filename = filename
|
|
20
|
+
self.version = version
|
|
21
|
+
self.installer = installer
|
|
22
|
+
self.tmp_dir = tmp_dir
|
|
23
|
+
self.description = description
|
|
24
|
+
self.author = author
|
|
25
|
+
self.extract_mode = extract_mode
|
|
26
|
+
self.engine_mode = engine_mode
|
|
27
|
+
self.icon = icon
|
|
28
|
+
self.main = main
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def from_file(cls, config_path):
|
|
32
|
+
"""从配置文件加载配置"""
|
|
33
|
+
try:
|
|
34
|
+
with open(config_path, 'r') as f:
|
|
35
|
+
data = json.load(f)
|
|
36
|
+
return cls(**data)
|
|
37
|
+
except (ValueError, IOError) as e:
|
|
38
|
+
raise ValueError("Invalid configuration file {0}: {1}".format(config_path, e))
|
|
39
|
+
|
|
40
|
+
def to_dict(self):
|
|
41
|
+
"""转换为字典"""
|
|
42
|
+
return {
|
|
43
|
+
'filename': self.filename,
|
|
44
|
+
'version': self.version,
|
|
45
|
+
'installer': self.installer,
|
|
46
|
+
'tmp_dir': self.tmp_dir,
|
|
47
|
+
'description': self.description,
|
|
48
|
+
'author': self.author,
|
|
49
|
+
'extract_mode': self.extract_mode,
|
|
50
|
+
'engine_mode': self.engine_mode,
|
|
51
|
+
'icon': self.icon,
|
|
52
|
+
'main': self.main
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
def validate(self):
|
|
56
|
+
"""验证配置有效性"""
|
|
57
|
+
# 不再强制要求filename必须指定,因为会在运行时根据脚本名设置
|
|
58
|
+
if not self.version:
|
|
59
|
+
raise ValueError("Version must be specified")
|
|
60
|
+
return True
|
|
61
|
+
|
|
62
|
+
def render_template(self, script_name, script_md5):
|
|
63
|
+
"""渲染配置模板,替换占位符"""
|
|
64
|
+
filename = self.filename.replace("{filename}", script_name)
|
|
65
|
+
tmp_dir = self.tmp_dir.replace("{filename}", script_name).replace("{md5}", script_md5)
|
|
66
|
+
|
|
67
|
+
# 处理installer默认值
|
|
68
|
+
installer = self.installer
|
|
69
|
+
if not installer:
|
|
70
|
+
if os.name == 'nt': # Windows
|
|
71
|
+
installer = os.environ.get('TEMP', '/tmp')
|
|
72
|
+
else: # Unix/Linux
|
|
73
|
+
installer = '/tmp'
|
|
74
|
+
|
|
75
|
+
return PackageConfig(
|
|
76
|
+
filename=filename,
|
|
77
|
+
version=self.version,
|
|
78
|
+
installer=installer,
|
|
79
|
+
tmp_dir=tmp_dir,
|
|
80
|
+
description=self.description,
|
|
81
|
+
author=self.author,
|
|
82
|
+
extract_mode=self.extract_mode,
|
|
83
|
+
engine_mode=self.engine_mode,
|
|
84
|
+
icon=self.icon,
|
|
85
|
+
main=self.main
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def save_to_file(self, output_path):
|
|
89
|
+
"""保存配置到文件"""
|
|
90
|
+
with open(output_path, 'w') as f:
|
|
91
|
+
json.dump(self.to_dict(), f, indent=2)
|