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
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
PyInstallerEx工具函数模块
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .logging import setup_logging, PackageError, handle_error
|
|
7
|
+
from .system_utils import SystemInfo
|
|
8
|
+
from .file_utils import FileUtils
|
|
9
|
+
from .compression import CompressionUtils
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"setup_logging", "PackageError", "handle_error",
|
|
13
|
+
"SystemInfo", "FileUtils", "CompressionUtils"
|
|
14
|
+
]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
压缩和解压工具模块
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import print_function
|
|
7
|
+
import zipfile
|
|
8
|
+
import shutil
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CompressionUtils(object):
|
|
13
|
+
"""压缩工具类"""
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
def create_zip_from_directory(source_dir, zip_path):
|
|
17
|
+
"""将目录压缩为ZIP文件"""
|
|
18
|
+
def add_files_to_zip(zipf, folder, base_path=""):
|
|
19
|
+
for item in os.listdir(folder):
|
|
20
|
+
item_path = os.path.join(folder, item)
|
|
21
|
+
arcname = os.path.join(base_path, item)
|
|
22
|
+
|
|
23
|
+
if os.path.isfile(item_path):
|
|
24
|
+
zipf.write(item_path, arcname)
|
|
25
|
+
elif os.path.isdir(item_path):
|
|
26
|
+
add_files_to_zip(zipf, item_path, arcname)
|
|
27
|
+
|
|
28
|
+
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
|
29
|
+
add_files_to_zip(zipf, source_dir)
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def combine_binary_and_zip(binary_path, zip_path, output_path):
|
|
33
|
+
"""将二进制文件和ZIP文件合并为单个二进制文件"""
|
|
34
|
+
# 简单的文件合并:二进制文件 + ZIP文件
|
|
35
|
+
with open(output_path, 'wb') as outfile:
|
|
36
|
+
# 先写入二进制启动器
|
|
37
|
+
with open(binary_path, 'rb') as binfile:
|
|
38
|
+
outfile.write(binfile.read())
|
|
39
|
+
|
|
40
|
+
# 写入分隔符(可选,用于识别ZIP部分)
|
|
41
|
+
outfile.write(b'\x50\x4B\x03\x04') # ZIP文件头作为分隔符
|
|
42
|
+
|
|
43
|
+
# 再写入ZIP文件内容
|
|
44
|
+
with open(zip_path, 'rb') as zipfile:
|
|
45
|
+
outfile.write(zipfile.read())
|
|
46
|
+
|
|
47
|
+
@staticmethod
|
|
48
|
+
def extract_zip_from_combined(combined_path, output_dir):
|
|
49
|
+
"""从合并文件中提取ZIP部分"""
|
|
50
|
+
# 这里需要根据实际的合并格式来实现
|
|
51
|
+
# 简化实现:假设ZIP部分在特定偏移量开始
|
|
52
|
+
with open(combined_path, 'rb') as infile:
|
|
53
|
+
content = infile.read()
|
|
54
|
+
|
|
55
|
+
# 查找ZIP文件头位置
|
|
56
|
+
zip_header = b'\x50\x4B\x03\x04'
|
|
57
|
+
zip_start = content.find(zip_header)
|
|
58
|
+
|
|
59
|
+
if zip_start == -1:
|
|
60
|
+
raise ValueError("Could not find ZIP section in combined file")
|
|
61
|
+
|
|
62
|
+
# 提取ZIP部分
|
|
63
|
+
zip_content = content[zip_start:]
|
|
64
|
+
zip_output_path = os.path.join(output_dir, "extracted.zip")
|
|
65
|
+
|
|
66
|
+
with open(zip_output_path, 'wb') as zipfile:
|
|
67
|
+
zipfile.write(zip_content)
|
|
68
|
+
|
|
69
|
+
return zip_output_path
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
文件操作工具函数
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import print_function
|
|
7
|
+
import hashlib
|
|
8
|
+
import shutil
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FileUtils(object):
|
|
13
|
+
"""文件操作工具类"""
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
def calculate_md5(file_path):
|
|
17
|
+
"""计算文件的MD5哈希值"""
|
|
18
|
+
hash_md5 = hashlib.md5()
|
|
19
|
+
with open(file_path, "rb") as f:
|
|
20
|
+
for chunk in iter(lambda: f.read(4096), b""):
|
|
21
|
+
hash_md5.update(chunk)
|
|
22
|
+
return hash_md5.hexdigest()
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def copy_config_to_dir(config_content, target_dir, config_name="config.json"):
|
|
26
|
+
"""将配置内容复制到目标目录"""
|
|
27
|
+
config_path = os.path.join(target_dir, config_name)
|
|
28
|
+
with open(config_path, 'w') as f:
|
|
29
|
+
f.write(config_content)
|
|
30
|
+
return config_path
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def ensure_directory_exists(directory):
|
|
34
|
+
"""确保目录存在,不存在则创建"""
|
|
35
|
+
if not os.path.exists(directory):
|
|
36
|
+
os.makedirs(directory)
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def clean_directory(directory):
|
|
40
|
+
"""清空目录内容"""
|
|
41
|
+
if os.path.exists(directory):
|
|
42
|
+
shutil.rmtree(directory)
|
|
43
|
+
os.makedirs(directory)
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def get_script_name(script_path):
|
|
47
|
+
"""从脚本路径获取脚本名称(不含扩展名)"""
|
|
48
|
+
base_name = os.path.basename(script_path)
|
|
49
|
+
script_name = os.path.splitext(base_name)[0]
|
|
50
|
+
return script_name
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
图标设置辅助工具
|
|
4
|
+
用于在 Windows 上为 exe 文件设置图标
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import subprocess
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def set_icon_with_resource_hacker(exe_path, icon_path, resource_hacker_path=None):
|
|
13
|
+
"""
|
|
14
|
+
使用 Resource Hacker 为 exe 文件设置图标
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
exe_path: exe 文件路径
|
|
18
|
+
icon_path: ico 图标文件路径
|
|
19
|
+
resource_hacker_path: Resource Hacker 可执行文件路径(可选)
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
bool: 是否成功
|
|
23
|
+
"""
|
|
24
|
+
# 查找 Resource Hacker
|
|
25
|
+
if resource_hacker_path is None:
|
|
26
|
+
# 常见安装路径
|
|
27
|
+
common_paths = [
|
|
28
|
+
r"C:\Program Files (x86)\Resource Hacker\ResourceHacker.exe",
|
|
29
|
+
r"C:\Program Files\Resource Hacker\ResourceHacker.exe",
|
|
30
|
+
os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Resource Hacker', 'ResourceHacker.exe'),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
for path in common_paths:
|
|
34
|
+
if os.path.exists(path):
|
|
35
|
+
resource_hacker_path = path
|
|
36
|
+
break
|
|
37
|
+
|
|
38
|
+
if not resource_hacker_path or not os.path.exists(resource_hacker_path):
|
|
39
|
+
print("Error: Resource Hacker not found. Please install it from:")
|
|
40
|
+
print("http://www.angusj.com/resourcehacker/")
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
# 构建命令
|
|
44
|
+
# ResourceHacker.exe -open input.exe -save output.exe -action addoverwrite -res icon.ico -mask ICONGROUP,MAINICON,
|
|
45
|
+
cmd = [
|
|
46
|
+
resource_hacker_path,
|
|
47
|
+
"-open", exe_path,
|
|
48
|
+
"-save", exe_path,
|
|
49
|
+
"-action", "addoverwrite",
|
|
50
|
+
"-res", icon_path,
|
|
51
|
+
"-mask", "ICONGROUP,MAINICON,"
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
56
|
+
if result.returncode == 0:
|
|
57
|
+
print("Icon set successfully using Resource Hacker")
|
|
58
|
+
return True
|
|
59
|
+
else:
|
|
60
|
+
print("Error: Resource Hacker failed")
|
|
61
|
+
print(result.stderr)
|
|
62
|
+
return False
|
|
63
|
+
except Exception as e:
|
|
64
|
+
print("Error running Resource Hacker: {0}".format(e))
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == "__main__":
|
|
69
|
+
if len(sys.argv) < 3:
|
|
70
|
+
print("Usage: python icon_helper.py <exe_path> <icon_path> [resource_hacker_path]")
|
|
71
|
+
sys.exit(1)
|
|
72
|
+
|
|
73
|
+
exe_path = sys.argv[1]
|
|
74
|
+
icon_path = sys.argv[2]
|
|
75
|
+
rh_path = sys.argv[3] if len(sys.argv) > 3 else None
|
|
76
|
+
|
|
77
|
+
success = set_icon_with_resource_hacker(exe_path, icon_path, rh_path)
|
|
78
|
+
sys.exit(0 if success else 1)
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
日志记录和错误处理模块
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import print_function
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def setup_logging(verbose=False):
|
|
11
|
+
"""设置统一的日志格式"""
|
|
12
|
+
# Python 2.7 兼容性处理 - 动态导入 logging 模块并添加缺失的功能
|
|
13
|
+
import logging
|
|
14
|
+
|
|
15
|
+
# 确保在Python 2.7中也能正确导入logging模块常量
|
|
16
|
+
if not hasattr(logging, 'INFO'):
|
|
17
|
+
logging.INFO = 20
|
|
18
|
+
if not hasattr(logging, 'DEBUG'):
|
|
19
|
+
logging.DEBUG = 10
|
|
20
|
+
if not hasattr(logging, 'WARNING'):
|
|
21
|
+
logging.WARNING = 30
|
|
22
|
+
if not hasattr(logging, 'ERROR'):
|
|
23
|
+
logging.ERROR = 40
|
|
24
|
+
if not hasattr(logging, 'CRITICAL'):
|
|
25
|
+
logging.CRITICAL = 50
|
|
26
|
+
|
|
27
|
+
level = logging.DEBUG if verbose else logging.INFO
|
|
28
|
+
|
|
29
|
+
# 确保至少有一个处理器
|
|
30
|
+
try:
|
|
31
|
+
if hasattr(logging, 'getLogger'):
|
|
32
|
+
root_logger = logging.getLogger()
|
|
33
|
+
else:
|
|
34
|
+
# 如果没有 getLogger,尝试使用 root 属性
|
|
35
|
+
root_logger = getattr(logging, 'root', None)
|
|
36
|
+
|
|
37
|
+
if root_logger is not None:
|
|
38
|
+
if not hasattr(root_logger, 'handlers') or not root_logger.handlers:
|
|
39
|
+
# 添加一个默认的处理器以避免 "No handlers could be found" 警告
|
|
40
|
+
try:
|
|
41
|
+
if hasattr(logging, 'StreamHandler'):
|
|
42
|
+
handler = logging.StreamHandler(sys.stdout)
|
|
43
|
+
if hasattr(logging, 'Formatter'):
|
|
44
|
+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
45
|
+
handler.setFormatter(formatter)
|
|
46
|
+
root_logger.addHandler(handler)
|
|
47
|
+
except:
|
|
48
|
+
# 如果无法创建处理器,至少确保日志级别被设置
|
|
49
|
+
pass
|
|
50
|
+
root_logger.setLevel(level)
|
|
51
|
+
except:
|
|
52
|
+
# 忽略日志初始化错误
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
# Python 2.7 兼容性处理
|
|
56
|
+
if hasattr(logging, 'basicConfig'):
|
|
57
|
+
# Python 2.7.9+ 和 Python 3 中有 basicConfig 函数
|
|
58
|
+
try:
|
|
59
|
+
# 尝试使用 handlers 参数
|
|
60
|
+
logging.basicConfig(
|
|
61
|
+
level=level,
|
|
62
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
63
|
+
handlers=[logging.StreamHandler(sys.stdout)]
|
|
64
|
+
)
|
|
65
|
+
except (TypeError, AttributeError):
|
|
66
|
+
# 更早版本的 Python 2.7 不支持 handlers 参数
|
|
67
|
+
try:
|
|
68
|
+
logging.basicConfig(
|
|
69
|
+
level=level,
|
|
70
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
71
|
+
)
|
|
72
|
+
except:
|
|
73
|
+
pass
|
|
74
|
+
else:
|
|
75
|
+
# 如果没有 basicConfig 函数,则手动配置根日志记录器
|
|
76
|
+
try:
|
|
77
|
+
if hasattr(logging, 'getLogger'):
|
|
78
|
+
root_logger = logging.getLogger()
|
|
79
|
+
else:
|
|
80
|
+
root_logger = getattr(logging, 'root', None)
|
|
81
|
+
|
|
82
|
+
if root_logger is not None:
|
|
83
|
+
root_logger.setLevel(level)
|
|
84
|
+
|
|
85
|
+
# 清除现有的处理器
|
|
86
|
+
if hasattr(root_logger, 'handlers'):
|
|
87
|
+
for handler in root_logger.handlers[:]:
|
|
88
|
+
root_logger.removeHandler(handler)
|
|
89
|
+
|
|
90
|
+
# 添加新的处理器
|
|
91
|
+
if hasattr(logging, 'StreamHandler'):
|
|
92
|
+
try:
|
|
93
|
+
handler = logging.StreamHandler(sys.stdout)
|
|
94
|
+
except:
|
|
95
|
+
# 如果无法创建 StreamHandler,使用备用方案
|
|
96
|
+
handler = None
|
|
97
|
+
else:
|
|
98
|
+
handler = None
|
|
99
|
+
|
|
100
|
+
if handler is not None:
|
|
101
|
+
if hasattr(logging, 'Formatter'):
|
|
102
|
+
try:
|
|
103
|
+
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
104
|
+
handler.setFormatter(formatter)
|
|
105
|
+
except:
|
|
106
|
+
pass # 忽略格式化器错误
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
root_logger.addHandler(handler)
|
|
110
|
+
except:
|
|
111
|
+
pass # 忽略添加处理器的错误
|
|
112
|
+
except:
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class PackageError(Exception):
|
|
117
|
+
"""自定义打包异常"""
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def handle_error(error, message=""):
|
|
122
|
+
"""统一的错误处理"""
|
|
123
|
+
# Python 2.7 兼容性处理 - 动态导入 logging 模块
|
|
124
|
+
import logging
|
|
125
|
+
|
|
126
|
+
# 尝试使用 logging 记录错误,如果失败则直接打印
|
|
127
|
+
try:
|
|
128
|
+
# 检查是否有 getLogger 函数
|
|
129
|
+
if hasattr(logging, 'getLogger'):
|
|
130
|
+
try:
|
|
131
|
+
logger = logging.getLogger(__name__)
|
|
132
|
+
except:
|
|
133
|
+
# 如果无法获取特定名称的 logger,则使用 None
|
|
134
|
+
logger = None
|
|
135
|
+
else:
|
|
136
|
+
logger = None
|
|
137
|
+
|
|
138
|
+
if logger is not None:
|
|
139
|
+
# 尝试记录错误日志
|
|
140
|
+
try:
|
|
141
|
+
logger.error("{0}: {1}".format(message, error))
|
|
142
|
+
except:
|
|
143
|
+
# 如果记录日志失败,则使用 print
|
|
144
|
+
print("ERROR: {0}: {1}".format(message, error))
|
|
145
|
+
else:
|
|
146
|
+
# 直接使用 print 输出错误
|
|
147
|
+
print("ERROR: {0}: {1}".format(message, error))
|
|
148
|
+
except:
|
|
149
|
+
# 最后的备用方案
|
|
150
|
+
print("ERROR: {0}: {1}".format(message, error))
|
|
151
|
+
|
|
152
|
+
# Python 2兼容的异常抛出方式
|
|
153
|
+
new_error = PackageError("{0}: {1}".format(message, error))
|
|
154
|
+
raise new_error
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
系统检测和平台相关工具
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import print_function
|
|
7
|
+
import platform
|
|
8
|
+
import sys
|
|
9
|
+
import tempfile
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SystemInfo(object):
|
|
14
|
+
"""系统信息检测类"""
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def get_platform():
|
|
18
|
+
"""获取当前平台标识"""
|
|
19
|
+
system = platform.system().lower()
|
|
20
|
+
arch = platform.machine().lower()
|
|
21
|
+
|
|
22
|
+
if system == "windows":
|
|
23
|
+
return "win"
|
|
24
|
+
elif system == "linux":
|
|
25
|
+
if "arm" in arch or "aarch" in arch:
|
|
26
|
+
# 根据架构位数判断是arm还是arm64
|
|
27
|
+
if "64" in arch or "aarch64" in arch:
|
|
28
|
+
return "linux_arm64"
|
|
29
|
+
else:
|
|
30
|
+
return "linux_arm"
|
|
31
|
+
else:
|
|
32
|
+
return "linux_x86"
|
|
33
|
+
else:
|
|
34
|
+
raise ValueError("Unsupported platform: {0}_{1}".format(system, arch))
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def get_temp_dir():
|
|
38
|
+
"""获取系统临时目录"""
|
|
39
|
+
return tempfile.gettempdir()
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def get_binary_name(platform_type):
|
|
43
|
+
"""根据平台类型获取对应的二进制启动器名称"""
|
|
44
|
+
binary_map = {
|
|
45
|
+
"win": "launcher_windows.exe",
|
|
46
|
+
"linux_x86": "launcher_linux_x86",
|
|
47
|
+
"linux_arm": "launcher_linux_arm",
|
|
48
|
+
"linux_arm64": "launcher_linux_arm64"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if platform_type not in binary_map:
|
|
52
|
+
raise ValueError("Unsupported platform type: {0}".format(platform_type))
|
|
53
|
+
|
|
54
|
+
return binary_map[platform_type]
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def is_windows():
|
|
58
|
+
"""判断是否为Windows系统"""
|
|
59
|
+
return platform.system().lower() in ["windows", "win32", "win64"]
|
|
Binary file
|